Skip to content

Commit dac6ca1

Browse files
authored
Merge pull request #4841 from alexandr-san4ez/T7255-current
VRF: T7255: Impossible to delete protocols under VRF
2 parents 0447d71 + b63388c commit dac6ca1

File tree

2 files changed

+75
-32
lines changed

2 files changed

+75
-32
lines changed

python/vyos/frrender.py

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ def dict_helper_nhrp_defaults(nhrp):
208208
'security_profile'] = name
209209
return nhrp
210210

211+
deleted_protocol = {'deleted' : ''}
212+
211213
# Ethernet and bonding interfaces can participate in EVPN which is configured via FRR
212214
tmp = {}
213215
for if_type in ['ethernet', 'bonding']:
@@ -258,7 +260,7 @@ def dict_helper_nhrp_defaults(nhrp):
258260
with_recursive_defaults=True)
259261
dict.update({'babel' : babel})
260262
elif conf.exists_effective(babel_cli_path):
261-
dict.update({'babel' : {'deleted' : ''}})
263+
dict.update({'babel' : deleted_protocol})
262264

263265
# We need to check the CLI if the BFD node is present and thus load in all the default
264266
# values present on the CLI - that's why we have if conf.exists()
@@ -270,7 +272,7 @@ def dict_helper_nhrp_defaults(nhrp):
270272
with_recursive_defaults=True)
271273
dict.update({'bfd' : bfd})
272274
elif conf.exists_effective(bfd_cli_path):
273-
dict.update({'bfd' : {'deleted' : ''}})
275+
dict.update({'bfd' : deleted_protocol})
274276

275277
# We need to check the CLI if the BGP node is present and thus load in all the default
276278
# values present on the CLI - that's why we have if conf.exists()
@@ -295,7 +297,7 @@ def dict_helper_nhrp_defaults(nhrp):
295297
with_recursive_defaults=True)
296298
dict.update({'eigrp' : eigrp})
297299
elif conf.exists_effective(eigrp_cli_path):
298-
dict.update({'eigrp' : {'deleted' : ''}})
300+
dict.update({'eigrp' : deleted_protocol})
299301

300302
# We need to check the CLI if the ISIS node is present and thus load in all the default
301303
# values present on the CLI - that's why we have if conf.exists()
@@ -307,7 +309,7 @@ def dict_helper_nhrp_defaults(nhrp):
307309
with_recursive_defaults=True)
308310
dict.update({'isis' : isis})
309311
elif conf.exists_effective(isis_cli_path):
310-
dict.update({'isis' : {'deleted' : ''}})
312+
dict.update({'isis' : deleted_protocol})
311313

312314
# We need to check the CLI if the MPLS node is present and thus load in all the default
313315
# values present on the CLI - that's why we have if conf.exists()
@@ -317,7 +319,7 @@ def dict_helper_nhrp_defaults(nhrp):
317319
get_first_key=True)
318320
dict.update({'mpls' : mpls})
319321
elif conf.exists_effective(mpls_cli_path):
320-
dict.update({'mpls' : {'deleted' : ''}})
322+
dict.update({'mpls' : deleted_protocol})
321323

322324
# We need to check the CLI if the OPENFABRIC node is present and thus load in all the default
323325
# values present on the CLI - that's why we have if conf.exists()
@@ -328,7 +330,7 @@ def dict_helper_nhrp_defaults(nhrp):
328330
no_tag_node_value_mangle=True)
329331
dict.update({'openfabric' : openfabric})
330332
elif conf.exists_effective(openfabric_cli_path):
331-
dict.update({'openfabric' : {'deleted' : ''}})
333+
dict.update({'openfabric' : deleted_protocol})
332334

333335
# We need to check the CLI if the OSPF node is present and thus load in all the default
334336
# values present on the CLI - that's why we have if conf.exists()
@@ -339,7 +341,7 @@ def dict_helper_nhrp_defaults(nhrp):
339341
ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path)
340342
dict.update({'ospf' : ospf})
341343
elif conf.exists_effective(ospf_cli_path):
342-
dict.update({'ospf' : {'deleted' : ''}})
344+
dict.update({'ospf' : deleted_protocol})
343345

