Skip to content

Commit a5e1e2b

Browse files
authored
[GCU] Add RemoveCreateOnlyDependency Validator/Generator (sonic-net#2500)
What I did Add RemoveCreateOnlyDependency Generator and Validator. How I did it Added new validator/generator for handling the lane replacement case. The validator/generator understands that for which create-only fields and their dependencies need to be removed. How to verify it Unit Test.
1 parent 6411b52 commit a5e1e2b

File tree

4 files changed

+438
-271
lines changed

4 files changed

+438
-271
lines changed

generic_config_updater/patch_sorter.py

Lines changed: 186 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,125 @@ def _get_default_value_from_settings(self, parent_path, field_name):
544544

545545
return None
546546

547+
class CreateOnlyFilter:
548+
"""
549+
A filtering class for create-only fields.
550+
"""
551+
def __init__(self, path_addressing):
552+
# TODO: create-only fields are hard-coded for now, it should be moved to YANG model
553+
self.path_addressing = path_addressing
554+
self.patterns = [
555+
["PORT", "*", "lanes"],
556+
["LOOPBACK_INTERFACE", "*", "vrf_name"],
557+
["BGP_NEIGHBOR", "*", "holdtime"],
558+
["BGP_NEIGHBOR", "*", "keepalive"],
559+
["BGP_NEIGHBOR", "*", "name"],
560+
["BGP_NEIGHBOR", "*", "asn"],
561+
["BGP_NEIGHBOR", "*", "local_addr"],
562+
["BGP_NEIGHBOR", "*", "nhopself"],
563+
["BGP_NEIGHBOR", "*", "rrclient"],
564+
["BGP_PEER_RANGE", "*", "*"],
565+
["BGP_MONITORS", "*", "holdtime"],
566+
["BGP_MONITORS", "*", "keepalive"],
567+
["BGP_MONITORS", "*", "name"],
568+
["BGP_MONITORS", "*", "asn"],
569+
["BGP_MONITORS", "*", "local_addr"],
570+
["BGP_MONITORS", "*", "nhopself"],
571+
["BGP_MONITORS", "*", "rrclient"],
572+
["MIRROR_SESSION", "*", "*"],
573+
]
574+
575+
def get_filter(self):
576+
return JsonPointerFilter(self.patterns,
577+
self.path_addressing)
578+
579+
class RemoveCreateOnlyDependencyMoveValidator:
580+
"""
581+
A class to validate all dependencies of create-only fields have been removed before
582+
modifying the create-only fields.
583+
"""
584+
def __init__(self, path_addressing):
585+
self.path_addressing = path_addressing
586+
self.create_only_filter = CreateOnlyFilter(path_addressing).get_filter()
587+
588+
def validate(self, move, diff):
589+
current_config = diff.current_config
590+
target_config = diff.target_config # Final config after applying whole patch
591+
592+
processed_tables = set()
593+
for path in self.create_only_filter.get_paths(current_config):
594+
tokens = self.path_addressing.get_path_tokens(path)
595+
table_to_check = tokens[0]
596+
597+
if table_to_check in processed_tables:
598+
continue
599+
else:
600+
processed_tables.add(table_to_check)
601+
602+
if table_to_check not in current_config:
603+
continue
604+
605+
current_members = current_config[table_to_check]
606+
if not current_members:
607+
continue
608+
609+
if table_to_check not in target_config:
610+
continue
611+
612+
target_members = target_config[table_to_check]
613+
if not target_members:
614+
continue
615+
616+
simulated_config = move.apply(current_config) # Config after applying just this move
617+
618+
for member_name in current_members:
619+
if member_name not in target_members:
620+
continue
621+
622+
if not self._validate_member(tokens, member_name,
623+
current_config, target_config, simulated_config):
624+
return False
625+
626+
return True
627+
628+
def _validate_member(self, tokens, member_name, current_config, target_config, simulated_config):
629+
table_to_check, create_only_field = tokens[0], tokens[-1]
630+
631+
current_field = self._get_create_only_field(
632+
current_config, table_to_check, member_name, create_only_field)
633+
target_field = self._get_create_only_field(
634+
target_config, table_to_check, member_name, create_only_field)
635+
636+
if current_field == target_field:
637+
return True
638+
639+
simulated_member = self.path_addressing.get_from_path(
640+
simulated_config, f"/{table_to_check}/{member_name}")
641+
642+
if simulated_member is None:
643+
return True
644+
645+
if table_to_check == "PORT":
646+
current_admin_status = self.path_addressing.get_from_path(
647+
current_config, f"/{table_to_check}/{member_name}/admin_status"
648+
)
649+
simulated_admin_status = self.path_addressing.get_from_path(
650+
simulated_config, f"/{table_to_check}/{member_name}/admin_status"
651+
)
652+
if current_admin_status != simulated_admin_status and current_admin_status != "up":
653+
return False
654+
655+
member_path = f"/{table_to_check}/{member_name}"
656+
for ref_path in self.path_addressing.find_ref_paths(member_path, simulated_config):
657+
if not self.path_addressing.has_path(current_config, ref_path):
658+
return False
659+
660+
return True
661+
662+
def _get_create_only_field(self, config, table_to_check,
663+
member_name, create_only_field):
664+
return config[table_to_check][member_name].get(create_only_field, None)
665+
547666
class DeleteWholeConfigMoveValidator:
548667
"""
549668
A class to validate not deleting whole config as it is not supported by JsonPatch lib.
@@ -576,27 +695,7 @@ def __init__(self, path_addressing):
576695
self.path_addressing = path_addressing
577696

578697
# TODO: create-only fields are hard-coded for now, it should be moved to YANG models
579-
self.create_only_filter = JsonPointerFilter([
580-
["PORT", "*", "lanes"],
581-
["LOOPBACK_INTERFACE", "*", "vrf_name"],
582-
["BGP_NEIGHBOR", "*", "holdtime"],
583-
["BGP_NEIGHBOR", "*", "keepalive"],
584-
["BGP_NEIGHBOR", "*", "name"],
585-
["BGP_NEIGHBOR", "*", "asn"],
586-
["BGP_NEIGHBOR", "*", "local_addr"],
587-
["BGP_NEIGHBOR", "*", "nhopself"],
588-
["BGP_NEIGHBOR", "*", "rrclient"],
589-
["BGP_PEER_RANGE", "*", "*"],
590-
["BGP_MONITORS", "*", "holdtime"],
591-
["BGP_MONITORS", "*", "keepalive"],
592-
["BGP_MONITORS", "*", "name"],
593-
["BGP_MONITORS", "*", "asn"],
594-
["BGP_MONITORS", "*", "local_addr"],
595-
["BGP_MONITORS", "*", "nhopself"],
596-
["BGP_MONITORS", "*", "rrclient"],
597-
["MIRROR_SESSION", "*", "*"],
598-
],
599-
path_addressing)
698+
self.create_only_filter = CreateOnlyFilter(path_addressing).get_filter()
600699

601700
def validate(self, move, diff):
602701
simulated_config = move.apply(diff.current_config)
@@ -921,6 +1020,8 @@ def validate(self, move, diff):
9211020
for required_path, required_value in data[path]:
9221021
current_value = self.identifier.get_value_or_default(current_config, required_path)
9231022
simulated_value = self.identifier.get_value_or_default(simulated_config, required_path)
1023+
if simulated_value is None: # Simulated config does not have this value at all.
1024+
continue
9241025
if current_value != simulated_value and simulated_value != required_value:
9251026
return False
9261027

@@ -1000,6 +1101,65 @@ def generate(self, diff):
10001101
for move in single_run_generator.generate():
10011102
yield move
10021103

1104+
class RemoveCreateOnlyDependencyMoveGenerator:
1105+
"""
1106+
A class to generate the create-only fields' dependency removing moves
1107+
"""
1108+
def __init__(self, path_addressing):
1109+
self.path_addressing = path_addressing
1110+
self.create_only_filter = CreateOnlyFilter(path_addressing).get_filter()
1111+
1112+
def generate(self, diff):
1113+
current_config = diff.current_config
1114+
target_config = diff.target_config # Final config after applying whole patch
1115+
1116+
processed_tables = set()
1117+
for path in self.create_only_filter.get_paths(current_config):
1118+
tokens = self.path_addressing.get_path_tokens(path)
1119+
table_to_check, create_only_field = tokens[0], tokens[-1]
1120+
1121+
if table_to_check in processed_tables:
1122+
continue
1123+
else:
1124+
processed_tables.add(table_to_check)
1125+
1126+
if table_to_check not in current_config:
1127+
continue
1128+
1129+
current_members = current_config[table_to_check]
1130+
if not current_members:
1131+
continue
1132+
1133+
if table_to_check not in target_config:
1134+
continue
1135+
1136+
target_members = target_config[table_to_check]
1137+
if not target_members:
1138+
continue
1139+
1140+
for member_name in current_members:
1141+
if member_name not in target_members:
1142+
continue
1143+
1144+
current_field = self._get_create_only_field(
1145+
current_config, table_to_check, member_name, create_only_field)
1146+
target_field = self._get_create_only_field(
1147+
target_config, table_to_check, member_name, create_only_field)
1148+
1149+
if current_field == target_field:
1150+
continue
1151+
1152+
member_path = f"/{table_to_check}/{member_name}"
1153+
1154+
for ref_path in self.path_addressing.find_ref_paths(member_path, current_config):
1155+
yield JsonMove(diff, OperationType.REMOVE,
1156+
self.path_addressing.get_path_tokens(ref_path))
1157+
1158+
def _get_create_only_field(self, config, table_to_check,
1159+
member_name, create_only_field):
1160+
return config[table_to_check][member_name].get(create_only_field, None)
1161+
1162+
10031163
class SingleRunLowLevelMoveGenerator:
10041164
"""
10051165
A class that can only run once to assist LowLevelMoveGenerator with generating the moves.
@@ -1271,6 +1431,8 @@ def extend(self, move, diff):
12711431
for required_path, required_value in data[path]:
12721432
current_value = self.identifier.get_value_or_default(current_config, required_path)
12731433
simulated_value = self.identifier.get_value_or_default(simulated_config, required_path)
1434+
if simulated_value is None: # Simulated config does not have this value at all.
1435+
continue
12741436
if current_value != simulated_value and simulated_value != required_value:
12751437
flip_path_value_tuples.add((required_path, required_value))
12761438

@@ -1473,7 +1635,8 @@ def __init__(self, operation_wrapper, config_wrapper, path_addressing):
14731635
self.path_addressing = path_addressing
14741636

14751637
def create(self, algorithm=Algorithm.DFS):
1476-
move_generators = [LowLevelMoveGenerator(self.path_addressing)]
1638+
move_generators = [RemoveCreateOnlyDependencyMoveGenerator(self.path_addressing),
1639+
LowLevelMoveGenerator(self.path_addressing)]
14771640
# TODO: Enable TableLevelMoveGenerator once it is confirmed whole table can be updated at the same time
14781641
move_non_extendable_generators = [KeyLevelMoveGenerator()]
14791642
move_extenders = [RequiredValueMoveExtender(self.path_addressing, self.operation_wrapper),
@@ -1485,6 +1648,7 @@ def create(self, algorithm=Algorithm.DFS):
14851648
NoDependencyMoveValidator(self.path_addressing, self.config_wrapper),
14861649
CreateOnlyMoveValidator(self.path_addressing),
14871650
RequiredValueMoveValidator(self.path_addressing),
1651+
RemoveCreateOnlyDependencyMoveValidator(self.path_addressing),
14881652
NoEmptyTableMoveValidator(self.path_addressing)]
14891653

14901654
move_wrapper = MoveWrapper(move_generators, move_non_extendable_generators, move_extenders, move_validators)

tests/generic_config_updater/files/config_db_with_port_critical.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@
3636
"lanes": "41,42,43,44",
3737
"pfc_asym": "off",
3838
"speed": "40000"
39+
},
40+
"Ethernet28": {
41+
"alias": "fortyGigE0/28",
42+
"description": "Servers5:eth0",
43+
"index": "6",
44+
"lanes": "53,54,55,56",
45+
"pfc_asym": "off",
46+
"speed": "40000"
47+
},
48+
"Ethernet32": {
49+
"alias": "fortyGigE0/32",
50+
"description": "Servers6:eth0",
51+
"index": "7",
52+
"lanes": "57,58,59,60",
53+
"mtu": "9100",
54+
"pfc_asym": "off",
55+
"speed": "40000"
3956
}
4057
},
4158
"BUFFER_PG": {
@@ -44,6 +61,9 @@
4461
},
4562
"Ethernet12|0": {
4663
"profile": "ingress_lossy_profile"
64+
},
65+
"Ethernet28|0": {
66+
"profile": "ingress_lossy_profile"
4767
}
4868
}
4969
}

0 commit comments

Comments
 (0)