Skip to content

Commit e44c3f6

Browse files
authored
[generic-config-updater] Improving CreateOnly validator and marking /LOOPBACK_INTERFACE/LOOPBACK#/vrf_name as create-only (#1969)
#### What I did Fix #1962 Updated `create-only` flag meaning. From, Field is not replaceable but can be added or deleted. In other words: - Field can be added - Field can be deleted - Field cannot be replaced To, Field is only created, but never modified/updated. In other words: - Field cannot be added, only if the parent is added - Field cannot be deleted, only if the parent is deleted - Field cannot be replaced Also marked `/LOOPBACK_INTERFACE/LOOPBACK#/vrf_name` as `create-only` #### How I did it - Field was already not replaceable -- so no changes - If field is added, but parent already exist -- fail create-only validation - If field is deleted, but parent remain -- fail create-only validation #### How to verify it unit-test #### Examples Check issue to see how `apply-patch` behaved before this fix. Each example below we show configdb, vrf_01, vrf_02, and ip interfaces before and after the update. **Adding vrf_name** ```sh admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": {}, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ sudo config apply-patch add-lo0-vrf01.json-patch -i /BGP_NEIGHBOR -i /DEVICE_METADATA -i /FEATURE -i /FLEX_COUNTER_TABLE -i /VLAN/Vlan1000/members -i /SCHEDULER -i /QUEUE Patch Applier: Patch application starting. Patch Applier: Patch: [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0/vrf_name", "value": "Vrf_01"}] Patch Applier: Getting current config db. Patch Applier: Simulating the target full config after applying the patch. Patch Applier: Validating target config does not have empty tables, since they do not show up in ConfigDb. Patch Applier: Sorting patch updates. Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, libyang[0]: Must condition "(current() = ../../LOOPBACK_INTERFACE_LIST[name=current()]/name)" not satisfied. (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) libyang[0]: Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) sonic_yang(3):Data Loading Failed:Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} Patch Applier: The patch was sorted into 4 changes: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {"vrf_name": "Vrf_01"}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Applying 4 changes in order: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {"vrf_name": "Vrf_01"}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Verifying patch updates are reflected on ConfigDB. Patch Applier: Patch application completed. Patch applied successfully. admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": { "vrf_name": "Vrf_01" }, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, > - selected route, * - FIB route, q - queued, r - rejected, b - backup VRF Vrf_01: C>* 10.1.0.32/32 is directly connected, Loopback0, 00:00:19 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 Vrf_01 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ ``` **Replacing vrf_name** ```sh admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": { "vrf_name": "Vrf_01" }, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, > - selected route, * - FIB route, q - queued, r - rejected, b - backup VRF Vrf_01: C>* 10.1.0.32/32 is directly connected, Loopback0, 00:00:19 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 Vrf_01 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ sudo config apply-patch replace-lo0-vrf02.json-patch -i /BGP_NEIGHBOR -i /DEVICE_METADATA -i /FEATURE -i /FLEX_COUNTER_TABLE -i /VLAN/Vlan1000/members -i /SCHEDULER -i /QUEUE Patch Applier: Patch application starting. Patch Applier: Patch: [{"op": "replace", "path": "/LOOPBACK_INTERFACE/Loopback0/vrf_name", "value": "Vrf_02"}] Patch Applier: Getting current config db. Patch Applier: Simulating the target full config after applying the patch. Patch Applier: Validating target config does not have empty tables, since they do not show up in ConfigDb. Patch Applier: Sorting patch updates. Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, libyang[0]: Must condition "(current() = ../../LOOPBACK_INTERFACE_LIST[name=current()]/name)" not satisfied. (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) libyang[0]: Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) sonic_yang(3):Data Loading Failed:Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} Patch Applier: The patch was sorted into 4 changes: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {"vrf_name": "Vrf_02"}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Applying 4 changes in order: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {"vrf_name": "Vrf_02"}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Verifying patch updates are reflected on ConfigDB. Patch Applier: Patch application completed. Patch applied successfully. admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": { "vrf_name": "Vrf_02" }, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, > - selected route, * - FIB route, q - queued, r - rejected, b - backup VRF Vrf_02: C>* 10.1.0.32/32 is directly connected, Loopback0, 00:00:29 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 Vrf_02 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ ``` **Removing vrf_name** ```sh admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": { "vrf_name": "Vrf_02" }, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, > - selected route, * - FIB route, q - queued, r - rejected, b - backup VRF Vrf_02: C>* 10.1.0.32/32 is directly connected, Loopback0, 00:00:29 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 Vrf_02 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ sudo config apply-patch remove-lo0-vrf02.json-patch -i /BGP_NEIGHBOR -i /DEVICE_METADATA -i /FEATURE -i /FLEX_COUNTER_TABLE -i /VLAN/Vlan1000/members -i /SCHEDULER -i /QUEUE Patch Applier: Patch application starting. Patch Applier: Patch: [{"op": "remove", "path": "/LOOPBACK_INTERFACE/Loopback0/vrf_name"}] Patch Applier: Getting current config db. Patch Applier: Simulating the target full config after applying the patch. Patch Applier: Validating target config does not have empty tables, since they do not show up in ConfigDb. Patch Applier: Sorting patch updates. Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, Note: Below table(s) have no YANG models: BGP_PEER_RANGE, CABLE_LENGTH, CONSOLE_SWITCH, DEVICE_NEIGHBOR_METADATA, DHCP_SERVER, KDUMP, RESTAPI, SNMP, SNMP_COMMUNITY, SYSLOG_SERVER, TELEMETRY, libyang[0]: Must condition "(current() = ../../LOOPBACK_INTERFACE_LIST[name=current()]/name)" not satisfied. (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) libyang[0]: Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} (path: /sonic-loopback-interface:sonic-loopback-interface/LOOPBACK_INTERFACE/LOOPBACK_INTERFACE_IPPREFIX_LIST[name='Loopback0'][ip-prefix='10.1.0.32/32']/name) sonic_yang(3):Data Loading Failed:Must condition not satisfied, Try adding lo<>: {}, Example: 'lo1': {} Patch Applier: The patch was sorted into 4 changes: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Applying 4 changes in order: Patch Applier: * [{"op": "remove", "path": "/LOOPBACK_INTERFACE"}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE", "value": {"Loopback0": {}}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|10.1.0.32~132", "value": {}}] Patch Applier: * [{"op": "add", "path": "/LOOPBACK_INTERFACE/Loopback0|FC00:1::32~1128", "value": {}}] Patch Applier: Verifying patch updates are reflected on ConfigDB. Patch Applier: Patch application completed. Patch applied successfully. admin@vlab-01:~/lo$ show run all | grep -i 'loopback0\|vrf_01' -a3 } }, "LOOPBACK_INTERFACE": { "Loopback0": {}, "Loopback0|10.1.0.32/32": {}, "Loopback0|FC00:1::32/128": {} }, "MAP_PFC_PRIORITY_TO_QUEUE": { "AZURE": { -- } }, "VRF": { "Vrf_01": {}, "Vrf_02": {} }, "WRED_PROFILE": { admin@vlab-01:~/lo$ show ip route vrf Vrf_01 admin@vlab-01:~/lo$ show ip route vrf Vrf_02 admin@vlab-01:~/lo$ show ip interfaces | grep -i loopback0 Loopback0 10.1.0.32/32 up/up N/A N/A admin@vlab-01:~/lo$ ```
1 parent 0067cc4 commit e44c3f6

