From 0688aa38ae83feda6ce7af50941600413fedc662 Mon Sep 17 00:00:00 2001 From: Vladimir Kuk <31180446+VladimirKuk@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:51:04 +0200 Subject: [PATCH] Missing container in list support in YANG Model #16704 (#18091) Fixes "Missing container in list support in YANG Model https://github.com/sonic-net/sonic-buildimage/issues/16704" Why I did it Adds support for container in list How I did it Identify container in list's leaf and add its data. Fixes "Update dhcpv6 option yang model" #16290 Why I did it Adds support for single "choice" statement in container/list How I did it Check if choice data is dictionary (instead of list). How to verify it Reconstruction details in bug's description. Tested branch (Please provide the tested image version) 202311 Description for the changelog Adds support for container in list to yang parsing Link to config_db schema for YANG module changes https://github.com/sonic-net/sonic-buildimage/blob/master/src/sonic-yang-models/doc/Configuration.md#dhcp_relay Signed-off-by: vkuk [vkuk@marvell.com] --- src/sonic-yang-mgmt/sonic_yang_ext.py | 66 ++++++++++- .../libyang-python-tests/config_data.json | 34 ++++++ .../test-yang-structure.yang | 105 ++++++++++++++++++ .../libyang-python-tests/test_SonicYang.json | 8 ++ 4 files changed, 210 insertions(+), 3 deletions(-) create mode 100755 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-yang-structure.yang diff --git a/src/sonic-yang-mgmt/sonic_yang_ext.py b/src/sonic-yang-mgmt/sonic_yang_ext.py index 6fd4f4ef4e..c1624e6958 100644 --- a/src/sonic-yang-mgmt/sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/sonic_yang_ext.py @@ -378,10 +378,17 @@ class SonicYangExtMixin: #choices, this is tricky, since leafs are under cases in tree. choices = model.get('choice') if choices: - for choice in choices: - cases = choice['case'] + # If single choice exists in container/list + if isinstance(choices, dict): + cases = choices['case'] for case in cases: self._fillLeafDict(case.get('leaf'), leafDict) + # If multiple choices exist in container/list + else: + for choice in choices: + cases = choice['case'] + for case in cases: + self._fillLeafDict(case.get('leaf'), leafDict) # leaf-lists self._fillLeafDict(model.get('leaf-list'), leafDict, True) @@ -535,6 +542,29 @@ class SonicYangExtMixin: continue return + """ + Process container inside a List. + This function will call xlateContainer based on Container(s) present + in outer List. + """ + def _xlateContainerInList(self, model, yang, configC, table): + ccontainer = model + ccName = ccontainer.get('@name') + if ccName not in configC: + # Inner container doesn't exist in config + return + + if bool(configC[ccName]): + # Empty container - return + return + + self.sysLog(msg="xlateProcessListOfContainer: {}".format(ccName)) + self.elementPath.append(ccName) + self._xlateContainer(ccontainer, yang, configC[ccName], table) + self.elementPath.pop() + + return + """ Xlate a list This function will xlate from a dict in config DB to a Yang JSON list @@ -553,6 +583,9 @@ class SonicYangExtMixin: self._xlateType1MapList(model, yang, config, table, exceptionList) return + # For handling of container(s) in list + ccontainer = model.get('container') + #create a dict to map each key under primary key with a dict yang model. #This is done to improve performance of mapping from values of TABLEs in #config DB to leaf in YANG LIST. @@ -572,6 +605,19 @@ class SonicYangExtMixin: keyDict = self._extractKey(pkey, listKeys) # fill rest of the values in keyDict for vKey in config[pkey]: + if ccontainer and vKey == ccontainer.get('@name'): + self.sysLog(syslog.LOG_DEBUG, "xlateList Handle container {} in list {}".\ + format(vKey, table)) + yangContainer = dict() + if isinstance(ccontainer, dict) and bool(config): + self._xlateContainerInList(ccontainer, yangContainer, config[pkey], table) + # If multi-list exists in container, + elif ccontainer and isinstance(ccontainer, list) and bool(config): + for modelContainer in ccontainer: + self._xlateContainerInList(modelContainer, yangContainer, config[pkey], table) + if len(yangContainer): + keyDict[vKey] = yangContainer + continue self.elementPath.append(vKey) self.sysLog(syslog.LOG_DEBUG, "xlateList vkey {}".format(vKey)) try: @@ -617,7 +663,7 @@ class SonicYangExtMixin: """ def _xlateContainerInContainer(self, model, yang, configC, table): ccontainer = model - ccName = ccontainer['@name'] + ccName = ccontainer.get('@name') yang[ccName] = dict() if ccName not in configC: # Inner container doesn't exist in config @@ -876,6 +922,9 @@ class SonicYangExtMixin: self._revXlateType1MapList(model, yang, config, table) return + # For handling of container(s) in list + ccontainer = model.get('container') + # get keys from YANG model list itself listKeys = model['key']['@value'] # create a dict to map each key under primary key with a dict yang model. @@ -894,6 +943,17 @@ class SonicYangExtMixin: # fill rest of the entries for key in entry: if key not in pkeydict: + if ccontainer and key == ccontainer['@name']: + self.sysLog(syslog.LOG_DEBUG, "revXlateList handle container {} in list {}".format(pkey, table)) + # IF container has only one inner container + if isinstance(ccontainer, dict): + self._revXlateContainerInContainer(ccontainer, entry, config[pkey], table) + # IF container has many inner container + elif isinstance(ccontainer, list): + for modelContainer in ccontainer: + self._revXlateContainerInContainer(modelContainer, entry, config[pkey], table) + continue + self.elementPath.append(key) config[pkey][key] = self._revFindYangTypedValue(key, \ entry[key], leafDict) diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json index 8a4eaed176..154ef21b49 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json @@ -256,5 +256,39 @@ } ] } + }, + + "test-yang-structure:test-yang-container": { + "test-yang-structure:YANG_STRUCT_TEST": { + "YANG_LIST_TEST_LIST": [{ + "name" : "Vlan1001", + "leaf-list-test": [ + "fc02:2000::1", + "fc02:2000::2" + ], + "container-in-list-test": { + "leaf-1": true, + "leaf-2": "test1", + "mc-case-leaf-1": 55, + "mc-case-leaf-3": 1234 + }, + "case-leaf-1": 101 + }, + { + "name" : "Test123", + "leaf-list-test": [ + "3003:2000::1", + "2002:2001::2" + ], + "container-in-list-test": { + "leaf-1": false, + "leaf-2": "test2", + "mc-case-leaf-2": 77, + "mc-case-leaf-3": 4321 + }, + "case-leaf-2": 1001 + } + ] + } } } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-yang-structure.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-yang-structure.yang new file mode 100755 index 0000000000..1f57ae337a --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-yang-structure.yang @@ -0,0 +1,105 @@ +module test-yang-structure { + + namespace "http://github.com/sonic-net/test"; + prefix yangstructtest; + + yang-version 1.1; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import test-head { + prefix head; + revision-date 2019-07-01; + } + + revision 2021-10-30 { + description "First Revision"; + } + + container test-yang-container { + + container YANG_STRUCT_TEST { + + description "sample test container"; + + list YANG_LIST_TEST_LIST { + + key "name"; + + leaf name { + type string; + } + + leaf-list leaf-list-test { + description "Test leaf-list statement"; + type inet:ipv6-address; + } + + container container-in-list-test { + leaf leaf-1 { + description "test leaf in container"; + type string { + pattern "false|true"; + } + } + + leaf leaf-2 { + description "test leaf in container"; + type string; + } + + choice multi-choice-in-container-test-1 { + case mc-case-test-1 { + leaf mc-case-leaf-1 { + description "test leaf in multi choice"; + type uint32; + } + } + + case mc-case-test-2 { + leaf mc-case-leaf-2 { + description "test leaf in multi choice"; + type uint8; + } + } + } + + choice multi-choice-in-container-test-2 { + case mc-case-test-3 { + leaf mc-case-leaf-3 { + description "test leaf in multi choice"; + type uint16; + } + } + } + } + + choice single-choice-in-list-test { + case case-test-1 { + leaf case-leaf-1 { + description "test leaf in single choice"; + type uint32; + } + } + + case case-test-2 { + leaf case-leaf-2 { + description "test leaf in single choice"; + type uint16; + } + } + } + } + /* end of YANG_LIST_TEST_LIST */ + } + /* end of container YANG_STRUCT_TEST */ + } + /* end of container test-yang-container */ +} +/* end of module test-yang-structure */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json index b62322d50b..2376c357b7 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json @@ -168,6 +168,10 @@ { "file" : "test-vlan.yang", "module" : "test-vlan" + }, + { + "file" : "test-yang-structure.yang", + "module" : "test-yang-structure" } ], "new_nodes" : [ @@ -244,6 +248,10 @@ { "module_name" : "test-vlan", "module_prefix" : "vlan" + }, + { + "module_name" : "test-yang-structure", + "module_prefix" : "yangstructtest" } ], "schema_dependencies" : [