344346
# We need to check the CLI if the OSPFv3 node is present and thus load in all the default
345347
# values present on the CLI - that's why we have if conf.exists()
@@ -350,7 +352,7 @@ def dict_helper_nhrp_defaults(nhrp):
350352
ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path)
351353
dict.update({'ospfv3' : ospfv3})
352354
elif conf.exists_effective(ospfv3_cli_path):
353-
dict.update({'ospfv3' : {'deleted' : ''}})
355+
dict.update({'ospfv3' : deleted_protocol})
354356

355357
# We need to check the CLI if the PIM node is present and thus load in all the default
356358
# values present on the CLI - that's why we have if conf.exists()
@@ -361,7 +363,7 @@ def dict_helper_nhrp_defaults(nhrp):
361363
pim = dict_helper_pim_defaults(pim, pim_cli_path)
362364
dict.update({'pim' : pim})
363365
elif conf.exists_effective(pim_cli_path):
364-
dict.update({'pim' : {'deleted' : ''}})
366+
dict.update({'pim' : deleted_protocol})
365367

366368
# We need to check the CLI if the PIM6 node is present and thus load in all the default
367369
# values present on the CLI - that's why we have if conf.exists()
@@ -372,7 +374,7 @@ def dict_helper_nhrp_defaults(nhrp):
372374
with_recursive_defaults=True)
373375
dict.update({'pim6' : pim6})
374376
elif conf.exists_effective(pim6_cli_path):
375-
dict.update({'pim6' : {'deleted' : ''}})
377+
dict.update({'pim6' : deleted_protocol})
376378

377379
# We need to check the CLI if the RIP node is present and thus load in all the default
378380
# values present on the CLI - that's why we have if conf.exists()
@@ -383,7 +385,7 @@ def dict_helper_nhrp_defaults(nhrp):
383385
with_recursive_defaults=True)
384386
dict.update({'rip' : rip})
385387
elif conf.exists_effective(rip_cli_path):
386-
dict.update({'rip' : {'deleted' : ''}})
388+
dict.update({'rip' : deleted_protocol})
387389

388390
# We need to check the CLI if the RIPng node is present and thus load in all the default
389391
# values present on the CLI - that's why we have if conf.exists()
@@ -394,7 +396,7 @@ def dict_helper_nhrp_defaults(nhrp):
394396
with_recursive_defaults=True)
395397
dict.update({'ripng' : ripng})
396398
elif conf.exists_effective(ripng_cli_path):
397-
dict.update({'ripng' : {'deleted' : ''}})
399+
dict.update({'ripng' : deleted_protocol})
398400

399401
# We need to check the CLI if the RPKI node is present and thus load in all the default
400402
# values present on the CLI - that's why we have if conf.exists()
@@ -410,7 +412,7 @@ def dict_helper_nhrp_defaults(nhrp):
410412
cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
411413
dict.update({'rpki' : rpki})
412414
elif conf.exists_effective(rpki_cli_path):
413-
dict.update({'rpki' : {'deleted' : ''}})
415+
dict.update({'rpki' : deleted_protocol})
414416

415417
# We need to check the CLI if the Segment Routing node is present and thus load in
416418
# all the default values present on the CLI - that's why we have if conf.exists()
@@ -422,7 +424,7 @@ def dict_helper_nhrp_defaults(nhrp):
422424
with_recursive_defaults=True)
423425
dict.update({'segment_routing' : sr})
424426
elif conf.exists_effective(sr_cli_path):
425-
dict.update({'segment_routing' : {'deleted' : ''}})
427+
dict.update({'segment_routing' : deleted_protocol})
426428

427429
# We need to check the CLI if the static node is present and thus load in
428430
# all the default values present on the CLI - that's why we have if conf.exists()
@@ -433,7 +435,7 @@ def dict_helper_nhrp_defaults(nhrp):
433435
no_tag_node_value_mangle=True)
434436
dict.update({'static' : static})
435437
elif conf.exists_effective(static_cli_path):
436-
dict.update({'static' : {'deleted' : ''}})
438+
dict.update({'static' : deleted_protocol})
437439