File tree

3 files changed

+169
-26
lines changed

3 files changed

+169
-26
lines changed

generic_config_updater/patch_sorter.py

+74-16
Original file line numberDiff line numberDiff line change
@@ -372,40 +372,58 @@ def validate(self, move, diff):
372372

373373
class CreateOnlyMoveValidator:
374374
"""
375-
A class to validate create-only fields are only added/removed but never replaced.
376-
Parents of create-only fields are also only added/removed but never replaced when they contain
377-
a modified create-only field.
375+
A class to validate create-only fields are only created, but never modified/updated. In other words:
376+
- Field cannot be replaced.
377+
- Field cannot be added, only if the parent is added.
378+
- Field cannot be deleted, only if the parent is deleted.
378379
"""
379380
def __init__(self, path_addressing):
380381
self.path_addressing = path_addressing
381382

382-
def validate(self, move, diff):
383-
if move.op_type != OperationType.REPLACE:
384-
return True
383+
# TODO: create-only fields are hard-coded for now, it should be moved to YANG models
384+
# Each pattern consist of a list of tokens. Token matching starts from the root level of the config.
385+
# Each token is either a specific key or '*' to match all keys.
386+
self.create_only_patterns = [
387+
["PORT", "*", "lanes"],
388+
["LOOPBACK_INTERFACE", "*", "vrf_name"],
389+
]
385390

