-
Notifications
You must be signed in to change notification settings - Fork 810
YANG Validation for ConfigDB Updates: TACPLUS, TACPLUS_SERVER, AAA, VLAN_SUB_INTERFACE tables + decorated validated_mod_entry #2452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b4f466d
c3544a2
6313ce7
3e25495
b20dfb9
650e723
18a5e1a
c257eb9
0bc9fa7
3ce3dbc
5899db9
08ee911
f29e499
07fd1ce
b7902d8
d945fb2
3c57a56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import jsonpatch | ||
| import copy | ||
| from jsonpointer import JsonPointer | ||
|
|
||
| from sonic_py_common import device_info | ||
|
|
@@ -17,33 +18,83 @@ def __getattr__(self, name): | |
| return self.validated_set_entry | ||
| if name == "delete_table": | ||
| return self.validated_delete_table | ||
| if name == "mod_entry": | ||
| return self.validated_mod_entry | ||
| return self.connector.__getattribute__(name) | ||
|
|
||
| def stringify_value(self, value): | ||
| if isinstance(value, dict): | ||
| value = {str(k):str(v) for k, v in value.items()} | ||
| else: | ||
| value = str(value) | ||
| return value | ||
|
|
||
| def make_path_value_jsonpatch_compatible(self, table, key, value): | ||
| if type(key) == tuple: | ||
| path = JsonPointer.from_parts([table, '|'.join(key)]).path | ||
| elif type(key) == list: | ||
| path = JsonPointer.from_parts([table, *key]).path | ||
| else: | ||
| path = JsonPointer.from_parts([table, key]).path | ||
| if value == {"NULL" : "NULL"}: | ||
| value = {} | ||
| else: | ||
| value = self.stringify_value(value) | ||
| return path, value | ||
|
|
||
| def create_gcu_patch(self, op, table, key=None, value=None): | ||
| if key: | ||
| path, value = self.make_path_value_jsonpatch_compatible(table, key, value) | ||
| else: | ||
| path = "/{}".format(table) | ||
|
|
||
| def create_gcu_patch(self, op, table, key=None, value=None, mod_entry=False): | ||
| gcu_json_input = [] | ||
| gcu_json = {"op": "{}".format(op), | ||
| "path": "{}".format(path)} | ||
| if op == "add": | ||
| gcu_json["value"] = value | ||
| """Add patch element to create new table if necessary, as GCU is unable to add to nonexistent table""" | ||
| if op == "add" and not self.get_table(table): | ||
| gcu_json = {"op": "{}".format(op), | ||
| "path": "/{}".format(table), | ||
| "value": {}} | ||
| gcu_json_input.append(gcu_json) | ||
|
|
||
| """Add patch element to create ConfigDB path if necessary, as GCU is unable to add to a nonexistent path""" | ||
| if op == "add" and not self.get_entry(table, key): | ||
| path = JsonPointer.from_parts([table, key]).path | ||
| gcu_json = {"op": "{}".format(op), | ||
| "path": "{}".format(path), | ||
| "value": {}} | ||
| gcu_json_input.append(gcu_json) | ||
|
|
||
| def add_patch_entry(): | ||
| if key: | ||
| patch_path, patch_value = self.make_path_value_jsonpatch_compatible(table, key, value) | ||
| else: | ||
| patch_path = "/{}".format(table) | ||
|
|
||
| gcu_json = {"op": "{}".format(op), | ||
| "path": "{}".format(patch_path)} | ||
| if op == "add": | ||
| gcu_json["value"] = patch_value | ||
|
|
||
| gcu_json_input.append(gcu_json) | ||
|
|
||
| """mod_entry makes path more granular so that preexisting fields in db are not removed""" | ||
| if mod_entry: | ||
| key_start = key | ||
| value_copy = copy.deepcopy(value) | ||
| for key_end, cleaned_value in value_copy.items(): | ||
| key = [key_start, key_end] | ||
| value = cleaned_value | ||
| add_patch_entry() | ||
| else: | ||
| add_patch_entry() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. set_entry and mod_entry both use jsonpatch The difference between set_entry and mod_entry is that set_entry will remove extra fields in the db which are not in the data. So this difference is captured by differing paths in the jsonpatch. The path for mod_entry is more granular.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. set_entry is not able to be directly translated to jsonpatch For example, if we try to add a new portchannel, we will get the following error: |
||
|
|
||
| gcu_json_input.append(gcu_json) | ||
| gcu_patch = jsonpatch.JsonPatch(gcu_json_input) | ||
| return gcu_patch | ||
|
|
||
| def apply_patch(self, gcu_patch, table): | ||
| format = ConfigFormat.CONFIGDB.name | ||
| config_format = ConfigFormat[format.upper()] | ||
|
|
||
| try: | ||
| GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None) | ||
| except EmptyTableError: | ||
| self.validated_delete_table(table) | ||
|
|
||
| def validated_delete_table(self, table): | ||
| gcu_patch = self.create_gcu_patch("remove", table) | ||
| format = ConfigFormat.CONFIGDB.name | ||
|
|
@@ -54,17 +105,20 @@ def validated_delete_table(self, table): | |
| logger = genericUpdaterLogging.get_logger(title="Patch Applier", print_all_to_console=True) | ||
| logger.log_notice("Unable to remove entry, as doing so will result in invalid config. Error: {}".format(e)) | ||
|
|
||
| def validated_mod_entry(self, table, key, value): | ||
| if value is not None: | ||
| op = "add" | ||
| else: | ||
| op = "remove" | ||
|
|
||
| gcu_patch = self.create_gcu_patch(op, table, key, value, mod_entry=True) | ||
| self.apply_patch(gcu_patch, table) | ||
|
|
||
| def validated_set_entry(self, table, key, value): | ||
| if value is not None: | ||
| op = "add" | ||
| else: | ||
| op = "remove" | ||
|
|
||
| gcu_patch = self.create_gcu_patch(op, table, key, value) | ||
| format = ConfigFormat.CONFIGDB.name | ||
| config_format = ConfigFormat[format.upper()] | ||
|
|
||
| try: | ||
| GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None) | ||
| except EmptyTableError: | ||
| self.validated_delete_table(table) | ||
| gcu_patch = self.create_gcu_patch(op, table, key, value) | ||
| self.apply_patch(gcu_patch, table) | ||
Uh oh!
There was an error while loading. Please reload this page.