438440
# We need to check the CLI if the NHRP node is present and thus load in all the default
439441
# values present on the CLI - that's why we have if conf.exists()
@@ -445,7 +447,7 @@ def dict_helper_nhrp_defaults(nhrp):
445447
nhrp = dict_helper_nhrp_defaults(nhrp)
446448
dict.update({'nhrp' : nhrp})
447449
elif conf.exists_effective(nhrp_cli_path):
448-
dict.update({'nhrp' : {'deleted' : ''}})
450+
dict.update({'nhrp' : deleted_protocol})
449451

450452
# T3680 - get a list of all interfaces currently configured to use DHCP
451453
tmp = get_dhcp_interfaces(conf)
@@ -471,6 +473,8 @@ def dict_helper_nhrp_defaults(nhrp):
471473
# come into place under the protocols tree, thus we can safely merge them with the
472474
# appropriate routing protocols
473475
for vrf_name, vrf_config in vrf['name'].items():
476+
protocol_dict_path = f'name.{vrf_name}.protocols'
477+
474478
bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp']
475479
if 'bgp' in vrf_config.get('protocols', []):
476480
# We have gathered the dict representation of the CLI, but there are default
@@ -513,50 +517,47 @@ def dict_helper_nhrp_defaults(nhrp):
513517
if 'bgp' in dict:
514518
dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} })
515519

516-
if 'protocols' not in vrf['name'][vrf_name]:
517-
vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}})
518-
else:
519-
vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp})
520+
dict_set_nested(f'{protocol_dict_path}.bgp', tmp, vrf)
520521

521522
# We need to check the CLI if the EIGRP node is present and thus load in all the default
522523
# values present on the CLI - that's why we have if conf.exists()
523524
eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp']
524525
if 'eigrp' in vrf_config.get('protocols', []):
525526
eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True,
526527
no_tag_node_value_mangle=True)
527-
vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis})
528+
dict_set_nested(f'{protocol_dict_path}.eigrp', eigrp, vrf)
528529
elif conf.exists_effective(eigrp_vrf_path):
529-
vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}})
530+
dict_set_nested(f'{protocol_dict_path}.eigrp', deleted_protocol, vrf)
530531

531532
# We need to check the CLI if the ISIS node is present and thus load in all the default
532533
# values present on the CLI - that's why we have if conf.exists()
533534
isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis']
534535
if 'isis' in vrf_config.get('protocols', []):
535536
isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True,
536537
no_tag_node_value_mangle=True, with_recursive_defaults=True)
537-
vrf['name'][vrf_name]['protocols'].update({'isis' : isis})
538+
dict_set_nested(f'{protocol_dict_path}.isis', isis, vrf)
538539
elif conf.exists_effective(isis_vrf_path):
539-
vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}})
540+
dict_set_nested(f'{protocol_dict_path}.isis', deleted_protocol, vrf)
540541

541542
# We need to check the CLI if the OSPF node is present and thus load in all the default
542543
# values present on the CLI - that's why we have if conf.exists()
543544
ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf']
544545
if 'ospf' in vrf_config.get('protocols', []):
545546
ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True)
546547
ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path)
547-
vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf})
548+
dict_set_nested(f'{protocol_dict_path}.ospf', ospf, vrf)
548549
elif conf.exists_effective(ospf_vrf_path):
549-
vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}})
550+
dict_set_nested(f'{protocol_dict_path}.ospf', deleted_protocol, vrf)
550551

551552
# We need to check the CLI if the OSPFv3 node is present and thus load in all the default
552553
# values present on the CLI - that's why we have if conf.exists()
553554
ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3']
554555
if 'ospfv3' in vrf_config.get('protocols', []):
555556
ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True)
556557
ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path)
557-
vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3})
558+
dict_set_nested(f'{protocol_dict_path}.ospfv3', ospfv3, vrf)
558559
elif conf.exists_effective(ospfv3_vrf_path):
559-
vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
560+
dict_set_nested(f'{protocol_dict_path}.ospfv3', deleted_protocol, vrf)
560561