386-
# The 'create-only' field needs to be common between current and simulated anyway but different.
387-
# This means it is enough to just get the paths from current_config, paths that are not common can be ignored.
388-
paths = self._get_create_only_paths(diff.current_config)
391+
def validate(self, move, diff):
389392
simulated_config = move.apply(diff.current_config)
393+
paths = set(list(self._get_create_only_paths(diff.current_config)) + list(self._get_create_only_paths(simulated_config)))
390394

391395
for path in paths:
392396
tokens = self.path_addressing.get_path_tokens(path)
393397
if self._value_exist_but_different(tokens, diff.current_config, simulated_config):
394398
return False
399+
if self._value_added_but_parent_exist(tokens, diff.current_config, simulated_config):
400+
return False
401+
if self._value_removed_but_parent_remain(tokens, diff.current_config, simulated_config):
402+
return False
395403

396404
return True
397405

398-
# TODO: create-only fields are hard-coded for now, it should be moved to YANG models
399406
def _get_create_only_paths(self, config):
400-
if "PORT" not in config:
407+
for pattern in self.create_only_patterns:
408+
for create_only_path in self._get_create_only_path_recursive(config, pattern, [], 0):
409+
yield create_only_path
410+
411+
def _get_create_only_path_recursive(self, config, pattern_tokens, matching_tokens, idx):
412+
if idx == len(pattern_tokens):
413+
yield '/' + '/'.join(matching_tokens)
401414
return
402415

403-
ports = config["PORT"]
416+
matching_keys = []
417+
if pattern_tokens[idx] == "*":
418+
matching_keys = config.keys()
419+
elif pattern_tokens[idx] in config:
420+
matching_keys = [pattern_tokens[idx]]
404421

405-
for port in ports:
406-
attrs = ports[port]
407-
if "lanes" in attrs:
408-
yield f"/PORT/{port}/lanes"
422+
for key in matching_keys:
423+
matching_tokens.append(key)
424+
for create_only_path in self._get_create_only_path_recursive(config[key], pattern_tokens, matching_tokens, idx+1):
425+
yield create_only_path
426+
matching_tokens.pop()
409427

