diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/_sonic_yang_ext.py index c0e04c217406..29f1872c34ec 100644 --- a/src/sonic-yang-mgmt/_sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/_sonic_yang_ext.py @@ -12,649 +12,650 @@ from os.path import isfile, join, splitext from glob import glob -# class sonic_yang methods - -""" -load all YANG models, create JSON of yang models -""" -def loadYangModel(self): - - try: - # get all files - self.yangFiles = glob(self.yang_dir +"/*.yang") - # load yang modules - for file in self.yangFiles: - m = self.load_schema_module(file) - if m is not None: - self.sysLog(msg="module: {} is loaded successfully".format(m.name())) +# class sonic_yang methods, use mixin to extend sonic_yang +class sonic_yang_ext_mixin: + + """ + load all YANG models, create JSON of yang models + """ + def loadYangModel(self): + + try: + # get all files + self.yangFiles = glob(self.yang_dir +"/*.yang") + # load yang modules + for file in self.yangFiles: + m = self.load_schema_module(file) + if m is not None: + self.sysLog(msg="module: {} is loaded successfully".format(m.name())) + else: + raise(Exception("Could not load module {}".format(file))) + + # keep only modules name in self.yangFiles + self.yangFiles = [f.split('/')[-1] for f in self.yangFiles] + self.yangFiles = [f.split('.')[0] for f in self.yangFiles] + print('Loaded below Yang Models') + print(self.yangFiles) + + # load json for each yang model + self.loadJsonYangModel() + # create a map from config DB table to yang container + self.createDBTableToModuleMap() + + except Exception as e: + print("Yang Models Load failed") + raise e + + return True + + """ + load JSON schema format from yang models + """ + def loadJsonYangModel(self): + + try: + for f in self.yangFiles: + m = self.ctx.get_module(f) + if m is not None: + xml = m.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) + self.yJson.append(parse(xml)) + self.sysLog(msg="Parsed Json for {}".format(m.name())) + except Exception as e: + print('JSON conversion for yang models failed') + 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 + written using below Guidelines: + https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md. + """ + def createDBTableToModuleMap(self): + + for j in self.yJson: + # get module name + moduleName = j['module']['@name'] + # topLevelContainer does not exist in sonic-head and sonic-extension. + if "sonic-head" in moduleName or "sonic-extension" in moduleName: + continue; + # get all top level container + topLevelContainer = j['module']['container'] + if topLevelContainer is None: + raise Exception("topLevelContainer not found") + + assert topLevelContainer['@name'] == moduleName + + container = topLevelContainer['container'] + # container is a list + if isinstance(container, list): + for c in container: + self.confDbYangMap[c['@name']] = { + "module" : moduleName, + "topLevelContainer": topLevelContainer['@name'], + "container": c + } + # container is a dict else: - raise(Exception("Could not load module {}".format(file))) - - # keep only modules name in self.yangFiles - self.yangFiles = [f.split('/')[-1] for f in self.yangFiles] - self.yangFiles = [f.split('.')[0] for f in self.yangFiles] - print('Loaded below Yang Models') - print(self.yangFiles) - - # load json for each yang model - self.loadJsonYangModel() - # create a map from config DB table to yang container - self.createDBTableToModuleMap() - - except Exception as e: - print("Yang Models Load failed") - raise e - - return True - -""" -load JSON schema format from yang models -""" -def loadJsonYangModel(self): - - try: - for f in self.yangFiles: - m = self.ctx.get_module(f) - if m is not None: - xml = m.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) - self.yJson.append(parse(xml)) - self.sysLog(msg="Parsed Json for {}".format(m.name())) - except Exception as e: - print('JSON conversion for yang models failed') - 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 -written using below Guidelines: -https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md. -""" -def createDBTableToModuleMap(self): - - for j in self.yJson: - # get module name - moduleName = j['module']['@name'] - # topLevelContainer does not exist in sonic-head and sonic-extension. - if "sonic-head" in moduleName or "sonic-extension" in moduleName: - continue; - # get all top level container - topLevelContainer = j['module']['container'] - if topLevelContainer is None: - raise Exception("topLevelContainer not found") - - assert topLevelContainer['@name'] == moduleName - - container = topLevelContainer['container'] - # container is a list - if isinstance(container, list): - for c in container: - self.confDbYangMap[c['@name']] = { + self.confDbYangMap[container['@name']] = { "module" : moduleName, "topLevelContainer": topLevelContainer['@name'], - "container": c + "container": container } - # container is a dict + return + + """ + Get module, topLevelContainer(TLC) and json container for a config DB table + """ + def get_module_TLC_container(self, table): + cmap = self.confDbYangMap + m = cmap[table]['module'] + t = cmap[table]['topLevelContainer'] + c = cmap[table]['container'] + return m, t, c + + """ + Crop config as per yang models, + This Function crops from config only those TABLEs, for which yang models is + provided. + """ + def cropConfigDB(self, croppedFile=None, allowExtraTables=True): + + for table in self.jIn.keys(): + if table not in self.confDbYangMap: + if allowExtraTables: + del self.jIn[table] + else: + raise(Exception("No Yang Model Exist for {}".format(table))) + + if croppedFile: + with open(croppedFile, 'w') as f: + dump(self.jIn, f, indent=4) + + return + + """ + Extract keys from table entry in Config DB and return in a dict + + Input: + tableKey: Config DB Primary Key, Example tableKey = "Vlan111|2a04:5555:45:6709::1/64" + keys: key string from YANG list, i.e. 'vlan_name ip-prefix'. + regex: A regex to extract keys from tableKeys, good to have it as accurate as possible. + + Return: + KeyDict = {"vlan_name": "Vlan111", "ip-prefix": "2a04:5555:45:6709::1/64"} + """ + def extractKey(self, tableKey, keys, regex): + + keyList = keys.split() + # get the value groups + value = re.match(regex, tableKey) + # create the keyDict + i = 1 + keyDict = dict() + for k in keyList: + if value.group(i): + keyDict[k] = value.group(i) + else: + raise Exception("Value not found for {} in {}".format(k, tableKey)) + i = i + 1 + + return keyDict + + """ + Fill the dict based on leaf as a list or dict @model yang model object + """ + def fillLeafDict(self, leafs, leafDict, isleafList=False): + + if leafs == None: + return + + # fill default values + def fillSteps(leaf): + leaf['__isleafList'] = isleafList + leafDict[leaf['@name']] = leaf + return + + if isinstance(leafs, list): + for leaf in leafs: + #print("{}:{}".format(leaf['@name'], leaf)) + fillSteps(leaf) else: - self.confDbYangMap[container['@name']] = { - "module" : moduleName, - "topLevelContainer": topLevelContainer['@name'], - "container": container - } - return - -""" -Get module, topLevelContainer(TLC) and json container for a config DB table -""" -def get_module_TLC_container(self, table): - cmap = self.confDbYangMap - m = cmap[table]['module'] - t = cmap[table]['topLevelContainer'] - c = cmap[table]['container'] - return m, t, c - -""" -Crop config as per yang models, -This Function crops from config only those TABLEs, for which yang models is -provided. -""" -def cropConfigDB(self, croppedFile=None, allowExtraTables=True): - - for table in self.jIn.keys(): - if table not in self.confDbYangMap: - if allowExtraTables: - del self.jIn[table] + #print("{}:{}".format(leaf['@name'], leaf)) + fillSteps(leafs) + + 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): + + leafDict = dict() + #Iterate over leaf, choices and leaf-list. + self.fillLeafDict(model.get('leaf'), leafDict) + + #choices, this is tricky, since leafs are under cases in tree. + choices = model.get('choice') + if choices: + 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) + + return leafDict + + """ + Convert a string from Config DB value to Yang Value based on type of the + key in Yang model. + @model : A List of Leafs in Yang model list + """ + def findYangTypedValue(self, key, value, leafDict): + + # convert config DB string to yang Type + def yangConvert(val): + # Convert everything to string + val = str(val) + # find type of this key from yang leaf + type = leafDict[key]['type']['@name'] + + if 'uint' in type: + vValue = int(val, 10) + # TODO: find type of leafref from schema node + elif 'leafref' in type: + vValue = val + #TODO: find type in sonic-head, as of now, all are enumeration + elif 'head:' in type: + vValue = val else: - raise(Exception("No Yang Model Exist for {}".format(table))) - - if croppedFile: - with open(croppedFile, 'w') as f: - dump(self.jIn, f, indent=4) - - return - -""" -Extract keys from table entry in Config DB and return in a dict - -Input: -tableKey: Config DB Primary Key, Example tableKey = "Vlan111|2a04:5555:45:6709::1/64" -keys: key string from YANG list, i.e. 'vlan_name ip-prefix'. -regex: A regex to extract keys from tableKeys, good to have it as accurate as possible. - -Return: -KeyDict = {"vlan_name": "Vlan111", "ip-prefix": "2a04:5555:45:6709::1/64"} -""" -def extractKey(self, tableKey, keys, regex): - - keyList = keys.split() - # get the value groups - value = re.match(regex, tableKey) - # create the keyDict - i = 1 - keyDict = dict() - for k in keyList: - if value.group(i): - keyDict[k] = value.group(i) + vValue = val + return vValue + + # if it is a leaf-list do it for each element + if leafDict[key]['__isleafList']: + vValue = list() + for v in value: + vValue.append(yangConvert(v)) else: - raise Exception("Value not found for {} in {}".format(k, tableKey)) - i = i + 1 + vValue = yangConvert(value) - return keyDict + return vValue -""" -Fill the dict based on leaf as a list or dict @model yang model object -""" -def fillLeafDict(self, leafs, leafDict, isleafList=False): + """ + Xlate a list + This function will xlate from a dict in config DB to a Yang JSON list + using yang model. Output will be go in self.xlateJson + """ + def xlateList(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) + + # fetch regex from YANG models. + keyRegEx = model['ext:key-regex-configdb-to-yang']['@value'] + # seperator `|` has special meaning in regex, so change it appropriately. + keyRegEx = re.sub('\|', '\\|', keyRegEx) + # get keys from YANG model list itself + listKeys = model['key']['@value'] + self.sysLog(msg="xlateList regex:{} keyList:{}".\ + format(keyRegEx, listKeys)) + + for pkey in config.keys(): + try: + vKey = None + self.sysLog(syslog.LOG_DEBUG, "xlateList Extract pkey:{}".\ + format(pkey)) + # Find and extracts key from each dict in config + keyDict = self.extractKey(pkey, listKeys, keyRegEx) + # fill rest of the values in keyDict + for vKey in config[pkey]: + self.sysLog(syslog.LOG_DEBUG, "xlateList vkey {}".format(vKey)) + keyDict[vKey] = self.findYangTypedValue(vKey, \ + config[pkey][vKey], leafDict) + yang.append(keyDict) + # delete pkey from config, done to match one key with one list + del config[pkey] + + except Exception as e: + # log debug, because this exception may occur with multilists + self.sysLog(syslog.LOG_DEBUG, "xlateList Exception {}".format(e)) + # with multilist, we continue matching other keys. + continue - if leafs == None: return - # fill default values - def fillSteps(leaf): - leaf['__isleafList'] = isleafList - leafDict[leaf['@name']] = leaf + """ + Xlate a container + This function will xlate from a dict in config DB to a Yang JSON container + using yang model. Output will be stored in self.xlateJson + """ + def xlateContainer(self, model, yang, config, table): + + # To Handle multiple List, Make a copy of config, because we delete keys + # from config after each match. This is done to match one pkey with one list. + configC = config.copy() + + clist = model.get('list') + # If single list exists in container, + if clist and isinstance(clist, dict) and \ + clist['@name'] == model['@name']+"_LIST" and bool(configC): + #print(clist['@name']) + yang[clist['@name']] = list() + self.sysLog(msg="xlateContainer listD {}".format(clist['@name'])) + self.xlateList(clist, yang[clist['@name']], \ + configC, table) + # clean empty lists + if len(yang[clist['@name']]) == 0: + del yang[clist['@name']] + #print(yang[clist['@name']]) + + # If multi-list exists in container, + elif clist and isinstance(clist, list) and bool(configC): + for modelList in clist: + yang[modelList['@name']] = list() + self.sysLog(msg="xlateContainer listL {}".format(modelList['@name'])) + self.xlateList(modelList, yang[modelList['@name']], configC, table) + # clean empty lists + if len(yang[modelList['@name']]) == 0: + del yang[modelList['@name']] + + if len(configC): + self.sysLog(syslog.LOG_ERR, "Alert: Remaining keys in Config") + raise(Exception("All Keys are not parsed in {}".format(table))) + return - if isinstance(leafs, list): - for leaf in leafs: - #print("{}:{}".format(leaf['@name'], leaf)) - fillSteps(leaf) - else: - #print("{}:{}".format(leaf['@name'], leaf)) - fillSteps(leafs) - - 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): - - leafDict = dict() - #Iterate over leaf, choices and leaf-list. - self.fillLeafDict(model.get('leaf'), leafDict) - - #choices, this is tricky, since leafs are under cases in tree. - choices = model.get('choice') - if choices: - 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) - - return leafDict - -""" -Convert a string from Config DB value to Yang Value based on type of the -key in Yang model. -@model : A List of Leafs in Yang model list -""" -def findYangTypedValue(self, key, value, leafDict): - - # convert config DB string to yang Type - def yangConvert(val): - # Convert everything to string - val = str(val) - # find type of this key from yang leaf - type = leafDict[key]['type']['@name'] - - if 'uint' in type: - vValue = int(val, 10) - # TODO: find type of leafref from schema node - elif 'leafref' in type: - vValue = val - #TODO: find type in sonic-head, as of now, all are enumeration - elif 'head:' in type: - vValue = val + """ + xlate ConfigDB json to Yang json + """ + def xlateConfigDBtoYang(self, jIn, yangJ): + + # find top level container for each table, and run the xlate_container. + for table in jIn.keys(): + cmap = self.confDbYangMap[table] + # create top level containers + key = cmap['module']+":"+cmap['topLevelContainer'] + subkey = cmap['topLevelContainer']+":"+cmap['container']['@name'] + # Add new top level container for first table in this container + yangJ[key] = dict() if yangJ.get(key) is None else yangJ[key] + yangJ[key][subkey] = dict() + self.sysLog(msg="xlateConfigDBtoYang {}:{}".format(key, subkey)) + self.xlateContainer(cmap['container'], yangJ[key][subkey], \ + jIn[table], table) + + return + + """ + Read config file and crop it as per yang models + """ + def xlateConfigDB(self, xlateFile=None): + + jIn= self.jIn + yangJ = self.xlateJson + # xlation is written in self.xlateJson + self.xlateConfigDBtoYang(jIn, yangJ) + + if xlateFile: + with open(xlateFile, 'w') as f: + dump(self.xlateJson, f, indent=4) + + return + + """ + create config DB table key from entry in yang JSON + """ + def createKey(self, entry, regex): + + keyDict = dict() + keyV = regex + # get the keys from regex of key extractor + keyList = re.findall(r'<(.*?)>', regex) + for key in keyList: + val = entry.get(key) + if val: + #print("pair: {} {}".format(key, val)) + keyDict[key] = sval = str(val) + keyV = re.sub(r'<'+key+'>', sval, keyV) + #print("VAL: {} {}".format(regex, keyV)) + else: + raise Exception("key {} not found in entry".format(key)) + #print("kDict {}".format(keyDict)) + return keyV, keyDict + + """ + Convert a string from Config DB value to Yang Value based on type of the + key in Yang model. + @model : A List of Leafs in Yang model list + """ + def revFindYangTypedValue(self, key, value, leafDict): + + # convert yang Type to config DB string + def revYangConvert(val): + # config DB has only strings, thank god for that :), wait not yet!!! + return str(val) + + # if it is a leaf-list do it for each element + if leafDict[key]['__isleafList']: + vValue = list() + for v in value: + vValue.append(revYangConvert(v)) else: - vValue = val + vValue = revYangConvert(value) + return vValue - # if it is a leaf-list do it for each element - if leafDict[key]['__isleafList']: - vValue = list() - for v in value: - vValue.append(yangConvert(v)) - else: - vValue = yangConvert(value) - - return vValue - -""" -Xlate a list -This function will xlate from a dict in config DB to a Yang JSON list -using yang model. Output will be go in self.xlateJson -""" -def xlateList(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) - - # fetch regex from YANG models. - keyRegEx = model['ext:key-regex-configdb-to-yang']['@value'] - # seperator `|` has special meaning in regex, so change it appropriately. - keyRegEx = re.sub('\|', '\\|', keyRegEx) - # get keys from YANG model list itself - listKeys = model['key']['@value'] - self.sysLog(msg="xlateList regex:{} keyList:{}".\ - format(keyRegEx, listKeys)) - - for pkey in config.keys(): - try: - vKey = None - self.sysLog(syslog.LOG_DEBUG, "xlateList Extract pkey:{}".\ - format(pkey)) - # Find and extracts key from each dict in config - keyDict = self.extractKey(pkey, listKeys, keyRegEx) - # fill rest of the values in keyDict - for vKey in config[pkey]: - self.sysLog(syslog.LOG_DEBUG, "xlateList vkey {}".format(vKey)) - keyDict[vKey] = self.findYangTypedValue(vKey, \ - config[pkey][vKey], leafDict) - yang.append(keyDict) - # delete pkey from config, done to match one key with one list - del config[pkey] + """ + Rev xlate from