561562
# We need to check the CLI if the RPKI node is present and thus load in all the default
562563
# values present on the CLI - that's why we have if conf.exists()
@@ -569,9 +570,9 @@ def dict_helper_nhrp_defaults(nhrp):
569570
if 'ssh' in cache_config:
570571
cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
571572
cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
572-
vrf['name'][vrf_name]['protocols'].update({'rpki' : rpki})
573+
dict_set_nested(f'{protocol_dict_path}.rpki', rpki, vrf)
573574
elif conf.exists_effective(rpki_vrf_path):
574-
vrf['name'][vrf_name]['protocols'].update({'rpki' : {'deleted' : ''}})
575+
dict_set_nested(f'{protocol_dict_path}.rpki', deleted_protocol, vrf)
575576

576577
# We need to check the CLI if the static node is present and thus load in all the default
577578
# values present on the CLI - that's why we have if conf.exists()
@@ -580,9 +581,9 @@ def dict_helper_nhrp_defaults(nhrp):
580581
static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'),
581582
get_first_key=True,
582583
no_tag_node_value_mangle=True)
583-
vrf['name'][vrf_name]['protocols'].update({'static': static})
584+
dict_set_nested(f'{protocol_dict_path}.static', static, vrf)
584585
elif conf.exists_effective(static_vrf_path):
585-
vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}})
586+
dict_set_nested(f'{protocol_dict_path}.static', deleted_protocol, vrf)
586587

587588
# T3680 - get a list of all interfaces currently configured to use DHCP
588589
tmp = get_dhcp_interfaces(conf, vrf_name)

smoketest/scripts/cli/test_vrf.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,48 @@ def test_vrf_link_local_ip_addresses(self):
326326
self.cli_delete(['interfaces', 'dummy', interface])
327327
self.cli_commit()
328328

329+
def test_delete_vrf_protocols_should_not_crash(self):
330+
# Testcase for issue T7255:
331+
# - verify that deleting the 'protocols' node under a VRF does not crash.
332+
333+
table = '3000'
334+
vrf = 'purple'
335+
interface = 'dum3000'
336+
router_id = '10.2.0.2'
337+
338+
# Configure dummy interface and assign to VRF
339+
self.cli_set(['interfaces', 'dummy', interface, 'address', '10.1.0.254/24'])
340+
self.cli_set(['interfaces', 'dummy', interface, 'vrf', vrf])
341+
342+
# Configure OSPF under the VRF
343+
base_ospf_path = base_path + ['name', vrf, 'protocols', 'ospf']
344+
self.cli_set(base_ospf_path + ['interface', interface, 'area', '0'])
345+
self.cli_set(base_ospf_path + ['parameters', 'router-id', router_id])
346+
self.cli_set(['protocols', 'ospf'])
347+
348+
# Assign routing table number to the VRF
349+
self.cli_set(base_path + ['name', vrf, 'table', table])
350+
351+
# Commit configuration and verify VRF was successfully created
352+
self.cli_commit()
353+
self.assertTrue(interface_exists(vrf))
354+
frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', stop_section='^exit')
355+
self.assertIn(f'ospf router-id {router_id}', frrconfig)
356+
357+
try:
358+
# Attempt to delete the entire 'protocols' subtree under VRF
359+
self.cli_delete(base_path + ['name', vrf, 'protocols'])
360+
self.cli_commit()
361+
362+
# Verify result of deleting 'protocols' subtree
363+
frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', stop_section='^exit')
364+
self.assertNotIn(f'ospf router-id {router_id}', frrconfig)
365+
finally:
366+
# Clean up dummy interface and VRF and re-commit
367+
self.cli_delete(['interfaces', 'dummy', interface])
368+
self.cli_delete(base_path + ['name', vrf])
369+
self.cli_commit()
370+
329371
def test_vrf_disable_forwarding(self):
330372
table = '2000'
331373
for vrf in vrfs:

0 commit comments

Comments
 (0)