410428
def _value_exist_but_different(self, tokens, current_config_ptr, simulated_config_ptr):
411429
for token in tokens:
@@ -422,6 +440,46 @@ def _value_exist_but_different(self, tokens, current_config_ptr, simulated_confi
422440

423441
return current_config_ptr != simulated_config_ptr
424442

443+
def _value_added_but_parent_exist(self, tokens, current_config_ptr, simulated_config_ptr):
444+
# if value is not added, return false
445+
if not self._exist_only_in_first(tokens, simulated_config_ptr, current_config_ptr):
446+
return False
447+
448+
# if parent is added, return false
449+
if self._exist_only_in_first(tokens[:-1], simulated_config_ptr, current_config_ptr):
450+
return False
451+
452+
# otherwise parent exist and value is added
453+
return True
454+
455+
def _value_removed_but_parent_remain(self, tokens, current_config_ptr, simulated_config_ptr):
456+
# if value is not removed, return false
457+
if not self._exist_only_in_first(tokens, current_config_ptr, simulated_config_ptr):
458+
return False
459+
460+
# if parent is removed, return false
461+
if self._exist_only_in_first(tokens[:-1], current_config_ptr, simulated_config_ptr):
462+
return False
463+
464+
# otherwise parent remained and value is removed
465+
return True
466+
467+
def _exist_only_in_first(self, tokens, first_config_ptr, second_config_ptr):
468+
for token in tokens:
469+
mod_token = int(token) if isinstance(first_config_ptr, list) else token
470+
471+
if mod_token not in second_config_ptr:
472+
return True
473+
474+
if mod_token not in first_config_ptr:
475+
return False
476+
477+
first_config_ptr = first_config_ptr[mod_token]
478+
second_config_ptr = second_config_ptr[mod_token]
479+
480+
# tokens exist in both
481+
return False
482+
425483
class NoDependencyMoveValidator:
426484
"""
427485
A class to validate that the modified configs do not have dependency on each other. This should prevent
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"LOOPBACK_INTERFACE": {
3+
"Loopback0": {},
4+
"Loopback0|10.1.0.32/32": {},
5+
"Loopback0|1100:1::32/128": {},
6+
"Loopback1": {
7+
"vrf_name": "Vrf_02"
8+
},
9+
"Loopback1|20.2.0.32/32": {},
10+
"Loopback1|2200:2::32/128": {}
11+
},
12+
"VRF": {
13+
"Vrf_01": {},
14+
"Vrf_02": {}
15+
}
16+
}

tests/generic_config_updater/patch_sorter_test.py

+79-10
Original file line numberDiff line numberDiff line change
@@ -784,13 +784,6 @@ def setUp(self):
784784
self.validator = ps.CreateOnlyMoveValidator(ps.PathAddressing())
785785
self.any_diff = ps.Diff({}, {})
786786

787-
def test_validate__non_replace_operation__success(self):
788-
# Assert
789-
self.assertTrue(self.validator.validate( \
790-
ps.JsonMove(self.any_diff, OperationType.ADD, [], []), self.any_diff))
791-
self.assertTrue(self.validator.validate( \
792-
ps.JsonMove(self.any_diff, OperationType.REMOVE, [], []), self.any_diff))
793-
794787
def test_validate__no_create_only_field__success(self):
795788
current_config = {"PORT": {}}
796789
target_config = {"PORT": {}, "ACL_TABLE": {}}
@@ -833,30 +826,86 @@ def test_validate__different_create_only_field_updating_grandparent__failure(sel
833826
["PORT"],
834827
False)
835828

836-
def test_validate__same_create_only_field_directly_updated__failure(self):
829+
def test_validate__same_create_only_field_directly_updated__success(self):
837830
current_config = {"PORT": {"Ethernet0":{"lanes":"65"}}}
838831
target_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
839832
self.verify_diff(current_config,
840833
target_config,
841834
["PORT", "Ethernet0", "lanes"],
842835
["PORT", "Ethernet0", "lanes"])
843836

844-
def test_validate__same_create_only_field_updating_parent__failure(self):
837+
def test_validate__same_create_only_field_updating_parent__success(self):
845838
current_config = {"PORT": {"Ethernet0":{"lanes":"65"}}}
846839
target_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
847840
self.verify_diff(current_config,
848841
target_config,
849842
["PORT", "Ethernet0"],
850843
["PORT", "Ethernet0"])
851844

852-
def test_validate__same_create_only_field_updating_grandparent__failure(self):
845+
def test_validate__same_create_only_field_updating_grandparent__success(self):
853846
current_config = {"PORT": {"Ethernet0":{"lanes":"65"}}}
854847
target_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
855848
self.verify_diff(current_config,
856849
target_config,
857850
["PORT"],
858851
["PORT"])
859852

853+
def test_validate__added_create_only_field_parent_exist__failure(self):
854+
current_config = {"PORT": {"Ethernet0":{}}}
855+
target_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
856+
self.verify_diff(current_config,
857+
target_config,
858+
["PORT"],
859+
["PORT"],
860+
expected=False)
861+
862+
def test_validate__added_create_only_field_parent_doesnot_exist__success(self):
863+
current_config = {"PORT": {}}
864+
target_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
865+
self.verify_diff(current_config,
866+
target_config,
867+
["PORT"],
868+
["PORT"])
869+
870+
def test_validate__removed_create_only_field_parent_remain__failure(self):
871+
current_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
872+
target_config = {"PORT": {"Ethernet0":{}}}
873+
self.verify_diff(current_config,
874+
target_config,
875+
["PORT"],
876+
["PORT"],
877+
expected=False)
878+
879+
def test_validate__removed_create_only_field_parent_doesnot_remain__success(self):
880+
current_config = {"PORT": {"Ethernet0":{"lanes":"65"}}, "ACL_TABLE": {}}
881+
target_config = {"PORT": {}}
882+
self.verify_diff(current_config,
883+
target_config,
884+
["PORT"],
885+
["PORT"])
886+
887+
def test_hard_coded_create_only_paths(self):
888+
config = {
889+
"PORT": {
890+
"Ethernet0":{"lanes":"65"},
891+
"Ethernet1":{},
892+
"Ethernet2":{"lanes":"66,67"}
893+
},
894+
"LOOPBACK_INTERFACE": {
895+
"Loopback0":{"vrf_name":"vrf0"},
896+
"Loopback1":{},
897+
"Loopback2":{"vrf_name":"vrf1"},
898+
}}
899+
expected = [
900+
"/PORT/Ethernet0/lanes",
901+
"/PORT/Ethernet2/lanes",
902+
"/LOOPBACK_INTERFACE/Loopback0/vrf_name",
903+
"/LOOPBACK_INTERFACE/Loopback2/vrf_name"
904+
]
905+
actual = self.validator._get_create_only_paths(config)
906+
907+
self.assertCountEqual(expected, actual)
908+
860909
def verify_diff(self, current_config, target_config, current_config_tokens=None, target_config_tokens=None, expected=True):
861910
# Arrange
862911
current_config_tokens = current_config_tokens if current_config_tokens else []
@@ -1741,6 +1790,26 @@ def test_sort__replacing_create_only_field__success(self):
17411790
# Assert
17421791
self.assertNotEqual(None, actual)
17431792

1793+
def test_sort__adding_create_only_field__success(self):
1794+
# Arrange
1795+
patch = jsonpatch.JsonPatch([{"op":"add", "path": "/LOOPBACK_INTERFACE/Loopback0/vrf_name", "value":"Vrf_01"}])
1796+
1797+
# Act
1798+
actual = self.create_patch_sorter(Files.CONFIG_DB_WITH_LOOPBACK_INTERFACES).sort(patch)
1799+
1800+
# Assert
1801+
self.assertNotEqual(None, actual)
1802+
1803+
def test_sort__removing_create_only_field__success(self):
1804+
# Arrange
1805+
patch = jsonpatch.JsonPatch([{"op":"remove", "path": "/LOOPBACK_INTERFACE/Loopback1/vrf_name"}])
1806+
1807+
# Act
1808+
actual = self.create_patch_sorter(Files.CONFIG_DB_WITH_LOOPBACK_INTERFACES).sort(patch)
1809+
1810+
# Assert
1811+
self.assertNotEqual(None, actual)
1812+
17441813
def test_sort__inter_dependency_within_same_table__success(self):
17451814
# Arrange
17461815
patch = jsonpatch.JsonPatch([{"op":"add", "path":"/VLAN_INTERFACE", "value": {

0 commit comments

Comments
 (0)