Skip to content

Commit 83108d9

Browse files
author
Praveen Chaudhary
authored
[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 [email protected]
1 parent 1e35915 commit 83108d9

File tree

5 files changed

+175
-23
lines changed

5 files changed

+175
-23
lines changed

src/sonic-yang-mgmt/setup.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
long_description=readme + '\n\n',
2727
install_requires = [
2828
'xmltodict==0.12.0',
29-
'ijson==2.6.1'
29+
'ijson==2.6.1',
30+
'jsondiff>=1.2.0',
3031
],
3132
tests_require = [
3233
'pytest>3',
3334
'xmltodict==0.12.0',
34-
'ijson==2.6.1'
35+
'ijson==2.6.1',
36+
'jsondiff>=1.2.0'
3537
],
3638
setup_requires = [
3739
'pytest-runner',

src/sonic-yang-mgmt/sonic_yang.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def __init__(self, yang_dir, debug=False):
3636
self.revXlateJson = dict()
3737
# below dict store the input config tables which have no YANG models
3838
self.tablesWithOutYang = dict()
39+
# below dict will store preProcessed yang objects, which may be needed by
40+
# all yang modules, such as grouping.
41+
self.preProcessedYang = dict()
3942

4043
try:
4144
self.ctx = ly.Context(yang_dir)

src/sonic-yang-mgmt/sonic_yang_ext.py

+159-14
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ def loadYangModel(self):
4444
self._loadJsonYangModel()
4545
# create a map from config DB table to yang container
4646
self._createDBTableToModuleMap()
47-
4847
except Exception as e:
4948
self.sysLog(msg="Yang Models Load failed:{}".format(str(e)), \
5049
debug=syslog.LOG_ERR, doPrint=True)
@@ -71,6 +70,70 @@ def _loadJsonYangModel(self):
7170

7271
return
7372

73+
def _preProcessYangGrouping(self, moduleName, module):
74+
'''
75+
PreProcess Grouping Section of YANG models, and store it in
76+
self.preProcessedYang['grouping'] as
77+
{'<moduleName>':
78+
{'<groupingName>':
79+
[<List of Leafs>]
80+
}
81+
}
82+
83+
Parameters:
84+
moduleName (str): name of yang module.
85+
module (dict): json format of yang module.
86+
87+
Returns:
88+
void
89+
'''
90+
try:
91+
# create grouping dict
92+
if self.preProcessedYang.get('grouping') is None:
93+
self.preProcessedYang['grouping'] = dict()
94+
self.preProcessedYang['grouping'][moduleName] = dict()
95+
96+
# get groupings from yang module
97+
groupings = module['grouping']
98+
99+
# if grouping is a dict, make it a list for common processing
100+
if isinstance(groupings, dict):
101+
groupings = [groupings]
102+
103+
for grouping in groupings:
104+
gName = grouping["@name"]
105+
gLeaf = grouping["leaf"]
106+
self.preProcessedYang['grouping'][moduleName][gName] = gLeaf
107+
108+
except Exception as e:
109+
self.sysLog(msg="_preProcessYangGrouping failed:{}".format(str(e)), \
110+
debug=syslog.LOG_ERR, doPrint=True)
111+
raise e
112+
return
113+
114+
# preProcesss Generic Yang Objects
115+
def _preProcessYang(self, moduleName, module):
116+
'''
117+
PreProcess Generic Section of YANG models by calling
118+
_preProcessYang<SectionName> methods.
119+
120+
Parameters:
121+
moduleName (str): name of yang module.
122+
module (dict): json format of yang module.
123+
124+
Returns:
125+
void
126+
'''
127+
try:
128+
# preProcesss Grouping
129+
if module.get('grouping') is not None:
130+
self._preProcessYangGrouping(moduleName, module)
131+
except Exception as e:
132+
self.sysLog(msg="_preProcessYang failed:{}".format(str(e)), \
133+
debug=syslog.LOG_ERR, doPrint=True)
134+
raise e
135+
return
136+
74137
"""
75138
Create a map from config DB tables to container in yang model
76139
This module name and topLevelContainer are fetched considering YANG models are
@@ -82,6 +145,8 @@ def _createDBTableToModuleMap(self):
82145
for j in self.yJson:
83146
# get module name
84147
moduleName = j['module']['@name']
148+
# preProcesss Generic Yang Objects
149+
self._preProcessYang(moduleName, j['module'])
85150
# get top level container
86151
topLevelContainer = j['module'].get('container')
87152
# if top level container is none, this is common yang files, which may
@@ -104,14 +169,16 @@ def _createDBTableToModuleMap(self):
104169
self.confDbYangMap[c['@name']] = {
105170
"module" : moduleName,
106171
"topLevelContainer": topLevelContainer['@name'],
107-
"container": c
172+
"container": c,
173+
"yangModule": j['module']
108174
}
109175
# container is a dict
110176
else:
111177
self.confDbYangMap[container['@name']] = {
112178
"module" : moduleName,
113179
"topLevelContainer": topLevelContainer['@name'],
114-
"container": container
180+
"container": container,
181+
"yangModule": j['module']
115182
}
116183
return
117184

@@ -201,13 +268,87 @@ def _fillSteps(leaf):
201268

202269
return
203270

204-
"""
205-
create a dict to map each key under primary key with a dict yang model.
206-
This is done to improve performance of mapping from values of TABLEs in
207-
config DB to leaf in YANG LIST.
208-
"""
209-
def _createLeafDict(self, model):
271+
def _findYangModuleFromPrefix(self, prefix, module):
272+
'''
273+
Find yang module name from prefix used in given yang module.
210274
275+
Parameters:
276+
prefix (str): prefix used in given yang module.
277+
module (dict): json format of yang module.
278+
279+
Returns:
280+
(str): module name or None
281+
'''
282+
try:
283+
# get imports
284+
yangImports = module.get("import");
285+
if yangImports is None:
286+
return None
287+
# make a list
288+
if isinstance(yangImports, dict):
289+
yangImports = [yangImports]
290+
# find module for given prefix
291+
for yImport in yangImports:
292+
if yImport['prefix']['@value'] == prefix:
293+
return yImport['@module']
294+
except Exception as e:
295+
self.sysLog(msg="_findYangModuleFromPrefix failed:{}".format(str(e)), \
296+
debug=syslog.LOG_ERR, doPrint=True)
297+
raise e
298+
return None
299+
300+
def _fillLeafDictUses(self, uses_s, table, leafDict):
301+
'''
302+
Find the leaf(s) in a grouping which maps to given uses statement,
303+
then fill leafDict with leaf(s) information.
304+
305+
Parameters:
306+
uses_s (str): uses statement in yang module.
307+
table (str): config DB table, this table is being translated.
308+
leafDict (dict): dict with leaf(s) information for List\Container
309+
corresponding to config DB table.
310+
311+
Returns:
312+
(void)
313+
'''
314+
try:
315+
# make a list
316+
if isinstance(uses_s, dict):
317+
uses_s = [uses_s]
318+
# find yang module for current table
319+
table_module = self.confDbYangMap[table]['yangModule']
320+
# uses Example: "@name": "bgpcmn:sonic-bgp-cmn"
321+
for uses in uses_s:
322+
# Assume ':' means reference to another module
323+
if ':' in uses['@name']:
324+
prefix = uses['@name'].split(':')[0].strip()
325+
uses_module = self._findYangModuleFromPrefix(prefix, table_module)
326+
else:
327+
uses_module = table_module
328+
grouping = uses['@name'].split(':')[-1].strip()
329+
leafs = self.preProcessedYang['grouping'][uses_module][grouping]
330+
self._fillLeafDict(leafs, leafDict)
331+
except Exception as e:
332+
self.sysLog(msg="_fillLeafDictUses failed:{}".format(str(e)), \
333+
debug=syslog.LOG_ERR, doPrint=True)
334+
raise e
335+
336+
return
337+
338+
def _createLeafDict(self, model, table):
339+
'''
340+
create a dict to map each key under primary key with a leaf in yang model.
341+
This is done to improve performance of mapping from values of TABLEs in
342+
config DB to leaf in YANG LIST.
343+
344+
Parameters:
345+
module (dict): json format of yang module.
346+
table (str): config DB table, this table is being translated.
347+
348+
Returns:
349+
leafDict (dict): dict with leaf(s) information for List\Container
350+
corresponding to config DB table.
351+
'''
211352
leafDict = dict()
212353
#Iterate over leaf, choices and leaf-list.
213354
self._fillLeafDict(model.get('leaf'), leafDict)
@@ -223,6 +364,10 @@ def _createLeafDict(self, model):
223364
# leaf-lists
224365
self._fillLeafDict(model.get('leaf-list'), leafDict, True)
225366

367+
# uses should map to grouping,
368+
if model.get('uses') is not None:
369+
self._fillLeafDictUses(model.get('uses'), table, leafDict)
370+
226371
return leafDict
227372

228373
"""
@@ -245,7 +390,7 @@ def _yangConvert(val):
245390
elif 'leafref' in type:
246391
vValue = val
247392
#TODO: find type in sonic-head, as of now, all are enumeration
248-
elif 'head:' in type:
393+
elif 'stypes:' in type:
249394
vValue = val
250395
else:
251396
vValue = val
@@ -275,7 +420,7 @@ def _xlateList(self, model, yang, config, table, exceptionList):
275420
#create a dict to map each key under primary key with a dict yang model.
276421
#This is done to improve performance of mapping from values of TABLEs in
277422
#config DB to leaf in YANG LIST.
278-
leafDict = self._createLeafDict(model)
423+
leafDict = self._createLeafDict(model, table)
279424

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

382527
## Handle other leaves in container,
383-
leafDict = self._createLeafDict(model)
528+
leafDict = self._createLeafDict(model, table)
384529
vKeys = list(configC.keys())
385530
for vKey in vKeys:
386531
#vkey must be a leaf\leaf-list\choice in container
@@ -494,7 +639,7 @@ def _revXlateList(self, model, yang, config, table):
494639
# create a dict to map each key under primary key with a dict yang model.
495640
# This is done to improve performance of mapping from values of TABLEs in
496641
# config DB to leaf in YANG LIST.
497-
leafDict = self._createLeafDict(model)
642+
leafDict = self._createLeafDict(model, table)
498643

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

561706
## Handle other leaves in container,
562-
leafDict = self._createLeafDict(model)
707+
leafDict = self._createLeafDict(model, table)
563708
for vKey in yang:
564709
#vkey must be a leaf\leaf-list\choice in container
565710
if leafDict.get(vKey):

src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,8 @@ def test_xlate_rev_xlate(self, sonic_yang_data):
341341
else:
342342
print("Xlate and Rev Xlate failed")
343343
# print for better debugging, in case of failure.
344-
print("syc.jIn: {}".format({t:syc.jIn[t].keys() \
345-
for t in syc.jIn.keys()}))
346-
print("syc.revXlateJson: {}".format({t:syc.revXlateJson[t].keys() \
347-
for t in syc.revXlateJson.keys()}))
344+
from jsondiff import diff
345+
print(diff(syc.jIn, syc.revXlateJson, syntax='symmetric'))
348346
# make it fail
349347
assert False == True
350348

src/sonic-yang-models/tests/files/sample_config_db.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@
928928
},
929929
"AAA": {
930930
"authentication": {
931-
"login": "local"
931+
"login": "local"
932932
}
933933
},
934934
"TACPLUS": {
@@ -1009,6 +1009,10 @@
10091009
"rrclient":"0"
10101010
},
10111011
"default|192.168.1.1": {
1012+
"local_asn": "65200",
1013+
"asn": "65100",
1014+
"name": "bgp peer 65100",
1015+
"ebgp_multihop_ttl": "3"
10121016
}
10131017
},
10141018
"BGP_NEIGHBOR_AF": {
@@ -1043,7 +1047,7 @@
10431047
},
10441048
"PREFIX_SET": {
10451049
"prefix1": {
1046-
}
1050+
}
10471051
},
10481052
"PREFIX": {
10491053
"prefix1|1|10.0.0.0/8|8..16": {
@@ -1093,5 +1097,5 @@
10931097
"Error": "This Table is for testing, This Table does not have YANG models."
10941098
}
10951099
}
1096-
1100+
10971101
}

0 commit comments

Comments
 (0)