Skip to content

Commit d06b225

Browse files
author
Praveen Chaudhary
committed
[_sonic_yang_ext.py]: Parse multilist in YANG Container.
This is needed to support VRF feature in SONiC. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com
1 parent 537199c commit d06b225

3 files changed

Lines changed: 92 additions & 52 deletions

File tree

src/sonic-yang-mgmt/_sonic_yang_ext.py

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -129,31 +129,28 @@ def cropConfigDB(self, croppedFile=None, allowExtraTables=True):
129129

130130
"""
131131
Extract keys from table entry in Config DB and return in a dict
132-
For Example: regex = <vlan_name>| and tableKey = "Vlan111|2a04:5555:45:6709::1/64"
133132
134-
1.) first code will extract key list from regex, i.e. vlan_name and ip_prefix.
135-
2.) then will create another regex(regexV) to extract Values from tableKey by
136-
replacing " --> extractor i.e. (.*?)" in regex.
137-
3.) Then will extract values from tableKey with regexV.
138-
4.) Resulting Dict will be:
133+
Input:
134+
tableKey: Config DB Primary Key, Example tableKey = "Vlan111|2a04:5555:45:6709::1/64"
135+
keys: key string from YANG list, i.e. 'vlan_name ip-prefix'.
136+
regex: A regex to extract keys from tableKeys, good to have it as accurate as possible.
137+
138+
Return:
139139
KeyDict = {"vlan_name": "Vlan111", "ip-prefix": "2a04:5555:45:6709::1/64"}
140140
"""
141-
def extractKey(self, tableKey, regex):
141+
def extractKey(self, tableKey, keys, regex):
142142

143-
# get the keys from regex of key extractor
144-
keyList = re.findall(r'<(.*?)>', regex)
145-
# create a regex to get values from tableKey
146-
# and change separator to text in regexV
147-
regexV = re.sub('<.*?>', '(.*?)', regex)
148-
regexV = re.sub('\|', '\\|', regexV)
143+
keyList = keys.split()
144+
#self.logInFile("extractKey {}".format(keyList))
149145
# get the value groups
150-
value = re.match(r'^'+regexV+'$', tableKey)
146+
value = re.match(regex, tableKey)
151147
# create the keyDict
152148
i = 1
153149
keyDict = dict()
154150
for k in keyList:
155151
if value.group(i):
156152
keyDict[k] = value.group(i)
153+
# self.logInFile("extractKey {} {}".format(k, keyDict[k]))
157154
else:
158155
raise Exception("Value not found for {} in {}".format(k, tableKey))
159156
i = i + 1
@@ -254,37 +251,49 @@ def xlateList(self, model, yang, config, table):
254251
# TODO: define a keyExt dict as of now, but we should be able to extract
255252
# this from YANG model extentions.
256253
keyExt = {
257-
"VLAN_INTERFACE": "<vlan_name>|<ip-prefix>",
258-
"ACL_RULE": "<ACL_TABLE_NAME>|<RULE_NAME>",
259-
"VLAN": "<vlan_name>",
260-
"VLAN_MEMBER": "<vlan_name>|<port>",
261-
"ACL_TABLE": "<ACL_TABLE_NAME>",
262-
"INTERFACE": "<interface>|<ip-prefix>",
263-
"PORT": "<port_name>"
254+
"VLAN_INTERFACE_LIST": "^(Vlan[a-zA-Z0-9_-]+)$",
255+
"VLAN_LIST": "^(Vlan[a-zA-Z0-9_-]+)$",
256+
"VLAN_INTERFACE_IPPREFIX_LIST": "^(Vlan[a-zA-Z0-9_-]+)\|([a-fA-F0-9:./]+$)",
257+
"VLAN_MEMBER_LIST": "^(Vlan[a-zA-Z0-9-_]+)\|(Ethernet[0-9]+)$",
258+
"ACL_RULE_LIST": "^([a-zA-Z0-9_-]+)\|([a-zA-Z0-9_-]+)$",
259+
"ACL_TABLE_LIST": "^([a-zA-Z0-9-_]+)$",
260+
"INTERFACE_LIST": "^(Ethernet[0-9]+)$",
261+
"INTERFACE_IPPREFIX_LIST": "^(Ethernet[0-9]+)\|([a-fA-F0-9:./]+)$",
262+
"PORT_LIST": "^(Ethernet[0-9]+)$",
263+
"LOOPBACK_INTERFACE_LIST": "^([a-zA-Z0-9-_]+)$",
264+
"LOOPBACK_INTERFACE_IPPREFIX_LIST": "^([a-zA-Z0-9-_]+)\|([a-fA-F0-9:./]+)$",
264265
}
265266
#create a dict to map each key under primary key with a dict yang model.
266267
#This is done to improve performance of mapping from values of TABLEs in
267268
#config DB to leaf in YANG LIST.
268269

269270
leafDict = self.createLeafDict(model)
270271

