From 310bff82b02663e21f3d9deb2f3832446ed3e98b Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Mon, 9 Dec 2019 12:09:38 -0800 Subject: [PATCH 01/18] [sonic-yang-mgmt]: Basic build infra for sonic-yang-mgmt package. This package includes python yang libraries which will be used with sonic utilities pacakge to validate the config. This python libraries are written on top of libyang and also provides functionality to translate the config from SONiC ConfigDB to SONiC YANG and vice-versa. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com Signed-off-by: Ping Mao pmao@linkedin.com --- .../build_templates/sonic_debian_extension.j2 | 5 ++ rules/sonic-yang-mgmt-py2.mk | 8 ++ slave.mk | 3 +- src/sonic-yang-mgmt/AUTHORS.rst | 15 ++++ src/sonic-yang-mgmt/LICENSE | 13 ++++ src/sonic-yang-mgmt/README.rst | 6 ++ src/sonic-yang-mgmt/setup.py | 77 +++++++++++++++++++ src/sonic-yang-mgmt/tests/__init__.py | 3 + .../tests/test_sonic_yang_mgmt.py | 20 +++++ 9 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 rules/sonic-yang-mgmt-py2.mk create mode 100644 src/sonic-yang-mgmt/AUTHORS.rst create mode 100644 src/sonic-yang-mgmt/LICENSE create mode 100644 src/sonic-yang-mgmt/README.rst create mode 100644 src/sonic-yang-mgmt/setup.py create mode 100644 src/sonic-yang-mgmt/tests/__init__.py create mode 100644 src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 9ad2ba5eabae..b34a686957fb 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -124,6 +124,11 @@ SONIC_YANG_MODEL_PY3_WHEEL_NAME=$(basename {{sonic_yang_models_py3_wheel_path}}) sudo cp {{sonic_yang_models_py3_wheel_path}} $FILESYSTEM_ROOT/$SONIC_YANG_MODEL_PY3_WHEEL_NAME sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip3 install $SONIC_YANG_MODEL_PY3_WHEEL_NAME sudo rm -rf $FILESYSTEM_ROOT/$SONIC_YANG_MODEL_PY3_WHEEL_NAME +# Install sonic-yang-mgmt Python package +SONIC_YANG_MGMT_PY_WHEEL_NAME=$(basename {{sonic_yang_mgmt_py_wheel_path}}) +sudo cp {{sonic_yang_mgmt_py_wheel_path}} $FILESYSTEM_ROOT/$SONIC_YANG_MGMT_PY_WHEEL_NAME +sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip install $SONIC_YANG_MGMT_PY_WHEEL_NAME +sudo rm -rf $FILESYSTEM_ROOT/$SONIC_YANG_MGMT_PY_WHEEL_NAME # Install sonic-platform-common Python 2 package PLATFORM_COMMON_PY2_WHEEL_NAME=$(basename {{platform_common_py2_wheel_path}}) diff --git a/rules/sonic-yang-mgmt-py2.mk b/rules/sonic-yang-mgmt-py2.mk new file mode 100644 index 000000000000..47c5cc00c3ec --- /dev/null +++ b/rules/sonic-yang-mgmt-py2.mk @@ -0,0 +1,8 @@ +# sonic-yang-mgmt python wheel + +SONIC_YANG_MGMT_PY = sonic_yang_mgmt-1.0-py-none-any.whl +$(SONIC_YANG_MGMT_PY)_SRC_PATH = $(SRC_PATH)/sonic-yang-mgmt +$(SONIC_YANG_MGMT_PY)_PYTHON_VERSION = 2 +$(SONIC_YANG_MGMT_PY)_DEBS_DEPENDS = $(LIBYANG) + +SONIC_PYTHON_WHEELS += $(SONIC_YANG_MGMT_PY) diff --git a/slave.mk b/slave.mk index f30260157ffe..4be457988e88 100644 --- a/slave.mk +++ b/slave.mk @@ -542,7 +542,6 @@ SONIC_TARGET_LIST += $(addprefix $(PYTHON_DEBS_PATH)/, $(SONIC_PYTHON_STDEB_DEBS $(addprefix $(PYTHON_WHEELS_PATH)/, $(SONIC_PYTHON_WHEELS)) : $(PYTHON_WHEELS_PATH)/% : .platform $$(addsuffix -install,$$(addprefix $(PYTHON_WHEELS_PATH)/,$$($$*_DEPENDS))) \ $(call dpkg_depend,$(PYTHON_WHEELS_PATH)/%.dep) \ $$(addsuffix -install,$$(addprefix $(DEBS_PATH)/,$$($$*_DEBS_DEPENDS))) - $(HEADER) # Load the target deb from DPKG cache @@ -787,6 +786,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(REDIS_DUMP_LOAD_PY2)) \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_PLATFORM_API_PY2)) \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3)) + $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY)) $(HEADER) # Pass initramfs and linux kernel explicitly. They are used for all platforms export debs_path="$(IMAGE_DISTRO_DEBS_PATH)" @@ -817,6 +817,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ export redis_dump_load_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(REDIS_DUMP_LOAD_PY2))" export install_debug_image="$(INSTALL_DEBUG_TOOLS)" export sonic_yang_models_py3_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3))" + export sonic_yang_mgmt_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY))" export multi_instance="false" $(foreach docker, $($*_DOCKERS),\ diff --git a/src/sonic-yang-mgmt/AUTHORS.rst b/src/sonic-yang-mgmt/AUTHORS.rst new file mode 100644 index 000000000000..b5530bc13df3 --- /dev/null +++ b/src/sonic-yang-mgmt/AUTHORS.rst @@ -0,0 +1,15 @@ +======= +Credits +======= + +Development Lead +---------------- + +LNOS-CODERS +MSFT-LINUX-DEV + +Contributors +------------ + +Praveen Chaudhary +Ping Mao diff --git a/src/sonic-yang-mgmt/LICENSE b/src/sonic-yang-mgmt/LICENSE new file mode 100644 index 000000000000..cf593b111eab --- /dev/null +++ b/src/sonic-yang-mgmt/LICENSE @@ -0,0 +1,13 @@ +Copyright 2019 Microsoft, Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/sonic-yang-mgmt/README.rst b/src/sonic-yang-mgmt/README.rst new file mode 100644 index 000000000000..4d6c90640de8 --- /dev/null +++ b/src/sonic-yang-mgmt/README.rst @@ -0,0 +1,6 @@ +" +This package includes python yang libraries which will be used with sonic utilities +pacakge to validate the config. This python libraries are written on top of libyang +and also provides functionality to translate the config from SONiC ConfigDB to SONiC +YANG and vice-versa. +" diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py new file mode 100644 index 000000000000..ff227b29af86 --- /dev/null +++ b/src/sonic-yang-mgmt/setup.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""The setup script.""" + +from setuptools import setup, find_packages +from setuptools.command.build_py import build_py +from os import system +from sys import exit + +# important reuirements parameters +build_requirements = ['../../target/debs/stretch/libyang_1.0.73_amd64.deb', + '../../target/debs/stretch/libyang-cpp_1.0.73_amd64.deb', + '../../target/debs/stretch/python2-yang_1.0.73_amd64.deb'] + +install_requirements = [] + +setup_requirements = ['pytest-runner',] + +test_requirements = ['pytest>=3',] + +# read me +with open('README.rst') as readme_file: + readme = readme_file.read() + +# class for prerequisites to build this package +class pkgBuild(build_py): + """Custom Build PLY""" + + def run (self): + # install libyang, it will be used for testing a build time + for req in build_requirements: + if 'target/debs'in req: + pkg_install_cmd = "sudo dpkg -i {}".format(req) + if (system(pkg_install_cmd)): + print("{} installed failed".format(req)) + else: + print("{} installed".format(req)) + + # Continue usual build steps + build_py.run(self) + + +setup( + cmdclass={ + 'build_py': pkgBuild, + }, + author="lnos-coders", + author_email='lnos-coders@linkedin.com', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + ], + description="Package contains YANG models for sonic.", + install_requires=install_requirements, + tests_require = test_requirements, + license="GNU General Public License v3", + long_description=readme + '\n\n', + include_package_data=True, + keywords='sonic_yang_mgmt', + name='sonic_yang_mgmt', + # py_modules=['sonic_yang'], + packages=find_packages(), + setup_requires=setup_requirements, + version='1.0', + zip_safe=False, +) diff --git a/src/sonic-yang-mgmt/tests/__init__.py b/src/sonic-yang-mgmt/tests/__init__.py new file mode 100644 index 000000000000..ec983d6590c7 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +"""Unit test package for sonic_yang_mgmt.""" diff --git a/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py b/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py new file mode 100644 index 000000000000..5e7924c83616 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `sonic_yang_mgmt` package.""" + +import pytest + +@pytest.fixture +def response(): + """Sample pytest fixture. + See more at: http://doc.pytest.org/en/latest/fixture.html + """ + # import requests + # return requests.get('https://github.com/audreyr/cookiecutter-pypackage') + + +def test_content(response): + """Sample pytest test function with the pytest fixture as an argument.""" + # from bs4 import BeautifulSoup + # assert 'GitHub' in BeautifulSoup(response.content).title.string From fbfa7320e797b4df79638cffd2dfdf3df820c1f9 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Wed, 18 Mar 2020 14:53:44 -0700 Subject: [PATCH 02/18] [sonic-yang-mgmt/setup.py]: Change Python tag from Py2 to Py. Changes: Change Python tag from Py2 to Py. Find path of dependencies from os environment. [slave.mk]: Resolving a minor conflict. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- slave.mk | 2 +- src/sonic-yang-mgmt/setup.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/slave.mk b/slave.mk index 4be457988e88..a982f0271a59 100644 --- a/slave.mk +++ b/slave.mk @@ -817,7 +817,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ export redis_dump_load_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(REDIS_DUMP_LOAD_PY2))" export install_debug_image="$(INSTALL_DEBUG_TOOLS)" export sonic_yang_models_py3_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3))" - export sonic_yang_mgmt_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY))" + export sonic_yang_mgmt_py_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY))" export multi_instance="false" $(foreach docker, $($*_DOCKERS),\ diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index ff227b29af86..830adddc0e2e 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -7,11 +7,23 @@ from setuptools.command.build_py import build_py from os import system from sys import exit +import pytest +import os + +# find path of pkgs from os environment vars +prefix = '/sonic'; debs = os.environ["STRETCH_DEBS_PATH"] +wheels = os.environ["PYTHON_WHEELS_PATH"] +wheels_path = '{}/{}'.format(prefix, wheels) +deps_path = '{}/{}'.format(prefix, debs) +# dependencies +libyang = '{}/{}'.format(deps_path, os.environ["LIBYANG"]) +libyangCpp = '{}/{}'.format(deps_path, os.environ["LIBYANG_CPP"]) +libyangPy2 = '{}/{}'.format(deps_path, os.environ["LIBYANG_PY2"]) +libyangPy3 = '{}/{}'.format(deps_path, os.environ["LIBYANG_PY3"]) +sonicYangModels = '{}/{}'.format(wheels_path, os.environ["SONIC_YANG_MODELS_PY3"]) # important reuirements parameters -build_requirements = ['../../target/debs/stretch/libyang_1.0.73_amd64.deb', - '../../target/debs/stretch/libyang-cpp_1.0.73_amd64.deb', - '../../target/debs/stretch/python2-yang_1.0.73_amd64.deb'] +build_requirements = [libyang, libyangCpp, libyangPy2, libyangPy3, sonicYangModels,] install_requirements = [] @@ -28,12 +40,20 @@ class pkgBuild(build_py): """Custom Build PLY""" def run (self): - # install libyang, it will be used for testing a build time + # install libyang and sonic_yang_models for req in build_requirements: - if 'target/debs'in req: + if '.deb' in req: pkg_install_cmd = "sudo dpkg -i {}".format(req) if (system(pkg_install_cmd)): - print("{} installed failed".format(req)) + print("{} installation failed".format(req)) + exit(1) + else: + print("{} installed".format(req)) + elif '.whl' in req: + pkg_install_cmd = "pip3 install {}".format(req) + if (system(pkg_install_cmd)): + print("{} installation failed".format(req)) + exit(1) else: print("{} installed".format(req)) @@ -61,7 +81,8 @@ def run (self): 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', ], - description="Package contains YANG models for sonic.", + description="Package contains Python Library for YANG for sonic.", + options={'bdist_wheel':{'python_tag':'py'}}, install_requires=install_requirements, tests_require = test_requirements, license="GNU General Public License v3", From de86133cbdb5a343fa2b0a1831ad210050af91fc Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Fri, 20 Mar 2020 15:26:08 -0700 Subject: [PATCH 03/18] [sonic_yang.py]: Dev Code for Python Config Validation Librabry. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com Signed-off-by: Ping Mao pmao@linkedin.com --- src/sonic-yang-mgmt/_sonic_yang_ext.py | 660 ++++++++++++++++++ src/sonic-yang-mgmt/setup.py | 16 +- src/sonic-yang-mgmt/sonic_yang.py | 660 ++++++++++++++++++ .../tests/test_sonic_yang_mgmt.py | 1 + 4 files changed, 1330 insertions(+), 7 deletions(-) create mode 100644 src/sonic-yang-mgmt/_sonic_yang_ext.py create mode 100644 src/sonic-yang-mgmt/sonic_yang.py diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/_sonic_yang_ext.py new file mode 100644 index 000000000000..e66997da6383 --- /dev/null +++ b/src/sonic-yang-mgmt/_sonic_yang_ext.py @@ -0,0 +1,660 @@ +# This script is used as extension of sonic_yang class. It has methods of +# class sonic_yang. A separate file is used to avoid a single large file. + +import yang as ly +import re +import pprint +import syslog + +from json import dump, load, dumps, loads +from xmltodict import parse +from os import listdir, walk, path +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())) + 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: + 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] + 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: + #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: + 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: + 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] + + 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 + + return + +""" +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 + +""" +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 = revYangConvert(value) + + return vValue + +""" +Rev xlate from _LIST to table in config DB +""" +def revXlateList(self, model, yang, config, table): + + # fetch regex from YANG models + keyRegEx = model['ext:key-regex-yang-to-configdb']['@value'] + self.sysLog(msg="revXlateList regex:{}".format(keyRegEx)) + + # 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) + + # list with name _LIST should be removed, + if "_LIST" in model['@name']: + for entry in yang: + # create key of config DB table + pkey, pkeydict = self.createKey(entry, keyRegEx) + self.sysLog(syslog.LOG_DEBUG, "revXlateList pkey:{}".format(pkey)) + config[pkey]= dict() + # fill rest of the entries + for key in entry: + if key not in pkeydict: + config[pkey][key] = self.revFindYangTypedValue(key, \ + entry[key], leafDict) + + return + +""" +Rev xlate from yang container to table in config DB +""" +def revXlateContainer(self, model, yang, config, table): + + # Note: right now containers has only LISTs. + # IF container has only one list + if isinstance(model['list'], dict): + modelList = model['list'] + # Pass matching list from Yang Json + self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) + self.revXlateList(modelList, yang[modelList['@name']], config, table) + + elif isinstance(model['list'], list): + for modelList in model['list']: + self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) + self.revXlateList(modelList, yang[modelList['@name']], config, table) + + return + +""" +rev xlate ConfigDB json to Yang json +""" +def revXlateYangtoConfigDB(self, yangJ, cDbJson): + + yangJ = self.xlateJson + cDbJson = self.revXlateJson + + # find table in config DB, use name as a KEY + for module_top in yangJ.keys(): + # module _top will be of from module:top + for container in yangJ[module_top].keys(): + #table = container.split(':')[1] + table = container + #print("revXlate " + table) + cmap = self.confDbYangMap[table] + cDbJson[table] = dict() + #print(key + "--" + subkey) + self.sysLog(msg="revXlateYangtoConfigDB {}".format(table)) + self.revXlateContainer(cmap['container'], yangJ[module_top][container], \ + cDbJson[table], table) + + return + +""" +Reverse Translate tp config DB +""" +def revXlateConfigDB(self, revXlateFile=None): + + yangJ = self.xlateJson + cDbJson = self.revXlateJson + # xlation is written in self.xlateJson + self.revXlateYangtoConfigDB(yangJ, cDbJson) + + if revXlateFile: + with open(revXlateFile, 'w') as f: + dump(self.revXlateJson, f, indent=4) + + return + +""" +Find a list in YANG Container +c = container +l = list name +return: list if found else None +""" +def findYangList(self, container, listName): + + if isinstance(container['list'], dict): + clist = container['list'] + if clist['@name'] == listName: + return clist + + elif isinstance(container['list'], list): + clist = [l for l in container['list'] if l['@name'] == listName] + return clist[0] + + return None + +""" +Find xpath of the PORT Leaf in PORT container/list. Xpath of Leaf is needed, +because only leaf can have leafrefs depend on them. +""" +def findXpathPortLeaf(self, portName): + + try: + table = "PORT" + xpath = self.findXpathPort(portName) + module, topc, container = self.get_module_TLC_container(table) + list = self.findYangList(container, table+"_LIST") + xpath = xpath + "/" + list['key']['@value'].split()[0] + except Exception as e: + print("find xpath of port Leaf failed") + raise e + + return xpath + + +""" +Find xpath of PORT +""" +def findXpathPort(self, portName): + + try: + table = "PORT" + module, topc, container = self.get_module_TLC_container(table) + xpath = "/" + module + ":" + topc + "/" + table + + list = self.findYangList(container, table+"_LIST") + xpath = self.findXpathList(xpath, list, [portName]) + except Exception as e: + print("find xpath of port failed") + raise e + + return xpath + +""" +Find xpath of a YANG LIST from keys, +xpath: xpath till list +list: YANG List +keys: list of keys in YANG LIST +""" +def findXpathList(self, xpath, list, keys): + + try: + # add list name in xpath + xpath = xpath + "/" + list['@name'] + listKeys = list['key']['@value'].split() + i = 0; + for listKey in listKeys: + xpath = xpath + '['+listKey+'=\''+keys[i]+'\']' + i = i + 1 + except Exception as e: + print("find xpath of list failed") + raise e + + return xpath + +""" +load_data: load Config DB, crop, xlate and create data tree from it. +input: data +returns: True - success False - failed +""" +def load_data(self, configdbJson, allowExtraTables=True): + + try: + self.jIn = configdbJson + # reset xlate + self.xlateJson = dict() + # self.jIn will be cropped + self.cropConfigDB(allowExtraTables=allowExtraTables) + # xlated result will be in self.xlateJson + self.xlateConfigDB() + #print(self.xlateJson) + self.sysLog(msg="Try to load Data in the tree") + self.root = self.ctx.parse_data_mem(dumps(self.xlateJson), \ + ly.LYD_JSON, ly.LYD_OPT_CONFIG|ly.LYD_OPT_STRICT) + + except Exception as e: + self.root = None + print("Data Loading Failed") + raise e + + return True + +""" +Get data from Data tree, data tree will be assigned in self.xlateJson +""" +def get_data(self): + + try: + self.xlateJson = loads(self.print_data_mem('JSON')) + # reset reverse xlate + self.revXlateJson = dict() + # result will be stored self.revXlateJson + self.revXlateConfigDB() + + except Exception as e: + print("Get Data Tree Failed") + raise e + + return self.revXlateJson + +""" +Delete a node from data tree, if this is LEAF and KEY Delete the Parent +""" +def delete_node(self, xpath): + + # These MACROS used only here, can we get it from Libyang Header ? + try: + LYS_LEAF = 4 + node = self.find_data_node(xpath) + if node is None: + raise('Node {} not found'.format(xpath)) + + snode = node.schema() + # check for a leaf if it is a key. If yes delete the parent + if (snode.nodetype() == LYS_LEAF): + leaf = ly.Schema_Node_Leaf(snode) + if leaf.is_key(): + # try to delete parent + nodeP = self.find_parent_node(xpath) + xpathP = nodeP.path() + if self._delete_node(xpath=xpathP, node=nodeP) == False: + raise('_delete_node failed') + else: + return True + + # delete non key element + if self._delete_node(xpath=xpath, node=node) == False: + raise('_delete_node failed') + except Exception as e: + print(e) + raise('Failed to delete node {}'.format(xpath)) + + return True + +# End of class sonic_yang diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index 830adddc0e2e..2bacd4b7012f 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -25,11 +25,9 @@ # important reuirements parameters build_requirements = [libyang, libyangCpp, libyangPy2, libyangPy3, sonicYangModels,] -install_requirements = [] +setup_requirements = ['pytest-runner'] -setup_requirements = ['pytest-runner',] - -test_requirements = ['pytest>=3',] +test_requirements = ['pytest>=3'] # read me with open('README.rst') as readme_file: @@ -57,10 +55,15 @@ def run (self): else: print("{} installed".format(req)) + # run pytest for libyang python APIs + self.pytest_args = [] + errno = pytest.main(self.pytest_args) + if (errno): + exit(errno) + # Continue usual build steps build_py.run(self) - setup( cmdclass={ 'build_py': pkgBuild, @@ -83,14 +86,13 @@ def run (self): ], description="Package contains Python Library for YANG for sonic.", options={'bdist_wheel':{'python_tag':'py'}}, - install_requires=install_requirements, tests_require = test_requirements, license="GNU General Public License v3", long_description=readme + '\n\n', include_package_data=True, keywords='sonic_yang_mgmt', name='sonic_yang_mgmt', - # py_modules=['sonic_yang'], + py_modules=['sonic_yang', '_sonic_yang_ext'], packages=find_packages(), setup_requires=setup_requirements, version='1.0', diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py new file mode 100644 index 000000000000..1393594834ad --- /dev/null +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -0,0 +1,660 @@ +import yang as ly +import syslog + +from json import dump +from glob import glob +from datetime import datetime + +""" +Yang schema and data tree python APIs based on libyang python +""" +class sonic_yang: + + def __init__(self, yang_dir, debug=False): + self.yang_dir = yang_dir + self.ctx = None + self.module = None + self.root = None + + # logging vars + self.SYSLOG_IDENTIFIER = "sonic_yang" + self.DEBUG = debug + + # yang model files, need this map it to module + self.yangFiles = list() + # map from TABLE in config DB to container and module + self.confDbYangMap = dict() + # JSON format of yang model [similar to pyang conversion] + self.yJson = list() + # config DB json input, will be cropped as yang models + self.jIn = dict() + # YANG JSON, this is traslated from config DB json + self.xlateJson = dict() + # reverse translation from yang JSON, == config db json + self.revXlateJson = dict() + + try: + self.ctx = ly.Context(yang_dir) + except Exception as e: + self.fail(e) + + return + + def __del__(self): + pass + + def sysLog(self, debug=syslog.LOG_INFO, msg=None): + + # log debug only if enabled + if self.DEBUG == False and debug == syslog.LOG_DEBUG: + return + syslog.openlog(self.SYSLOG_IDENTIFIER) + syslog.syslog(debug, msg) + syslog.closelog() + + return + + def fail(self, e): + print(e) + raise e + + """ + import all function from extension file + """ + from _sonic_yang_ext import * + + """ + load_schema_module(): load a Yang model file + input: yang_file - full path of a Yang model file + returns: Exception if error + """ + def load_schema_module(self, yang_file): + try: + return self.ctx.parse_module_path(yang_file, ly.LYS_IN_YANG) + except Exception as e: + print("Failed to load yang module file: " + yang_file) + self.fail(e) + + """ + load_schema_module_list(): load all Yang model files in the list + input: yang_files - a list of Yang model file full path + returns: Exception if error + """ + def load_schema_module_list(self, yang_files): + for file in yang_files: + try: + self.load_schema_module(file) + except Exception as e: + self.fail(e) + + """ + load_schema_modules(): load all Yang model files in the directory + input: yang_dir - the directory of the yang model files to be loaded + returns: Exception if error + """ + def load_schema_modules(self, yang_dir): + py = glob(yang_dir+"/*.yang") + for file in py: + try: + self.load_schema_module(file) + except Exception as e: + self.fail(e) + + """ + load_schema_modules_ctx(): load all Yang model files in the directory to context: ctx + input: yang_dir, context + returns: Exception if error, returrns context object if no error + """ + def load_schema_modules_ctx(self, yang_dir=None): + if not yang_dir: + yang_dir = self.yang_dir + + ctx = ly.Context(yang_dir) + + py = glob(yang_dir+"/*.yang") + for file in py: + try: + ctx.parse_module_path(str(file), ly.LYS_IN_YANG) + except Exception as e: + print("Failed to parse yang module file: " + file) + self.fail(e) + + return ctx + + """ + load_data_file(): load a Yang data json file + input: data_file - the full path of the yang json data file to be loaded + returns: Exception if error + """ + def load_data_file(self, data_file): + try: + node = self.ctx.parse_data_path(data_file, ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) + except Exception as e: + print("Failed to load data file: " + str(data_file)) + self.fail(e) + else: + self.root = node + + """ + get module name from xpath + input: path + returns: module name + """ + def get_module_name(self, schema_xpath): + module_name = schema_xpath.split(':')[0].strip('/') + return module_name + + """ + get_module(): get module object from Yang module name + input: yang module name + returns: Schema_Node object + """ + def get_module(self, module_name): + mod = self.ctx.get_module(module_name) + return mod + + """ + load_data_model(): load both Yang module fileis and data json files + input: yang directory, list of yang files and list of data files (full path) + returns: returns (context, root) if no error, or Exception if failed + """ + def load_data_model (self, yang_dir, yang_files, data_files, output=None): + if (self.ctx is None): + self.ctx = ly.Context(yang_dir) + + try: + self.load_schema_module_list(yang_files) + if len(data_files) == 0: + return (self.ctx, self.root) + + self.load_data_file(data_files[0]) + + for i in range(2, len(data_files)): + self.merge_data(data_files[i]) + except Exception as e: + print("Failed to load data files") + self.fail(e) + return + + if output is not None: + self.print_data_mem(output) + + return (self.ctx, self.root) + + """ + print_data_mem(): print the data tree + input: option: "JSON" or "XML" + """ + def print_data_mem (self, option): + if (option == "JSON"): + mem = self.root.print_mem(ly.LYD_JSON, ly.LYP_WITHSIBLINGS | ly.LYP_FORMAT) + else: + mem = self.root.print_mem(ly.LYD_XML, ly.LYP_WITHSIBLINGS | ly.LYP_FORMAT) + + return mem + + """ + save_data_file_json(): save the data tree in memory into json file + input: outfile - full path of the file to save the data tree to + """ + def save_data_file_json(self, outfile): + mem = self.root.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) + with open(outfile, 'w') as out: + dump(mem, out, indent=4) + + """ + get_module_tree(): get yang module tree in JSON or XMAL format + input: module name + returns: JSON or XML format of the input yang module schema tree + """ + def get_module_tree(self, module_name, format): + result = None + + try: + module = self.ctx.get_module(str(module_name)) + except Exception as e: + print("Cound not get module: " + str(module_name)) + self.fail(e) + else: + if (module is not None): + if (format == "XML"): + #libyang bug with format + result = module.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) + else: + result = module.print_mem(ly.LYD_XML, ly.LYP_FORMAT) + + return result + + """ + validate_data(): validate data tree + input: + node: root of the data tree + ctx: context + returns: Exception if failed + """ + def validate_data (self, node=None, ctx=None): + if not node: + node = self.root + + if not ctx: + ctx = self.ctx + + try: + rc = node.validate(ly.LYD_OPT_CONFIG, ctx) + except Exception as e: + self.fail(e) + + """ + validate_data_tree(): validate the data tree + returns: Exception if failed + """ + def validate_data_tree (self): + try: + self.validate_data(self.root, self.ctx) + except Exception as e: + print("Failed to validate data tree") + self.fail(e) + + """ + find_parent_node(): find the parent node object + input: data_xpath - xpath of the data node + returns: parent node + """ + def find_parent_node (self, data_xpath): + if (self.root is None): + print("data not loaded") + return None + try: + node = self.find_data_node(data_xpath) + except Exception as e: + print("Failed to find data node from xpath: " + str(data_xpath)) + self.fail(e) + else: + if node is not None: + return node.parent() + + return None + + """ + get_parent_xpath(): find the parent node xpath + input: data_xpath - xpathof the data node + returns: - xpath of parent node + - Exception if error + """ + def get_parent_xpath (self, data_xpath): + path="" + try: + node = self.find_parent_node(data_xpath) + except Exception as e: + print("Failed to find parent node from xpath: " + str(data_xpath)) + self.fail(e) + else: + if (node is not None): + path = node.path() + return path + + """ + new_node(): create a new data node in the data tree + input: + xpath: xpath of the new node + value: value of the new node + returns: new Data_Node object if success, Exception if falied + """ + def new_node(self, xpath, value): + val = str(value) + try: + node = self.root.new_path(self.ctx, xpath, val, 0, 0) + except Exception as e: + print("Failed to add data node for path: " + str(xpath)) + self.fail(e) + else: + return node + + """ + find_data_node(): find the data node from xpath + input: data_xpath: xpath of the data node + returns - Data_Node object if found + - None if not exist + - Exception if there is error + """ + def find_data_node(self, data_xpath): + try: + set = self.root.find_path(data_xpath) + except Exception as e: + print("Failed to find data node from xpath: " + str(data_xpath)) + self.fail(e) + else: + if set is not None: + for node in set.data(): + if (data_xpath == node.path()): + return node + return None + """ + find_schema_node(): find the schema node from schema xpath + example schema xpath: + "/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name" + input: xpath of the node + returns: Schema_Node oject or None if not found + """ + def find_schema_node(self, schema_xpath): + try: + schema_set = self.ctx.find_path(schema_xpath) + for snode in schema_set.schema(): + if (schema_xpath == snode.path()): + return snode + except Exception as e: + self.fail(e) + return None + else: + for snode in schema_set.schema(): + if schema_xapth == snode.path(): + return snode + return None + """ + find_node_schema_xpath(): find the xpath of the schema node from data xpath + data xpath example: + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']/port_name" + input: data_xpath - xpath of the data node + returns: - xpath of the schema node if success + - Exception if error + """ + def find_node_schema_xpath(self, data_xpath): + path = "" + try: + set = self.root.find_path(data_xpath) + except Exception as e: + self.fail(e) + else: + for node in set.data(): + if data_xpath == node.path(): + return node.schema().path() + return path + + """ + add_node(): add a node to Yang schema or data tree + input: xpath and value of the node to be added + returns: Exception if failed + """ + def add_node(self, xpath, value): + try: + node = self.new_node(xpath, value) + #check if the node added to the data tree + self.find_data_node(xpath) + except Exception as e: + print("add_node(): Failed to add data node for xpath: " + str(data_xpath)) + self.fail(e) + + """ + merge_data(): merge a data file to the existing data tree + input: yang model directory and full path of the data json file to be merged + returns: Exception if failed + """ + def merge_data(self, data_file, yang_dir=None): + #load all yang models to ctx + if not yang_dir: + yang_dir = self.yang_dir + + try: + ctx = self.load_schema_modules_ctx(yang_dir) + + #source data node + source_node = ctx.parse_data_path(str(data_file), ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) + + #merge + self.root.merge(source_node, 0) + except Exception as e: + self.fail(e) + + """ + _delete_node(): delete a node from the schema/data tree, internal function + input: xpath of the schema/data node + returns: True - success False - failed + """ + def _delete_node(self, xpath=None, node=None): + if node is None: + node = self.find_data_node(xpath) + + if (node): + node.unlink() + dnode = self.find_data_node(xpath) + if (dnode is None): + #deleted node not found + return True + else: + print('Could not delete Node') + return False + else: + print("failed to find node, xpath: " + xpath) + + return False + + """ + find_node_value(): find the value of a node from the schema/data tree + input: data_xpath of the data node + returns: value string of the node + """ + def find_node_value(self, data_xpath): + output = "" + try: + node = self.find_data_node(data_xpath) + except Exception as e: + print("find_node_value(): Failed to find data node from xpath: {}".format(data_xpath)) + self.fail(e) + else: + if (node is not None): + subtype = node.subtype() + if (subtype is not None): + value = subtype.value_str() + return value + return output + + """ + set the value of a node in the data tree + input: xpath of the data node + returns: Exception if failed + """ + def set_dnode_value(self, data_xpath, value): + try: + node = self.root.new_path(self.ctx, data_xpath, str(value), ly.LYD_ANYDATA_STRING, ly.LYD_PATH_OPT_UPDATE) + except Exception as e: + print("set data node value failed for xpath: " + str(data_xpath)) + self.fail(e) + + """ + find_data_nodes(): find the set of nodes for the xpath + input: xpath of the data node + returns: list of xpath of the dataset + """ + def find_data_nodes(self, data_xpath): + list = [] + node = self.root.child() + try: + node_set = node.find_path(data_xpath); + except Exception as e: + self.fail(e) + else: + if node_set is None: + raise Exception('data node not found') + + for data_set in node_set.data(): + schema = data_set.schema() + list.append(data_set.path()) + return list + + """ + find_schema_dependencies(): find the schema dependencies from schema xpath + input: schema_xpath of the schema node + returns: - list of xpath of the dependencies + - Exception if schema node not found + """ + def find_schema_dependencies (self, schema_xpath): + ref_list = [] + node = self.root + try: + schema_node = self.find_schema_node(schema_xpath) + except Exception as e: + print("Cound not find the schema node from xpath: " + str(schema_xpath)) + self.fail(e) + return ref_list + + snode = ly.Schema_Node_Leaf(schema_node) + backlinks = snode.backlinks() + if backlinks.number() > 0: + for link in backlinks.schema(): + print("backlink schema: {}".format(link.path())) + ref_list.append(link.path()) + return ref_list + + """ + find_data_dependencies(): find the data dependencies from data xpath + input: data_xpath - xpath of data node + returns: - list of xpath + - Exception if error + """ + def find_data_dependencies (self, data_xpath): + ref_list = [] + node = self.root + try: + data_node = self.find_data_node(data_xpath) + except Exception as e: + print("find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xapth)) + self.fail(e) + return ref_list + + try: + value = str(self.find_node_value(data_xpath)) + + schema_node = ly.Schema_Node_Leaf(data_node.schema()) + backlinks = schema_node.backlinks() + if backlinks.number() > 0: + for link in backlinks.schema(): + node_set = node.find_path(link.path()) + for data_set in node_set.data(): + schema = data_set.schema() + casted = data_set.subtype() + if value == casted.value_str(): + ref_list.append(data_set.path()) + except Exception as e: + print('Failed to find node or dependencies for {}'.format(data_xpath)) + self.fail(e) + + return ref_list + + """ + get_module_prefix: get the prefix of a Yang module + input: name of the Yang module + output: prefix of the Yang module + """ + def get_module_prefix(self, module_name): + prefix = "" + try: + module = self.get_module(module_name) + except Exception as e: + self.fail(e) + return prefix + else: + return module.prefix() + + """ + str_to_type: map string to type of node + input: string + output: type + """ + def str_to_type (self, type_str): + mapped_type = { + "LY_TYPE_DER":ly.LY_TYPE_DER, + "LY_TYPE_BINARY":ly.LY_TYPE_BINARY, + "LY_TYPE_BITS":ly.LY_TYPE_BITS, + "LY_TYPE_BOOL":ly.LY_TYPE_BOOL, + "LY_TYPE_DEC64":ly.LY_TYPE_DEC64, + "LY_TYPE_EMPTY":ly.LY_TYPE_EMPTY, + "LY_TYPE_ENUM":ly.LY_TYPE_ENUM, + "LY_TYPE_IDENT":ly.LY_TYPE_IDENT, + "LY_TYPE_INST":ly.LY_TYPE_INST, + "LY_TYPE_LEAFREF":ly.LY_TYPE_LEAFREF, + "LY_TYPE_STRING":ly.LY_TYPE_STRING, + "LY_TYPE_UNION":ly.LY_TYPE_UNION, + "LY_TYPE_INT8":ly.LY_TYPE_INT8, + "LY_TYPE_UINT8":ly.LY_TYPE_UINT8, + "LY_TYPE_INT16":ly.LY_TYPE_INT16, + "LY_TYPE_UINT16":ly.LY_TYPE_UINT16, + "LY_TYPE_INT32":ly.LY_TYPE_INT32, + "LY_TYPE_UINT32":ly.LY_TYPE_UINT32, + "LY_TYPE_INT64":ly.LY_TYPE_INT64, + "LY_TYPE_UINT64":ly.LY_TYPE_UINT64, + "LY_TYPE_UNKNOWN":ly.LY_TYPE_UNKNOWN + } + + if type_str not in mapped_type: + return ly.LY_TYPE_UNKNOWN + + return mapped_type[type_str] + + def get_data_type (self, schema_xpath): + try: + schema_node = self.find_schema_node(schema_xpath) + except Exception as e: + print("get_data_type(): Failed to find schema node from xpath: {}".format(schema_xpath)) + self.fail(e) + return None + + if (schema_node is not None): + return schema_node.subtype().type().base() + + return ly.LY_TYPE_UNKNOWN + + """ + get_leafref_type: find the type of node that leafref references to + input: data_xpath - xpath of a data node + output: type of the node this leafref references to + """ + def get_leafref_type (self, data_xpath): + data_node = self.find_data_node(data_xpath) + if (data_node is not None): + subtype = data_node.subtype() + if (subtype is not None): + if data_node.schema().subtype().type().base() != ly.LY_TYPE_LEAFREF: + print("get_leafref_type() node type for data xpath: {} is not LEAFREF".format(data_xpath)) + return ly.LY_TYPE_UNKNOWN + else: + return subtype.value_type() + + return ly.LY_TYPE_UNKNOWN + + """ + get_leafref_path(): find the leafref path + input: schema_xpath - xpath of a schema node + output: path value of the leafref node + """ + def get_leafref_path (self, schema_xpath): + schema_node = self.find_schema_node(schema_xpath) + if (schema_node is not None): + subtype = schema_node.subtype() + if (subtype is not None): + if subtype.type().base() != ly.LY_TYPE_LEAFREF: + return None + else: + return subtype.type().info().lref().path() + + return None + + """ + get_leafref_type_schema: find the type of node that leafref references to + input: schema_xpath - xpath of a schema node + output: type of the node this leafref references to + """ + def get_leafref_type_schema (self, schema_xpath): + schema_node = self.find_schema_node(schema_xpath) + if (schema_node is not None): + subtype = schema_node.subtype() + if (subtype is not None): + if subtype.type().base() != ly.LY_TYPE_LEAFREF: + return None + else: + leafref_path = subtype.type().info().lref().path() + target = subtype.type().info().lref().target() + target_path = target.path() + target_type = self.get_data_type(target_path) + return target_type + + return None diff --git a/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py b/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py index 5e7924c83616..e5364fb172a9 100644 --- a/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py +++ b/src/sonic-yang-mgmt/tests/test_sonic_yang_mgmt.py @@ -8,6 +8,7 @@ @pytest.fixture def response(): """Sample pytest fixture. + See more at: http://doc.pytest.org/en/latest/fixture.html """ # import requests From 15a471c7b461ca22da7bad84896e1dafa1c5380e Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Tue, 7 Apr 2020 16:06:13 -0700 Subject: [PATCH 04/18] [libyang-python-tests]: Bring in test code for Python Library. Signed-off-by: Ping Mao pmao@linkedin.com --- .../sample-yang-models/sonic-acl.yang | 274 ++++++++ .../sample-yang-models/sonic-head.yang | 72 ++ .../sample-yang-models/sonic-interface.yang | 78 +++ .../sample-yang-models/sonic-module.yang | 144 ++++ .../sample-yang-models/sonic-port.yang | 84 +++ .../sample-yang-models/sonic-portchannel.yang | 85 +++ .../sample-yang-models/sonic-vlan.yang | 158 +++++ .../sample_config_db.json | 648 ++++++++++++++++++ .../sonic_config_data.json | 276 ++++++++ .../sonic_config_data_merge.json | 177 +++++ .../libyang-python-tests/test_SonicYang.json | 141 ++++ .../libyang-python-tests/test_sonic_yang.py | 288 ++++++++ 12 files changed, 2425 insertions(+) create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample_config_db.json create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang new file mode 100644 index 000000000000..1aab4d7de0b1 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang @@ -0,0 +1,274 @@ +module sonic-acl { + + yang-version 1.1; + + namespace "http://github.com/Azure/sonic-acl"; + prefix acl; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import sonic-port { + prefix port; + revision-date 2019-07-01; + } + + import sonic-portchannel { + prefix lag; + revision-date 2019-07-01; + } + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "ACL YANG Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + container sonic-acl { + + container ACL_RULE { + + description "ACL_RULE part of config_db.json"; + + list ACL_RULE_LIST { + + key "ACL_TABLE_NAME RULE_NAME"; + + leaf ACL_TABLE_NAME { + type leafref { + path "/acl:sonic-acl/acl:ACL_TABLE/acl:ACL_TABLE_LIST/acl:ACL_TABLE_NAME"; + } + } + + leaf RULE_NAME { + type string { + length 1..255; + } + } + + leaf PACKET_ACTION { + type head:packet_action; + } + + leaf IP_TYPE { + type head:ip_type; + } + + leaf PRIORITY { + type uint32 { + range 0..999999; + } + } + + choice ip_prefix { + + case ip4_prefix { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV4' or .='IPV4ANY' or .='ARP'])"; + leaf SRC_IP { + type inet:ipv4-prefix; + } + + leaf DST_IP { + type inet:ipv4-prefix; + } + } + + case ip6_prefix { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV6' or .='IPV6ANY'])"; + leaf SRC_IPV6 { + type inet:ipv6-prefix; + } + + leaf DST_IPV6 { + type inet:ipv6-prefix; + } + } + } + + leaf-list IN_PORTS { + /* Values in leaf list are UNIQUE */ + type uint16; + } + + leaf-list OUT_PORTS { + /* Values in leaf list are UNIQUE */ + type uint16; + } + + choice src_port { + case l4_src_port { + leaf L4_SRC_PORT { + type uint16; + } + } + + case l4_src_port_range { + leaf L4_SRC_PORT_RANGE { + type string { + pattern '([0-9]{1,4}|[0-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-2][0-9]{2}|[6][5][3][0-5]{2}|[6][5][3][6][0-5])-([0-9]{1,4}|[0-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-2][0-9]{2}|[6][5][3][0-5]{2}|[6][5][3][6][0-5])'; + } + } + } + } + + choice dst_port { + case l4_dst_port { + leaf L4_DST_PORT { + type uint16; + } + } + + case l4_dst_port_range { + leaf L4_DST_PORT_RANGE { + type string { + pattern '([0-9]{1,4}|[0-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-2][0-9]{2}|[6][5][3][0-5]{2}|[6][5][3][6][0-5])-([0-9]{1,4}|[0-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-2][0-9]{2}|[6][5][3][0-5]{2}|[6][5][3][6][0-5])'; + } + } + } + } + + leaf ETHER_TYPE { + type string { + pattern "(0x88CC|0x8100|0x8915|0x0806|0x0800|0x86DD|0x8847)"; + } + } + + leaf IP_PROTOCOL { + type uint8 { + range 1..143; + } + } + + leaf TCP_FLAGS { + type string { + pattern '0[x][0-9a-fA-F]{1,2}|0[X][0-9a-fA-F]{1,2}'; + } + } + + leaf DSCP { + type uint8; + } + + leaf TC { + type uint8; + } + + choice icmp { + + case icmp4 { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV4' or .='IPV4ANY' or .='ARP'])"; + leaf ICMP_TYPE { + type uint8 { + range 1..44; + } + } + + leaf ICMP_CODE { + type uint8 { + range 1..16; + } + } + } + + case icmp6 { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV6' or .='IPV6ANY'])"; + leaf ICMPV6_TYPE { + type uint8 { + range 1..44; + } + } + + leaf ICMPV6_CODE { + type uint8 { + range 1..16; + } + } + } + } + + leaf INNER_ETHER_TYPE { + type string { + pattern "(0x88CC|0x8100|0x8915|0x0806|0x0800|0x86DD|0x8847)"; + } + } + + leaf INNER_IP_PROTOCOL { + type uint8 { + range 1..143; + } + } + + leaf INNER_L4_SRC_PORT { + type uint16; + } + + leaf INNER_L4_DST_PORT { + type uint16; + } + } + /* end of ACL_RULE_LIST */ + } + /* end of container ACL_RULE */ + + container ACL_TABLE { + + description "ACL_TABLE part of config_db.json"; + + list ACL_TABLE_LIST { + + key "ACL_TABLE_NAME"; + + leaf ACL_TABLE_NAME { + type string; + } + + leaf policy_desc { + type string { + length 1..255; + } + } + + leaf type { + type head:acl_table_type; + } + + leaf stage { + type enumeration { + enum INGRESS; + enum EGRESS; + } + } + + leaf-list ports { + /* union of leafref is allowed in YANG 1.1 */ + type union { + type leafref { + path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + } + type leafref { + path /lag:sonic-portchannel/lag:PORTCHANNEL/lag:PORTCHANNEL_LIST/lag:portchannel_name; + } + } + } + } + /* end of ACL_TABLE_LIST */ + } + /* end of container ACL_TABLE */ + } + /* end of container sonic-acl */ +} +/* end of module sonic-acl */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang new file mode 100644 index 000000000000..dd0da92c7152 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang @@ -0,0 +1,72 @@ +module sonic-head { + + namespace "http://sonic-head"; + prefix sonic-head; + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "Head yang Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + typedef ip-family { + type enumeration { + enum IPv4; + enum IPv6; + } + } + + typedef admin_status { + type enumeration { + enum up; + enum down; + } + } + + typedef packet_action{ + type enumeration { + enum DROP; + enum FORWARD; + enum REDIRECT; + } + } + + typedef ip_type { + type enumeration { + enum ANY; + enum IP; + enum NON_IP; + enum IPV4; + enum IPV6; + enum IPV4ANY; + enum NON_IPv4; + enum IPV6ANY; + enum NON_IPv6; + enum ARP; + } + } + + typedef acl_table_type { + type enumeration { + enum L2; + enum L3; + enum L3V6; + enum MIRROR; + enum MIRRORV6; + enum MIRROR_DSCP; + enum CTRLPLANE; + } + } + + typedef vlan_tagging_mode { + type enumeration { + enum tagged; + enum untagged; + enum priority_tagged; + } + } +} \ No newline at end of file diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang new file mode 100644 index 000000000000..dc8bc7389ea1 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang @@ -0,0 +1,78 @@ +module sonic-interface { + + namespace "http://github.com/Azure/sonic-interface"; + prefix intf; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import sonic-port { + prefix port; + revision-date 2019-07-01; + } + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "INTERFACE yang Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + container sonic-interface { + container INTERFACE { + + description "INTERFACE part of config_db.json"; + + list INTERFACE_LIST { + + key "interface ip-prefix"; + + leaf interface { + type leafref { + path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + } + } + + leaf ip-prefix { + type inet:ip-prefix; + } + + leaf scope { + type enumeration { + enum global; + enum local; + } + } + + leaf family { + + /* family leaf needed for backward compatibility + Both ip4 and ip6 address are string in IETF RFC 6021, + so must statement can check based on : or ., family + should be IPv4 or IPv6 according. + */ + + must "(contains(../ip-prefix, ':') and current()='IPv6') or + (contains(../ip-prefix, '.') and current()='IPv4')"; + type head:ip-family; + } + } + /* end of INTERFACE_LIST */ + + } + /* end of INTERFACE container */ + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang new file mode 100644 index 000000000000..687bb478aacb --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang @@ -0,0 +1,144 @@ +module sonic-module { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:sonic-module"; + + prefix tm; + + import ietf-inet-types { + prefix inet; + } + + organization "organization"; + description + "example yang module"; + contact + "example@example.org"; + + container ports { + list port { + config true; + key "name"; + + leaf name { + type string; + } + + leaf lanes { + //choice + type uint8; + } + +/* + leaf fec { + type string; + } + + leaf mtu { + type uint16; + description + "Set the max transmission unit size in octets + for the physical interface. If this is not set, the mtu is + set to the operational default -- e.g., 1514 bytes on an + Ethernet interface."; + } + leaf admin_status { + type enumeration { + enum UP { + description + "Ready to pass packets."; + } + enum DOWN { + description + "Not ready to pass packets and not in some test mode."; + } + enum TESTING { + //TODO: This is generally not supported as a configured + //admin state, though it's in the standard interfaces MIB. + //Consider removing it. + description + "In some test mode."; + } + } + } +*/ + leaf alias { + type string; + } + + leaf speed { + type string; + units "bits/second"; +/* + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; +*/ + } + + } + } + + container vlans { + list vlan { + config true; + + key "name"; + + leaf name { + type string; + } + + leaf vlanid { + type string; + } + + leaf admin_status { + type string; +/* + type enumeration { + enum UP { + description + "Ready to pass packets."; + } + enum DOWN { + description + "Not ready to pass packets and not in some test mode."; + } + enum TESTING { + //TODO: This is generally not supported as a configured + //admin state, though it's in the standard interfaces MIB. + //Consider removing it. + description + "In some test mode."; + } + } +*/ + } + + leaf description { + type string; + } + + leaf mtu { + type uint16; + description + "Set the max transmission unit size in octets + for the physical interface. If this is not set, the mtu is + set to the operational default -- e.g., 1514 bytes on an + Ethernet interface."; + } + leaf-list members { + type leafref { + path "../../../ports/port/name"; + } + } + } + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang new file mode 100644 index 000000000000..be04a8aea4bd --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang @@ -0,0 +1,84 @@ +module sonic-port{ + + namespace "http://github.com/Azure/sonic-port"; + prefix port; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import sonic-head { + prefix head; + revision-date 2019-07-01; + } + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "PORT yang Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + container sonic-port{ + container PORT { + + description "PORT part of config_db.json"; + + list PORT_LIST { + + key "port_name"; + + leaf port_name { + type string { + length 1..128; + } + } + + leaf alias { + type string { + length 1..128; + } + } + + leaf lanes { + type string { + length 1..128; + } + } + + leaf description { + type string { + length 1..255; + } + } + + leaf speed { + type uint32 { + range 1..100000; + } + } + + leaf mtu { + type uint16 { + range 1..9216; + } + } + + leaf admin_status { + mandatory true; + type head:admin_status; + } + } /* end of list PORT_LIST */ + + } /* end of container PORT */ + + } /* end of container sonic-port */ + +} /* end of module sonic-port */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang new file mode 100644 index 000000000000..656351f4e635 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang @@ -0,0 +1,85 @@ +module sonic-portchannel { + + namespace "http://github.com/Azure/sonic-portchannel"; + prefix lag; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import sonic-port { + prefix port; + revision-date 2019-07-01; + } + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "PORTCHANNEL yang Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + container sonic-portchannel { + container PORTCHANNEL { + + description "PORTCHANNEL part of config_db.json"; + + list PORTCHANNEL_LIST { + + key "portchannel_name"; + + leaf portchannel_name { + type string { + length 1..128; + pattern 'PortChannel[0-9]{1,4}'; + } + } + + leaf-list members { + /* leaf-list members are unique by default */ + type leafref { + path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + } + } + + leaf min_links { + type uint8 { + range 1..128; + } + } + + leaf description { + type string { + length 1..255; + } + } + + leaf mtu { + type uint16 { + range 1..9216; + } + } + + leaf admin_status { + mandatory true; + type head:admin_status; + } + } /* end of list PORTCHANNEL_LIST */ + + } /* end of container PORTCHANNEL */ + + } /* end of container sonic-portchannel */ + +} /* end of module sonic-port */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang new file mode 100644 index 000000000000..6058ce3f1bfc --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang @@ -0,0 +1,158 @@ +module sonic-vlan { + + namespace "http://github.com/Azure/sonic-vlan"; + prefix vlan; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import sonic-port { + prefix port; + revision-date 2019-07-01; + } + + organization "Linkedin Corporation"; + + contact "lnos_coders@linkedin.com"; + + description "VLAN yang Module for SONiC OS"; + + revision 2019-07-01 { + description "First Revision"; + } + + container sonic-vlan { + container VLAN_INTERFACE { + + description "VLAN_INTERFACE part of config_db.json"; + + list VLAN_INTERFACE_LIST { + + key "vlanid ip-prefix"; + + leaf vlanid { + type leafref { + path ../../../VLAN/VLAN_LIST/vlanid; + } + } + + leaf ip-prefix { + mandatory true; + type inet:ip-prefix; + } + + leaf scope { + type enumeration { + enum global; + enum local; + } + } + + leaf family { + + /* family leaf needed for backward compatibility + Both ip4 and ip6 address are string in IETF RFC 6021, + so must statement can check based on : or ., family + should be IPv4 or IPv6 according. + */ + + must "(contains(../ip-prefix, ':') and current()='IPv6') or + (contains(../ip-prefix, '.') and current()='IPv4')"; + type head:ip-family; + } + } + /* end of VLAN_INTERFACE_LIST */ + } + /* end of VLAN_INTERFACE container */ + + container VLAN { + + description "VLAN part of config_db.json"; + + list VLAN_LIST { + + key "vlanid"; + + leaf vlanid { + type uint16 { + range 1..4094; + } + } + + leaf description { + type string { + length 1..255; + } + } + + leaf-list dhcp_servers { + type inet:ip-address; + } + + leaf mtu { + type uint16 { + range 1..9216; + } + } + + leaf admin_status { + mandatory true; + type head:admin_status; + } + + leaf-list members { + /* leaf-list members are unique by default */ + + type leafref { + path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + } + } + } + /* end of VLAN_LIST */ + } + /* end of container VLAN */ + + container VLAN_MEMBER { + + description "VLAN_MEMBER part of config_db.json"; + + list VLAN_MEMBER_LIST { + + key "vlanid port"; + + leaf vlanid { + type leafref { + path ../../../VLAN/VLAN_LIST/vlanid; + } + } + + leaf port { + /* key elements are mandatory by default */ + mandatory true; + type leafref { + path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + } + } + + leaf tagging_mode { + mandatory true; + type head:vlan_tagging_mode; + } + } + /* end of list VLAN_MEMBER_LIST */ + } + /* end of container VLAN_MEMBER */ + } + /* end of container sonic-vlan */ +} +/* end of module sonic-vlan */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample_config_db.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample_config_db.json new file mode 100644 index 000000000000..a37639fb4b73 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample_config_db.json @@ -0,0 +1,648 @@ +{ + "VLAN_INTERFACE": { + "Vlan111|2a04:5555:45:6709::1/64": { + "scope": "global", + "family": "IPv6" + }, + "Vlan111|10.222.10.65/26": { + "scope": "global", + "family": "IPv4" + }, + "Vlan111|fe80::1/10": { + "scope": "local", + "family": "IPv6" + }, + "Vlan777|2a04:5555:41:4e9::1/64": { + "scope": "global", + "family": "IPv6" + }, + "Vlan777|10.111.58.65/26": { + "scope": "global", + "family": "IPv4" + }, + "Vlan777|fe80::1/10": { + "scope": "local", + "family": "IPv6" + } + }, + "ACL_RULE": { + "V4-ACL-TABLE|DEFAULT_DENY": { + "PACKET_ACTION": "DROP", + "IP_TYPE": "IPv4ANY", + "PRIORITY": "0" + }, + "V4-ACL-TABLE|Rule_20": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.222.72.0/26", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777780", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_40": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.222.72.64/26", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777760", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_60": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.222.80.0/26", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777740", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_80": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.222.80.64/26", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777720", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_111": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.152.17.52/32", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777700", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_120": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.252.208.41/32", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777880", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_140": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.148.128.245/32", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777860", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_160": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.222.1.245/32", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777840", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_180": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.252.222.21/32", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "777820", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_9000": { + "PACKET_ACTION": "DROP", + "DST_IP": "0.0.0.0/0", + "SRC_IP": "10.222.0.0/15", + "PRIORITY": "991110", + "IP_TYPE": "IPv4ANY" + }, + "V4-ACL-TABLE|Rule_11100": { + "PACKET_ACTION": "FORWARD", + "DST_IP": "0.0.0.0/0", + "SRC_IP": "0.0.0.0/0", + "PRIORITY": "990000", + "IP_TYPE": "IPv4ANY" + }, + "V6-ACL-TBLE|DEFAULT_DENY": { + "PACKET_ACTION": "DROP", + "IP_TYPE": "IPv6ANY", + "PRIORITY": "0" + }, + "V6-ACL-TBLE|Rule_20": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "777780", + "DST_IPV6": "2a04:5555:43:320::/64" + }, + "V6-ACL-TBLE|Rule_40": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "777760", + "DST_IPV6": "2a04:5555:43:321::/64" + }, + "V6-ACL-TBLE|Rule_60": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "777740", + "DST_IPV6": "2a04:5555:43:340::/64" + }, + "V6-ACL-TBLE|Rule_80": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "777720", + "DST_IPV6": "2a04:5555:43:341::/64" + }, + "V6-ACL-TBLE|Rule_111": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "777700", + "DST_IPV6": "2a04:5555:32:12::/64" + }, + "V6-ACL-TBLE|Rule_9000": { + "PACKET_ACTION": "DROP", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "2a04:5555:41::/48", + "PRIORITY": "991110", + "DST_IPV6": "::/0" + }, + "V6-ACL-TBLE|Rule_11100": { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPv6ANY", + "SRC_IPV6": "::/0", + "PRIORITY": "990000", + "DST_IPV6": "::/0" + } + }, + "DEVICE_METADATA": { + "localhost": { + "mg_type": "ToR", + "mac": "00:11:22:33:dd:5a", + "hostname": "asw.dc", + "bgp_asn": "64850", + "hwsku": "Stone" + } + }, + "VLAN": { + "Vlan111": { + "description": "svlan", + "dhcp_servers": [ + "10.222.72.116" + ], + "vlanid": "111", + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet8", + "Ethernet3", + "Ethernet0", + "Ethernet1", + "Ethernet6", + "Ethernet4", + "Ethernet5", + "Ethernet9", + "Ethernet2", + "Ethernet7", + "Ethernet32", + "Ethernet30", + "Ethernet31", + "Ethernet36", + "Ethernet34", + "Ethernet33", + "Ethernet35", + "Ethernet29", + "Ethernet21", + "Ethernet20", + "Ethernet23", + "Ethernet22", + "Ethernet27", + "Ethernet26", + "Ethernet18", + "Ethernet19", + "Ethernet14", + "Ethernet15", + "Ethernet16", + "Ethernet17", + "Ethernet10", + "Ethernet11", + "Ethernet12", + "Ethernet13", + "Ethernet28" + ] + }, + "Vlan777": { + "description": "pvlan", + "dhcp_servers": [ + "10.222.72.116" + ], + "vlanid": "777", + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet9", + "Ethernet2", + "Ethernet8", + "Ethernet27", + "Ethernet14", + "Ethernet35" + ] + } + }, + "DEVICE_NEIGHBOR": { + "Ethernet112": { + "name": "dccsw01.nw", + "port": "Eth18" + }, + "Ethernet114": { + "name": "dccsw02.nw", + "port": "Eth18" + }, + "Ethernet116": { + "name": "dccsw03.nw", + "port": "Eth18" + }, + "Ethernet118": { + "name": "dccsw04.nw", + "port": "Eth18" + } + }, + "PORT": { + "Ethernet0": { + "alias": "Eth1/1", + "lanes": "65", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet1": { + "alias": "Eth1/2", + "lanes": "66", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet2": { + "alias": "Eth1/3", + "lanes": "67", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet3": { + "alias": "Eth1/4", + "lanes": "68", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet4": { + "alias": "Eth2/1", + "lanes": "69", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet5": { + "alias": "Eth2/2", + "lanes": "70", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet6": { + "alias": "Eth2/3", + "lanes": "71", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet7": { + "alias": "Eth2/4", + "lanes": "72", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet8": { + "alias": "Eth3/1", + "lanes": "73", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet9": { + "alias": "Eth3/2", + "lanes": "74", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet10": { + "alias": "Eth3/3", + "lanes": "75", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet11": { + "alias": "Eth3/4", + "lanes": "76", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet12": { + "alias": "Eth4/1", + "lanes": "77", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet13": { + "alias": "Eth4/2", + "lanes": "78", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet14": { + "alias": "Eth4/3", + "lanes": "79", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet15": { + "alias": "Eth4/4", + "lanes": "80", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet16": { + "alias": "Eth5/1", + "lanes": "33", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet17": { + "alias": "Eth5/2", + "lanes": "34", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet18": { + "alias": "Eth5/3", + "lanes": "35", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet19": { + "alias": "Eth5/4", + "lanes": "36", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet20": { + "alias": "Eth6/1", + "lanes": "37", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet21": { + "alias": "Eth6/2", + "lanes": "38", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet22": { + "alias": "Eth6/3", + "lanes": "39", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet23": { + "alias": "Eth6/4", + "lanes": "40", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet24": { + "alias": "Eth7/1", + "lanes": "41", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet25": { + "alias": "Eth7/2", + "lanes": "42", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet26": { + "alias": "Eth7/3", + "lanes": "43", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet27": { + "alias": "Eth7/4", + "lanes": "44", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet28": { + "alias": "Eth8/1", + "lanes": "45", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet29": { + "alias": "Eth8/2", + "lanes": "46", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet30": { + "alias": "Eth8/3", + "lanes": "47", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet31": { + "alias": "Eth8/4", + "lanes": "48", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet32": { + "alias": "Eth9/1", + "lanes": "49", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet33": { + "alias": "Eth9/2", + "lanes": "50", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet34": { + "alias": "Eth9/3", + "lanes": "51", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet35": { + "alias": "Eth9/4", + "lanes": "52", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet36": { + "alias": "Eth10/1", + "lanes": "53", + "description": "", + "speed": "11100", + "admin_status": "up" + }, + "Ethernet112": { + "alias": "Eth29/1", + "lanes": "113,114", + "description": "50G|dccsw01.nw|Eth18", + "fec": "fc", + "admin_status": "up" + } + }, + "ACL_TABLE": { + "V4-ACL-TABLE": { + "type": "L3", + "policy_desc": "V4-ACL-TABLE", + "ports": [ + "Ethernet26", + "Ethernet27", + "Ethernet24" + ] + }, + "V6-ACL-TBLE": { + "type": "L3V6", + "policy_desc": "V6-ACL-TBLE", + "ports": [ + "Ethernet14", + "Ethernet15", + "Ethernet23", + "Ethernet30", + "Ethernet31", + "Ethernet18", + "Ethernet19", + "Ethernet25", + "Ethernet24" + ] + } + }, + "INTERFACE": { + "Ethernet112|2a04:5555:40:a709::2/126": { + "scope": "global", + "family": "IPv6" + }, + "Ethernet112|10.184.228.211/31": { + "scope": "global", + "family": "IPv4" + }, + "Ethernet14|2a04:5555:40:a749::2/126": { + "scope": "global", + "family": "IPv6" + }, + "Ethernet14|10.184.229.211/31": { + "scope": "global", + "family": "IPv4" + }, + "Ethernet16|2a04:5555:40:a789::2/126": { + "scope": "global", + "family": "IPv6" + }, + "Ethernet16|10.184.230.211/31": { + "scope": "global", + "family": "IPv4" + }, + "Ethernet18|2a04:5555:40:a7c9::2/126": { + "scope": "global", + "family": "IPv6" + }, + "Ethernet18|10.184.231.211/31": { + "scope": "global", + "family": "IPv4" + } + }, + "VLAN_MEMBER": { + "Vlan111|Ethernet0": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet1": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet2": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet3": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet5": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet6": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet29": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet30": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet31": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet32": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet33": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet34": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet35": { + "tagging_mode": "untagged" + }, + "Vlan111|Ethernet36": { + "tagging_mode": "untagged" + } + }, + "LOOPBACK_INTERFACE": { + "Loopback0|2a04:5555:40:4::4e9/128": { + "scope": "global", + "family": "IPv6" + }, + "Loopback0|10.184.8.233/32": { + "scope": "global", + "family": "IPv4" + } + }, + "CRM": { + "Config": { + "polling_interval": "0" + } + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json new file mode 100644 index 000000000000..f7de6902c5d0 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json @@ -0,0 +1,276 @@ +{ + "sonic-vlan:sonic-vlan": { + "sonic-vlan:VLAN_INTERFACE": { + "VLAN_INTERFACE_LIST": [{ + "vlanid": 111, + "ip-prefix": "2000:f500:45:6709::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 111, + "ip-prefix": "10.1.1.65/26", + "scope": "global", + "family": "IPv4" + }, + { + "vlanid": 111, + "ip-prefix": "fe80::1/10", + "scope": "local", + "family": "IPv6" + }, + { + "vlanid": 555, + "ip-prefix": "2000:f500:41:4e9::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 555, + "ip-prefix": "10.1.5.65/26", + "scope": "global", + "family": "IPv4" + }, + { + "vlanid": 555, + "ip-prefix": "fe80::1/10", + "scope": "local", + "family": "IPv6" + } + ] + }, + + "sonic-vlan:VLAN": { + "VLAN_LIST": [{ + "vlanid": 111, + "description": "server_vlan", + "dhcp_servers": [ + "10.1.7.116" + ], + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet8", + "Ethernet3", + "Ethernet0", + "Ethernet1", + "Ethernet6", + "Ethernet4", + "Ethernet5", + "Ethernet9", + "Ethernet2" + ] + }, + { + "vlanid": 555, + "description": "ipmi_vlan", + "dhcp_servers": [ + "10.1.7.116" + ], + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet9", + "Ethernet2", + "Ethernet8" + ] + } + ] + }, + + "sonic-vlan:VLAN_MEMBER": { + "VLAN_MEMBER_LIST": [{ + "vlanid": 111, + "port": "Ethernet0", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet1", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet2", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet3", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet4", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet5", + "tagging_mode": "tagged" + }, + { + "vlanid": 111, + "port": "Ethernet6", + "tagging_mode": "tagged" + } + ] + } + }, + "sonic-port:sonic-port": { + "sonic-port:PORT": { + "PORT_LIST": [{ + "port_name": "Ethernet0", + "alias": "eth0", + "description": "Ethernet0", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet1", + "alias": "eth1", + "description": "Ethernet1", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet2", + "alias": "eth2", + "description": "Ethernet2", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet3", + "alias": "eth2", + "description": "Ethernet3", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet4", + "alias": "eth4", + "description": "Ethernet4", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet5", + "alias": "eth5", + "description": "Ethernet5", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet6", + "alias": "eth6", + "description": "Ethernet6", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet7", + "alias": "eth7", + "description": "Ethernet7", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet8", + "alias": "eth8", + "description": "Ethernet8", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet9", + "alias": "eth9", + "description": "Ethernet9", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + } + ] + } + }, + + "sonic-acl:sonic-acl": { + "sonic-acl:ACL_RULE": { + "ACL_RULE_LIST": [{ + "ACL_TABLE_NAME": "PACL-V4", + "RULE_NAME": "Rule_20", + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.1.72.0/26", + "SRC_IP": "10.1.0.0/15", + "PRIORITY": "999980", + "IP_TYPE": "IPV4ANY" + }, + { + "ACL_TABLE_NAME": "PACL-V4", + "RULE_NAME": "Rule_40", + "PACKET_ACTION": "FORWARD", + "DST_IP": "10.1.72.64/26", + "SRC_IP": "10.1.0.0/15", + "PRIORITY": "999960", + "IP_TYPE": "IPV4ANY", + "INNER_ETHER_TYPE": "0x88CC" + }, + { + "ACL_TABLE_NAME": "PACL-V6", + "RULE_NAME": "Rule_20", + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IP", + "SRC_IPV6": "2000:f500:41::/48", + "PRIORITY": "999980", + "DST_IPV6": "2000:f500:43:320::/64", + "L4_SRC_PORT_RANGE": "653-1053" + } + ] + }, + + "sonic-acl:ACL_TABLE": { + "ACL_TABLE_LIST": [{ + "ACL_TABLE_NAME": "PACL-V6", + "policy_desc": "Filter IPv6", + "type": "L3V6", + "stage": "EGRESS", + "ports": ["Ethernet7", "Ethernet9", "Ethernet8"] + }, + { + "ACL_TABLE_NAME": "PACL-V4", + "policy_desc": "Filter IPv6", + "type": "L3", + "stage": "INGRESS", + "ports": ["Ethernet2", "Ethernet0", "Ethernet1"] + } + ] + } + }, + + "sonic-interface:sonic-interface": { + "sonic-interface:INTERFACE": { + "INTERFACE_LIST": [{ + "interface": "Ethernet8", + "ip-prefix": "10.1.1.65/26", + "scope": "global", + "family": "IPv4" + }, + { + "interface": "Ethernet8", + "ip-prefix": "2000:f500:40:a749::2/126", + "scope": "global", + "family": "IPv6" + } + ] + } + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json new file mode 100644 index 000000000000..3bb4f0f5954b --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json @@ -0,0 +1,177 @@ +{ + "sonic-vlan:sonic-vlan": { + "sonic-vlan:VLAN_INTERFACE": { + "VLAN_INTERFACE_LIST": [{ + "vlanid": 111, + "ip-prefix": "2000:f500:45:6709::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 111, + "ip-prefix": "10.1.1.64/26", + "scope": "global", + "family": "IPv4" + }, + { + "vlanid": 200, + "ip-prefix": "2000:f500:45:6708::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 200, + "ip-prefix": "2000:f500:45:6709::1/64", + "scope": "global", + "family": "IPv6" + } + ] + }, + + "sonic-vlan:VLAN": { + "VLAN_LIST": [{ + "vlanid": 200, + "description": "server_vlan", + "dhcp_servers": [ + "10.1.72.116" + ], + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet8", + "Ethernet3", + "Ethernet0", + "Ethernet1", + "Ethernet6", + "Ethernet4", + "Ethernet5", + "Ethernet9", + "Ethernet2" + ] + }, + { + "vlanid": 111, + "description": "server_vlan", + "dhcp_servers": [ + "10.1.72.116" + ], + "mtu": "9216", + "admin_status": "up", + "members": [ + "Ethernet8", + "Ethernet3", + "Ethernet0", + "Ethernet1", + "Ethernet6", + "Ethernet4", + "Ethernet5", + "Ethernet9", + "Ethernet2" + ] + } + ] + }, + + "sonic-vlan:VLAN_MEMBER": { + "VLAN_MEMBER_LIST": [{ + "vlanid": 200, + "port": "Ethernet0", + "tagging_mode": "tagged" + } + ] + } + }, + "sonic-port:sonic-port": { + "sonic-port:PORT": { + "PORT_LIST": [{ + "port_name": "Ethernet0", + "alias": "eth0", + "description": "Ethernet0", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet1", + "alias": "eth1", + "description": "Ethernet1", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet2", + "alias": "eth2", + "description": "Ethernet2", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet3", + "alias": "eth2", + "description": "Ethernet3", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet4", + "alias": "eth4", + "description": "Ethernet4", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet5", + "alias": "eth5", + "description": "Ethernet5", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet6", + "alias": "eth6", + "description": "Ethernet6", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet7", + "alias": "eth7", + "description": "Ethernet7", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet8", + "alias": "eth8", + "description": "Ethernet8", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet9", + "alias": "eth9", + "description": "Ethernet9", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet10", + "alias": "eth10", + "description": "Ethernet10", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + } + ] + } + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json new file mode 100644 index 000000000000..b023ba57860e --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json @@ -0,0 +1,141 @@ +{ + "yang_dir":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/", + "data_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json", + "data_merge_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json", + "modules":[ + {"file":"sonic-head.yang", "module":"sonic-head"}, + {"file":"sonic-port.yang", "module":"sonic-port"}, + {"file":"sonic-acl.yang", "module":"sonic-acl"}, + {"file":"sonic-interface.yang", "module":"sonic-interface"}, + {"file":"sonic-portchannel.yang", "module":"sonic-portchannel"}, + {"file":"sonic-vlan.yang", "module":"sonic-vlan"} + ], + + "new_nodes":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/alias", "value":"Ethernet10_alias"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/speed", "value":"5000"}, + {"xpath":"/sonic-acl:sonic-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/RULE_NAME", + "value":"rule_20"} + ], + + "data_nodes":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/alias", "valid":"True"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/alias", "valid":"False"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE", "valid":"True"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST", "valid":"False"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']", "valid":"True"} + ], + + "set_nodes":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"10000"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "value":"1500"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/description", "value":"server_vlan111"} + ], + + "node_values":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", "value":"25000"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + "value":"IPv6"} + ], + + "schema_nodes":[ + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "value":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:family"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "value":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:speed"} + ], + + "delete_nodes":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "valid":"False"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "valid":"True"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/mtu", "valid":"False"} + ], + + "dependencies":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name", + "dependencies": + ["/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/members[.='Ethernet8']", + "/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='555']/members[.='Ethernet8']", + "/sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']", + "/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface", + "/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface"]} + ], + "schema_dependencies":[ + {"xpath":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name", + "schema_dependencies": + ["/sonic-acl:sonic-acl/sonic-acl:ACL_TABLE/sonic-acl:ACL_TABLE_LIST/sonic-acl:ports", + "/sonic-portchannel:sonic-portchannel/sonic-portchannel:PORTCHANNEL/sonic-portchannel:PORTCHANNEL_LIST/sonic-portchannel:members", + "/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", + "/sonic-vlan:sonic-vlan/sonic-vlan:VLAN/sonic-vlan:VLAN_LIST/sonic-vlan:members", + "/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port"]} + ], + "members":[ + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST", + "members": + ["/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet1']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet2']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet3']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet4']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet5']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet6']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet7']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']", + "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']"]} + ], + + "parents":[ + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/scope", + "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid", + "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/ip-prefix", + "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "parent":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']"} + ], + "prefix":[ + {"module_name":"sonic-head", "module_prefix":"sonic-head"}, + {"module_name":"sonic-port", "module_prefix":"port"}, + {"module_name":"sonic-acl", "module_prefix":"acl"}, + {"module_name":"sonic-interface", "module_prefix":"intf"}, + {"module_name":"sonic-portchannel", "module_prefix":"lag"}, + {"module_name":"sonic-vlan", "module_prefix":"vlan"} + ], + "data_type":[ + {"xpath":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", "data_type":"LY_TYPE_LEAFREF"} + ], + "leafref_type":[ + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid", "data_type":"LY_TYPE_UINT16"}, + {"xpath":"/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid", "data_type":"LY_TYPE_UINT16"} + ], + "leafref_type_schema":[ + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", + "data_type":"LY_TYPE_UINT16"}, + {"xpath":"/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", + "data_type":"LY_TYPE_STRING"}, + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port", + "data_type":"LY_TYPE_STRING"}, + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:vlanid", + "data_type":"LY_TYPE_UINT16"} + ], + "leafref_path":[ + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", + "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"}, + {"xpath":"/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", + "leafref_path":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name"}, + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port", + "leafref_path":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name"}, + {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:vlanid", + "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"} + ] +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py new file mode 100644 index 000000000000..7de04351ed33 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -0,0 +1,288 @@ +import sys +import os +import pytest +import yang as ly +import sonic_yang as sy +import json +import getopt +import subprocess +import glob +import logging +from ijson import items as ijson_itmes + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +logging.basicConfig(level=logging.DEBUG) +log = logging.getLogger("YANG-TEST") +log.setLevel(logging.INFO) +log.addHandler(logging.NullHandler()) + +class Test_SonicYang(object): + # class vars + + @pytest.fixture(autouse=True, scope='class') + def data(self): + test_file = "/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json" + data = self.jsonTestParser(test_file) + return data + + @pytest.fixture(autouse=True, scope='class') + def yang_s(self, data): + yang_dir = str(data['yang_dir']) + data_file = str(data['data_file']) + yang_s = sy.sonic_yang(yang_dir) + return yang_s + + def jsonTestParser(self, file): + """ + Open the json test file + """ + with open(file) as data_file: + data = json.load(data_file) + return data + + """ + Get the JSON input based on func name + and return jsonInput + """ + def readIjsonInput(self, yang_test_file, test): + try: + # load test specific Dictionary, using Key = func + # this is to avoid loading very large JSON in memory + print(" Read JSON Section: " + test) + jInput = "" + with open(yang_test_file, 'rb') as f: + jInst = ijson_itmes(f, test) + for it in jInst: + jInput = jInput + json.dumps(it) + except Exception as e: + print("Reading Ijson failed") + raise(e) + return jInput + + def setup_class(cls): + pass + + def load_yang_model_file(self, yang_s, yang_dir, yang_file, module_name): + yfile = yang_dir + yang_file + try: + yang_s.load_schema_module(str(yfile)) + except Exception as e: + print(e) + raise + + #test load and get yang module + def test_load_yang_model_files(self, data, yang_s): + yang_dir = data['yang_dir'] + for module in data['modules']: + file = str(module['file']) + module = str(module['module']) + + self.load_yang_model_file(yang_s, yang_dir, file, module) + assert yang_s.get_module(module) is not None + + #test load non-exist yang module file + def test_load_invalid_model_files(self, data, yang_s): + yang_dir = data['yang_dir'] + file = "invalid.yang" + module = "invalid" + + with pytest.raises(Exception): + assert self.load_yang_model_file(yang_s, yang_dir, file, module) + + #test load yang modules in directory + def test_load_yang_model_dir(self, data, yang_s): + yang_dir = data['yang_dir'] + yang_s.load_schema_modules(str(yang_dir)) + + for module_name in data['modules']: + assert yang_s.get_module(str(module_name['module'])) is not None + + #test load yang modules and data files + def test_load_yang_model_data(self, data, yang_s): + yang_dir = str(data['yang_dir']) + yang_files = glob.glob(yang_dir+"/*.yang") + data_file = str(data['data_file']) + data_merge_file = str(data['data_merge_file']) + + data_files = [] + data_files.append(data_file) + data_files.append(data_merge_file) + print(yang_files) + yang_s.load_data_model(yang_dir, yang_files, data_files) + + #test load data file + def test_load_data_file(self, data, yang_s): + data_file = str(data['data_file']) + yang_s.load_data_file(data_file) + + #test_validate_data_tree(): + def test_validate_data_tree(self, data, yang_s): + yang_s.validate_data_tree() + + #test find node + def test_find_node(self, data, yang_s): + for node in data['data_nodes']: + expected = node['valid'] + xpath = str(node['xpath']) + dnode = yang_s.find_data_node(xpath) + + if(expected == "True"): + assert dnode is not None + assert dnode.path() == xpath + else: + assert dnode == None + + #test add node + def test_add_node(self, data, yang_s): + for node in data['new_nodes']: + xpath = str(node['xpath']) + value = node['value'] + status = yang_s.add_node(xpath, str(value)) + + node = yang_s.find_data_node(xpath) + assert node is not None + + #test find node value + def test_find_node_value(self, data, yang_s): + for node in data['node_values']: + xpath = str(node['xpath']) + value = str(node['value']) + print(xpath) + print(value) + val = yang_s.find_node_value(xpath) + assert str(val) == str(value) + + #test delete data node + def test_delete_node(self, data, yang_s): + for node in data['delete_nodes']: + expected = node['valid'] + xpath = str(node['xpath']) + yang_s._delete_node(xpath) + + #test set node's value + def test_set_datanode_value(self, data, yang_s): + for node in data['set_nodes']: + xpath = str(node['xpath']) + value = node['value'] + yang_s.set_dnode_value(xpath, value) + + val = yang_s.find_node_value(xpath) + assert str(val) == str(value) + + #test list of members + def test_find_members(self, yang_s, data): + for node in data['members']: + members = node['members'] + xpath = str(node['xpath']) + list = yang_s.find_data_nodes(xpath) + assert list.sort() == members.sort() + + #get parent xpath + def test_get_parent_xpath(self, yang_s, data): + for node in data['parents']: + xpath = str(node['xpath']) + expected_xpath = str(node['parent']) + path = yang_s.get_parent_xpath(xpath) + assert path == expected_xpath + + #test find_node_schema_xpath + def test_find_node_schema_xpath(self, yang_s, data): + for node in data['schema_nodes']: + xpath = str(node['xpath']) + schema_xpath = str(node['value']) + path = yang_s.find_node_schema_xpath(xpath) + assert path == schema_xpath + + #test data dependencies + def test_find_data_dependencies(self, yang_s, data): + for node in data['dependencies']: + xpath = str(node['xpath']) + list = node['dependencies'] + depend = yang_s.find_data_dependencies(xpath) + assert set(depend) == set(list) + + #test data dependencies + def test_find_schema_dependencies(self, yang_s, data): + for node in data['schema_dependencies']: + xpath = str(node['xpath']) + list = node['schema_dependencies'] + depend = yang_s.find_schema_dependencies(xpath) + assert set(depend) == set(list) + + #test merge data tree + def test_merge_data_tree(self, data, yang_s): + data_merge_file = data['data_merge_file'] + yang_dir = str(data['yang_dir']) + yang_s.merge_data(data_merge_file, yang_dir) + #yang_s.root.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) + + #test get module prefix + def test_get_module_prefix(self, yang_s, data): + for node in data['prefix']: + xpath = str(node['module_name']) + expected = node['module_prefix'] + prefix = yang_s.get_module_prefix(xpath) + assert expected == prefix + + #test get data type + def test_get_data_type(self, yang_s, data): + for node in data['data_type']: + xpath = str(node['xpath']) + expected = node['data_type'] + expected_type = yang_s.str_to_type(expected) + data_type = yang_s.get_data_type(xpath) + assert expected_type == data_type + + def test_get_leafref_type(self, yang_s, data): + for node in data['leafref_type']: + xpath = str(node['xpath']) + expected = node['data_type'] + expected_type = yang_s.str_to_type(expected) + data_type = yang_s.get_leafref_type(xpath) + assert expected_type == data_type + + def test_get_leafref_path(self, yang_s, data): + for node in data['leafref_path']: + xpath = str(node['xpath']) + expected_path = node['leafref_path'] + path = yang_s.get_leafref_path(xpath) + assert expected_path == path + + def test_get_leafref_type_schema(self, yang_s, data): + for node in data['leafref_type_schema']: + xpath = str(node['xpath']) + expected = node['data_type'] + expected_type = yang_s.str_to_type(expected) + data_type = yang_s.get_leafref_type_schema(xpath) + assert expected_type == data_type + + def test_xlate_rev_xlate(self): + # In this test, xlation and revXlation is tested with latest Sonic + # YANG model. + + yang_dir = "/sonic/src/sonic-yang-models/yang-models/" + yang_test_file = "/sonic/src/sonic-yang-models/tests/yang_model_tests/yangTest.json" + jIn = self.readIjsonInput(yang_test_file, 'SAMPLE_CONFIG_DB_JSON') + # load yang models + syc = sy.sonic_yang(yang_dir) + + syc.loadYangModel() + + syc.load_data(json.loads(jIn)) + + syc.get_data() + + if syc.jIn and syc.jIn == syc.revXlateJson: + print("Xlate and Rev Xlate Passed") + else: + print("Xlate and Rev Xlate failed") + # make it fail + assert False == True + + return + + def teardown_class(cls): + pass From ae53c3675e62c19ea04ff3a21aa9584de0ab65a8 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Tue, 7 Apr 2020 16:09:08 -0700 Subject: [PATCH 05/18] [sonic-yang-mgmt-py2.mk]: Falling back to PY2. Changes: 1.) Falling back to PY2. I will try PY3 then update it later. 2.) Remove PY custom tag from package because pip supports only PY2 tag. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- rules/sonic-yang-mgmt-py2.mk | 5 +++-- src/sonic-yang-mgmt/setup.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/sonic-yang-mgmt-py2.mk b/rules/sonic-yang-mgmt-py2.mk index 47c5cc00c3ec..f438a6bd5116 100644 --- a/rules/sonic-yang-mgmt-py2.mk +++ b/rules/sonic-yang-mgmt-py2.mk @@ -1,8 +1,9 @@ -# sonic-yang-mgmt python wheel +# sonic-yang-mgmt python2 wheel -SONIC_YANG_MGMT_PY = sonic_yang_mgmt-1.0-py-none-any.whl +SONIC_YANG_MGMT_PY = sonic_yang_mgmt-1.0-py2-none-any.whl $(SONIC_YANG_MGMT_PY)_SRC_PATH = $(SRC_PATH)/sonic-yang-mgmt $(SONIC_YANG_MGMT_PY)_PYTHON_VERSION = 2 $(SONIC_YANG_MGMT_PY)_DEBS_DEPENDS = $(LIBYANG) +$(SONIC_YANG_MGMT_PY)_DEPENDS = $(SONIC_YANG_MODELS_PY3) SONIC_PYTHON_WHEELS += $(SONIC_YANG_MGMT_PY) diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index 2bacd4b7012f..24a09cf958c7 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -85,7 +85,6 @@ def run (self): 'Programming Language :: Python :: 3.8', ], description="Package contains Python Library for YANG for sonic.", - options={'bdist_wheel':{'python_tag':'py'}}, tests_require = test_requirements, license="GNU General Public License v3", long_description=readme + '\n\n', From c46dbd5f9a1100967b7a284d6fb1902322155ed1 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Wed, 8 Apr 2020 17:12:57 -0700 Subject: [PATCH 06/18] [sonic-yang-mgmt]: Resolving LGTM. Changes: To address 'import *' related LGTM alerts 1.) Moved all functions of _sonic_yang_ext.py file in sonic_yang_ext_mixin class. 2.) Use this class in sonic_yang to extend funtionality, i.e. python mixin. Note: Python mixin are different from parent class since it can access all variables of the class which is using it. Other LGTM issues 3.) Address Other LGTM minor issues found in below link. https://lgtm.com/projects/g/Azure/sonic-buildimage/rev/pr-0f82616403c02577e1155347eb5b693a51c76a9e Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- src/sonic-yang-mgmt/_sonic_yang_ext.py | 1244 ++++++++--------- src/sonic-yang-mgmt/setup.py | 17 +- src/sonic-yang-mgmt/sonic_yang.py | 24 +- .../libyang-python-tests/test_sonic_yang.py | 13 +- 4 files changed, 643 insertions(+), 655 deletions(-) diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/_sonic_yang_ext.py index e66997da6383..8c69f56faf6b 100644 --- a/src/sonic-yang-mgmt/_sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/_sonic_yang_ext.py @@ -3,658 +3,656 @@ import yang as ly import re -import pprint import syslog -from json import dump, load, dumps, loads +from json import dump, dumps, loads from xmltodict import parse -from os import listdir, walk, path -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 is 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
_LIST to table in config DB + """ + def revXlateList(self, model, yang, config, table): + + # fetch regex from YANG models + keyRegEx = model['ext:key-regex-yang-to-configdb']['@value'] + self.sysLog(msg="revXlateList regex:{}".format(keyRegEx)) + + # 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) + + # list with name _LIST should be removed, + if "_LIST" in model['@name']: + for entry in yang: + # create key of config DB table + pkey, pkeydict = self.createKey(entry, keyRegEx) + self.sysLog(syslog.LOG_DEBUG, "revXlateList pkey:{}".format(pkey)) + config[pkey]= dict() + # fill rest of the entries + for key in entry: + if key not in pkeydict: + config[pkey][key] = self.revFindYangTypedValue(key, \ + entry[key], leafDict) - 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 - - return - -""" -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 - -""" -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 = revYangConvert(value) - - return vValue - -""" -Rev xlate from
_LIST to table in config DB -""" -def revXlateList(self, model, yang, config, table): - - # fetch regex from YANG models - keyRegEx = model['ext:key-regex-yang-to-configdb']['@value'] - self.sysLog(msg="revXlateList regex:{}".format(keyRegEx)) - - # 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) - - # list with name _LIST should be removed, - if "_LIST" in model['@name']: - for entry in yang: - # create key of config DB table - pkey, pkeydict = self.createKey(entry, keyRegEx) - self.sysLog(syslog.LOG_DEBUG, "revXlateList pkey:{}".format(pkey)) - config[pkey]= dict() - # fill rest of the entries - for key in entry: - if key not in pkeydict: - config[pkey][key] = self.revFindYangTypedValue(key, \ - entry[key], leafDict) - - return - -""" -Rev xlate from yang container to table in config DB -""" -def revXlateContainer(self, model, yang, config, table): - - # Note: right now containers has only LISTs. - # IF container has only one list - if isinstance(model['list'], dict): - modelList = model['list'] - # Pass matching list from Yang Json - self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) - self.revXlateList(modelList, yang[modelList['@name']], config, table) - - elif isinstance(model['list'], list): - for modelList in model['list']: + return + + """ + Rev xlate from yang container to table in config DB + """ + def revXlateContainer(self, model, yang, config, table): + + # Note: right now containers has only LISTs. + # IF container has only one list + if isinstance(model['list'], dict): + modelList = model['list'] + # Pass matching list from Yang Json self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) self.revXlateList(modelList, yang[modelList['@name']], config, table) - return + elif isinstance(model['list'], list): + for modelList in model['list']: + self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) + self.revXlateList(modelList, yang[modelList['@name']], config, table) -""" -rev xlate ConfigDB json to Yang json -""" -def revXlateYangtoConfigDB(self, yangJ, cDbJson): + return - yangJ = self.xlateJson - cDbJson = self.revXlateJson + """ + rev xlate ConfigDB json to Yang json + """ + def revXlateYangtoConfigDB(self, yangJ, cDbJson): + + yangJ = self.xlateJson + cDbJson = self.revXlateJson + + # find table in config DB, use name as a KEY + for module_top in yangJ.keys(): + # module _top will be of from module:top + for container in yangJ[module_top].keys(): + #table = container.split(':')[1] + table = container + #print("revXlate " + table) + cmap = self.confDbYangMap[table] + cDbJson[table] = dict() + #print(key + "--" + subkey) + self.sysLog(msg="revXlateYangtoConfigDB {}".format(table)) + self.revXlateContainer(cmap['container'], yangJ[module_top][container], \ + cDbJson[table], table) - # find table in config DB, use name as a KEY - for module_top in yangJ.keys(): - # module _top will be of from module:top - for container in yangJ[module_top].keys(): - #table = container.split(':')[1] - table = container - #print("revXlate " + table) - cmap = self.confDbYangMap[table] - cDbJson[table] = dict() - #print(key + "--" + subkey) - self.sysLog(msg="revXlateYangtoConfigDB {}".format(table)) - self.revXlateContainer(cmap['container'], yangJ[module_top][container], \ - cDbJson[table], table) - - return - -""" -Reverse Translate tp config DB -""" -def revXlateConfigDB(self, revXlateFile=None): - - yangJ = self.xlateJson - cDbJson = self.revXlateJson - # xlation is written in self.xlateJson - self.revXlateYangtoConfigDB(yangJ, cDbJson) - - if revXlateFile: - with open(revXlateFile, 'w') as f: - dump(self.revXlateJson, f, indent=4) - - return - -""" -Find a list in YANG Container -c = container -l = list name -return: list if found else None -""" -def findYangList(self, container, listName): - - if isinstance(container['list'], dict): - clist = container['list'] - if clist['@name'] == listName: - return clist - - elif isinstance(container['list'], list): - clist = [l for l in container['list'] if l['@name'] == listName] - return clist[0] - - return None - -""" -Find xpath of the PORT Leaf in PORT container/list. Xpath of Leaf is needed, -because only leaf can have leafrefs depend on them. -""" -def findXpathPortLeaf(self, portName): - - try: - table = "PORT" - xpath = self.findXpathPort(portName) - module, topc, container = self.get_module_TLC_container(table) - list = self.findYangList(container, table+"_LIST") - xpath = xpath + "/" + list['key']['@value'].split()[0] - except Exception as e: - print("find xpath of port Leaf failed") - raise e - - return xpath - - -""" -Find xpath of PORT -""" -def findXpathPort(self, portName): - - try: - table = "PORT" - module, topc, container = self.get_module_TLC_container(table) - xpath = "/" + module + ":" + topc + "/" + table - - list = self.findYangList(container, table+"_LIST") - xpath = self.findXpathList(xpath, list, [portName]) - except Exception as e: - print("find xpath of port failed") - raise e - - return xpath - -""" -Find xpath of a YANG LIST from keys, -xpath: xpath till list -list: YANG List -keys: list of keys in YANG LIST -""" -def findXpathList(self, xpath, list, keys): - - try: - # add list name in xpath - xpath = xpath + "/" + list['@name'] - listKeys = list['key']['@value'].split() - i = 0; - for listKey in listKeys: - xpath = xpath + '['+listKey+'=\''+keys[i]+'\']' - i = i + 1 - except Exception as e: - print("find xpath of list failed") - raise e - - return xpath - -""" -load_data: load Config DB, crop, xlate and create data tree from it. -input: data -returns: True - success False - failed -""" -def load_data(self, configdbJson, allowExtraTables=True): - - try: - self.jIn = configdbJson - # reset xlate - self.xlateJson = dict() - # self.jIn will be cropped - self.cropConfigDB(allowExtraTables=allowExtraTables) - # xlated result will be in self.xlateJson - self.xlateConfigDB() - #print(self.xlateJson) - self.sysLog(msg="Try to load Data in the tree") - self.root = self.ctx.parse_data_mem(dumps(self.xlateJson), \ - ly.LYD_JSON, ly.LYD_OPT_CONFIG|ly.LYD_OPT_STRICT) - - except Exception as e: - self.root = None - print("Data Loading Failed") - raise e - - return True - -""" -Get data from Data tree, data tree will be assigned in self.xlateJson -""" -def get_data(self): - - try: - self.xlateJson = loads(self.print_data_mem('JSON')) - # reset reverse xlate - self.revXlateJson = dict() - # result will be stored self.revXlateJson - self.revXlateConfigDB() - - except Exception as e: - print("Get Data Tree Failed") - raise e - - return self.revXlateJson - -""" -Delete a node from data tree, if this is LEAF and KEY Delete the Parent -""" -def delete_node(self, xpath): - - # These MACROS used only here, can we get it from Libyang Header ? - try: - LYS_LEAF = 4 - node = self.find_data_node(xpath) - if node is None: - raise('Node {} not found'.format(xpath)) - - snode = node.schema() - # check for a leaf if it is a key. If yes delete the parent - if (snode.nodetype() == LYS_LEAF): - leaf = ly.Schema_Node_Leaf(snode) - if leaf.is_key(): - # try to delete parent - nodeP = self.find_parent_node(xpath) - xpathP = nodeP.path() - if self._delete_node(xpath=xpathP, node=nodeP) == False: - raise('_delete_node failed') - else: - return True + return + + """ + Reverse Translate tp config DB + """ + def revXlateConfigDB(self, revXlateFile=None): + + yangJ = self.xlateJson + cDbJson = self.revXlateJson + # xlation is written in self.xlateJson + self.revXlateYangtoConfigDB(yangJ, cDbJson) + + if revXlateFile: + with open(revXlateFile, 'w') as f: + dump(self.revXlateJson, f, indent=4) + + return + + """ + Find a list in YANG Container + c = container + l = list name + return: list if found else None + """ + def findYangList(self, container, listName): + + if isinstance(container['list'], dict): + clist = container['list'] + if clist['@name'] == listName: + return clist + + elif isinstance(container['list'], list): + clist = [l for l in container['list'] if l['@name'] == listName] + return clist[0] + + return None + + """ + Find xpath of the PORT Leaf in PORT container/list. Xpath of Leaf is needed, + because only leaf can have leafrefs depend on them. + """ + def findXpathPortLeaf(self, portName): - # delete non key element - if self._delete_node(xpath=xpath, node=node) == False: - raise('_delete_node failed') - except Exception as e: - print(e) - raise('Failed to delete node {}'.format(xpath)) + try: + table = "PORT" + xpath = self.findXpathPort(portName) + module, topc, container = self.get_module_TLC_container(table) + list = self.findYangList(container, table+"_LIST") + xpath = xpath + "/" + list['key']['@value'].split()[0] + except Exception as e: + print("find xpath of port Leaf failed") + raise e + + return xpath + + + """ + Find xpath of PORT + """ + def findXpathPort(self, portName): + + try: + table = "PORT" + module, topc, container = self.get_module_TLC_container(table) + xpath = "/" + module + ":" + topc + "/" + table + + list = self.findYangList(container, table+"_LIST") + xpath = self.findXpathList(xpath, list, [portName]) + except Exception as e: + print("find xpath of port failed") + raise e + + return xpath + + """ + Find xpath of a YANG LIST from keys, + xpath: xpath till list + list: YANG List + keys: list of keys in YANG LIST + """ + def findXpathList(self, xpath, list, keys): + + try: + # add list name in xpath + xpath = xpath + "/" + list['@name'] + listKeys = list['key']['@value'].split() + i = 0; + for listKey in listKeys: + xpath = xpath + '['+listKey+'=\''+keys[i]+'\']' + i = i + 1 + except Exception as e: + print("find xpath of list failed") + raise e + + return xpath + + """ + load_data: load Config DB, crop, xlate and create data tree from it. + input: data + returns: True - success False - failed + """ + def load_data(self, configdbJson, allowExtraTables=True): + + try: + self.jIn = configdbJson + # reset xlate + self.xlateJson = dict() + # self.jIn will be cropped + self.cropConfigDB(allowExtraTables=allowExtraTables) + # xlated result will be in self.xlateJson + self.xlateConfigDB() + #print(self.xlateJson) + self.sysLog(msg="Try to load Data in the tree") + self.root = self.ctx.parse_data_mem(dumps(self.xlateJson), \ + ly.LYD_JSON, ly.LYD_OPT_CONFIG|ly.LYD_OPT_STRICT) + + except Exception as e: + self.root = None + print("Data Loading Failed") + raise e + + return True + + """ + Get data from Data tree, data tree will be assigned in self.xlateJson + """ + def get_data(self): + + try: + self.xlateJson = loads(self.print_data_mem('JSON')) + # reset reverse xlate + self.revXlateJson = dict() + # result will be stored self.revXlateJson + self.revXlateConfigDB() + + except Exception as e: + print("Get Data Tree Failed") + raise e + + return self.revXlateJson + + """ + Delete a node from data tree, if this is LEAF and KEY Delete the Parent + """ + def delete_node(self, xpath): + + # These MACROS used only here, can we get it from Libyang Header ? + try: + LYS_LEAF = 4 + node = self.find_data_node(xpath) + if node is None: + raise('Node {} not found'.format(xpath)) + + snode = node.schema() + # check for a leaf if it is a key. If yes delete the parent + if (snode.nodetype() == LYS_LEAF): + leaf = ly.Schema_Node_Leaf(snode) + if leaf.is_key(): + # try to delete parent + nodeP = self.find_parent_node(xpath) + xpathP = nodeP.path() + if self._delete_node(xpath=xpathP, node=nodeP) == False: + raise Exception('_delete_node failed') + else: + return True + + # delete non key element + if self._delete_node(xpath=xpath, node=node) == False: + raise Exception('_delete_node failed') + except Exception as e: + print(e) + raise Exception('Failed to delete node {}'.format(xpath)) - return True + return True -# End of class sonic_yang + # End of class sonic_yang diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index 24a09cf958c7..15c8243aad7f 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -5,22 +5,21 @@ from setuptools import setup, find_packages from setuptools.command.build_py import build_py -from os import system +from os import system, environ from sys import exit import pytest -import os # find path of pkgs from os environment vars -prefix = '/sonic'; debs = os.environ["STRETCH_DEBS_PATH"] -wheels = os.environ["PYTHON_WHEELS_PATH"] +prefix = '/sonic'; debs = environ["STRETCH_DEBS_PATH"] +wheels = environ["PYTHON_WHEELS_PATH"] wheels_path = '{}/{}'.format(prefix, wheels) deps_path = '{}/{}'.format(prefix, debs) # dependencies -libyang = '{}/{}'.format(deps_path, os.environ["LIBYANG"]) -libyangCpp = '{}/{}'.format(deps_path, os.environ["LIBYANG_CPP"]) -libyangPy2 = '{}/{}'.format(deps_path, os.environ["LIBYANG_PY2"]) -libyangPy3 = '{}/{}'.format(deps_path, os.environ["LIBYANG_PY3"]) -sonicYangModels = '{}/{}'.format(wheels_path, os.environ["SONIC_YANG_MODELS_PY3"]) +libyang = '{}/{}'.format(deps_path, environ["LIBYANG"]) +libyangCpp = '{}/{}'.format(deps_path, environ["LIBYANG_CPP"]) +libyangPy2 = '{}/{}'.format(deps_path, environ["LIBYANG_PY2"]) +libyangPy3 = '{}/{}'.format(deps_path, environ["LIBYANG_PY3"]) +sonicYangModels = '{}/{}'.format(wheels_path, environ["SONIC_YANG_MODELS_PY3"]) # important reuirements parameters build_requirements = [libyang, libyangCpp, libyangPy2, libyangPy3, sonicYangModels,] diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py index 1393594834ad..28e784c481c9 100644 --- a/src/sonic-yang-mgmt/sonic_yang.py +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -3,12 +3,14 @@ from json import dump from glob import glob -from datetime import datetime +from _sonic_yang_ext import sonic_yang_ext_mixin """ Yang schema and data tree python APIs based on libyang python +Here, sonic_yang_ext_mixin extends funtionality of sonic_yang, +i.e. it is mixin not parent class. """ -class sonic_yang: +class sonic_yang(sonic_yang_ext_mixin): def __init__(self, yang_dir, debug=False): self.yang_dir = yang_dir @@ -58,11 +60,6 @@ def fail(self, e): print(e) raise e - """ - import all function from extension file - """ - from _sonic_yang_ext import * - """ load_schema_module(): load a Yang model file input: yang_file - full path of a Yang model file @@ -240,7 +237,7 @@ def validate_data (self, node=None, ctx=None): ctx = self.ctx try: - rc = node.validate(ly.LYD_OPT_CONFIG, ctx) + node.validate(ly.LYD_OPT_CONFIG, ctx) except Exception as e: self.fail(e) @@ -377,7 +374,7 @@ def find_node_schema_xpath(self, data_xpath): """ def add_node(self, xpath, value): try: - node = self.new_node(xpath, value) + self.new_node(xpath, value) #check if the node added to the data tree self.find_data_node(xpath) except Exception as e: @@ -455,7 +452,7 @@ def find_node_value(self, data_xpath): """ def set_dnode_value(self, data_xpath, value): try: - node = self.root.new_path(self.ctx, data_xpath, str(value), ly.LYD_ANYDATA_STRING, ly.LYD_PATH_OPT_UPDATE) + self.root.new_path(self.ctx, data_xpath, str(value), ly.LYD_ANYDATA_STRING, ly.LYD_PATH_OPT_UPDATE) except Exception as e: print("set data node value failed for xpath: " + str(data_xpath)) self.fail(e) @@ -477,7 +474,7 @@ def find_data_nodes(self, data_xpath): raise Exception('data node not found') for data_set in node_set.data(): - schema = data_set.schema() + data_set.schema() list.append(data_set.path()) return list @@ -489,7 +486,6 @@ def find_data_nodes(self, data_xpath): """ def find_schema_dependencies (self, schema_xpath): ref_list = [] - node = self.root try: schema_node = self.find_schema_node(schema_xpath) except Exception as e: @@ -530,7 +526,7 @@ def find_data_dependencies (self, data_xpath): for link in backlinks.schema(): node_set = node.find_path(link.path()) for data_set in node_set.data(): - schema = data_set.schema() + data_set.schema() casted = data_set.subtype() if value == casted.value_str(): ref_list.append(data_set.path()) @@ -651,7 +647,7 @@ def get_leafref_type_schema (self, schema_xpath): if subtype.type().base() != ly.LY_TYPE_LEAFREF: return None else: - leafref_path = subtype.type().info().lref().path() + subtype.type().info().lref().path() target = subtype.type().info().lref().target() target_path = target.path() target_type = self.get_data_type(target_path) diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 7de04351ed33..0dba6942f240 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -1,11 +1,8 @@ import sys import os import pytest -import yang as ly import sonic_yang as sy import json -import getopt -import subprocess import glob import logging from ijson import items as ijson_itmes @@ -31,7 +28,6 @@ def data(self): @pytest.fixture(autouse=True, scope='class') def yang_s(self, data): yang_dir = str(data['yang_dir']) - data_file = str(data['data_file']) yang_s = sy.sonic_yang(yang_dir) return yang_s @@ -62,7 +58,7 @@ def readIjsonInput(self, yang_test_file, test): raise(e) return jInput - def setup_class(cls): + def setup_class(self): pass def load_yang_model_file(self, yang_s, yang_dir, yang_file, module_name): @@ -133,14 +129,14 @@ def test_find_node(self, data, yang_s): assert dnode is not None assert dnode.path() == xpath else: - assert dnode == None + assert dnode is None #test add node def test_add_node(self, data, yang_s): for node in data['new_nodes']: xpath = str(node['xpath']) value = node['value'] - status = yang_s.add_node(xpath, str(value)) + yang_s.add_node(xpath, str(value)) node = yang_s.find_data_node(xpath) assert node is not None @@ -158,7 +154,6 @@ def test_find_node_value(self, data, yang_s): #test delete data node def test_delete_node(self, data, yang_s): for node in data['delete_nodes']: - expected = node['valid'] xpath = str(node['xpath']) yang_s._delete_node(xpath) @@ -284,5 +279,5 @@ def test_xlate_rev_xlate(self): return - def teardown_class(cls): + def teardown_class(self): pass From a3a81092482d2768e7ae9c1a2c9c12f14e0a25f8 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Mon, 13 Apr 2020 15:28:47 -0700 Subject: [PATCH 07/18] [_sonic_yang_ext.py]: Stores modules irrespective of top-level-container. Stores modules in the map irrespective of top-level-container. This map is used to xlate and revXlate sonic config DB to Yang config. Also Rebasing. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- slave.mk | 2 +- src/sonic-yang-mgmt/_sonic_yang_ext.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/slave.mk b/slave.mk index a982f0271a59..b35254e4fcb2 100644 --- a/slave.mk +++ b/slave.mk @@ -785,7 +785,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_PLATFORM_COMMON_PY2)) \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(REDIS_DUMP_LOAD_PY2)) \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_PLATFORM_API_PY2)) \ - $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3)) + $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3)) \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY)) $(HEADER) # Pass initramfs and linux kernel explicitly. They are used for all platforms diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/_sonic_yang_ext.py index 8c69f56faf6b..b699b8dca383 100644 --- a/src/sonic-yang-mgmt/_sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/_sonic_yang_ext.py @@ -74,16 +74,19 @@ 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'] + # get top level container + topLevelContainer = j['module'].get('container') + # if top level container is none, this is common yang files, which may + # have definitions. Store module. if topLevelContainer is None: - raise Exception("topLevelContainer not found") + self.confDbYangMap[moduleName] = j['module'] + continue + # top level container must exist for rest of the yang files and it should + # have same name as module name. assert topLevelContainer['@name'] == moduleName + # Each container inside topLevelContainer maps to a sonic config table. container = topLevelContainer['container'] # container is a list if isinstance(container, list): From 6debe616a59b24a8c1de0fb0b985541689bee622 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Thu, 16 Apr 2020 17:22:05 -0700 Subject: [PATCH 08/18] [_sonic_yang_ext.py]: Process container and leaves in container. Changes: Support container(s), list(s) and leaves inside a container. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- src/sonic-yang-mgmt/_sonic_yang_ext.py | 138 ++++++++++++++++++++----- 1 file changed, 110 insertions(+), 28 deletions(-) diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/_sonic_yang_ext.py index b699b8dca383..300986cb3a2b 100644 --- a/src/sonic-yang-mgmt/_sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/_sonic_yang_ext.py @@ -292,6 +292,44 @@ def xlateList(self, model, yang, config, table): return + """ + Process list inside a Container. + This function will call xlateList based on list(s) present in Container. + """ + def xlateListInContainer(self, model, yang, configC, table): + clist = model + #print(clist['@name']) + yang[clist['@name']] = list() + self.sysLog(msg="xlateProcessListOfContainer: {}".format(clist['@name'])) + self.xlateList(clist, yang[clist['@name']], configC, table) + # clean empty lists + if len(yang[clist['@name']]) == 0: + del yang[clist['@name']] + + return + + """ + Process container inside a Container. + This function will call xlateContainer based on Container(s) present + in outer Container. + """ + def xlateContainerInContainer(self, model, yang, configC, table): + ccontainer = model + #print(ccontainer['@name']) + yang[ccontainer['@name']] = dict() + if not configC.get(ccontainer['@name']): + return + self.sysLog(msg="xlateProcessListOfContainer: {}".format(ccontainer['@name'])) + self.xlateContainer(ccontainer, yang[ccontainer['@name']], \ + configC[ccontainer['@name']], table) + # clean empty container + if len(yang[ccontainer['@name']]) == 0: + del yang[ccontainer['@name']] + # remove copy after processing + del configC[ccontainer['@name']] + + return + """ Xlate a container This function will xlate from a dict in config DB to a Yang JSON container @@ -299,7 +337,7 @@ def xlateList(self, model, yang, config, table): """ def xlateContainer(self, model, yang, config, table): - # To Handle multiple List, Make a copy of config, because we delete keys + # To Handle multiple Lists, 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() @@ -307,26 +345,33 @@ def xlateContainer(self, model, yang, config, table): # 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']]) - + self.xlateListInContainer(clist, yang, configC, table) # 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']] + self.xlateListInContainer(modelList, yang, configC, table) + # Handle container(s) in container + ccontainer = model.get('container') + # If single list exists in container, + if ccontainer and isinstance(ccontainer, dict) and bool(configC): + self.xlateContainerInContainer(ccontainer, yang, configC, table) + # If multi-list exists in container, + elif ccontainer and isinstance(ccontainer, list) and bool(configC): + for modelContainer in ccontainer: + self.xlateContainerInContainer(modelContainer, yang, configC, table) + + ## Handle other leaves in container, + leafDict = self.createLeafDict(model) + for vKey in configC.keys(): + #vkey must be a leaf\leaf-list\choice in container + if leafDict.get(vKey): + self.sysLog(syslog.LOG_DEBUG, "xlateContainer vkey {}".format(vKey)) + yang[vKey] = self.findYangTypedValue(vKey, configC[vKey], leafDict) + # delete entry from copy of config + del configC[vKey] + + # All entries in copy of config must have been parsed. if len(configC): self.sysLog(syslog.LOG_ERR, "Alert: Remaining keys in Config") raise(Exception("All Keys are not parsed in {}".format(table))) @@ -441,23 +486,60 @@ def revXlateList(self, model, yang, config, table): return + """ + Rev xlate a list inside a yang container + """ + def revXlateListInContainer(self, model, yang, config, table): + modelList = model + # Pass matching list from Yang Json if exist + if yang.get(modelList['@name']): + self.sysLog(msg="revXlateListInContainer {}".format(modelList['@name'])) + self.revXlateList(modelList, yang[modelList['@name']], config, table) + return + + """ + Rev xlate a container inside a yang container + """ + def revXlateContainerInContainer(self, model, yang, config, table): + modelContainer = model + # Pass matching list from Yang Json if exist + if yang.get(modelContainer['@name']): + config[modelContainer['@name']] = dict() + self.sysLog(msg="revXlateContainerInContainer {}".format(modelContainer['@name'])) + self.revXlateContainer(modelContainer, yang[modelContainer['@name']], \ + config[modelContainer['@name']], table) + return + """ Rev xlate from yang container to table in config DB """ def revXlateContainer(self, model, yang, config, table): - # Note: right now containers has only LISTs. # IF container has only one list - if isinstance(model['list'], dict): - modelList = model['list'] - # Pass matching list from Yang Json - self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) - self.revXlateList(modelList, yang[modelList['@name']], config, table) - - elif isinstance(model['list'], list): - for modelList in model['list']: - self.sysLog(msg="revXlateContainer {}".format(modelList['@name'])) - self.revXlateList(modelList, yang[modelList['@name']], config, table) + clist = model.get('list') + if isinstance(clist, dict): + self.revXlateListInContainer(clist, yang, config, table) + # IF container has lists + elif isinstance(clist, list): + for modelList in clist: + self.revXlateListInContainer(modelList, yang, config, table) + + ccontainer = model.get('container') + # IF container has only one inner container + if isinstance(ccontainer, dict): + self.revXlateContainerInContainer(ccontainer, yang, config, table) + # IF container has only many inner container + elif isinstance(ccontainer, list): + for modelContainer in ccontainer: + self.revXlateContainerInContainer(modelContainer, yang, config, table) + + ## Handle other leaves in container, + leafDict = self.createLeafDict(model) + for vKey in yang: + #vkey must be a leaf\leaf-list\choice in container + if leafDict.get(vKey): + self.sysLog(syslog.LOG_DEBUG, "revXlateContainer vkey {}".format(vKey)) + config[vKey] = self.revFindYangTypedValue(vKey, yang[vKey], leafDict) return From 75965151cfa138e0fd700a5cf0e55d9f4ceb95ae Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Thu, 23 Apr 2020 13:39:55 -0700 Subject: [PATCH 09/18] [_sonic_yang_ext.py]: Test Code for Process container and leaves in container. Changes: Test Code for Support container(s), list(s) and leaves inside a container. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- .../libyang-python-tests/test_sonic_yang.py | 2 +- .../tests/yang_model_tests/yangTest.json | 48 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 0dba6942f240..a01349361b9b 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -266,7 +266,7 @@ def test_xlate_rev_xlate(self): syc.loadYangModel() - syc.load_data(json.loads(jIn)) + syc.load_data(json.loads(jIn), allowExtraTables=False) syc.get_data() diff --git a/src/sonic-yang-models/tests/yang_model_tests/yangTest.json b/src/sonic-yang-models/tests/yang_model_tests/yangTest.json index 523eedf3c4b1..5a662a2d00c3 100644 --- a/src/sonic-yang-models/tests/yang_model_tests/yangTest.json +++ b/src/sonic-yang-models/tests/yang_model_tests/yangTest.json @@ -1275,10 +1275,46 @@ "family": "IPv4" } }, - "CRM": { - "Config": { - "polling_interval": "0" - } - } - } + "BREAKOUT_CFG": { + "Ethernet0": { + "brkout_mode": "1x100G[40G]" + }, + "Ethernet4": { + "brkout_mode": "4x25G" + }, + "Ethernet8": { + "brkout_mode": "1x100G[40G]" + } + }, + "VERSIONS": { + "DATABASE": { + "VERSION": "version_1_0_3" + } + }, + "FLEX_COUNTER_TABLE": { + "PFCWD": { + "FLEX_COUNTER_STATUS": "enable" + }, + "PG_WATERMARK": { + "FLEX_COUNTER_STATUS": "enable" + }, + "PORT": { + "FLEX_COUNTER_STATUS": "enable" + }, + "QUEUE": { + "FLEX_COUNTER_STATUS": "enable" + }, + "QUEUE_WATERMARK": { + "FLEX_COUNTER_STATUS": "enable" + } + }, + "CRM": { + "Config": { + "acl_counter_high_threshold": "85", + "acl_counter_low_threshold": "70", + "acl_counter_threshold_type": "percentage", + "polling_interval": "0" + } + } + } } From 334e528a34f3d6ad31651542433f87b5dd454abb Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Thu, 23 Apr 2020 18:17:47 -0700 Subject: [PATCH 10/18] [sonic_yang_ext.py]: Rename _sonic_yang_ext.py as sonic_yang_ext.py. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- src/sonic-yang-mgmt/setup.py | 2 +- src/sonic-yang-mgmt/sonic_yang.py | 2 +- src/sonic-yang-mgmt/{_sonic_yang_ext.py => sonic_yang_ext.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/sonic-yang-mgmt/{_sonic_yang_ext.py => sonic_yang_ext.py} (100%) diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index 15c8243aad7f..eb6c38e5a10e 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -90,7 +90,7 @@ def run (self): include_package_data=True, keywords='sonic_yang_mgmt', name='sonic_yang_mgmt', - py_modules=['sonic_yang', '_sonic_yang_ext'], + py_modules=['sonic_yang', 'sonic_yang_ext'], packages=find_packages(), setup_requires=setup_requirements, version='1.0', diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py index 28e784c481c9..cc89338accdd 100644 --- a/src/sonic-yang-mgmt/sonic_yang.py +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -3,7 +3,7 @@ from json import dump from glob import glob -from _sonic_yang_ext import sonic_yang_ext_mixin +from sonic_yang_ext import sonic_yang_ext_mixin """ Yang schema and data tree python APIs based on libyang python diff --git a/src/sonic-yang-mgmt/_sonic_yang_ext.py b/src/sonic-yang-mgmt/sonic_yang_ext.py similarity index 100% rename from src/sonic-yang-mgmt/_sonic_yang_ext.py rename to src/sonic-yang-mgmt/sonic_yang_ext.py From 524b5cd21a88f91f82a8481fb4d1b5dfdaba5793 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Fri, 24 Apr 2020 14:59:55 -0700 Subject: [PATCH 11/18] [setup.py]: Add IMAGE_DISTRO_DEBS_PATH after recent master branch changes. --- src/sonic-yang-mgmt/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index eb6c38e5a10e..ab8fafa1ebfb 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -10,7 +10,7 @@ import pytest # find path of pkgs from os environment vars -prefix = '/sonic'; debs = environ["STRETCH_DEBS_PATH"] +prefix = '../../'; debs = environ["IMAGE_DISTRO_DEBS_PATH"] wheels = environ["PYTHON_WHEELS_PATH"] wheels_path = '{}/{}'.format(prefix, wheels) deps_path = '{}/{}'.format(prefix, debs) From 49c46a63014d45e82b5102148f2b62af98b48023 Mon Sep 17 00:00:00 2001 From: Ping Mao Date: Sun, 22 Mar 2020 14:55:37 -0700 Subject: [PATCH 12/18] Address feedback from pull request Azure#3874. Changes: 1.) prefix test_ for sample yang models. 2.) name properly with data_node or schema_node. 3.) Update function calls after libyang PLY APIs updates. 4.) Fix test cases after sample yang files name changes. --- src/sonic-yang-mgmt/sonic_yang.py | 90 ++++----- .../sample-yang-models/sonic-module.yang | 144 -------------- .../{sonic-acl.yang => test-sonic-acl.yang} | 8 +- .../{sonic-head.yang => test-sonic-head.yang} | 4 +- ...terface.yang => test-sonic-interface.yang} | 6 +- .../{sonic-port.yang => test-sonic-port.yang} | 4 +- ...annel.yang => test-sonic-portchannel.yang} | 6 +- .../{sonic-vlan.yang => test-sonic-vlan.yang} | 6 +- .../sonic_config_data.json | 22 +-- .../sonic_config_data_merge.json | 12 +- .../libyang-python-tests/test_SonicYang.json | 178 +++++++++--------- .../libyang-python-tests/test_sonic_yang.py | 31 +-- 12 files changed, 190 insertions(+), 321 deletions(-) delete mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-acl.yang => test-sonic-acl.yang} (97%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-head.yang => test-sonic-head.yang} (98%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-interface.yang => test-sonic-interface.yang} (94%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-port.yang => test-sonic-port.yang} (95%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-portchannel.yang => test-sonic-portchannel.yang} (94%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{sonic-vlan.yang => test-sonic-vlan.yang} (97%) diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py index cc89338accdd..70eafef371a4 100644 --- a/src/sonic-yang-mgmt/sonic_yang.py +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -125,12 +125,12 @@ def load_schema_modules_ctx(self, yang_dir=None): """ def load_data_file(self, data_file): try: - node = self.ctx.parse_data_path(data_file, ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) + data_node = self.ctx.parse_data_path(data_file, ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) except Exception as e: print("Failed to load data file: " + str(data_file)) self.fail(e) else: - self.root = node + self.root = data_node """ get module name from xpath @@ -166,7 +166,7 @@ def load_data_model (self, yang_dir, yang_files, data_files, output=None): self.load_data_file(data_files[0]) - for i in range(2, len(data_files)): + for i in range(1, len(data_files)): self.merge_data(data_files[i]) except Exception as e: print("Failed to load data files") @@ -253,59 +253,59 @@ def validate_data_tree (self): self.fail(e) """ - find_parent_node(): find the parent node object + find_parent_data_node(): find the parent node object input: data_xpath - xpath of the data node returns: parent node """ - def find_parent_node (self, data_xpath): + def find_parent_data_node (self, data_xpath): if (self.root is None): print("data not loaded") return None try: - node = self.find_data_node(data_xpath) + data_node = self.find_data_node(data_xpath) except Exception as e: print("Failed to find data node from xpath: " + str(data_xpath)) self.fail(e) else: - if node is not None: - return node.parent() + if data_node is not None: + return data_node.parent() return None """ - get_parent_xpath(): find the parent node xpath + get_parent_data_xpath(): find the parent data node's xpath input: data_xpath - xpathof the data node - returns: - xpath of parent node + returns: - xpath of parent data node - Exception if error """ - def get_parent_xpath (self, data_xpath): + def get_parent_data_xpath (self, data_xpath): path="" try: - node = self.find_parent_node(data_xpath) + data_node = self.find_parent_data_node(data_xpath) except Exception as e: print("Failed to find parent node from xpath: " + str(data_xpath)) self.fail(e) else: - if (node is not None): - path = node.path() + if (data_node is not None): + path = data_node.path() return path """ - new_node(): create a new data node in the data tree + new_data_node(): create a new data node in the data tree input: xpath: xpath of the new node value: value of the new node returns: new Data_Node object if success, Exception if falied """ - def new_node(self, xpath, value): + def new_data_node(self, xpath, value): val = str(value) try: - node = self.root.new_path(self.ctx, xpath, val, 0, 0) + data_node = self.root.new_path(self.ctx, xpath, val, 0, 0) except Exception as e: print("Failed to add data node for path: " + str(xpath)) self.fail(e) else: - return node + return data_node """ find_data_node(): find the data node from xpath @@ -322,9 +322,9 @@ def find_data_node(self, data_xpath): self.fail(e) else: if set is not None: - for node in set.data(): - if (data_xpath == node.path()): - return node + for data_node in set.data(): + if (data_xpath == data_node.path()): + return data_node return None """ find_schema_node(): find the schema node from schema xpath @@ -336,35 +336,35 @@ def find_data_node(self, data_xpath): def find_schema_node(self, schema_xpath): try: schema_set = self.ctx.find_path(schema_xpath) - for snode in schema_set.schema(): - if (schema_xpath == snode.path()): - return snode + for schema_node in schema_set.schema(): + if (schema_xpath == schema_node.path()): + return schema_node except Exception as e: self.fail(e) return None else: - for snode in schema_set.schema(): - if schema_xapth == snode.path(): - return snode + for schema_node in schema_set.schema(): + if schema_xapth == schema_node.path(): + return schema_node return None """ - find_node_schema_xpath(): find the xpath of the schema node from data xpath + find_data_node_schema_xpath(): find the xpath of the schema node from data xpath data xpath example: "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']/port_name" input: data_xpath - xpath of the data node returns: - xpath of the schema node if success - Exception if error """ - def find_node_schema_xpath(self, data_xpath): + def find_data_node_schema_xpath(self, data_xpath): path = "" try: set = self.root.find_path(data_xpath) except Exception as e: self.fail(e) else: - for node in set.data(): - if data_xpath == node.path(): - return node.schema().path() + for data_node in set.data(): + if data_xpath == data_node.path(): + return data_node.schema().path() return path """ @@ -372,11 +372,11 @@ def find_node_schema_xpath(self, data_xpath): input: xpath and value of the node to be added returns: Exception if failed """ - def add_node(self, xpath, value): + def add_data_node(self, data_xpath, value): try: - self.new_node(xpath, value) + self.new_data_node(data_xpath, value) #check if the node added to the data tree - self.find_data_node(xpath) + self.find_data_node(data_xpath) except Exception as e: print("add_node(): Failed to add data node for xpath: " + str(data_xpath)) self.fail(e) @@ -426,20 +426,20 @@ def _delete_node(self, xpath=None, node=None): return False """ - find_node_value(): find the value of a node from the schema/data tree + find_data_node_value(): find the value of a node from the data tree input: data_xpath of the data node returns: value string of the node """ - def find_node_value(self, data_xpath): + def find_data_node_value(self, data_xpath): output = "" try: - node = self.find_data_node(data_xpath) + data_node = self.find_data_node(data_xpath) except Exception as e: - print("find_node_value(): Failed to find data node from xpath: {}".format(data_xpath)) + print("find_data_node_value(): Failed to find data node from xpath: {}".format(data_xpath)) self.fail(e) else: - if (node is not None): - subtype = node.subtype() + if (data_node is not None): + subtype = data_node.subtype() if (subtype is not None): value = subtype.value_str() return value @@ -450,7 +450,7 @@ def find_node_value(self, data_xpath): input: xpath of the data node returns: Exception if failed """ - def set_dnode_value(self, data_xpath, value): + def set_data_node_value(self, data_xpath, value): try: self.root.new_path(self.ctx, data_xpath, str(value), ly.LYD_ANYDATA_STRING, ly.LYD_PATH_OPT_UPDATE) except Exception as e: @@ -493,8 +493,8 @@ def find_schema_dependencies (self, schema_xpath): self.fail(e) return ref_list - snode = ly.Schema_Node_Leaf(schema_node) - backlinks = snode.backlinks() + schema_node = ly.Schema_Node_Leaf(schema_node) + backlinks = schema_node.backlinks() if backlinks.number() > 0: for link in backlinks.schema(): print("backlink schema: {}".format(link.path())) @@ -518,7 +518,7 @@ def find_data_dependencies (self, data_xpath): return ref_list try: - value = str(self.find_node_value(data_xpath)) + value = str(self.find_data_node_value(data_xpath)) schema_node = ly.Schema_Node_Leaf(data_node.schema()) backlinks = schema_node.backlinks() diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang deleted file mode 100644 index 687bb478aacb..000000000000 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-module.yang +++ /dev/null @@ -1,144 +0,0 @@ -module sonic-module { - yang-version 1.1; - namespace "urn:ietf:params:xml:ns:yang:sonic-module"; - - prefix tm; - - import ietf-inet-types { - prefix inet; - } - - organization "organization"; - description - "example yang module"; - contact - "example@example.org"; - - container ports { - list port { - config true; - key "name"; - - leaf name { - type string; - } - - leaf lanes { - //choice - type uint8; - } - -/* - leaf fec { - type string; - } - - leaf mtu { - type uint16; - description - "Set the max transmission unit size in octets - for the physical interface. If this is not set, the mtu is - set to the operational default -- e.g., 1514 bytes on an - Ethernet interface."; - } - leaf admin_status { - type enumeration { - enum UP { - description - "Ready to pass packets."; - } - enum DOWN { - description - "Not ready to pass packets and not in some test mode."; - } - enum TESTING { - //TODO: This is generally not supported as a configured - //admin state, though it's in the standard interfaces MIB. - //Consider removing it. - description - "In some test mode."; - } - } - } -*/ - leaf alias { - type string; - } - - leaf speed { - type string; - units "bits/second"; -/* - description - "An estimate of the interface's current bandwidth in bits - per second. For interfaces that do not vary in - bandwidth or for those where no accurate estimation can - be made, this node should contain the nominal bandwidth. - For interfaces that have no concept of bandwidth, this - node is not present."; - reference - "RFC 2863: The Interfaces Group MIB - - ifSpeed, ifHighSpeed"; -*/ - } - - } - } - - container vlans { - list vlan { - config true; - - key "name"; - - leaf name { - type string; - } - - leaf vlanid { - type string; - } - - leaf admin_status { - type string; -/* - type enumeration { - enum UP { - description - "Ready to pass packets."; - } - enum DOWN { - description - "Not ready to pass packets and not in some test mode."; - } - enum TESTING { - //TODO: This is generally not supported as a configured - //admin state, though it's in the standard interfaces MIB. - //Consider removing it. - description - "In some test mode."; - } - } -*/ - } - - leaf description { - type string; - } - - leaf mtu { - type uint16; - description - "Set the max transmission unit size in octets - for the physical interface. If this is not set, the mtu is - set to the operational default -- e.g., 1514 bytes on an - Ethernet interface."; - } - leaf-list members { - type leafref { - path "../../../ports/port/name"; - } - } - } - } -} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang similarity index 97% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang index 1aab4d7de0b1..79f2cdded177 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-acl.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang @@ -1,4 +1,4 @@ -module sonic-acl { +module test-sonic-acl { yang-version 1.1; @@ -13,17 +13,17 @@ module sonic-acl { prefix inet; } - import sonic-head { + import test-sonic-head { prefix head; revision-date 2019-07-01; } - import sonic-port { + import test-sonic-port { prefix port; revision-date 2019-07-01; } - import sonic-portchannel { + import test-sonic-portchannel { prefix lag; revision-date 2019-07-01; } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang similarity index 98% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang index dd0da92c7152..547ac8ac1cb3 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-head.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang @@ -1,4 +1,4 @@ -module sonic-head { +module test-sonic-head { namespace "http://sonic-head"; prefix sonic-head; @@ -69,4 +69,4 @@ module sonic-head { enum priority_tagged; } } -} \ No newline at end of file +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang similarity index 94% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang index dc8bc7389ea1..fd573b17707d 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-interface.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang @@ -1,4 +1,4 @@ -module sonic-interface { +module test-sonic-interface { namespace "http://github.com/Azure/sonic-interface"; prefix intf; @@ -11,12 +11,12 @@ module sonic-interface { prefix inet; } - import sonic-head { + import test-sonic-head { prefix head; revision-date 2019-07-01; } - import sonic-port { + import test-sonic-port { prefix port; revision-date 2019-07-01; } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang similarity index 95% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang index be04a8aea4bd..494205ce4d92 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-port.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang @@ -1,4 +1,4 @@ -module sonic-port{ +module test-sonic-port{ namespace "http://github.com/Azure/sonic-port"; prefix port; @@ -11,7 +11,7 @@ module sonic-port{ prefix inet; } - import sonic-head { + import test-sonic-head { prefix head; revision-date 2019-07-01; } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang similarity index 94% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang index 656351f4e635..9b664c8289f0 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-portchannel.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang @@ -1,4 +1,4 @@ -module sonic-portchannel { +module test-sonic-portchannel { namespace "http://github.com/Azure/sonic-portchannel"; prefix lag; @@ -11,12 +11,12 @@ module sonic-portchannel { prefix inet; } - import sonic-head { + import test-sonic-head { prefix head; revision-date 2019-07-01; } - import sonic-port { + import test-sonic-port { prefix port; revision-date 2019-07-01; } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang similarity index 97% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang index 6058ce3f1bfc..227c3c6dbe6b 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/sonic-vlan.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang @@ -1,4 +1,4 @@ -module sonic-vlan { +module test-sonic-vlan { namespace "http://github.com/Azure/sonic-vlan"; prefix vlan; @@ -11,12 +11,12 @@ module sonic-vlan { prefix inet; } - import sonic-head { + import test-sonic-head { prefix head; revision-date 2019-07-01; } - import sonic-port { + import test-sonic-port { prefix port; revision-date 2019-07-01; } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json index f7de6902c5d0..35857fd6609c 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json @@ -1,6 +1,6 @@ { - "sonic-vlan:sonic-vlan": { - "sonic-vlan:VLAN_INTERFACE": { + "test-sonic-vlan:sonic-vlan": { + "test-sonic-vlan:VLAN_INTERFACE": { "VLAN_INTERFACE_LIST": [{ "vlanid": 111, "ip-prefix": "2000:f500:45:6709::1/64", @@ -40,7 +40,7 @@ ] }, - "sonic-vlan:VLAN": { + "test-sonic-vlan:VLAN": { "VLAN_LIST": [{ "vlanid": 111, "description": "server_vlan", @@ -78,7 +78,7 @@ ] }, - "sonic-vlan:VLAN_MEMBER": { + "test-sonic-vlan:VLAN_MEMBER": { "VLAN_MEMBER_LIST": [{ "vlanid": 111, "port": "Ethernet0", @@ -117,8 +117,8 @@ ] } }, - "sonic-port:sonic-port": { - "sonic-port:PORT": { + "test-sonic-port:sonic-port": { + "test-sonic-port:PORT": { "PORT_LIST": [{ "port_name": "Ethernet0", "alias": "eth0", @@ -203,8 +203,8 @@ } }, - "sonic-acl:sonic-acl": { - "sonic-acl:ACL_RULE": { + "test-sonic-acl:sonic-acl": { + "test-sonic-acl:ACL_RULE": { "ACL_RULE_LIST": [{ "ACL_TABLE_NAME": "PACL-V4", "RULE_NAME": "Rule_20", @@ -237,7 +237,7 @@ ] }, - "sonic-acl:ACL_TABLE": { + "test-sonic-acl:ACL_TABLE": { "ACL_TABLE_LIST": [{ "ACL_TABLE_NAME": "PACL-V6", "policy_desc": "Filter IPv6", @@ -256,8 +256,8 @@ } }, - "sonic-interface:sonic-interface": { - "sonic-interface:INTERFACE": { + "test-sonic-interface:sonic-interface": { + "test-sonic-interface:INTERFACE": { "INTERFACE_LIST": [{ "interface": "Ethernet8", "ip-prefix": "10.1.1.65/26", diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json index 3bb4f0f5954b..ae668566d9a3 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json @@ -1,6 +1,6 @@ { - "sonic-vlan:sonic-vlan": { - "sonic-vlan:VLAN_INTERFACE": { + "test-sonic-vlan:sonic-vlan": { + "test-sonic-vlan:VLAN_INTERFACE": { "VLAN_INTERFACE_LIST": [{ "vlanid": 111, "ip-prefix": "2000:f500:45:6709::1/64", @@ -28,7 +28,7 @@ ] }, - "sonic-vlan:VLAN": { + "test-sonic-vlan:VLAN": { "VLAN_LIST": [{ "vlanid": 200, "description": "server_vlan", @@ -72,7 +72,7 @@ ] }, - "sonic-vlan:VLAN_MEMBER": { + "test-sonic-vlan:VLAN_MEMBER": { "VLAN_MEMBER_LIST": [{ "vlanid": 200, "port": "Ethernet0", @@ -81,8 +81,8 @@ ] } }, - "sonic-port:sonic-port": { - "sonic-port:PORT": { + "test-sonic-port:sonic-port": { + "test-sonic-port:PORT": { "PORT_LIST": [{ "port_name": "Ethernet0", "alias": "eth0", diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json index b023ba57860e..5369cd3aaac4 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json @@ -3,139 +3,145 @@ "data_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json", "data_merge_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json", "modules":[ - {"file":"sonic-head.yang", "module":"sonic-head"}, - {"file":"sonic-port.yang", "module":"sonic-port"}, - {"file":"sonic-acl.yang", "module":"sonic-acl"}, - {"file":"sonic-interface.yang", "module":"sonic-interface"}, - {"file":"sonic-portchannel.yang", "module":"sonic-portchannel"}, - {"file":"sonic-vlan.yang", "module":"sonic-vlan"} + {"file":"test-sonic-head.yang", "module":"test-sonic-head"}, + {"file":"test-sonic-port.yang", "module":"test-sonic-port"}, + {"file":"test-sonic-acl.yang", "module":"test-sonic-acl"}, + {"file":"test-sonic-interface.yang", "module":"test-sonic-interface"}, + {"file":"test-sonic-portchannel.yang", "module":"test-sonic-portchannel"}, + {"file":"test-sonic-vlan.yang", "module":"test-sonic-vlan"} ], + "merged_nodes":[ + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"25000"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='200'][ip-prefix='2000:f500:45:6708::/64']/family", + "value":"IPv6"} + ], + "new_nodes":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/alias", "value":"Ethernet10_alias"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/speed", "value":"5000"}, - {"xpath":"/sonic-acl:sonic-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/RULE_NAME", + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/alias", "value":"Ethernet10_alias"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/speed", "value":"5000"}, + {"xpath":"/test-sonic-acl:sonic-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/RULE_NAME", "value":"rule_20"} ], "data_nodes":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/alias", "valid":"True"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/alias", "valid":"False"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE", "valid":"True"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST", "valid":"False"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']", "valid":"True"} + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/alias", "valid":"True"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/alias", "valid":"False"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE", "valid":"True"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST", "valid":"False"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']", "valid":"True"} ], "set_nodes":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"10000"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "value":"1500"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/description", "value":"server_vlan111"} + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"10000"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "value":"1500"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/description", "value":"server_vlan111"} ], "node_values":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", "value":"25000"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", "value":"25000"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", "value":"IPv6"} ], "schema_nodes":[ - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", - "value":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:family"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", - "value":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:speed"} + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "value":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:family"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "value":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:speed"} ], "delete_nodes":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "valid":"False"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "valid":"True"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/mtu", "valid":"False"} + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "valid":"False"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "valid":"True"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/mtu", "valid":"False"} ], "dependencies":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name", + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name", "dependencies": - ["/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/members[.='Ethernet8']", - "/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='555']/members[.='Ethernet8']", - "/sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']", - "/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface", - "/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface"]} + ["/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/members[.='Ethernet8']", + "/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='555']/members[.='Ethernet8']", + "/test-sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']", + "/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface", + "/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface"]} ], "schema_dependencies":[ - {"xpath":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name", + {"xpath":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name", "schema_dependencies": - ["/sonic-acl:sonic-acl/sonic-acl:ACL_TABLE/sonic-acl:ACL_TABLE_LIST/sonic-acl:ports", - "/sonic-portchannel:sonic-portchannel/sonic-portchannel:PORTCHANNEL/sonic-portchannel:PORTCHANNEL_LIST/sonic-portchannel:members", - "/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", - "/sonic-vlan:sonic-vlan/sonic-vlan:VLAN/sonic-vlan:VLAN_LIST/sonic-vlan:members", - "/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port"]} + ["/test-sonic-acl:sonic-acl/test-sonic-acl:ACL_TABLE/test-sonic-acl:ACL_TABLE_LIST/test-sonic-acl:ports", + "/test-sonic-portchannel:sonic-portchannel/test-sonic-portchannel:PORTCHANNEL/test-sonic-portchannel:PORTCHANNEL_LIST/test-sonic-portchannel:members", + "/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", + "/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN/test-sonic-vlan:VLAN_LIST/test-sonic-vlan:members", + "/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port"]} ], "members":[ - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST", + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST", "members": - ["/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet1']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet2']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet3']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet4']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet5']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet6']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet7']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']", - "/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']"]} + ["/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet1']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet2']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet3']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet4']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet5']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet6']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet7']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']", + "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']"]} ], "parents":[ - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", - "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/scope", - "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid", - "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/ip-prefix", - "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", - "parent":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", - "parent":"/sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']"} - ], + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/scope", + "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid", + "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/ip-prefix", + "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "parent":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']"} + ], "prefix":[ - {"module_name":"sonic-head", "module_prefix":"sonic-head"}, - {"module_name":"sonic-port", "module_prefix":"port"}, - {"module_name":"sonic-acl", "module_prefix":"acl"}, - {"module_name":"sonic-interface", "module_prefix":"intf"}, - {"module_name":"sonic-portchannel", "module_prefix":"lag"}, - {"module_name":"sonic-vlan", "module_prefix":"vlan"} + {"module_name":"test-sonic-head", "module_prefix":"sonic-head"}, + {"module_name":"test-sonic-port", "module_prefix":"port"}, + {"module_name":"test-sonic-acl", "module_prefix":"acl"}, + {"module_name":"test-sonic-interface", "module_prefix":"intf"}, + {"module_name":"test-sonic-portchannel", "module_prefix":"lag"}, + {"module_name":"test-sonic-vlan", "module_prefix":"vlan"} ], "data_type":[ - {"xpath":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", "data_type":"LY_TYPE_LEAFREF"} + {"xpath":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", "data_type":"LY_TYPE_LEAFREF"} ], "leafref_type":[ - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid", "data_type":"LY_TYPE_UINT16"}, - {"xpath":"/sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid", "data_type":"LY_TYPE_UINT16"} + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid", "data_type":"LY_TYPE_UINT16"}, + {"xpath":"/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid", "data_type":"LY_TYPE_UINT16"} ], "leafref_type_schema":[ - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", "data_type":"LY_TYPE_UINT16"}, - {"xpath":"/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", + {"xpath":"/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port", + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:vlanid", + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:vlanid", "data_type":"LY_TYPE_UINT16"} ], "leafref_path":[ - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_INTERFACE/sonic-vlan:VLAN_INTERFACE_LIST/sonic-vlan:vlanid", + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"}, - {"xpath":"/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_LIST/sonic-interface:interface", - "leafref_path":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name"}, - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:port", - "leafref_path":"/sonic-port:sonic-port/sonic-port:PORT/sonic-port:PORT_LIST/sonic-port:port_name"}, - {"xpath":"/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST/sonic-vlan:vlanid", + {"xpath":"/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", + "leafref_path":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port", + "leafref_path":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name"}, + {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:vlanid", "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"} ] } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index a01349361b9b..6fb7a6849a47 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -109,6 +109,13 @@ def test_load_yang_model_data(self, data, yang_s): print(yang_files) yang_s.load_data_model(yang_dir, yang_files, data_files) + #validate the data tree from data_merge_file is loaded + for node in data['merged_nodes']: + xpath = str(node['xpath']) + value = str(node['value']) + val = yang_s.find_data_node_value(xpath) + assert str(val) == str(value) + #test load data file def test_load_data_file(self, data, yang_s): data_file = str(data['data_file']) @@ -136,19 +143,19 @@ def test_add_node(self, data, yang_s): for node in data['new_nodes']: xpath = str(node['xpath']) value = node['value'] - yang_s.add_node(xpath, str(value)) + yang_s.add_data_node(xpath, str(value)) - node = yang_s.find_data_node(xpath) - assert node is not None + data_node = yang_s.find_data_node(xpath) + assert data_node is not None #test find node value - def test_find_node_value(self, data, yang_s): + def test_find_data_node_value(self, data, yang_s): for node in data['node_values']: xpath = str(node['xpath']) value = str(node['value']) print(xpath) print(value) - val = yang_s.find_node_value(xpath) + val = yang_s.find_data_node_value(xpath) assert str(val) == str(value) #test delete data node @@ -162,9 +169,9 @@ def test_set_datanode_value(self, data, yang_s): for node in data['set_nodes']: xpath = str(node['xpath']) value = node['value'] - yang_s.set_dnode_value(xpath, value) + yang_s.set_data_node_value(xpath, value) - val = yang_s.find_node_value(xpath) + val = yang_s.find_data_node_value(xpath) assert str(val) == str(value) #test list of members @@ -176,19 +183,19 @@ def test_find_members(self, yang_s, data): assert list.sort() == members.sort() #get parent xpath - def test_get_parent_xpath(self, yang_s, data): + def test_get_parent_data_xpath(self, yang_s, data): for node in data['parents']: xpath = str(node['xpath']) expected_xpath = str(node['parent']) - path = yang_s.get_parent_xpath(xpath) + path = yang_s.get_parent_data_xpath(xpath) assert path == expected_xpath - #test find_node_schema_xpath - def test_find_node_schema_xpath(self, yang_s, data): + #test find_data_node_schema_xpath + def test_find_data_node_schema_xpath(self, yang_s, data): for node in data['schema_nodes']: xpath = str(node['xpath']) schema_xpath = str(node['value']) - path = yang_s.find_node_schema_xpath(xpath) + path = yang_s.find_data_node_schema_xpath(xpath) assert path == schema_xpath #test data dependencies From fb66b2cfac74b375ffaceea88fd32829f901f30f Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Mon, 27 Apr 2020 09:37:29 -0700 Subject: [PATCH 13/18] [libyang-python-tests/test_sonic_yang.py]: Allow extra tables in config for the test. Changes: 1.) All the YANG models are not merged yet, so test config has tables with no YANG, allow this scenario for tests as of now. --- .../tests/libyang-python-tests/test_sonic_yang.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 6fb7a6849a47..035833e7ca64 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -273,7 +273,8 @@ def test_xlate_rev_xlate(self): syc.loadYangModel() - syc.load_data(json.loads(jIn), allowExtraTables=False) + # Allow additional tabbles in config, since all yang models are not merged # + syc.load_data(json.loads(jIn)) syc.get_data() From 57db2470fa6f07400dbd16dbf6ae974c4aa9172c Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Fri, 8 May 2020 11:56:53 -0700 Subject: [PATCH 14/18] [sonic-yang-mgmt-py2.dep]: DEP files for sonic-yang-mgmt-py2 PKG --- rules/sonic-yang-mgmt-py2.dep | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 rules/sonic-yang-mgmt-py2.dep diff --git a/rules/sonic-yang-mgmt-py2.dep b/rules/sonic-yang-mgmt-py2.dep new file mode 100644 index 000000000000..a4ec4264b49a --- /dev/null +++ b/rules/sonic-yang-mgmt-py2.dep @@ -0,0 +1,10 @@ + +SPATH := $($(SONIC_YANG_MGMT_PY)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/sonic-yang-mgmt-py2.mk rules/sonic-yang-mgmt-py2.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(SPATH)) + +$(SONIC_YANG_MGMT_PY)_CACHE_MODE := GIT_CONTENT_SHA +$(SONIC_YANG_MGMT_PY)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(SONIC_YANG_MGMT_PY)_DEP_FILES := $(DEP_FILES) + From f82637a701aa4ba22d3b4aab53cf607fd3d9d651 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Fri, 8 May 2020 16:12:40 -0700 Subject: [PATCH 15/18] [tests/libyang-python-tests]: Changing yang models to generic and removing reference to sonic. Changes: 1.) To avoid confusion, Changing yang models to generic and removing reference to sonic 2.) Additing Relative path whenever needed. --- ...onic_config_data.json => config_data.json} | 42 ++--- .../config_data_merge.json | 154 +++++++++++++++ .../{test-sonic-acl.yang => test-acl.yang} | 28 ++- .../{test-sonic-head.yang => test-head.yang} | 14 +- ...nic-interface.yang => test-interface.yang} | 18 +- .../{test-sonic-port.yang => test-port.yang} | 18 +- ...portchannel.yang => test-portchannel.yang} | 22 +-- .../{test-sonic-vlan.yang => test-vlan.yang} | 30 +-- .../sonic_config_data_merge.json | 177 ------------------ .../libyang-python-tests/test_SonicYang.json | 177 +++++++++--------- .../libyang-python-tests/test_sonic_yang.py | 6 +- 11 files changed, 300 insertions(+), 386 deletions(-) rename src/sonic-yang-mgmt/tests/libyang-python-tests/{sonic_config_data.json => config_data.json} (87%) create mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/config_data_merge.json rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-acl.yang => test-acl.yang} (87%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-head.yang => test-head.yang} (83%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-interface.yang => test-interface.yang} (74%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-port.yang => test-port.yang} (74%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-portchannel.yang => test-portchannel.yang} (69%) rename src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/{test-sonic-vlan.yang => test-vlan.yang} (79%) delete mode 100644 src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json similarity index 87% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json rename to src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json index 35857fd6609c..8a4eaed176d2 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data.json @@ -1,6 +1,6 @@ { - "test-sonic-vlan:sonic-vlan": { - "test-sonic-vlan:VLAN_INTERFACE": { + "test-vlan:vlan": { + "test-vlan:VLAN_INTERFACE": { "VLAN_INTERFACE_LIST": [{ "vlanid": 111, "ip-prefix": "2000:f500:45:6709::1/64", @@ -40,7 +40,7 @@ ] }, - "test-sonic-vlan:VLAN": { + "test-vlan:VLAN": { "VLAN_LIST": [{ "vlanid": 111, "description": "server_vlan", @@ -48,18 +48,7 @@ "10.1.7.116" ], "mtu": "9216", - "admin_status": "up", - "members": [ - "Ethernet8", - "Ethernet3", - "Ethernet0", - "Ethernet1", - "Ethernet6", - "Ethernet4", - "Ethernet5", - "Ethernet9", - "Ethernet2" - ] + "admin_status": "up" }, { "vlanid": 555, @@ -68,17 +57,12 @@ "10.1.7.116" ], "mtu": "9216", - "admin_status": "up", - "members": [ - "Ethernet9", - "Ethernet2", - "Ethernet8" - ] + "admin_status": "up" } ] }, - "test-sonic-vlan:VLAN_MEMBER": { + "test-vlan:VLAN_MEMBER": { "VLAN_MEMBER_LIST": [{ "vlanid": 111, "port": "Ethernet0", @@ -117,8 +101,8 @@ ] } }, - "test-sonic-port:sonic-port": { - "test-sonic-port:PORT": { + "test-port:port": { + "test-port:PORT": { "PORT_LIST": [{ "port_name": "Ethernet0", "alias": "eth0", @@ -203,8 +187,8 @@ } }, - "test-sonic-acl:sonic-acl": { - "test-sonic-acl:ACL_RULE": { + "test-acl:acl": { + "test-acl:ACL_RULE": { "ACL_RULE_LIST": [{ "ACL_TABLE_NAME": "PACL-V4", "RULE_NAME": "Rule_20", @@ -237,7 +221,7 @@ ] }, - "test-sonic-acl:ACL_TABLE": { + "test-acl:ACL_TABLE": { "ACL_TABLE_LIST": [{ "ACL_TABLE_NAME": "PACL-V6", "policy_desc": "Filter IPv6", @@ -256,8 +240,8 @@ } }, - "test-sonic-interface:sonic-interface": { - "test-sonic-interface:INTERFACE": { + "test-interface:interface": { + "test-interface:INTERFACE": { "INTERFACE_LIST": [{ "interface": "Ethernet8", "ip-prefix": "10.1.1.65/26", diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data_merge.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data_merge.json new file mode 100644 index 000000000000..73838a157c4a --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/config_data_merge.json @@ -0,0 +1,154 @@ +{ + "test-vlan:vlan": { + "test-vlan:VLAN_INTERFACE": { + "VLAN_INTERFACE_LIST": [{ + "vlanid": 111, + "ip-prefix": "2000:f500:45:6709::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 111, + "ip-prefix": "10.1.1.64/26", + "scope": "global", + "family": "IPv4" + }, + { + "vlanid": 200, + "ip-prefix": "2000:f500:45:6708::1/64", + "scope": "global", + "family": "IPv6" + }, + { + "vlanid": 200, + "ip-prefix": "2000:f500:45:6709::1/64", + "scope": "global", + "family": "IPv6" + } + ] + }, + + "test-vlan:VLAN": { + "VLAN_LIST": [{ + "vlanid": 200, + "description": "server_vlan", + "dhcp_servers": [ + "10.1.72.116" + ], + "mtu": "9216", + "admin_status": "up" + }, + { + "vlanid": 111, + "description": "server_vlan", + "dhcp_servers": [ + "10.1.72.116" + ], + "mtu": "9216", + "admin_status": "up" + } + ] + }, + + "test-vlan:VLAN_MEMBER": { + "VLAN_MEMBER_LIST": [{ + "vlanid": 200, + "port": "Ethernet0", + "tagging_mode": "tagged" + }] + } + }, + "test-port:port": { + "test-port:PORT": { + "PORT_LIST": [{ + "port_name": "Ethernet0", + "alias": "eth0", + "description": "Ethernet0", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet1", + "alias": "eth1", + "description": "Ethernet1", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet2", + "alias": "eth2", + "description": "Ethernet2", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet3", + "alias": "eth2", + "description": "Ethernet3", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet4", + "alias": "eth4", + "description": "Ethernet4", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet5", + "alias": "eth5", + "description": "Ethernet5", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet6", + "alias": "eth6", + "description": "Ethernet6", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet7", + "alias": "eth7", + "description": "Ethernet7", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet8", + "alias": "eth8", + "description": "Ethernet8", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet9", + "alias": "eth9", + "description": "Ethernet9", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + }, + { + "port_name": "Ethernet10", + "alias": "eth10", + "description": "Ethernet10", + "speed": 25000, + "mtu": 9000, + "admin_status": "up" + } + ] + } + } +} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-acl.yang similarity index 87% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-acl.yang index 79f2cdded177..0d7d93a1429a 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-acl.yang @@ -1,8 +1,8 @@ -module test-sonic-acl { +module test-acl { yang-version 1.1; - namespace "http://github.com/Azure/sonic-acl"; + namespace "http://github.com/Azure/acl"; prefix acl; import ietf-yang-types { @@ -13,32 +13,26 @@ module test-sonic-acl { prefix inet; } - import test-sonic-head { + import test-head { prefix head; revision-date 2019-07-01; } - import test-sonic-port { + import test-port { prefix port; revision-date 2019-07-01; } - import test-sonic-portchannel { + import test-portchannel { prefix lag; revision-date 2019-07-01; } - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "ACL YANG Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - container sonic-acl { + container acl { container ACL_RULE { @@ -50,7 +44,7 @@ module test-sonic-acl { leaf ACL_TABLE_NAME { type leafref { - path "/acl:sonic-acl/acl:ACL_TABLE/acl:ACL_TABLE_LIST/acl:ACL_TABLE_NAME"; + path "/acl:acl/acl:ACL_TABLE/acl:ACL_TABLE_LIST/acl:ACL_TABLE_NAME"; } } @@ -257,10 +251,10 @@ module test-sonic-acl { /* union of leafref is allowed in YANG 1.1 */ type union { type leafref { - path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + path /port:port/port:PORT/port:PORT_LIST/port:port_name; } type leafref { - path /lag:sonic-portchannel/lag:PORTCHANNEL/lag:PORTCHANNEL_LIST/lag:portchannel_name; + path /lag:portchannel/lag:PORTCHANNEL/lag:PORTCHANNEL_LIST/lag:portchannel_name; } } } @@ -269,6 +263,6 @@ module test-sonic-acl { } /* end of container ACL_TABLE */ } - /* end of container sonic-acl */ + /* end of container acl */ } -/* end of module sonic-acl */ +/* end of module acl */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-head.yang similarity index 83% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-head.yang index 547ac8ac1cb3..cdb45a3d0d15 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-head.yang @@ -1,18 +1,12 @@ -module test-sonic-head { +module test-head { - namespace "http://sonic-head"; - prefix sonic-head; + namespace "http://head"; + prefix head; - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "Head yang Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - + typedef ip-family { type enumeration { enum IPv4; diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-interface.yang similarity index 74% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-interface.yang index fd573b17707d..2f453ac6e278 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-interface.yang @@ -1,6 +1,6 @@ -module test-sonic-interface { +module test-interface { - namespace "http://github.com/Azure/sonic-interface"; + namespace "http://github.com/Azure/interface"; prefix intf; import ietf-yang-types { @@ -11,27 +11,21 @@ module test-sonic-interface { prefix inet; } - import test-sonic-head { + import test-head { prefix head; revision-date 2019-07-01; } - import test-sonic-port { + import test-port { prefix port; revision-date 2019-07-01; } - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "INTERFACE yang Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - container sonic-interface { + container interface { container INTERFACE { description "INTERFACE part of config_db.json"; @@ -42,7 +36,7 @@ module test-sonic-interface { leaf interface { type leafref { - path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + path /port:port/port:PORT/port:PORT_LIST/port:port_name; } } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-port.yang similarity index 74% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-port.yang index 494205ce4d92..5e2bf68f7914 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-port.yang @@ -1,6 +1,6 @@ -module test-sonic-port{ +module test-port{ - namespace "http://github.com/Azure/sonic-port"; + namespace "http://github.com/Azure/port"; prefix port; import ietf-yang-types { @@ -11,22 +11,16 @@ module test-sonic-port{ prefix inet; } - import test-sonic-head { + import test-head { prefix head; revision-date 2019-07-01; } - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "PORT yang Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - container sonic-port{ + container port{ container PORT { description "PORT part of config_db.json"; @@ -79,6 +73,6 @@ module test-sonic-port{ } /* end of container PORT */ - } /* end of container sonic-port */ + } /* end of container port */ -} /* end of module sonic-port */ +} /* end of module port */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-portchannel.yang similarity index 69% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-portchannel.yang index 9b664c8289f0..2081383aeda8 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-portchannel.yang @@ -1,6 +1,6 @@ -module test-sonic-portchannel { +module test-portchannel { - namespace "http://github.com/Azure/sonic-portchannel"; + namespace "http://github.com/Azure/portchannel"; prefix lag; import ietf-yang-types { @@ -11,27 +11,21 @@ module test-sonic-portchannel { prefix inet; } - import test-sonic-head { + import test-head { prefix head; revision-date 2019-07-01; } - import test-sonic-port { + import test-port { prefix port; revision-date 2019-07-01; } - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "PORTCHANNEL yang Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - container sonic-portchannel { + container portchannel { container PORTCHANNEL { description "PORTCHANNEL part of config_db.json"; @@ -50,7 +44,7 @@ module test-sonic-portchannel { leaf-list members { /* leaf-list members are unique by default */ type leafref { - path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + path /port:port/port:PORT/port:PORT_LIST/port:port_name; } } @@ -80,6 +74,6 @@ module test-sonic-portchannel { } /* end of container PORTCHANNEL */ - } /* end of container sonic-portchannel */ + } /* end of container portchannel */ -} /* end of module sonic-port */ +} /* end of module port */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-vlan.yang similarity index 79% rename from src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang rename to src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-vlan.yang index 227c3c6dbe6b..2ca80607b410 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-vlan.yang @@ -1,6 +1,6 @@ -module test-sonic-vlan { +module test-vlan { - namespace "http://github.com/Azure/sonic-vlan"; + namespace "http://github.com/Azure/vlan"; prefix vlan; import ietf-yang-types { @@ -11,27 +11,21 @@ module test-sonic-vlan { prefix inet; } - import test-sonic-head { + import test-head { prefix head; revision-date 2019-07-01; } - import test-sonic-port { + import test-port { prefix port; revision-date 2019-07-01; } - organization "Linkedin Corporation"; - - contact "lnos_coders@linkedin.com"; - - description "VLAN yang Module for SONiC OS"; - revision 2019-07-01 { description "First Revision"; } - container sonic-vlan { + container vlan { container VLAN_INTERFACE { description "VLAN_INTERFACE part of config_db.json"; @@ -109,14 +103,6 @@ module test-sonic-vlan { mandatory true; type head:admin_status; } - - leaf-list members { - /* leaf-list members are unique by default */ - - type leafref { - path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; - } - } } /* end of VLAN_LIST */ } @@ -140,7 +126,7 @@ module test-sonic-vlan { /* key elements are mandatory by default */ mandatory true; type leafref { - path /port:sonic-port/port:PORT/port:PORT_LIST/port:port_name; + path /port:port/port:PORT/port:PORT_LIST/port:port_name; } } @@ -153,6 +139,6 @@ module test-sonic-vlan { } /* end of container VLAN_MEMBER */ } - /* end of container sonic-vlan */ + /* end of container vlan */ } -/* end of module sonic-vlan */ +/* end of module vlan */ diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json deleted file mode 100644 index ae668566d9a3..000000000000 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "test-sonic-vlan:sonic-vlan": { - "test-sonic-vlan:VLAN_INTERFACE": { - "VLAN_INTERFACE_LIST": [{ - "vlanid": 111, - "ip-prefix": "2000:f500:45:6709::1/64", - "scope": "global", - "family": "IPv6" - }, - { - "vlanid": 111, - "ip-prefix": "10.1.1.64/26", - "scope": "global", - "family": "IPv4" - }, - { - "vlanid": 200, - "ip-prefix": "2000:f500:45:6708::1/64", - "scope": "global", - "family": "IPv6" - }, - { - "vlanid": 200, - "ip-prefix": "2000:f500:45:6709::1/64", - "scope": "global", - "family": "IPv6" - } - ] - }, - - "test-sonic-vlan:VLAN": { - "VLAN_LIST": [{ - "vlanid": 200, - "description": "server_vlan", - "dhcp_servers": [ - "10.1.72.116" - ], - "mtu": "9216", - "admin_status": "up", - "members": [ - "Ethernet8", - "Ethernet3", - "Ethernet0", - "Ethernet1", - "Ethernet6", - "Ethernet4", - "Ethernet5", - "Ethernet9", - "Ethernet2" - ] - }, - { - "vlanid": 111, - "description": "server_vlan", - "dhcp_servers": [ - "10.1.72.116" - ], - "mtu": "9216", - "admin_status": "up", - "members": [ - "Ethernet8", - "Ethernet3", - "Ethernet0", - "Ethernet1", - "Ethernet6", - "Ethernet4", - "Ethernet5", - "Ethernet9", - "Ethernet2" - ] - } - ] - }, - - "test-sonic-vlan:VLAN_MEMBER": { - "VLAN_MEMBER_LIST": [{ - "vlanid": 200, - "port": "Ethernet0", - "tagging_mode": "tagged" - } - ] - } - }, - "test-sonic-port:sonic-port": { - "test-sonic-port:PORT": { - "PORT_LIST": [{ - "port_name": "Ethernet0", - "alias": "eth0", - "description": "Ethernet0", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet1", - "alias": "eth1", - "description": "Ethernet1", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet2", - "alias": "eth2", - "description": "Ethernet2", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet3", - "alias": "eth2", - "description": "Ethernet3", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet4", - "alias": "eth4", - "description": "Ethernet4", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet5", - "alias": "eth5", - "description": "Ethernet5", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet6", - "alias": "eth6", - "description": "Ethernet6", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet7", - "alias": "eth7", - "description": "Ethernet7", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet8", - "alias": "eth8", - "description": "Ethernet8", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet9", - "alias": "eth9", - "description": "Ethernet9", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - }, - { - "port_name": "Ethernet10", - "alias": "eth10", - "description": "Ethernet10", - "speed": 25000, - "mtu": 9000, - "admin_status": "up" - } - ] - } - } -} diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json index 5369cd3aaac4..f3e0c46f76b2 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json @@ -1,147 +1,144 @@ { - "yang_dir":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/", - "data_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json", - "data_merge_file":"/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json", + "yang_dir":"./tests/libyang-python-tests/sample-yang-models/", + "data_file":"./tests/libyang-python-tests/config_data.json", + "data_merge_file":"./tests/libyang-python-tests/config_data_merge.json", "modules":[ - {"file":"test-sonic-head.yang", "module":"test-sonic-head"}, - {"file":"test-sonic-port.yang", "module":"test-sonic-port"}, - {"file":"test-sonic-acl.yang", "module":"test-sonic-acl"}, - {"file":"test-sonic-interface.yang", "module":"test-sonic-interface"}, - {"file":"test-sonic-portchannel.yang", "module":"test-sonic-portchannel"}, - {"file":"test-sonic-vlan.yang", "module":"test-sonic-vlan"} + {"file":"test-head.yang", "module":"test-head"}, + {"file":"test-port.yang", "module":"test-port"}, + {"file":"test-acl.yang", "module":"test-acl"}, + {"file":"test-interface.yang", "module":"test-interface"}, + {"file":"test-portchannel.yang", "module":"test-portchannel"}, + {"file":"test-vlan.yang", "module":"test-vlan"} ], "merged_nodes":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"25000"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='200'][ip-prefix='2000:f500:45:6708::/64']/family", + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"25000"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='200'][ip-prefix='2000:f500:45:6708::/64']/family", "value":"IPv6"} ], "new_nodes":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/alias", "value":"Ethernet10_alias"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']/speed", "value":"5000"}, - {"xpath":"/test-sonic-acl:sonic-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/RULE_NAME", + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet12']/alias", "value":"Ethernet10_alias"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet12']/speed", "value":"5000"}, + {"xpath":"/test-acl:acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/RULE_NAME", "value":"rule_20"} ], "data_nodes":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/alias", "valid":"True"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/alias", "valid":"False"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE", "valid":"True"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST", "valid":"False"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']", "valid":"True"} + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/alias", "valid":"True"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet20']/alias", "valid":"False"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE", "valid":"True"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST", "valid":"False"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']", "valid":"True"} ], "set_nodes":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"10000"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "value":"1500"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/description", "value":"server_vlan111"} + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "value":"10000"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "value":"1500"}, + {"xpath":"/test-vlan:vlan/VLAN/VLAN_LIST[vlanid='111']/description", "value":"server_vlan111"} ], "node_values":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", "value":"25000"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/speed", "value":"25000"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", "value":"IPv6"} ], "schema_nodes":[ - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", - "value":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:family"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", - "value":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:speed"} + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "value":"/test-vlan:vlan/test-vlan:VLAN_INTERFACE/test-vlan:VLAN_INTERFACE_LIST/test-vlan:family"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "value":"/test-port:port/test-port:PORT/test-port:PORT_LIST/test-port:speed"} ], "delete_nodes":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "valid":"False"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "valid":"True"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet20']/mtu", "valid":"False"} + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet10']/speed", "valid":"False"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/mtu", "valid":"True"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet20']/mtu", "valid":"False"} ], "dependencies":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name", + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet8']/port_name", "dependencies": - ["/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='111']/members[.='Ethernet8']", - "/test-sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[vlanid='555']/members[.='Ethernet8']", - "/test-sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']", - "/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface", - "/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface"]} + ["/test-acl:acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']", + "/test-interface:interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface", + "/test-interface:interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface"]} ], "schema_dependencies":[ - {"xpath":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name", + {"xpath":"/test-port:port/test-port:PORT/test-port:PORT_LIST/test-port:port_name", "schema_dependencies": - ["/test-sonic-acl:sonic-acl/test-sonic-acl:ACL_TABLE/test-sonic-acl:ACL_TABLE_LIST/test-sonic-acl:ports", - "/test-sonic-portchannel:sonic-portchannel/test-sonic-portchannel:PORTCHANNEL/test-sonic-portchannel:PORTCHANNEL_LIST/test-sonic-portchannel:members", - "/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", - "/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN/test-sonic-vlan:VLAN_LIST/test-sonic-vlan:members", - "/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port"]} + ["/test-acl:acl/test-acl:ACL_TABLE/test-acl:ACL_TABLE_LIST/test-acl:ports", + "/test-portchannel:portchannel/test-portchannel:PORTCHANNEL/test-portchannel:PORTCHANNEL_LIST/test-portchannel:members", + "/test-interface:interface/test-interface:INTERFACE/test-interface:INTERFACE_LIST/test-interface:interface", + "/test-vlan:vlan/test-vlan:VLAN_MEMBER/test-vlan:VLAN_MEMBER_LIST/test-vlan:port"]} ], "members":[ - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST", + {"xpath":"/test-port:port/PORT/PORT_LIST", "members": - ["/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet0']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet1']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet2']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet3']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet4']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet5']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet6']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet7']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet8']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet10']", - "/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet12']"]} + ["/test-port:port/PORT/PORT_LIST[port_name='Ethernet0']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet1']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet2']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet3']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet4']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet5']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet6']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet7']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet8']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet10']", + "/test-port:port/PORT/PORT_LIST[port_name='Ethernet12']"]} ], "parents":[ - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", - "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/scope", - "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid", - "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/ip-prefix", - "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", - "parent":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, - {"xpath":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']/speed", - "parent":"/test-sonic-port:sonic-port/PORT/PORT_LIST[port_name='Ethernet9']"} + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/family", + "parent":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/scope", + "parent":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid", + "parent":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/ip-prefix", + "parent":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/family", + "parent":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']"}, + {"xpath":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']/speed", + "parent":"/test-port:port/PORT/PORT_LIST[port_name='Ethernet9']"} ], "prefix":[ - {"module_name":"test-sonic-head", "module_prefix":"sonic-head"}, - {"module_name":"test-sonic-port", "module_prefix":"port"}, - {"module_name":"test-sonic-acl", "module_prefix":"acl"}, - {"module_name":"test-sonic-interface", "module_prefix":"intf"}, - {"module_name":"test-sonic-portchannel", "module_prefix":"lag"}, - {"module_name":"test-sonic-vlan", "module_prefix":"vlan"} + {"module_name":"test-head", "module_prefix":"head"}, + {"module_name":"test-port", "module_prefix":"port"}, + {"module_name":"test-acl", "module_prefix":"acl"}, + {"module_name":"test-interface", "module_prefix":"intf"}, + {"module_name":"test-portchannel", "module_prefix":"lag"}, + {"module_name":"test-vlan", "module_prefix":"vlan"} ], "data_type":[ - {"xpath":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", "data_type":"LY_TYPE_LEAFREF"} + {"xpath":"/test-port:port/test-port:PORT/test-port:PORT_LIST/test-port:port_name", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_INTERFACE/test-vlan:VLAN_INTERFACE_LIST/test-vlan:vlanid", "data_type":"LY_TYPE_LEAFREF"} ], "leafref_type":[ - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid", "data_type":"LY_TYPE_UINT16"}, - {"xpath":"/test-sonic-interface:sonic-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid", "data_type":"LY_TYPE_UINT16"} + {"xpath":"/test-vlan:vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid", "data_type":"LY_TYPE_UINT16"}, + {"xpath":"/test-interface:interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-vlan:vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port", "data_type":"LY_TYPE_STRING"}, + {"xpath":"/test-vlan:vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid", "data_type":"LY_TYPE_UINT16"} ], "leafref_type_schema":[ - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_INTERFACE/test-vlan:VLAN_INTERFACE_LIST/test-vlan:vlanid", "data_type":"LY_TYPE_UINT16"}, - {"xpath":"/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", + {"xpath":"/test-interface:interface/test-interface:INTERFACE/test-interface:INTERFACE_LIST/test-interface:interface", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port", + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_MEMBER/test-vlan:VLAN_MEMBER_LIST/test-vlan:port", "data_type":"LY_TYPE_STRING"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:vlanid", + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_MEMBER/test-vlan:VLAN_MEMBER_LIST/test-vlan:vlanid", "data_type":"LY_TYPE_UINT16"} ], "leafref_path":[ - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_INTERFACE/test-sonic-vlan:VLAN_INTERFACE_LIST/test-sonic-vlan:vlanid", + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_INTERFACE/test-vlan:VLAN_INTERFACE_LIST/test-vlan:vlanid", "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"}, - {"xpath":"/test-sonic-interface:sonic-interface/test-sonic-interface:INTERFACE/test-sonic-interface:INTERFACE_LIST/test-sonic-interface:interface", - "leafref_path":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:port", - "leafref_path":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-port:port_name"}, - {"xpath":"/test-sonic-vlan:sonic-vlan/test-sonic-vlan:VLAN_MEMBER/test-sonic-vlan:VLAN_MEMBER_LIST/test-sonic-vlan:vlanid", + {"xpath":"/test-interface:interface/test-interface:INTERFACE/test-interface:INTERFACE_LIST/test-interface:interface", + "leafref_path":"/test-port:port/test-port:PORT/test-port:PORT_LIST/test-port:port_name"}, + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_MEMBER/test-vlan:VLAN_MEMBER_LIST/test-vlan:port", + "leafref_path":"/test-port:port/test-port:PORT/test-port:PORT_LIST/test-port:port_name"}, + {"xpath":"/test-vlan:vlan/test-vlan:VLAN_MEMBER/test-vlan:VLAN_MEMBER_LIST/test-vlan:vlanid", "leafref_path":"../../../VLAN/VLAN_LIST/vlanid"} ] } diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 035833e7ca64..8c35893432d6 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -21,7 +21,7 @@ class Test_SonicYang(object): @pytest.fixture(autouse=True, scope='class') def data(self): - test_file = "/sonic/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json" + test_file = "./tests/libyang-python-tests/test_SonicYang.json" data = self.jsonTestParser(test_file) return data @@ -265,8 +265,8 @@ def test_xlate_rev_xlate(self): # In this test, xlation and revXlation is tested with latest Sonic # YANG model. - yang_dir = "/sonic/src/sonic-yang-models/yang-models/" - yang_test_file = "/sonic/src/sonic-yang-models/tests/yang_model_tests/yangTest.json" + yang_dir = "../sonic-yang-models/yang-models/" + yang_test_file = "../sonic-yang-models/tests/yang_model_tests/yangTest.json" jIn = self.readIjsonInput(yang_test_file, 'SAMPLE_CONFIG_DB_JSON') # load yang models syc = sy.sonic_yang(yang_dir) From 977df0a1605006493f1cce3087aab50acd0887cd Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Mon, 27 Apr 2020 14:52:57 -0700 Subject: [PATCH 16/18] [sonic_yang_ext.py]: Store Tables without YANG models seperately for user warning. Changes: 1.) Store Tables with YANG models seperately, so that a warning to user can be issued with enough information. 2.) print the tables without YANG models properly in cropConfigDB(). Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- src/sonic-yang-mgmt/sonic_yang.py | 2 ++ src/sonic-yang-mgmt/sonic_yang_ext.py | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py index 70eafef371a4..cb7f5873fa33 100644 --- a/src/sonic-yang-mgmt/sonic_yang.py +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -34,6 +34,8 @@ def __init__(self, yang_dir, debug=False): self.xlateJson = dict() # reverse translation from yang JSON, == config db json self.revXlateJson = dict() + # below dict store the input config tables which have no YANG models + self.tablesWithOutYang = dict() try: self.ctx = ly.Context(yang_dir) diff --git a/src/sonic-yang-mgmt/sonic_yang_ext.py b/src/sonic-yang-mgmt/sonic_yang_ext.py index 300986cb3a2b..7e293cbb67e2 100644 --- a/src/sonic-yang-mgmt/sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/sonic_yang_ext.py @@ -1,6 +1,7 @@ # This script is used as extension of sonic_yang class. It has methods of # class sonic_yang. A separate file is used to avoid a single large file. +from __future__ import print_function import yang as ly import re import syslog @@ -118,16 +119,22 @@ def get_module_TLC_container(self, table): """ Crop config as per yang models, This Function crops from config only those TABLEs, for which yang models is - provided. + provided. The Tables without YANG models are stored in + self.tablesWithOutYangModels. """ - def cropConfigDB(self, croppedFile=None, allowExtraTables=True): + def cropConfigDB(self, croppedFile=None): 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))) + # store in tablesWithOutYang + self.tablesWithOutYang[table] = self.jIn[table] + del self.jIn[table] + + if len(self.tablesWithOutYang): + print("Note: Below table(s) have no YANG models:") + for table in self.tablesWithOutYang.keys(): + print(unicode(table), end=", ") + print() if croppedFile: with open(croppedFile, 'w') as f: @@ -666,14 +673,15 @@ def findXpathList(self, xpath, list, keys): input: data returns: True - success False - failed """ - def load_data(self, configdbJson, allowExtraTables=True): + def load_data(self, configdbJson): try: self.jIn = configdbJson - # reset xlate + # reset xlate and tablesWithOutYang self.xlateJson = dict() + self.tablesWithOutYang = dict() # self.jIn will be cropped - self.cropConfigDB(allowExtraTables=allowExtraTables) + self.cropConfigDB() # xlated result will be in self.xlateJson self.xlateConfigDB() #print(self.xlateJson) From 97c266ecc6319b1a23e5f3dac55b1a808106107a Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Mon, 27 Apr 2020 15:49:53 -0700 Subject: [PATCH 17/18] [sonic-yang-mgmt]: Test for Store Tables without YANG models seperately for user warning. Changes: 1.) Added config for extra tables in yangTest.json. 2.) Added new test case to find tables without yang models. 3.) Move common part in a helper function. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- .../libyang-python-tests/test_sonic_yang.py | 51 +++++++++++++++---- .../tests/yang_model_tests/yangTest.json | 30 +++++++++++ 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 8c35893432d6..3a470ddd056a 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -261,21 +261,38 @@ def test_get_leafref_type_schema(self, yang_s, data): data_type = yang_s.get_leafref_type_schema(xpath) assert expected_type == data_type - def test_xlate_rev_xlate(self): + """ + This is helper function to load YANG models for tests cases, which works + on Real SONiC Yang models. Mainly tests for translation and reverse + translation. + """ + @pytest.fixture(autouse=True, scope='class') + def sonic_yang_data(self): + sonic_yang_dir = "../sonic-yang-models/yang-models/" + sonic_yang_test_file = "../sonic-yang-models/tests/yang_model_tests/yangTest.json" + + syc = sy.sonic_yang(sonic_yang_dir) + syc.loadYangModel() + + sonic_yang_data = dict() + sonic_yang_data['yang_dir'] = sonic_yang_dir + sonic_yang_data['test_file'] = sonic_yang_test_file + sonic_yang_data['syc'] = syc + + return sonic_yang_data + + def test_xlate_rev_xlate(self, sonic_yang_data): # In this test, xlation and revXlation is tested with latest Sonic # YANG model. + test_file = sonic_yang_data['test_file'] + syc = sonic_yang_data['syc'] - yang_dir = "../sonic-yang-models/yang-models/" - yang_test_file = "../sonic-yang-models/tests/yang_model_tests/yangTest.json" - jIn = self.readIjsonInput(yang_test_file, 'SAMPLE_CONFIG_DB_JSON') - # load yang models - syc = sy.sonic_yang(yang_dir) - - syc.loadYangModel() + jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON') - # Allow additional tabbles in config, since all yang models are not merged # syc.load_data(json.loads(jIn)) + # TODO: Make sure no extra table is loaded + syc.get_data() if syc.jIn and syc.jIn == syc.revXlateJson: @@ -287,5 +304,21 @@ def test_xlate_rev_xlate(self): return + def test_table_with_no_yang(self, sonic_yang_data): + # in this test, tables with no YANG models must be stored seperately + # by this library. + test_file = sonic_yang_data['test_file'] + syc = sonic_yang_data['syc'] + + jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON_1') + + syc.load_data(json.loads(jIn)) + + ty = syc.tablesWithOutYang + + assert (len(ty) and "UNKNOWN_TABLE" in ty) + + return + def teardown_class(self): pass diff --git a/src/sonic-yang-models/tests/yang_model_tests/yangTest.json b/src/sonic-yang-models/tests/yang_model_tests/yangTest.json index 5a662a2d00c3..2f85d0259bfa 100644 --- a/src/sonic-yang-models/tests/yang_model_tests/yangTest.json +++ b/src/sonic-yang-models/tests/yang_model_tests/yangTest.json @@ -1316,5 +1316,35 @@ "polling_interval": "0" } } + }, + "SAMPLE_CONFIG_DB_JSON_1": { + "FLEX_COUNTER_TABLE": { + "PFCWD": { + "FLEX_COUNTER_STATUS": "enable" + }, + "PG_WATERMARK": { + "FLEX_COUNTER_STATUS": "enable" + }, + "PORT": { + "FLEX_COUNTER_STATUS": "enable" + }, + "QUEUE": { + "FLEX_COUNTER_STATUS": "enable" + }, + "QUEUE_WATERMARK": { + "FLEX_COUNTER_STATUS": "enable" + } + }, + "CRM": { + "Config": { + "acl_counter_high_threshold": "85", + "acl_counter_low_threshold": "70", + "acl_counter_threshold_type": "percentage", + "polling_interval": "0" + } + }, + "UNKNOWN_TABLE": { + "Error": "This Table is for testing, This Table does not have YANG models." + } } } From 84beafbfd377ede11682119a3d47a83827d7209b Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Tue, 12 May 2020 23:11:41 -0700 Subject: [PATCH 18/18] [sonic_yang.py]: Categories public and private functions. Changes: 1.) Categories public and private functions. 2.) Minor spaces related changes. 3.) Defining class SonicYangException and using it in all public functions. 4.) Changing Class names to PascalCase. 5.) Keeping functions, which deals with Libyang, in lower_underscore_case, rest functions names are in camelCase. --- src/sonic-yang-mgmt/sonic_yang.py | 133 +++++------ src/sonic-yang-mgmt/sonic_yang_ext.py | 207 +++++++++--------- .../libyang-python-tests/test_sonic_yang.py | 64 +++--- 3 files changed, 205 insertions(+), 199 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py index cb7f5873fa33..a9ec2f0ad63e 100644 --- a/src/sonic-yang-mgmt/sonic_yang.py +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -3,14 +3,14 @@ from json import dump from glob import glob -from sonic_yang_ext import sonic_yang_ext_mixin +from sonic_yang_ext import SonicYangExtMixin, SonicYangException """ Yang schema and data tree python APIs based on libyang python Here, sonic_yang_ext_mixin extends funtionality of sonic_yang, i.e. it is mixin not parent class. """ -class sonic_yang(sonic_yang_ext_mixin): +class SonicYang(SonicYangExtMixin): def __init__(self, yang_dir, debug=False): self.yang_dir = yang_dir @@ -67,7 +67,7 @@ def fail(self, e): input: yang_file - full path of a Yang model file returns: Exception if error """ - def load_schema_module(self, yang_file): + def _load_schema_module(self, yang_file): try: return self.ctx.parse_module_path(yang_file, ly.LYS_IN_YANG) except Exception as e: @@ -79,10 +79,10 @@ def load_schema_module(self, yang_file): input: yang_files - a list of Yang model file full path returns: Exception if error """ - def load_schema_module_list(self, yang_files): + def _load_schema_module_list(self, yang_files): for file in yang_files: try: - self.load_schema_module(file) + self._load_schema_module(file) except Exception as e: self.fail(e) @@ -91,11 +91,11 @@ def load_schema_module_list(self, yang_files): input: yang_dir - the directory of the yang model files to be loaded returns: Exception if error """ - def load_schema_modules(self, yang_dir): + def _load_schema_modules(self, yang_dir): py = glob(yang_dir+"/*.yang") for file in py: try: - self.load_schema_module(file) + self._load_schema_module(file) except Exception as e: self.fail(e) @@ -104,7 +104,7 @@ def load_schema_modules(self, yang_dir): input: yang_dir, context returns: Exception if error, returrns context object if no error """ - def load_schema_modules_ctx(self, yang_dir=None): + def _load_schema_modules_ctx(self, yang_dir=None): if not yang_dir: yang_dir = self.yang_dir @@ -125,7 +125,7 @@ def load_schema_modules_ctx(self, yang_dir=None): input: data_file - the full path of the yang json data file to be loaded returns: Exception if error """ - def load_data_file(self, data_file): + def _load_data_file(self, data_file): try: data_node = self.ctx.parse_data_path(data_file, ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) except Exception as e: @@ -139,7 +139,7 @@ def load_data_file(self, data_file): input: path returns: module name """ - def get_module_name(self, schema_xpath): + def _get_module_name(self, schema_xpath): module_name = schema_xpath.split(':')[0].strip('/') return module_name @@ -148,7 +148,7 @@ def get_module_name(self, schema_xpath): input: yang module name returns: Schema_Node object """ - def get_module(self, module_name): + def _get_module(self, module_name): mod = self.ctx.get_module(module_name) return mod @@ -157,26 +157,26 @@ def get_module(self, module_name): input: yang directory, list of yang files and list of data files (full path) returns: returns (context, root) if no error, or Exception if failed """ - def load_data_model (self, yang_dir, yang_files, data_files, output=None): + def _load_data_model(self, yang_dir, yang_files, data_files, output=None): if (self.ctx is None): self.ctx = ly.Context(yang_dir) try: - self.load_schema_module_list(yang_files) + self._load_schema_module_list(yang_files) if len(data_files) == 0: return (self.ctx, self.root) - self.load_data_file(data_files[0]) + self._load_data_file(data_files[0]) for i in range(1, len(data_files)): - self.merge_data(data_files[i]) + self._merge_data(data_files[i]) except Exception as e: print("Failed to load data files") self.fail(e) return if output is not None: - self.print_data_mem(output) + self._print_data_mem(output) return (self.ctx, self.root) @@ -184,7 +184,7 @@ def load_data_model (self, yang_dir, yang_files, data_files, output=None): print_data_mem(): print the data tree input: option: "JSON" or "XML" """ - def print_data_mem (self, option): + def _print_data_mem(self, option): if (option == "JSON"): mem = self.root.print_mem(ly.LYD_JSON, ly.LYP_WITHSIBLINGS | ly.LYP_FORMAT) else: @@ -196,7 +196,7 @@ def print_data_mem (self, option): save_data_file_json(): save the data tree in memory into json file input: outfile - full path of the file to save the data tree to """ - def save_data_file_json(self, outfile): + def _save_data_file_json(self, outfile): mem = self.root.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) with open(outfile, 'w') as out: dump(mem, out, indent=4) @@ -206,7 +206,7 @@ def save_data_file_json(self, outfile): input: module name returns: JSON or XML format of the input yang module schema tree """ - def get_module_tree(self, module_name, format): + def _get_module_tree(self, module_name, format): result = None try: @@ -231,7 +231,7 @@ def get_module_tree(self, module_name, format): ctx: context returns: Exception if failed """ - def validate_data (self, node=None, ctx=None): + def _validate_data(self, node=None, ctx=None): if not node: node = self.root @@ -244,27 +244,28 @@ def validate_data (self, node=None, ctx=None): self.fail(e) """ - validate_data_tree(): validate the data tree + validate_data_tree(): validate the data tree. (Public) returns: Exception if failed """ - def validate_data_tree (self): + def validate_data_tree(self): try: - self.validate_data(self.root, self.ctx) + self._validate_data(self.root, self.ctx) except Exception as e: print("Failed to validate data tree") - self.fail(e) + raise SonicYangException("Failed to validate data tree\n{}".\ + format(str(e))) """ find_parent_data_node(): find the parent node object input: data_xpath - xpath of the data node returns: parent node """ - def find_parent_data_node (self, data_xpath): + def _find_parent_data_node(self, data_xpath): if (self.root is None): print("data not loaded") return None try: - data_node = self.find_data_node(data_xpath) + data_node = self._find_data_node(data_xpath) except Exception as e: print("Failed to find data node from xpath: " + str(data_xpath)) self.fail(e) @@ -280,15 +281,15 @@ def find_parent_data_node (self, data_xpath): returns: - xpath of parent data node - Exception if error """ - def get_parent_data_xpath (self, data_xpath): + def _get_parent_data_xpath(self, data_xpath): path="" try: - data_node = self.find_parent_data_node(data_xpath) + data_node = self._find_parent_data_node(data_xpath) except Exception as e: print("Failed to find parent node from xpath: " + str(data_xpath)) self.fail(e) else: - if (data_node is not None): + if data_node is not None: path = data_node.path() return path @@ -299,7 +300,7 @@ def get_parent_data_xpath (self, data_xpath): value: value of the new node returns: new Data_Node object if success, Exception if falied """ - def new_data_node(self, xpath, value): + def _new_data_node(self, xpath, value): val = str(value) try: data_node = self.root.new_path(self.ctx, xpath, val, 0, 0) @@ -316,7 +317,7 @@ def new_data_node(self, xpath, value): - None if not exist - Exception if there is error """ - def find_data_node(self, data_xpath): + def _find_data_node(self, data_xpath): try: set = self.root.find_path(data_xpath) except Exception as e: @@ -335,7 +336,7 @@ def find_data_node(self, data_xpath): input: xpath of the node returns: Schema_Node oject or None if not found """ - def find_schema_node(self, schema_xpath): + def _find_schema_node(self, schema_xpath): try: schema_set = self.ctx.find_path(schema_xpath) for schema_node in schema_set.schema(): @@ -357,7 +358,7 @@ def find_schema_node(self, schema_xpath): returns: - xpath of the schema node if success - Exception if error """ - def find_data_node_schema_xpath(self, data_xpath): + def _find_data_node_schema_xpath(self, data_xpath): path = "" try: set = self.root.find_path(data_xpath) @@ -374,11 +375,11 @@ def find_data_node_schema_xpath(self, data_xpath): input: xpath and value of the node to be added returns: Exception if failed """ - def add_data_node(self, data_xpath, value): + def _add_data_node(self, data_xpath, value): try: - self.new_data_node(data_xpath, value) + self._new_data_node(data_xpath, value) #check if the node added to the data tree - self.find_data_node(data_xpath) + self._find_data_node(data_xpath) except Exception as e: print("add_node(): Failed to add data node for xpath: " + str(data_xpath)) self.fail(e) @@ -388,13 +389,13 @@ def add_data_node(self, data_xpath, value): input: yang model directory and full path of the data json file to be merged returns: Exception if failed """ - def merge_data(self, data_file, yang_dir=None): + def _merge_data(self, data_file, yang_dir=None): #load all yang models to ctx if not yang_dir: yang_dir = self.yang_dir try: - ctx = self.load_schema_modules_ctx(yang_dir) + ctx = self._load_schema_modules_ctx(yang_dir) #source data node source_node = ctx.parse_data_path(str(data_file), ly.LYD_JSON, ly.LYD_OPT_CONFIG | ly.LYD_OPT_STRICT) @@ -405,17 +406,17 @@ def merge_data(self, data_file, yang_dir=None): self.fail(e) """ - _delete_node(): delete a node from the schema/data tree, internal function + _deleteNode(): delete a node from the schema/data tree, internal function input: xpath of the schema/data node returns: True - success False - failed """ - def _delete_node(self, xpath=None, node=None): + def _deleteNode(self, xpath=None, node=None): if node is None: - node = self.find_data_node(xpath) + node = self._find_data_node(xpath) if (node): node.unlink() - dnode = self.find_data_node(xpath) + dnode = self._find_data_node(xpath) if (dnode is None): #deleted node not found return True @@ -432,10 +433,10 @@ def _delete_node(self, xpath=None, node=None): input: data_xpath of the data node returns: value string of the node """ - def find_data_node_value(self, data_xpath): + def _find_data_node_value(self, data_xpath): output = "" try: - data_node = self.find_data_node(data_xpath) + data_node = self._find_data_node(data_xpath) except Exception as e: print("find_data_node_value(): Failed to find data node from xpath: {}".format(data_xpath)) self.fail(e) @@ -452,7 +453,7 @@ def find_data_node_value(self, data_xpath): input: xpath of the data node returns: Exception if failed """ - def set_data_node_value(self, data_xpath, value): + def _set_data_node_value(self, data_xpath, value): try: self.root.new_path(self.ctx, data_xpath, str(value), ly.LYD_ANYDATA_STRING, ly.LYD_PATH_OPT_UPDATE) except Exception as e: @@ -464,7 +465,7 @@ def set_data_node_value(self, data_xpath, value): input: xpath of the data node returns: list of xpath of the dataset """ - def find_data_nodes(self, data_xpath): + def _find_data_nodes(self, data_xpath): list = [] node = self.root.child() try: @@ -486,10 +487,10 @@ def find_data_nodes(self, data_xpath): returns: - list of xpath of the dependencies - Exception if schema node not found """ - def find_schema_dependencies (self, schema_xpath): + def _find_schema_dependencies(self, schema_xpath): ref_list = [] try: - schema_node = self.find_schema_node(schema_xpath) + schema_node = self._find_schema_node(schema_xpath) except Exception as e: print("Cound not find the schema node from xpath: " + str(schema_xpath)) self.fail(e) @@ -505,22 +506,21 @@ def find_schema_dependencies (self, schema_xpath): """ find_data_dependencies(): find the data dependencies from data xpath - input: data_xpath - xpath of data node + input: data_xpath - xpath of data node. (Public) returns: - list of xpath - Exception if error """ - def find_data_dependencies (self, data_xpath): + def find_data_dependencies(self, data_xpath): ref_list = [] node = self.root try: - data_node = self.find_data_node(data_xpath) + data_node = self._find_data_node(data_xpath) except Exception as e: print("find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xapth)) - self.fail(e) return ref_list try: - value = str(self.find_data_node_value(data_xpath)) + value = str(self._find_data_node_value(data_xpath)) schema_node = ly.Schema_Node_Leaf(data_node.schema()) backlinks = schema_node.backlinks() @@ -534,7 +534,8 @@ def find_data_dependencies (self, data_xpath): ref_list.append(data_set.path()) except Exception as e: print('Failed to find node or dependencies for {}'.format(data_xpath)) - self.fail(e) + raise SonicYangException("Failed to find node or dependencies for \ + {}\n{}".format(data_xpath, str(e))) return ref_list @@ -543,10 +544,10 @@ def find_data_dependencies (self, data_xpath): input: name of the Yang module output: prefix of the Yang module """ - def get_module_prefix(self, module_name): + def _get_module_prefix(self, module_name): prefix = "" try: - module = self.get_module(module_name) + module = self._get_module(module_name) except Exception as e: self.fail(e) return prefix @@ -558,7 +559,7 @@ def get_module_prefix(self, module_name): input: string output: type """ - def str_to_type (self, type_str): + def _str_to_type(self, type_str): mapped_type = { "LY_TYPE_DER":ly.LY_TYPE_DER, "LY_TYPE_BINARY":ly.LY_TYPE_BINARY, @@ -588,9 +589,9 @@ def str_to_type (self, type_str): return mapped_type[type_str] - def get_data_type (self, schema_xpath): + def _get_data_type(self, schema_xpath): try: - schema_node = self.find_schema_node(schema_xpath) + schema_node = self._find_schema_node(schema_xpath) except Exception as e: print("get_data_type(): Failed to find schema node from xpath: {}".format(schema_xpath)) self.fail(e) @@ -606,8 +607,8 @@ def get_data_type (self, schema_xpath): input: data_xpath - xpath of a data node output: type of the node this leafref references to """ - def get_leafref_type (self, data_xpath): - data_node = self.find_data_node(data_xpath) + def _get_leafref_type(self, data_xpath): + data_node = self._find_data_node(data_xpath) if (data_node is not None): subtype = data_node.subtype() if (subtype is not None): @@ -624,8 +625,8 @@ def get_leafref_type (self, data_xpath): input: schema_xpath - xpath of a schema node output: path value of the leafref node """ - def get_leafref_path (self, schema_xpath): - schema_node = self.find_schema_node(schema_xpath) + def _get_leafref_path(self, schema_xpath): + schema_node = self._find_schema_node(schema_xpath) if (schema_node is not None): subtype = schema_node.subtype() if (subtype is not None): @@ -641,8 +642,8 @@ def get_leafref_path (self, schema_xpath): input: schema_xpath - xpath of a schema node output: type of the node this leafref references to """ - def get_leafref_type_schema (self, schema_xpath): - schema_node = self.find_schema_node(schema_xpath) + def _get_leafref_type_schema(self, schema_xpath): + schema_node = self._find_schema_node(schema_xpath) if (schema_node is not None): subtype = schema_node.subtype() if (subtype is not None): @@ -652,7 +653,7 @@ def get_leafref_type_schema (self, schema_xpath): subtype.type().info().lref().path() target = subtype.type().info().lref().target() target_path = target.path() - target_type = self.get_data_type(target_path) + target_type = self._get_data_type(target_path) return target_type return None diff --git a/src/sonic-yang-mgmt/sonic_yang_ext.py b/src/sonic-yang-mgmt/sonic_yang_ext.py index 7e293cbb67e2..3cfad3aaaf63 100644 --- a/src/sonic-yang-mgmt/sonic_yang_ext.py +++ b/src/sonic-yang-mgmt/sonic_yang_ext.py @@ -10,11 +10,17 @@ from xmltodict import parse from glob import glob +""" +This is the Exception thrown out of all public function of this class. +""" +class SonicYangException(Exception): + pass + # class sonic_yang methods, use mixin to extend sonic_yang -class sonic_yang_ext_mixin: +class SonicYangExtMixin: """ - load all YANG models, create JSON of yang models + load all YANG models, create JSON of yang models. (Public function) """ def loadYangModel(self): @@ -23,7 +29,7 @@ def loadYangModel(self): self.yangFiles = glob(self.yang_dir +"/*.yang") # load yang modules for file in self.yangFiles: - m = self.load_schema_module(file) + m = self._load_schema_module(file) if m is not None: self.sysLog(msg="module: {} is loaded successfully".format(m.name())) else: @@ -36,20 +42,20 @@ def loadYangModel(self): print(self.yangFiles) # load json for each yang model - self.loadJsonYangModel() + self._loadJsonYangModel() # create a map from config DB table to yang container - self.createDBTableToModuleMap() + self._createDBTableToModuleMap() except Exception as e: print("Yang Models Load failed") - raise e + raise SonicYangException("Yang Models Load failed\n{}".format(str(e))) return True """ load JSON schema format from yang models """ - def loadJsonYangModel(self): + def _loadJsonYangModel(self): try: for f in self.yangFiles: @@ -59,7 +65,6 @@ def loadJsonYangModel(self): 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 @@ -70,7 +75,7 @@ def loadJsonYangModel(self): written using below Guidelines: https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md. """ - def createDBTableToModuleMap(self): + def _createDBTableToModuleMap(self): for j in self.yJson: # get module name @@ -109,7 +114,7 @@ def createDBTableToModuleMap(self): """ Get module, topLevelContainer(TLC) and json container for a config DB table """ - def get_module_TLC_container(self, table): + def _getModuleTLCcontainer(self, table): cmap = self.confDbYangMap m = cmap[table]['module'] t = cmap[table]['topLevelContainer'] @@ -122,7 +127,7 @@ def get_module_TLC_container(self, table): provided. The Tables without YANG models are stored in self.tablesWithOutYangModels. """ - def cropConfigDB(self, croppedFile=None): + def _cropConfigDB(self, croppedFile=None): for table in self.jIn.keys(): if table not in self.confDbYangMap: @@ -153,7 +158,7 @@ def cropConfigDB(self, croppedFile=None): Return: KeyDict = {"vlan_name": "Vlan111", "ip-prefix": "2a04:5555:45:6709::1/64"} """ - def extractKey(self, tableKey, keys, regex): + def _extractKey(self, tableKey, keys, regex): keyList = keys.split() # get the value groups @@ -173,13 +178,13 @@ def extractKey(self, tableKey, keys, regex): """ Fill the dict based on leaf as a list or dict @model yang model object """ - def fillLeafDict(self, leafs, leafDict, isleafList=False): + def _fillLeafDict(self, leafs, leafDict, isleafList=False): if leafs is None: return # fill default values - def fillSteps(leaf): + def _fillSteps(leaf): leaf['__isleafList'] = isleafList leafDict[leaf['@name']] = leaf return @@ -187,10 +192,10 @@ def fillSteps(leaf): if isinstance(leafs, list): for leaf in leafs: #print("{}:{}".format(leaf['@name'], leaf)) - fillSteps(leaf) + _fillSteps(leaf) else: #print("{}:{}".format(leaf['@name'], leaf)) - fillSteps(leafs) + _fillSteps(leafs) return @@ -199,11 +204,11 @@ def fillSteps(leaf): 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 _createLeafDict(self, model): leafDict = dict() #Iterate over leaf, choices and leaf-list. - self.fillLeafDict(model.get('leaf'), leafDict) + self._fillLeafDict(model.get('leaf'), leafDict) #choices, this is tricky, since leafs are under cases in tree. choices = model.get('choice') @@ -211,10 +216,10 @@ def createLeafDict(self, model): for choice in choices: cases = choice['case'] for case in cases: - self.fillLeafDict(case.get('leaf'), leafDict) + self._fillLeafDict(case.get('leaf'), leafDict) # leaf-lists - self.fillLeafDict(model.get('leaf-list'), leafDict, True) + self._fillLeafDict(model.get('leaf-list'), leafDict, True) return leafDict @@ -223,10 +228,10 @@ def createLeafDict(self, model): key in Yang model. @model : A List of Leafs in Yang model list """ - def findYangTypedValue(self, key, value, leafDict): + def _findYangTypedValue(self, key, value, leafDict): # convert config DB string to yang Type - def yangConvert(val): + def _yangConvert(val): # Convert everything to string val = str(val) # find type of this key from yang leaf @@ -248,9 +253,9 @@ def yangConvert(val): if leafDict[key]['__isleafList']: vValue = list() for v in value: - vValue.append(yangConvert(v)) + vValue.append(_yangConvert(v)) else: - vValue = yangConvert(value) + vValue = _yangConvert(value) return vValue @@ -259,12 +264,12 @@ def yangConvert(val): 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): + 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) + leafDict = self._createLeafDict(model) # fetch regex from YANG models. keyRegEx = model['ext:key-regex-configdb-to-yang']['@value'] @@ -281,11 +286,11 @@ def xlateList(self, model, yang, config, table): 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) + 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, \ + keyDict[vKey] = self._findYangTypedValue(vKey, \ config[pkey][vKey], leafDict) yang.append(keyDict) # delete pkey from config, done to match one key with one list @@ -303,12 +308,12 @@ def xlateList(self, model, yang, config, table): Process list inside a Container. This function will call xlateList based on list(s) present in Container. """ - def xlateListInContainer(self, model, yang, configC, table): + def _xlateListInContainer(self, model, yang, configC, table): clist = model #print(clist['@name']) yang[clist['@name']] = list() self.sysLog(msg="xlateProcessListOfContainer: {}".format(clist['@name'])) - self.xlateList(clist, yang[clist['@name']], configC, table) + self._xlateList(clist, yang[clist['@name']], configC, table) # clean empty lists if len(yang[clist['@name']]) == 0: del yang[clist['@name']] @@ -320,14 +325,14 @@ def xlateListInContainer(self, model, yang, configC, table): This function will call xlateContainer based on Container(s) present in outer Container. """ - def xlateContainerInContainer(self, model, yang, configC, table): + def _xlateContainerInContainer(self, model, yang, configC, table): ccontainer = model #print(ccontainer['@name']) yang[ccontainer['@name']] = dict() if not configC.get(ccontainer['@name']): return self.sysLog(msg="xlateProcessListOfContainer: {}".format(ccontainer['@name'])) - self.xlateContainer(ccontainer, yang[ccontainer['@name']], \ + self._xlateContainer(ccontainer, yang[ccontainer['@name']], \ configC[ccontainer['@name']], table) # clean empty container if len(yang[ccontainer['@name']]) == 0: @@ -342,7 +347,7 @@ def xlateContainerInContainer(self, model, yang, configC, table): 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): + def _xlateContainer(self, model, yang, config, table): # To Handle multiple Lists, Make a copy of config, because we delete keys # from config after each match. This is done to match one pkey with one list. @@ -352,43 +357,44 @@ def xlateContainer(self, model, yang, config, table): # If single list exists in container, if clist and isinstance(clist, dict) and \ clist['@name'] == model['@name']+"_LIST" and bool(configC): - self.xlateListInContainer(clist, yang, configC, table) + self._xlateListInContainer(clist, yang, configC, table) # If multi-list exists in container, elif clist and isinstance(clist, list) and bool(configC): for modelList in clist: - self.xlateListInContainer(modelList, yang, configC, table) + self._xlateListInContainer(modelList, yang, configC, table) # Handle container(s) in container ccontainer = model.get('container') # If single list exists in container, if ccontainer and isinstance(ccontainer, dict) and bool(configC): - self.xlateContainerInContainer(ccontainer, yang, configC, table) + self._xlateContainerInContainer(ccontainer, yang, configC, table) # If multi-list exists in container, elif ccontainer and isinstance(ccontainer, list) and bool(configC): for modelContainer in ccontainer: - self.xlateContainerInContainer(modelContainer, yang, configC, table) + self._xlateContainerInContainer(modelContainer, yang, configC, table) ## Handle other leaves in container, - leafDict = self.createLeafDict(model) + leafDict = self._createLeafDict(model) for vKey in configC.keys(): #vkey must be a leaf\leaf-list\choice in container if leafDict.get(vKey): self.sysLog(syslog.LOG_DEBUG, "xlateContainer vkey {}".format(vKey)) - yang[vKey] = self.findYangTypedValue(vKey, configC[vKey], leafDict) + yang[vKey] = self._findYangTypedValue(vKey, configC[vKey], leafDict) # delete entry from copy of config del configC[vKey] # All entries in copy of config must have been parsed. if len(configC): self.sysLog(syslog.LOG_ERR, "Alert: Remaining keys in Config") - raise(Exception("All Keys are not parsed in {}".format(table))) + raise(Exception("All Keys are not parsed in {}\n{}".format(table, \ + configC.keys()))) return """ xlate ConfigDB json to Yang json """ - def xlateConfigDBtoYang(self, jIn, yangJ): + def _xlateConfigDBtoYang(self, jIn, yangJ): # find top level container for each table, and run the xlate_container. for table in jIn.keys(): @@ -400,7 +406,7 @@ def xlateConfigDBtoYang(self, jIn, yangJ): 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], \ + self._xlateContainer(cmap['container'], yangJ[key][subkey], \ jIn[table], table) return @@ -408,12 +414,12 @@ def xlateConfigDBtoYang(self, jIn, yangJ): """ Read config file and crop it as per yang models """ - def xlateConfigDB(self, xlateFile=None): + def _xlateConfigDB(self, xlateFile=None): jIn= self.jIn yangJ = self.xlateJson # xlation is written in self.xlateJson - self.xlateConfigDBtoYang(jIn, yangJ) + self._xlateConfigDBtoYang(jIn, yangJ) if xlateFile: with open(xlateFile, 'w') as f: @@ -424,7 +430,7 @@ def xlateConfigDB(self, xlateFile=None): """ create config DB table key from entry in yang JSON """ - def createKey(self, entry, regex): + def _createKey(self, entry, regex): keyDict = dict() keyV = regex @@ -447,10 +453,10 @@ def createKey(self, entry, regex): key in Yang model. @model : A List of Leafs in Yang model list """ - def revFindYangTypedValue(self, key, value, leafDict): + def _revFindYangTypedValue(self, key, value, leafDict): # convert yang Type to config DB string - def revYangConvert(val): + def _revYangConvert(val): # config DB has only strings, thank god for that :), wait not yet!!! return str(val) @@ -458,16 +464,16 @@ def revYangConvert(val): if leafDict[key]['__isleafList']: vValue = list() for v in value: - vValue.append(revYangConvert(v)) + vValue.append(_revYangConvert(v)) else: - vValue = revYangConvert(value) + vValue = _revYangConvert(value) return vValue """ Rev xlate from
_LIST to table in config DB """ - def revXlateList(self, model, yang, config, table): + def _revXlateList(self, model, yang, config, table): # fetch regex from YANG models keyRegEx = model['ext:key-regex-yang-to-configdb']['@value'] @@ -476,19 +482,19 @@ 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) # list with name _LIST should be removed, if "_LIST" in model['@name']: for entry in yang: # create key of config DB table - pkey, pkeydict = self.createKey(entry, keyRegEx) + pkey, pkeydict = self._createKey(entry, keyRegEx) self.sysLog(syslog.LOG_DEBUG, "revXlateList pkey:{}".format(pkey)) config[pkey]= dict() # fill rest of the entries for key in entry: if key not in pkeydict: - config[pkey][key] = self.revFindYangTypedValue(key, \ + config[pkey][key] = self._revFindYangTypedValue(key, \ entry[key], leafDict) return @@ -496,64 +502,64 @@ def revXlateList(self, model, yang, config, table): """ Rev xlate a list inside a yang container """ - def revXlateListInContainer(self, model, yang, config, table): + def _revXlateListInContainer(self, model, yang, config, table): modelList = model # Pass matching list from Yang Json if exist if yang.get(modelList['@name']): self.sysLog(msg="revXlateListInContainer {}".format(modelList['@name'])) - self.revXlateList(modelList, yang[modelList['@name']], config, table) + self._revXlateList(modelList, yang[modelList['@name']], config, table) return """ Rev xlate a container inside a yang container """ - def revXlateContainerInContainer(self, model, yang, config, table): + def _revXlateContainerInContainer(self, model, yang, config, table): modelContainer = model # Pass matching list from Yang Json if exist if yang.get(modelContainer['@name']): config[modelContainer['@name']] = dict() self.sysLog(msg="revXlateContainerInContainer {}".format(modelContainer['@name'])) - self.revXlateContainer(modelContainer, yang[modelContainer['@name']], \ + self._revXlateContainer(modelContainer, yang[modelContainer['@name']], \ config[modelContainer['@name']], table) return """ Rev xlate from yang container to table in config DB """ - def revXlateContainer(self, model, yang, config, table): + def _revXlateContainer(self, model, yang, config, table): # IF container has only one list clist = model.get('list') if isinstance(clist, dict): - self.revXlateListInContainer(clist, yang, config, table) + self._revXlateListInContainer(clist, yang, config, table) # IF container has lists elif isinstance(clist, list): for modelList in clist: - self.revXlateListInContainer(modelList, yang, config, table) + self._revXlateListInContainer(modelList, yang, config, table) ccontainer = model.get('container') # IF container has only one inner container if isinstance(ccontainer, dict): - self.revXlateContainerInContainer(ccontainer, yang, config, table) + self._revXlateContainerInContainer(ccontainer, yang, config, table) # IF container has only many inner container elif isinstance(ccontainer, list): for modelContainer in ccontainer: - self.revXlateContainerInContainer(modelContainer, yang, config, table) + self._revXlateContainerInContainer(modelContainer, yang, config, table) ## Handle other leaves in container, - leafDict = self.createLeafDict(model) + leafDict = self._createLeafDict(model) for vKey in yang: #vkey must be a leaf\leaf-list\choice in container if leafDict.get(vKey): self.sysLog(syslog.LOG_DEBUG, "revXlateContainer vkey {}".format(vKey)) - config[vKey] = self.revFindYangTypedValue(vKey, yang[vKey], leafDict) + config[vKey] = self._revFindYangTypedValue(vKey, yang[vKey], leafDict) return """ rev xlate ConfigDB json to Yang json """ - def revXlateYangtoConfigDB(self, yangJ, cDbJson): + def _revXlateYangtoConfigDB(self, yangJ, cDbJson): yangJ = self.xlateJson cDbJson = self.revXlateJson @@ -569,7 +575,7 @@ def revXlateYangtoConfigDB(self, yangJ, cDbJson): cDbJson[table] = dict() #print(key + "--" + subkey) self.sysLog(msg="revXlateYangtoConfigDB {}".format(table)) - self.revXlateContainer(cmap['container'], yangJ[module_top][container], \ + self._revXlateContainer(cmap['container'], yangJ[module_top][container], \ cDbJson[table], table) return @@ -577,12 +583,12 @@ def revXlateYangtoConfigDB(self, yangJ, cDbJson): """ Reverse Translate tp config DB """ - def revXlateConfigDB(self, revXlateFile=None): + def _revXlateConfigDB(self, revXlateFile=None): yangJ = self.xlateJson cDbJson = self.revXlateJson # xlation is written in self.xlateJson - self.revXlateYangtoConfigDB(yangJ, cDbJson) + self._revXlateYangtoConfigDB(yangJ, cDbJson) if revXlateFile: with open(revXlateFile, 'w') as f: @@ -596,7 +602,7 @@ def revXlateConfigDB(self, revXlateFile=None): l = list name return: list if found else None """ - def findYangList(self, container, listName): + def _findYangList(self, container, listName): if isinstance(container['list'], dict): clist = container['list'] @@ -611,38 +617,37 @@ def findYangList(self, container, listName): """ Find xpath of the PORT Leaf in PORT container/list. Xpath of Leaf is needed, - because only leaf can have leafrefs depend on them. + because only leaf can have leafrefs depend on them. (Public) """ def findXpathPortLeaf(self, portName): try: table = "PORT" xpath = self.findXpathPort(portName) - module, topc, container = self.get_module_TLC_container(table) - list = self.findYangList(container, table+"_LIST") + module, topc, container = self._getModuleTLCcontainer(table) + list = self._findYangList(container, table+"_LIST") xpath = xpath + "/" + list['key']['@value'].split()[0] except Exception as e: print("find xpath of port Leaf failed") - raise e + raise SonicYangException("find xpath of port Leaf failed\n{}".format(str(e))) return xpath - """ - Find xpath of PORT + Find xpath of PORT. (Public) """ def findXpathPort(self, portName): try: table = "PORT" - module, topc, container = self.get_module_TLC_container(table) + module, topc, container = self._getModuleTLCcontainer(table) xpath = "/" + module + ":" + topc + "/" + table - list = self.findYangList(container, table+"_LIST") - xpath = self.findXpathList(xpath, list, [portName]) + list = self._findYangList(container, table+"_LIST") + xpath = self._findXpathList(xpath, list, [portName]) except Exception as e: print("find xpath of port failed") - raise e + raise SonicYangException("find xpath of port failed\n{}".format(str(e))) return xpath @@ -652,7 +657,7 @@ def findXpathPort(self, portName): list: YANG List keys: list of keys in YANG LIST """ - def findXpathList(self, xpath, list, keys): + def _findXpathList(self, xpath, list, keys): try: # add list name in xpath @@ -663,17 +668,16 @@ def findXpathList(self, xpath, list, keys): xpath = xpath + '['+listKey+'=\''+keys[i]+'\']' i = i + 1 except Exception as e: - print("find xpath of list failed") raise e return xpath """ - load_data: load Config DB, crop, xlate and create data tree from it. + load_data: load Config DB, crop, xlate and create data tree from it. (Public) input: data returns: True - success False - failed """ - def load_data(self, configdbJson): + def loadData(self, configdbJson): try: self.jIn = configdbJson @@ -681,9 +685,9 @@ def load_data(self, configdbJson): self.xlateJson = dict() self.tablesWithOutYang = dict() # self.jIn will be cropped - self.cropConfigDB() + self._cropConfigDB() # xlated result will be in self.xlateJson - self.xlateConfigDB() + self._xlateConfigDB() #print(self.xlateJson) self.sysLog(msg="Try to load Data in the tree") self.root = self.ctx.parse_data_mem(dumps(self.xlateJson), \ @@ -692,37 +696,38 @@ def load_data(self, configdbJson): except Exception as e: self.root = None print("Data Loading Failed") - raise e + raise SonicYangException("Data Loading Failed\n{}".format(str(e))) return True """ - Get data from Data tree, data tree will be assigned in self.xlateJson + Get data from Data tree, data tree will be assigned in self.xlateJson. (Public) """ - def get_data(self): + def getData(self): try: - self.xlateJson = loads(self.print_data_mem('JSON')) + self.xlateJson = loads(self._print_data_mem('JSON')) # reset reverse xlate self.revXlateJson = dict() # result will be stored self.revXlateJson - self.revXlateConfigDB() + self._revXlateConfigDB() except Exception as e: print("Get Data Tree Failed") - raise e + raise SonicYangException("Get Data Tree Failed\n{}".format(str(e))) return self.revXlateJson """ - Delete a node from data tree, if this is LEAF and KEY Delete the Parent + Delete a node from data tree, if this is LEAF and KEY Delete the Parent. + (Public) """ - def delete_node(self, xpath): + def deleteNode(self, xpath): # These MACROS used only here, can we get it from Libyang Header ? try: LYS_LEAF = 4 - node = self.find_data_node(xpath) + node = self._find_data_node(xpath) if node is None: raise('Node {} not found'.format(xpath)) @@ -732,19 +737,19 @@ def delete_node(self, xpath): leaf = ly.Schema_Node_Leaf(snode) if leaf.is_key(): # try to delete parent - nodeP = self.find_parent_node(xpath) + nodeP = self._find_parent_data_node(xpath) xpathP = nodeP.path() - if self._delete_node(xpath=xpathP, node=nodeP) == False: - raise Exception('_delete_node failed') + if self._deleteNode(xpath=xpathP, node=nodeP) == False: + raise Exception('_deleteNode failed') else: return True # delete non key element - if self._delete_node(xpath=xpath, node=node) == False: - raise Exception('_delete_node failed') + if self._deleteNode(xpath=xpath, node=node) == False: + raise Exception('_deleteNode failed') except Exception as e: - print(e) - raise Exception('Failed to delete node {}'.format(xpath)) + raise SonicYangException("Failed to delete node {}\n{}".\ + format( xpath, str(e))) return True diff --git a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py index 3a470ddd056a..b5fa35842990 100644 --- a/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -28,7 +28,7 @@ def data(self): @pytest.fixture(autouse=True, scope='class') def yang_s(self, data): yang_dir = str(data['yang_dir']) - yang_s = sy.sonic_yang(yang_dir) + yang_s = sy.SonicYang(yang_dir) return yang_s def jsonTestParser(self, file): @@ -64,7 +64,7 @@ def setup_class(self): def load_yang_model_file(self, yang_s, yang_dir, yang_file, module_name): yfile = yang_dir + yang_file try: - yang_s.load_schema_module(str(yfile)) + yang_s._load_schema_module(str(yfile)) except Exception as e: print(e) raise @@ -77,7 +77,7 @@ def test_load_yang_model_files(self, data, yang_s): module = str(module['module']) self.load_yang_model_file(yang_s, yang_dir, file, module) - assert yang_s.get_module(module) is not None + assert yang_s._get_module(module) is not None #test load non-exist yang module file def test_load_invalid_model_files(self, data, yang_s): @@ -91,10 +91,10 @@ def test_load_invalid_model_files(self, data, yang_s): #test load yang modules in directory def test_load_yang_model_dir(self, data, yang_s): yang_dir = data['yang_dir'] - yang_s.load_schema_modules(str(yang_dir)) + yang_s._load_schema_modules(str(yang_dir)) for module_name in data['modules']: - assert yang_s.get_module(str(module_name['module'])) is not None + assert yang_s._get_module(str(module_name['module'])) is not None #test load yang modules and data files def test_load_yang_model_data(self, data, yang_s): @@ -107,19 +107,19 @@ def test_load_yang_model_data(self, data, yang_s): data_files.append(data_file) data_files.append(data_merge_file) print(yang_files) - yang_s.load_data_model(yang_dir, yang_files, data_files) + yang_s._load_data_model(yang_dir, yang_files, data_files) #validate the data tree from data_merge_file is loaded for node in data['merged_nodes']: xpath = str(node['xpath']) value = str(node['value']) - val = yang_s.find_data_node_value(xpath) + val = yang_s._find_data_node_value(xpath) assert str(val) == str(value) #test load data file def test_load_data_file(self, data, yang_s): data_file = str(data['data_file']) - yang_s.load_data_file(data_file) + yang_s._load_data_file(data_file) #test_validate_data_tree(): def test_validate_data_tree(self, data, yang_s): @@ -130,7 +130,7 @@ def test_find_node(self, data, yang_s): for node in data['data_nodes']: expected = node['valid'] xpath = str(node['xpath']) - dnode = yang_s.find_data_node(xpath) + dnode = yang_s._find_data_node(xpath) if(expected == "True"): assert dnode is not None @@ -143,9 +143,9 @@ def test_add_node(self, data, yang_s): for node in data['new_nodes']: xpath = str(node['xpath']) value = node['value'] - yang_s.add_data_node(xpath, str(value)) + yang_s._add_data_node(xpath, str(value)) - data_node = yang_s.find_data_node(xpath) + data_node = yang_s._find_data_node(xpath) assert data_node is not None #test find node value @@ -155,23 +155,23 @@ def test_find_data_node_value(self, data, yang_s): value = str(node['value']) print(xpath) print(value) - val = yang_s.find_data_node_value(xpath) + val = yang_s._find_data_node_value(xpath) assert str(val) == str(value) #test delete data node def test_delete_node(self, data, yang_s): for node in data['delete_nodes']: xpath = str(node['xpath']) - yang_s._delete_node(xpath) + yang_s._deleteNode(xpath) #test set node's value def test_set_datanode_value(self, data, yang_s): for node in data['set_nodes']: xpath = str(node['xpath']) value = node['value'] - yang_s.set_data_node_value(xpath, value) + yang_s._set_data_node_value(xpath, value) - val = yang_s.find_data_node_value(xpath) + val = yang_s._find_data_node_value(xpath) assert str(val) == str(value) #test list of members @@ -179,7 +179,7 @@ def test_find_members(self, yang_s, data): for node in data['members']: members = node['members'] xpath = str(node['xpath']) - list = yang_s.find_data_nodes(xpath) + list = yang_s._find_data_nodes(xpath) assert list.sort() == members.sort() #get parent xpath @@ -187,7 +187,7 @@ def test_get_parent_data_xpath(self, yang_s, data): for node in data['parents']: xpath = str(node['xpath']) expected_xpath = str(node['parent']) - path = yang_s.get_parent_data_xpath(xpath) + path = yang_s._get_parent_data_xpath(xpath) assert path == expected_xpath #test find_data_node_schema_xpath @@ -195,7 +195,7 @@ def test_find_data_node_schema_xpath(self, yang_s, data): for node in data['schema_nodes']: xpath = str(node['xpath']) schema_xpath = str(node['value']) - path = yang_s.find_data_node_schema_xpath(xpath) + path = yang_s._find_data_node_schema_xpath(xpath) assert path == schema_xpath #test data dependencies @@ -211,14 +211,14 @@ def test_find_schema_dependencies(self, yang_s, data): for node in data['schema_dependencies']: xpath = str(node['xpath']) list = node['schema_dependencies'] - depend = yang_s.find_schema_dependencies(xpath) + depend = yang_s._find_schema_dependencies(xpath) assert set(depend) == set(list) #test merge data tree def test_merge_data_tree(self, data, yang_s): data_merge_file = data['data_merge_file'] yang_dir = str(data['yang_dir']) - yang_s.merge_data(data_merge_file, yang_dir) + yang_s._merge_data(data_merge_file, yang_dir) #yang_s.root.print_mem(ly.LYD_JSON, ly.LYP_FORMAT) #test get module prefix @@ -226,7 +226,7 @@ def test_get_module_prefix(self, yang_s, data): for node in data['prefix']: xpath = str(node['module_name']) expected = node['module_prefix'] - prefix = yang_s.get_module_prefix(xpath) + prefix = yang_s._get_module_prefix(xpath) assert expected == prefix #test get data type @@ -234,31 +234,31 @@ def test_get_data_type(self, yang_s, data): for node in data['data_type']: xpath = str(node['xpath']) expected = node['data_type'] - expected_type = yang_s.str_to_type(expected) - data_type = yang_s.get_data_type(xpath) + expected_type = yang_s._str_to_type(expected) + data_type = yang_s._get_data_type(xpath) assert expected_type == data_type def test_get_leafref_type(self, yang_s, data): for node in data['leafref_type']: xpath = str(node['xpath']) expected = node['data_type'] - expected_type = yang_s.str_to_type(expected) - data_type = yang_s.get_leafref_type(xpath) + expected_type = yang_s._str_to_type(expected) + data_type = yang_s._get_leafref_type(xpath) assert expected_type == data_type def test_get_leafref_path(self, yang_s, data): for node in data['leafref_path']: xpath = str(node['xpath']) expected_path = node['leafref_path'] - path = yang_s.get_leafref_path(xpath) + path = yang_s._get_leafref_path(xpath) assert expected_path == path def test_get_leafref_type_schema(self, yang_s, data): for node in data['leafref_type_schema']: xpath = str(node['xpath']) expected = node['data_type'] - expected_type = yang_s.str_to_type(expected) - data_type = yang_s.get_leafref_type_schema(xpath) + expected_type = yang_s._str_to_type(expected) + data_type = yang_s._get_leafref_type_schema(xpath) assert expected_type == data_type """ @@ -271,7 +271,7 @@ def sonic_yang_data(self): sonic_yang_dir = "../sonic-yang-models/yang-models/" sonic_yang_test_file = "../sonic-yang-models/tests/yang_model_tests/yangTest.json" - syc = sy.sonic_yang(sonic_yang_dir) + syc = sy.SonicYang(sonic_yang_dir) syc.loadYangModel() sonic_yang_data = dict() @@ -289,11 +289,11 @@ def test_xlate_rev_xlate(self, sonic_yang_data): jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON') - syc.load_data(json.loads(jIn)) + syc.loadData(json.loads(jIn)) # TODO: Make sure no extra table is loaded - syc.get_data() + syc.getData() if syc.jIn and syc.jIn == syc.revXlateJson: print("Xlate and Rev Xlate Passed") @@ -312,7 +312,7 @@ def test_table_with_no_yang(self, sonic_yang_data): jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON_1') - syc.load_data(json.loads(jIn)) + syc.loadData(json.loads(jIn)) ty = syc.tablesWithOutYang