Skip to content

Commit

Permalink
[sonic_yang_ext.py]: Support Grouping translation in YANG Models.
Browse files Browse the repository at this point in the history
Changes:
 -- pre Process Grouping section from all yang models, so it can be used
from any yang model.
-- add jsondiff in setup.py, it is useful for test debugging in case of
failures.
-- use 'stypes' instead of head.
-- pass config DB table name in _createLeafDict().
-- added test config for grouping.
-- white spaces changes .

Note: Changes are done in the way that we can add supoort for other
Generic YANG statement easily for tranlation.

Signed-off-by: Praveen Chaudhary [email protected]
  • Loading branch information
Praveen Chaudhary committed Aug 24, 2021
1 parent 5ff87e1 commit c2dc42f
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 23 deletions.
6 changes: 4 additions & 2 deletions src/sonic-yang-mgmt/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
long_description=readme + '\n\n',
install_requires = [
'xmltodict==0.12.0',
'ijson==2.6.1'
'ijson==2.6.1',
'jsondiff>=1.2.0',
],
tests_require = [
'pytest>3',
'xmltodict==0.12.0',
'ijson==2.6.1'
'ijson==2.6.1',
'jsondiff>=1.2.0'
],
setup_requires = [
'pytest-runner',
Expand Down
3 changes: 3 additions & 0 deletions src/sonic-yang-mgmt/sonic_yang.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __init__(self, yang_dir, debug=False):
self.revXlateJson = dict()
# below dict store the input config tables which have no YANG models
self.tablesWithOutYang = dict()
# below dict will store preProcessed yang objects, which may be needed by
# all yang modules, such as grouping.
self.preProcessedYang = dict()

try:
self.ctx = ly.Context(yang_dir)
Expand Down
173 changes: 159 additions & 14 deletions src/sonic-yang-mgmt/sonic_yang_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ def loadYangModel(self):
self._loadJsonYangModel()
# create a map from config DB table to yang container
self._createDBTableToModuleMap()

except Exception as e:
self.sysLog(msg="Yang Models Load failed:{}".format(str(e)), \
debug=syslog.LOG_ERR, doPrint=True)
Expand All @@ -71,6 +70,70 @@ def _loadJsonYangModel(self):

return

def _preProcessYangGrouping(self, moduleName, module):
'''
PreProcess Grouping Section of YANG models, and store it in
self.preProcessedYang['grouping'] as
{'<moduleName>':
{'<groupingName>':
[<List of Leafs>]
}
}
Parameters:
moduleName (str): name of yang module.
module (dict): json format of yang module.
Returns:
void
'''
try:
# create grouping dict
if self.preProcessedYang.get('grouping') is None:
self.preProcessedYang['grouping'] = dict()
self.preProcessedYang['grouping'][moduleName] = dict()

# get groupings from yang module
groupings = module['grouping']

# if grouping is a dict, make it a list for common processing
if isinstance(groupings, dict):
groupings = [groupings]

for grouping in groupings:
gName = grouping["@name"]
gLeaf = grouping["leaf"]
self.preProcessedYang['grouping'][moduleName][gName] = gLeaf

except Exception as e:
self.sysLog(msg="_preProcessYangGrouping failed:{}".format(str(e)), \
debug=syslog.LOG_ERR, doPrint=True)
raise e
return

# preProcesss Generic Yang Objects
def _preProcessYang(self, moduleName, module):
'''
PreProcess Generic Section of YANG models by calling
_preProcessYang<SectionName> methods.
Parameters:
moduleName (str): name of yang module.
module (dict): json format of yang module.
Returns:
void
'''
try:
# preProcesss Grouping
if module.get('grouping') is not None:
self._preProcessYangGrouping(moduleName, module)
except Exception as e:
self.sysLog(msg="_preProcessYang failed:{}".format(str(e)), \
debug=syslog.LOG_ERR, doPrint=True)
raise e
return

"""
Create a map from config DB tables to container in yang model
This module name and topLevelContainer are fetched considering YANG models are
Expand All @@ -82,6 +145,8 @@ def _createDBTableToModuleMap(self):
for j in self.yJson:
# get module name
moduleName = j['module']['@name']
# preProcesss Generic Yang Objects
self._preProcessYang(moduleName, j['module'])
# get top level container
topLevelContainer = j['module'].get('container')
# if top level container is none, this is common yang files, which may
Expand All @@ -104,14 +169,16 @@ def _createDBTableToModuleMap(self):
self.confDbYangMap[c['@name']] = {
"module" : moduleName,
"topLevelContainer": topLevelContainer['@name'],
"container": c
"container": c,
"yangModule": j['module']
}
# container is a dict
else:
self.confDbYangMap[container['@name']] = {
"module" : moduleName,
"topLevelContainer": topLevelContainer['@name'],
"container": container
"container": container,
"yangModule": j['module']
}
return

Expand Down Expand Up @@ -201,13 +268,87 @@ def _fillSteps(leaf):

return

"""
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.
"""
def _createLeafDict(self, model):
def _findYangModuleFromPrefix(self, prefix, module):
'''
Find yang module name from prefix used in given yang module.
Parameters:
prefix (str): prefix used in given yang module.
module (dict): json format of yang module.
Returns:
(str): module name or None
'''
try:
# get imports
yangImports = module.get("import");
if yangImports is None:
return None
# make a list
if isinstance(yangImports, dict):
yangImports = [yangImports]
# find module for given prefix
for yImport in yangImports:
if yImport['prefix']['@value'] == prefix:
return yImport['@module']
except Exception as e:
self.sysLog(msg="_findYangModuleFromPrefix failed:{}".format(str(e)), \
debug=syslog.LOG_ERR, doPrint=True)
raise e
return None

def _fillLeafDictUses(self, uses_s, table, leafDict):
'''
Find the leaf(s) in a grouping which maps to given uses statement,
then fill leafDict with leaf(s) information.
Parameters:
uses_s (str): uses statement in yang module.
table (str): config DB table, this table is being translated.
leafDict (dict): dict with leaf(s) information for List\Container
corresponding to config DB table.
Returns:
(void)
'''
try:
# make a list
if isinstance(uses_s, dict):
uses_s = [uses_s]
# find yang module for current table
table_module = self.confDbYangMap[table]['yangModule']
# uses Example: "@name": "bgpcmn:sonic-bgp-cmn"
for uses in uses_s:
# Assume ':' means reference to another module
if ':' in uses['@name']:
prefix = uses['@name'].split(':')[0].strip()
uses_module = self._findYangModuleFromPrefix(prefix, table_module)
else:
uses_module = table_module
grouping = uses['@name'].split(':')[-1].strip()
leafs = self.preProcessedYang['grouping'][uses_module][grouping]
self._fillLeafDict(leafs, leafDict)
except Exception as e:
self.sysLog(msg="_fillLeafDictUses failed:{}".format(str(e)), \
debug=syslog.LOG_ERR, doPrint=True)
raise e

return

def _createLeafDict(self, model, table):
'''
create a dict to map each key under primary key with a leaf in yang model.
This is done to improve performance of mapping from values of TABLEs in
config DB to leaf in YANG LIST.
Parameters:
module (dict): json format of yang module.
table (str): config DB table, this table is being translated.
Returns:
leafDict (dict): dict with leaf(s) information for List\Container
corresponding to config DB table.
'''
leafDict = dict()
#Iterate over leaf, choices and leaf-list.
self._fillLeafDict(model.get('leaf'), leafDict)
Expand All @@ -223,6 +364,10 @@ def _createLeafDict(self, model):
# leaf-lists
self._fillLeafDict(model.get('leaf-list'), leafDict, True)

# uses should map to grouping,
if model.get('uses') is not None:
self._fillLeafDictUses(model.get('uses'), table, leafDict)

return leafDict

"""
Expand All @@ -245,7 +390,7 @@ def _yangConvert(val):
elif 'leafref' in type:
vValue = val
#TODO: find type in sonic-head, as of now, all are enumeration
elif 'head:' in type:
elif 'stypes:' in type:
vValue = val
else:
vValue = val
Expand Down Expand Up @@ -275,7 +420,7 @@ def _xlateList(self, model, yang, config, table, exceptionList):
#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.
leafDict = self._createLeafDict(model)
leafDict = self._createLeafDict(model, table)

# get keys from YANG model list itself
listKeys = model['key']['@value']
Expand Down Expand Up @@ -380,7 +525,7 @@ def _xlateContainer(self, model, yang, config, table):
self._xlateContainerInContainer(modelContainer, yang, configC, table)

## Handle other leaves in container,
leafDict = self._createLeafDict(model)
leafDict = self._createLeafDict(model, table)
vKeys = list(configC.keys())
for vKey in vKeys:
#vkey must be a leaf\leaf-list\choice in container
Expand Down Expand Up @@ -494,7 +639,7 @@ def _revXlateList(self, model, yang, config, table):
# 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.
leafDict = self._createLeafDict(model)
leafDict = self._createLeafDict(model, table)

# list with name <NAME>_LIST should be removed,
if "_LIST" in model['@name']:
Expand Down Expand Up @@ -559,7 +704,7 @@ def _revXlateContainer(self, model, yang, config, table):
self._revXlateContainerInContainer(modelContainer, yang, config, table)

## Handle other leaves in container,
leafDict = self._createLeafDict(model)
leafDict = self._createLeafDict(model, table)
for vKey in yang:
#vkey must be a leaf\leaf-list\choice in container
if leafDict.get(vKey):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,8 @@ def test_xlate_rev_xlate(self, sonic_yang_data):
else:
print("Xlate and Rev Xlate failed")
# print for better debugging, in case of failure.
print("syc.jIn: {}".format({t:syc.jIn[t].keys() \
for t in syc.jIn.keys()}))
print("syc.revXlateJson: {}".format({t:syc.revXlateJson[t].keys() \
for t in syc.revXlateJson.keys()}))
from jsondiff import diff
print(diff(syc.jIn, syc.revXlateJson, syntax='symmetric'))
# make it fail
assert False == True

Expand Down
10 changes: 7 additions & 3 deletions src/sonic-yang-models/tests/files/sample_config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@

"AAA": {
"authentication": {
"login": "local"
"login": "local"
}
},
"TACPLUS": {
Expand Down Expand Up @@ -993,6 +993,10 @@
"rrclient":"0"
},
"default|192.168.1.1": {
"local_asn": "65200",
"asn": "65100",
"name": "bgp peer 65100",
"ebgp_multihop_ttl": "3"
}
},
"BGP_NEIGHBOR_AF": {
Expand Down Expand Up @@ -1027,7 +1031,7 @@
},
"PREFIX_SET": {
"prefix1": {
}
}
},
"PREFIX": {
"prefix1|1|10.0.0.0/8|8..16": {
Expand Down Expand Up @@ -1059,5 +1063,5 @@
"Error": "This Table is for testing, This Table does not have YANG models."
}
}

}

0 comments on commit c2dc42f

Please sign in to comment.