271-
self.logInFile("Xlate {}".format(table))
272-
# Find and extracts key from each dict in config
273-
for pkey in config:
272+
keyRegEx = keyExt[model['@name']]
273+
# get keys from YANG model list itself
274+
listKeys = model['key']['@value']
275+
276+
for pkey in config.keys():
274277
try:
275278
vKey = None
276-
self.logInFile("xlate Extract pkey {} {}".format(pkey,keyExt[table]))
277-
keyDict = self.extractKey(pkey, keyExt[table])
279+
self.logInFile("xlateList Extract pkey {}".format(pkey))
280+
# Find and extracts key from each dict in config
281+
keyDict = self.extractKey(pkey, listKeys, keyRegEx)
278282
# fill rest of the values in keyDict
279283
for vKey in config[pkey]:
280-
self.logInFile("xlate vkey {}".format(vKey), keyExt[table])
284+
self.logInFile("xlateList vkey {}".format(vKey))
281285
keyDict[vKey] = self.findYangTypedValue(vKey, \
282286
config[pkey][vKey], leafDict)
283287
yang.append(keyDict)
288+
# delete pkey from config, done to match one key with one list
289+
del config[pkey]
290+
284291
except Exception as e:
285-
print("Exception while Config DB --> YANG: pkey:{}, "\
292+
self.logInFile("xlateList Exception {}".format(e))
293+
self.logInFile("Exception while Config DB --> YANG: pkey:{}, "\
286294
"vKey:{}, value: {}".format(pkey, vKey, config[pkey].get(vKey)))
287-
raise e
295+
# with multilist, we continue matching other keys.
296+
continue
288297

289298
return
290299

@@ -295,19 +304,36 @@ def xlateList(self, model, yang, config, table):
295304
"""
296305
def xlateContainer(self, model, yang, config, table):
297306

298-
# if container contains single list with containerName_LIST and
299-
# config is not empty then xLate the list
307+
# To Handle mupltiple, Make a copy of config, because we delete keys
308+
# from config after each match. This is done to match one pkey with one list.
309+
configC = config.copy()
310+
300311
clist = model.get('list')
312+
# If single list exists in container,
301313
if clist and isinstance(clist, dict) and \
302-
clist['@name'] == model['@name']+"_LIST" and bool(config):
314+
clist['@name'] == model['@name']+"_LIST" and bool(configC):
303315
#print(clist['@name'])
304316
yang[clist['@name']] = list()
305-
self.xlateList(model['list'], yang[clist['@name']], \
306-
config, table)
317+
self.logInFile("xlateContainer listD {}".format(clist['@name']))
318+
self.xlateList(clist, yang[clist['@name']], \
319+
configC, table)
320+
# clean empty lists
321+
if len(yang[clist['@name']]) == 0:
322+
del yang[clist['@name']]
307323
#print(yang[clist['@name']])
308324

309-
# TODO: Handle mupltiple list and rest of the field in Container.
310-
# We do not have any such instance in Yang model today.
325+
# If multi-list exists in container,
326+
elif clist and isinstance(clist, list) and bool(configC):
327+
for modelList in clist:
328+
yang[modelList['@name']] = list()
329+
self.logInFile("xlateContainer listL {}".format(modelList['@name']))
330+
self.xlateList(modelList, yang[modelList['@name']], configC, table)
331+
# clean empty lists
332+
if len(yang[modelList['@name']]) == 0:
333+
del yang[modelList['@name']]
334+
335+
if len(configC):
336+
raise(Exception("All Keys are not parsed in {}".format(table)))
311337

312338
return
313339

@@ -325,6 +351,7 @@ def xlateConfigDBtoYang(self, jIn, yangJ):
325351
# Add new top level container for first table in this container
326352
yangJ[key] = dict() if yangJ.get(key) is None else yangJ[key]
327353
yangJ[key][subkey] = dict()
354+
self.logInFile("xlateConfigDBtoYang {}:{}".format(key, subkey))
328355
self.xlateContainer(cmap['container'], yangJ[key][subkey], \
329356
jIn[table], table)
330357

@@ -389,7 +416,6 @@ def revYangConvert(val):
389416

390417
return vValue
391418

392-
393419
"""
394420
Rev xlate from <TABLE>_LIST to table in config DB
395421
"""
@@ -398,26 +424,31 @@ def revXlateList(self, model, yang, config, table):
398424
# TODO: define a keyExt dict as of now, but we should be able to
399425
# extract this from YANG model extentions.
400426
keyExt = {
401-
"VLAN_INTERFACE": "<vlan_name>|<ip-prefix>",
402-
"ACL_RULE": "<ACL_TABLE_NAME>|<RULE_NAME>",
403-
"VLAN": "<vlan_name>",
404-
"VLAN_MEMBER": "<vlan_name>|<port>",
405-
"ACL_TABLE": "<ACL_TABLE_NAME>",
406-
"INTERFACE": "<interface>|<ip-prefix>",
407-
"PORT": "<port_name>"
427+
"VLAN_INTERFACE_IPPREFIX_LIST": "<vlan_name>|<ip-prefix>",
428+
"VLAN_INTERFACE_LIST": "<vlan_name>",
429+
"VLAN_MEMBER_LIST": "<vlan_name>|<port>",
430+
"VLAN_LIST": "<vlan_name>",
431+
"ACL_RULE_LIST": "<ACL_TABLE_NAME>|<RULE_NAME>",
432+
"ACL_TABLE_LIST": "<ACL_TABLE_NAME>",
433+
"INTERFACE_LIST": "<port_name>",
434+
"INTERFACE_IPPREFIX_LIST": "<port_name>|<ip-prefix>",
435+
"LOOPBACK_INTERFACE_LIST": "<loopback_interface_name>",
436+
"LOOPBACK_INTERFACE_IPPREFIX_LIST": "<loopback_interface_name>|<ip-prefix>",
437+
"PORT_LIST": "<port_name>"
438+
408439
}
409440

441+
keyRegEx = keyExt[model['@name']]
410442
# create a dict to map each key under primary key with a dict yang model.
411443
# This is done to improve performance of mapping from values of TABLEs in
412444
# config DB to leaf in YANG LIST.
413445
leafDict = self.createLeafDict(model)
414446

415-
# list with name <TABLE>_LIST should be removed,
416-
# right now we have only this instance of LIST
417-
if model['@name'] == table + "_LIST":
447+
# list with name <NAME>_LIST should be removed,
448+
if "_LIST" in model['@name']:
418449
for entry in yang:
419450
# create key of config DB table
420-
pkey, pkeydict = self.createKey(entry, keyExt[table])
451+
pkey, pkeydict = self.createKey(entry, keyRegEx)
421452
config[pkey]= dict()
422453
# fill rest of the entries
423454
for key in entry:
@@ -438,9 +469,8 @@ def revXlateContainer(self, model, yang, config, table):
438469
modelList = model['list']
439470
# Pass matching list from Yang Json
440471
self.revXlateList(modelList, yang[modelList['@name']], config, table)
441-
else:
442-
# TODO: Container[TABLE] contains multiple lists. [Test Pending]
443-
# No instance now.
472+
473+
elif isinstance(model['list'], list):
444474
for modelList in model['list']:
445475
self.revXlateList(modelList, yang[modelList['@name']], config, table)
446476

@@ -575,10 +605,11 @@ def load_data(self, configdbJson, allowExtraTables=True):
575605
# reset xlate
576606
self.xlateJson = dict()
577607
# self.jIn will be cropped
578-
self.cropConfigDB("cropped.json", allowExtraTables)
608+
self.cropConfigDB(allowExtraTables=allowExtraTables)
579609
# xlated result will be in self.xlateJson
580610
self.xlateConfigDB()
581611
#print(self.xlateJson)
612+
self.logInFile("Try to load Data in the tree")
582613
self.root = self.ctx.parse_data_mem(dumps(self.xlateJson), \
583614
ly.LYD_JSON, ly.LYD_OPT_CONFIG|ly.LYD_OPT_STRICT)
584615

src/sonic-yang-mgmt/tests/yang-model-tests/yangTest.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,8 @@
627627

628628
"SAMPLE_CONFIG_DB_JSON": {
629629
"VLAN_INTERFACE": {
630+
"Vlan111": {},
631+
"Vlan777": {},
630632
"Vlan111|2a04:5555:45:6709::1/64": {
631633
"scope": "global",
632634
"family": "IPv6"
@@ -1178,6 +1180,10 @@
11781180
}
11791181
},
11801182
"INTERFACE": {
1183+
"Ethernet112": {},
1184+
"Ethernet14": {},
1185+
"Ethernet16": {},
1186+
"Ethernet18": {},
11811187
"Ethernet112|2a04:5555:40:a709::2/126": {
11821188
"scope": "global",
11831189
"family": "IPv6"
@@ -1259,6 +1265,7 @@
12591265
}
12601266
},
12611267
"LOOPBACK_INTERFACE": {
1268+
"Loopback0": {},
12621269
"Loopback0|2a04:5555:40:4::4e9/128": {
12631270
"scope": "global",
12641271
"family": "IPv6"

src/sonic-yang-mgmt/yang-models/sonic-loopback-interface.yang

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ module sonic-loopback-interface {
4444
/* end of LOOPBACK_INTERFACE_LIST */
4545

4646
list LOOPBACK_INTERFACE_IPPREFIX_LIST {
47+
4748
key "loopback_interface_name ip-prefix";
4849

4950
leaf loopback_interface_name{
51+
5052
/* This node must be present in LOOPBACK_INTERFACE_LIST */
5153
must "(current() = ../../LOOPBACK_INTERFACE_LIST[loopback_interface_name=current()]/loopback_interface_name)"
5254
{

0 commit comments

Comments
 (0)