* iosxr_config crash if config has route-policy with multiple levels of 'ifelseif' and other caveats (#41091)
* diff in as-path-set or prefix-set
* fix caveat diff can not have last line with comma in prefix-set/as-path/community-set
* Simplify fix to include indentation before parse
* remove debugger
* route-policy diffs
* fix iosxr_config crash issue
* new changes in iosxr_config after git add
* end-policy-map and end-class-map are properly indented so match misplaced children only when end-* is at the beigining also fix pep8
* Remaining config blocks of route-policy which needs exclusion from diff. added new tests
* pylint/pep8 warnings
* Review comments , sanity test fix
* shbang warning
* remove unused import
(cherry picked from commit 2db6a8c26ac39f91e1c8e08abea53581fae2b957)
* changelog entry
* sanity fix
... | ... |
@@ -186,7 +186,38 @@ from ansible.module_utils.network.common.config import NetworkConfig, dumps |
186 | 186 |
DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config' |
187 | 187 |
|
188 | 188 |
CONFIG_MISPLACED_CHILDREN = [ |
189 |
- re.compile(r'end-\s*(.+)$') |
|
189 |
+ re.compile(r'^end-\s*(.+)$') |
|
190 |
+] |
|
191 |
+ |
|
192 |
+# Objects defined in Route-policy Language guide of IOS_XR. |
|
193 |
+# Reconfiguring these objects replace existing configurations. |
|
194 |
+# Hence these objects should be played direcly from candidate |
|
195 |
+# configurations |
|
196 |
+CONFIG_BLOCKS_FORCED_IN_DIFF = [ |
|
197 |
+ { |
|
198 |
+ 'start': re.compile(r'route-policy'), |
|
199 |
+ 'end': re.compile(r'end-policy') |
|
200 |
+ }, |
|
201 |
+ { |
|
202 |
+ 'start': re.compile(r'prefix-set'), |
|
203 |
+ 'end': re.compile(r'end-set') |
|
204 |
+ }, |
|
205 |
+ { |
|
206 |
+ 'start': re.compile(r'as-path-set'), |
|
207 |
+ 'end': re.compile(r'end-set') |
|
208 |
+ }, |
|
209 |
+ { |
|
210 |
+ 'start': re.compile(r'community-set'), |
|
211 |
+ 'end': re.compile(r'end-set') |
|
212 |
+ }, |
|
213 |
+ { |
|
214 |
+ 'start': re.compile(r'rd-set'), |
|
215 |
+ 'end': re.compile(r'end-set') |
|
216 |
+ }, |
|
217 |
+ { |
|
218 |
+ 'start': re.compile(r'extcommunity-set'), |
|
219 |
+ 'end': re.compile(r'end-set') |
|
220 |
+ } |
|
190 | 221 |
] |
191 | 222 |
|
192 | 223 |
|
... | ... |
@@ -214,46 +245,93 @@ def check_args(module, warnings): |
214 | 214 |
'removed in the future') |
215 | 215 |
|
216 | 216 |
|
217 |
+# A list of commands like {end-set, end-policy, ...} are part of configuration |
|
218 |
+# block like { prefix-set, as-path-set , ... } but they are not indented properly |
|
219 |
+# to be included with their parent. sanitize_config will add indentation to |
|
220 |
+# end-* commands so they are included with their parents |
|
221 |
+def sanitize_config(config, force_diff_prefix=None): |
|
222 |
+ conf_lines = config.split('\n') |
|
223 |
+ for regex in CONFIG_MISPLACED_CHILDREN: |
|
224 |
+ for index, line in enumerate(conf_lines): |
|
225 |
+ m = regex.search(line) |
|
226 |
+ if m and m.group(0): |
|
227 |
+ if force_diff_prefix: |
|
228 |
+ conf_lines[index] = ' ' + m.group(0) + force_diff_prefix |
|
229 |
+ else: |
|
230 |
+ conf_lines[index] = ' ' + m.group(0) |
|
231 |
+ conf = ('\n').join(conf_lines) |
|
232 |
+ return conf |
|
233 |
+ |
|
234 |
+ |
|
235 |
+def mask_config_blocks_from_diff(config, candidate, force_diff_prefix): |
|
236 |
+ conf_lines = config.split('\n') |
|
237 |
+ candidate_lines = candidate.split('\n') |
|
238 |
+ |
|
239 |
+ for regex in CONFIG_BLOCKS_FORCED_IN_DIFF: |
|
240 |
+ block_index_start_end = [] |
|
241 |
+ for index, line in enumerate(candidate_lines): |
|
242 |
+ startre = regex['start'].search(line) |
|
243 |
+ if startre and startre.group(0): |
|
244 |
+ start_index = index |
|
245 |
+ else: |
|
246 |
+ endre = regex['end'].search(line) |
|
247 |
+ if endre and endre.group(0): |
|
248 |
+ end_index = index |
|
249 |
+ new_block = True |
|
250 |
+ for prev_start, prev_end in block_index_start_end: |
|
251 |
+ if start_index == prev_start: |
|
252 |
+ # This might be end-set of another regex |
|
253 |
+ # otherwise we would be having new start |
|
254 |
+ new_block = False |
|
255 |
+ break |
|
256 |
+ if new_block: |
|
257 |
+ block_index_start_end.append((start_index, end_index)) |
|
258 |
+ |
|
259 |
+ for start, end in block_index_start_end: |
|
260 |
+ diff = False |
|
261 |
+ if candidate_lines[start] in conf_lines: |
|
262 |
+ run_conf_start_index = conf_lines.index(candidate_lines[start]) |
|
263 |
+ else: |
|
264 |
+ diff = False |
|
265 |
+ continue |
|
266 |
+ for i in range(start, end + 1): |
|
267 |
+ if conf_lines[run_conf_start_index] == candidate_lines[i]: |
|
268 |
+ run_conf_start_index = run_conf_start_index + 1 |
|
269 |
+ else: |
|
270 |
+ diff = True |
|
271 |
+ break |
|
272 |
+ if diff: |
|
273 |
+ run_conf_start_index = conf_lines.index(candidate_lines[start]) |
|
274 |
+ for i in range(start, end + 1): |
|
275 |
+ conf_lines[run_conf_start_index] = conf_lines[run_conf_start_index] + force_diff_prefix |
|
276 |
+ run_conf_start_index = run_conf_start_index + 1 |
|
277 |
+ |
|
278 |
+ conf = ('\n').join(conf_lines) |
|
279 |
+ return conf |
|
280 |
+ |
|
281 |
+ |
|
217 | 282 |
def get_running_config(module): |
218 | 283 |
contents = module.params['config'] |
219 | 284 |
if not contents: |
220 | 285 |
contents = get_config(module) |
286 |
+ if module.params['src']: |
|
287 |
+ contents = mask_config_blocks_from_diff(contents, module.params['src'], "ansible") |
|
288 |
+ contents = sanitize_config(contents) |
|
221 | 289 |
return NetworkConfig(indent=1, contents=contents) |
222 | 290 |
|
223 | 291 |
|
224 | 292 |
def get_candidate(module): |
225 | 293 |
candidate = NetworkConfig(indent=1) |
226 | 294 |
if module.params['src']: |
227 |
- candidate.load(module.params['src']) |
|
295 |
+ config = module.params['src'] |
|
296 |
+ config = sanitize_config(config) |
|
297 |
+ candidate.load(config) |
|
228 | 298 |
elif module.params['lines']: |
229 | 299 |
parents = module.params['parents'] or list() |
230 | 300 |
candidate.add(module.params['lines'], parents=parents) |
231 | 301 |
return candidate |
232 | 302 |
|
233 | 303 |
|
234 |
-def sanitize_candidate_config(config): |
|
235 |
- last_parents = None |
|
236 |
- for regex in CONFIG_MISPLACED_CHILDREN: |
|
237 |
- for index, line in enumerate(config): |
|
238 |
- if line._parents: |
|
239 |
- last_parents = line._parents |
|
240 |
- m = regex.search(line.text) |
|
241 |
- if m and m.group(0): |
|
242 |
- config[index]._parents = last_parents |
|
243 |
- |
|
244 |
- |
|
245 |
-def sanitize_running_config(config): |
|
246 |
- last_parents = None |
|
247 |
- for regex in CONFIG_MISPLACED_CHILDREN: |
|
248 |
- for index, line in enumerate(config): |
|
249 |
- if line._parents: |
|
250 |
- last_parents = line._parents |
|
251 |
- m = regex.search(line.text) |
|
252 |
- if m and m.group(0): |
|
253 |
- config[index].text = ' ' + m.group(0) |
|
254 |
- config[index]._parents = last_parents |
|
255 |
- |
|
256 |
- |
|
257 | 304 |
def run(module, result): |
258 | 305 |
match = module.params['match'] |
259 | 306 |
replace = module.params['replace'] |
... | ... |
@@ -266,9 +344,6 @@ def run(module, result): |
266 | 266 |
candidate_config = get_candidate(module) |
267 | 267 |
running_config = get_running_config(module) |
268 | 268 |
|
269 |
- sanitize_candidate_config(candidate_config.items) |
|
270 |
- sanitize_running_config(running_config.items) |
|
271 |
- |
|
272 | 269 |
commands = None |
273 | 270 |
if match != 'none' and replace != 'config': |
274 | 271 |
commands = candidate_config.difference(running_config, path=path, match=match, replace=replace) |
275 | 272 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,121 @@ |
0 |
+router ospf 1 |
|
1 |
+ area 0 |
|
2 |
+! |
|
3 |
+prefix-set EBGP-PEER-BOGONS |
|
4 |
+ 0.0.0.0/0, |
|
5 |
+ 0.0.0.0/8 le 32, |
|
6 |
+ 10.0.0.0/8 le 32, |
|
7 |
+ 127.0.0.0/8 le 32, |
|
8 |
+ 169.254.0.0/16 le 32, |
|
9 |
+ 172.16.0.0/12 le 32, |
|
10 |
+ 192.0.0.0/24 le 32, |
|
11 |
+ 192.0.2.0/24 le 32, |
|
12 |
+ 192.168.0.0/16 le 32, |
|
13 |
+ 198.18.0.0/15 le 32, |
|
14 |
+ 224.0.0.0/4 le 32, |
|
15 |
+ 240.0.0.0/4 le 32 |
|
16 |
+end-set |
|
17 |
+! |
|
18 |
+prefix-set cust-ddos-DDOS |
|
19 |
+end-set |
|
20 |
+! |
|
21 |
+prefix-set cust-no-export |
|
22 |
+end-set |
|
23 |
+! |
|
24 |
+prefix-set acme_DC_Internal |
|
25 |
+ 137.1.0.0/16, |
|
26 |
+ 137.1.16.0/24, |
|
27 |
+ 137.1.18.0/24, |
|
28 |
+ 137.1.20.0/24, |
|
29 |
+ 137.1.22.0/24, |
|
30 |
+ 137.1.23.0/24, |
|
31 |
+ 137.1.24.0/24, |
|
32 |
+ 137.1.29.0/24, |
|
33 |
+ 137.1.30.0/24, |
|
34 |
+ 137.1.31.0/24, |
|
35 |
+ 137.1.32.0/21, |
|
36 |
+ 137.1.40.0/22, |
|
37 |
+ 209.1.0.0/16 |
|
38 |
+end-set |
|
39 |
+! |
|
40 |
+as-path-set EBGP-PEER-AS16509-403-PERMIT-PATHS |
|
41 |
+ ios-regex '^11164_8075_', |
|
42 |
+ ios-regex '^11164_16509$', |
|
43 |
+ ios-regex '^1116_16509_[0-9]+$', |
|
44 |
+ ios-regex '^8075_', |
|
45 |
+ ios-regex '^16509$', |
|
46 |
+ ios-regex '^16509_[0-9]+$' |
|
47 |
+end-set |
|
48 |
+! |
|
49 |
+community-set cust-announce |
|
50 |
+ 1525:65298, |
|
51 |
+ 1525:65436, |
|
52 |
+ 1525:65438, |
|
53 |
+ 1525:65439, |
|
54 |
+ 1525:65498, |
|
55 |
+ 1525:65511, |
|
56 |
+ 1523:65418, |
|
57 |
+ 1523:65436, |
|
58 |
+ 1523:65438 |
|
59 |
+end-set |
|
60 |
+! |
|
61 |
+community-set cust-no-export |
|
62 |
+ 1525:65439, |
|
63 |
+ 1525:65511, |
|
64 |
+ 1525:65535 |
|
65 |
+end-set |
|
66 |
+! |
|
67 |
+ |
|
68 |
+route-policy POLICY2 |
|
69 |
+end-policy |
|
70 |
+! |
|
71 |
+route-policy cust2bgp |
|
72 |
+ set origin igp |
|
73 |
+ set next-hop 137.1.16.12 |
|
74 |
+end-policy |
|
75 |
+! |
|
76 |
+rd-set ebpg-1 |
|
77 |
+end-set |
|
78 |
+! |
|
79 |
+rd-set EBGP_INCOMING_RD_SET |
|
80 |
+ 172.16.0.0/16:*, |
|
81 |
+ 172.17.0.0/16:100, |
|
82 |
+ 192:*, |
|
83 |
+ 192:100 |
|
84 |
+end-set |
|
85 |
+! |
|
86 |
+extcommunity-set rt EBGP_INCOMIG_RT_SET |
|
87 |
+ 10:615, |
|
88 |
+ 10:6150, |
|
89 |
+ 15.15.15.15:15 |
|
90 |
+end-set |
|
91 |
+! |
|
92 |
+extcommunity-set rt ebpg-1 |
|
93 |
+end-set |
|
94 |
+! |
|
95 |
+route-policy static-to-bgp |
|
96 |
+ if destination in cust-no-export then |
|
97 |
+ apply cust2bgp |
|
98 |
+ set community cust-no-export additive |
|
99 |
+ elseif destination in cust-announce then |
|
100 |
+ apply cust2bgp |
|
101 |
+ set community cust-announce additive |
|
102 |
+ elseif destination in cust-announce-backup then |
|
103 |
+ apply cust2bgp |
|
104 |
+ set local-preference 100 |
|
105 |
+ set weight 0 |
|
106 |
+ set community cust-announce additive |
|
107 |
+ elseif destination in cust-no-export-backup then |
|
108 |
+ apply cust2bgp |
|
109 |
+ set local-preference 98 |
|
110 |
+ set weight 0 |
|
111 |
+ set community cust-no-export additive |
|
112 |
+ else |
|
113 |
+ drop |
|
114 |
+ endif |
|
115 |
+end-policy |
|
116 |
+! |
|
117 |
+class-map match-any data |
|
118 |
+ match precedence ipv4 0 1 |
|
119 |
+ end-class-map |
|
120 |
+! |
0 | 121 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,65 @@ |
0 |
+prefix-set EBGP-PEER-BOGONS |
|
1 |
+ 192.0.2.0/24 le 32, |
|
2 |
+ 192.168.0.0/16 le 32, |
|
3 |
+ 198.18.0.0/16 le 32, |
|
4 |
+ 224.0.0.0/4 le 32, |
|
5 |
+ 240.0.0.0/4 le 32 |
|
6 |
+end-set |
|
7 |
+! |
|
8 |
+as-path-set EBGP-PEER-AS16509-403-PERMIT-PATHS |
|
9 |
+ ios-regex '^11164_8075_', |
|
10 |
+ ios-regex '^1164_16509$', |
|
11 |
+ ios-regex '^1116_16409_[0-9]+$', |
|
12 |
+ ios-regex '^8075_' |
|
13 |
+end-set |
|
14 |
+! |
|
15 |
+community-set cust-announce |
|
16 |
+ 1525:65298, |
|
17 |
+ 1525:6546, |
|
18 |
+ 1525:6438, |
|
19 |
+ 1525:65439, |
|
20 |
+ 1525:65498 |
|
21 |
+end-set |
|
22 |
+! |
|
23 |
+rd-set EBGP_INCOMING_RD_SET |
|
24 |
+ 172.16.0.0/16:*, |
|
25 |
+ 172.14.0.0/16:100, |
|
26 |
+ 192:*, |
|
27 |
+ 192:100 |
|
28 |
+end-set |
|
29 |
+! |
|
30 |
+extcommunity-set rt EBGP_INCOMIG_RT_SET |
|
31 |
+ 10:615, |
|
32 |
+ 10:6120, |
|
33 |
+ 15.15.15.15:15 |
|
34 |
+end-set |
|
35 |
+! |
|
36 |
+route-policy POLICY2 |
|
37 |
+end-policy |
|
38 |
+! |
|
39 |
+route-policy static-to-bgp |
|
40 |
+ if destination in cust-no-export then |
|
41 |
+ apply cust2bgp |
|
42 |
+ set community cust-no-export additive |
|
43 |
+ elseif destination in cust-announce then |
|
44 |
+ apply cust2bgp |
|
45 |
+ set community cust-announce additive |
|
46 |
+ elseif destination in cust-announce-backup then |
|
47 |
+ apply cust2bgp |
|
48 |
+ set local-preference 100 |
|
49 |
+ set weight 23 |
|
50 |
+ set community cust-announce additive |
|
51 |
+ elseif destination in cust-no-export-backup then |
|
52 |
+ apply cust2bgp |
|
53 |
+ set local-preference 98 |
|
54 |
+ set weight 0 |
|
55 |
+ set community cust-no-export additive |
|
56 |
+ else |
|
57 |
+ drop |
|
58 |
+ endif |
|
59 |
+end-policy |
|
60 |
+! |
|
61 |
+class-map match-any data |
|
62 |
+ match precedence ipv4 0 1 2 |
|
63 |
+ end-class-map |
|
64 |
+! |
0 | 65 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,32 @@ |
0 |
+no router ospf 1 |
|
1 |
+! |
|
2 |
+no prefix-set EBGP-PEER-BOGONS |
|
3 |
+! |
|
4 |
+no prefix-set cust-ddos-DDOS |
|
5 |
+! |
|
6 |
+no prefix-set cust-no-export |
|
7 |
+! |
|
8 |
+no prefix-set acme_DC_Internal |
|
9 |
+! |
|
10 |
+no as-path-set EBGP-PEER-AS16509-403-PERMIT-PATHS |
|
11 |
+! |
|
12 |
+no community-set cust-announce |
|
13 |
+! |
|
14 |
+no community-set cust-no-export |
|
15 |
+! |
|
16 |
+no rd-set ebpg-1 |
|
17 |
+! |
|
18 |
+no rd-set EBGP_INCOMING_RD_SET |
|
19 |
+! |
|
20 |
+no extcommunity-set rt EBGP_INCOMIG_RT_SET |
|
21 |
+! |
|
22 |
+no extcommunity-set rt ebpg-1 |
|
23 |
+! |
|
24 |
+no route-policy POLICY2 |
|
25 |
+! |
|
26 |
+no route-policy cust2bgp |
|
27 |
+! |
|
28 |
+no route-policy static-to-bgp |
|
29 |
+! |
|
30 |
+no class-map match-any data |
|
31 |
+! |
0 | 32 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,53 @@ |
0 |
+--- |
|
1 |
+- debug: msg="START cli/route_policy.yaml on connection={{ ansible_connection }}" |
|
2 |
+ |
|
3 |
+- name: Cleanup |
|
4 |
+ iosxr_config: |
|
5 |
+ src: basic/route_policy_clean.j2 |
|
6 |
+ |
|
7 |
+- name: config setup route-policy/prefix-set/as-path-set/community-set |
|
8 |
+ iosxr_config: |
|
9 |
+ src: basic/route_policy.j2 |
|
10 |
+ register: result |
|
11 |
+ |
|
12 |
+- assert: |
|
13 |
+ that: |
|
14 |
+ - "result.changed == true" |
|
15 |
+ |
|
16 |
+- name: Configure same route-policy/prefix-set ... verify change=0 |
|
17 |
+ iosxr_config: |
|
18 |
+ src: basic/route_policy.j2 |
|
19 |
+ register: result |
|
20 |
+ |
|
21 |
+- assert: |
|
22 |
+ that: |
|
23 |
+ - "result.changed == false" |
|
24 |
+ |
|
25 |
+- name: Do a change in multi-sublevel route-policy/prefix-set/community-set |
|
26 |
+ iosxr_config: |
|
27 |
+ src: basic/route_policy_change.j2 |
|
28 |
+ register: result |
|
29 |
+ |
|
30 |
+- assert: |
|
31 |
+ that: |
|
32 |
+ - "result.changed == true" |
|
33 |
+ |
|
34 |
+- name: Configure same route-policy/prefix-set ... verify change=0 |
|
35 |
+ iosxr_config: |
|
36 |
+ src: basic/route_policy_change.j2 |
|
37 |
+ register: result |
|
38 |
+ |
|
39 |
+- assert: |
|
40 |
+ that: |
|
41 |
+ - "result.changed == false" |
|
42 |
+ |
|
43 |
+- name: Cleanup |
|
44 |
+ iosxr_config: |
|
45 |
+ src: basic/route_policy_clean.j2 |
|
46 |
+ register: result |
|
47 |
+ |
|
48 |
+- assert: |
|
49 |
+ that: |
|
50 |
+ - "result.changed == true" |
|
51 |
+ |
|
52 |
+- debug: msg="END cli/route_policy.yaml on connection={{ ansible_connection }}" |