[YANG MGMT]: Support Grouping translation in YANG Models. (#8318)
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 support for other Generic YANG statement easily for translation. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com
This commit is contained in:
parent
1e35915dcf
commit
83108d9c9a
@ -26,12 +26,14 @@ setup(
|
||||
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',
|
||||
|
@ -36,6 +36,9 @@ class SonicYang(SonicYangExtMixin):
|
||||
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)
|
||||
|
@ -44,7 +44,6 @@ class SonicYangExtMixin:
|
||||
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)
|
||||
@ -71,6 +70,70 @@ class SonicYangExtMixin:
|
||||
|
||||
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
|
||||
@ -82,6 +145,8 @@ class SonicYangExtMixin:
|
||||
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
|
||||
@ -104,14 +169,16 @@ class SonicYangExtMixin:
|
||||
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
|
||||
|
||||
@ -201,13 +268,87 @@ class SonicYangExtMixin:
|
||||
|
||||
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)
|
||||
@ -223,6 +364,10 @@ class SonicYangExtMixin:
|
||||
# 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
|
||||
|
||||
"""
|
||||
@ -245,7 +390,7 @@ class SonicYangExtMixin:
|
||||
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
|
||||
@ -275,7 +420,7 @@ class SonicYangExtMixin:
|
||||
#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']
|
||||
@ -380,7 +525,7 @@ class SonicYangExtMixin:
|
||||
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
|
||||
@ -494,7 +639,7 @@ class SonicYangExtMixin:
|
||||
# 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']:
|
||||
@ -559,7 +704,7 @@ class SonicYangExtMixin:
|
||||
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):
|
||||
|
@ -341,10 +341,8 @@ class Test_SonicYang(object):
|
||||
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
|
||||
|
||||
|
@ -928,7 +928,7 @@
|
||||
},
|
||||
"AAA": {
|
||||
"authentication": {
|
||||
"login": "local"
|
||||
"login": "local"
|
||||
}
|
||||
},
|
||||
"TACPLUS": {
|
||||
@ -1009,6 +1009,10 @@
|
||||
"rrclient":"0"
|
||||
},
|
||||
"default|192.168.1.1": {
|
||||
"local_asn": "65200",
|
||||
"asn": "65100",
|
||||
"name": "bgp peer 65100",
|
||||
"ebgp_multihop_ttl": "3"
|
||||
}
|
||||
},
|
||||
"BGP_NEIGHBOR_AF": {
|
||||
@ -1043,7 +1047,7 @@
|
||||
},
|
||||
"PREFIX_SET": {
|
||||
"prefix1": {
|
||||
}
|
||||
}
|
||||
},
|
||||
"PREFIX": {
|
||||
"prefix1|1|10.0.0.0/8|8..16": {
|
||||
@ -1093,5 +1097,5 @@
|
||||
"Error": "This Table is for testing, This Table does not have YANG models."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user