diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 4e81593298f6..139d2692e192 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -110,6 +110,15 @@ sudo cp {{swsssdk_py2_wheel_path}} $FILESYSTEM_ROOT/$SWSSSDK_PY2_WHEEL_NAME sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip install $SWSSSDK_PY2_WHEEL_NAME sudo rm -rf $FILESYSTEM_ROOT/$SWSSSDK_PY2_WHEEL_NAME +# Install sonic-yang-mgmt Python 2 package, install dependencies +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libyang_*.deb +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libyang-cpp_*.deb +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/python2-yang_*.deb +SONIC_YANG_MGMT_PY2_WHEEL_NAME=$(basename {{sonic_yang_mgmt_py2_wheel_path}}) +sudo cp {{sonic_yang_mgmt_py2_wheel_path}} $FILESYSTEM_ROOT/$SONIC_YANG_MGMT_PY2_WHEEL_NAME +sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip install $SONIC_YANG_MGMT_PY2_WHEEL_NAME +sudo rm -rf $FILESYSTEM_ROOT/$SONIC_YANG_MGMT_PY2_WHEEL_NAME + # Install sonic-platform-common Python 2 package PLATFORM_COMMON_PY2_WHEEL_NAME=$(basename {{platform_common_py2_wheel_path}}) sudo cp {{platform_common_py2_wheel_path}} $FILESYSTEM_ROOT/$PLATFORM_COMMON_PY2_WHEEL_NAME diff --git a/rules/sonic-yang-mgmt-py2.mk b/rules/sonic-yang-mgmt-py2.mk new file mode 100644 index 000000000000..c62dedd5b637 --- /dev/null +++ b/rules/sonic-yang-mgmt-py2.mk @@ -0,0 +1,8 @@ +# sonic-yang-mgmt python2 wheel + +SONIC_YANG_MGMT_PY2 = sonic_yang_mgmt-1.0-py2-none-any.whl +$(SONIC_YANG_MGMT_PY2)_SRC_PATH = $(SRC_PATH)/sonic-yang-mgmt +$(SONIC_YANG_MGMT_PY2)_PYTHON_VERSION = 2 +$(SONIC_YANG_MGMT_PY2)_DEBS_DEPENDS = $(LIBYANG) + +SONIC_PYTHON_WHEELS += $(SONIC_YANG_MGMT_PY2) diff --git a/slave.mk b/slave.mk index e518f1e4d8a5..239eec880f84 100644 --- a/slave.mk +++ b/slave.mk @@ -441,7 +441,8 @@ SONIC_TARGET_LIST += $(addprefix $(PYTHON_DEBS_PATH)/, $(SONIC_PYTHON_STDEB_DEBS # $(SOME_NEW_WHL)_PYTHON_VERSION = 2 (or 3) # $(SOME_NEW_WHL)_DEPENDS = $(SOME_OTHER_WHL1) $(SOME_OTHER_WHL2) ... # SONIC_PYTHON_WHEELS += $(SOME_NEW_WHL) -$(addprefix $(PYTHON_WHEELS_PATH)/, $(SONIC_PYTHON_WHEELS)) : $(PYTHON_WHEELS_PATH)/% : .platform $$(addsuffix -install,$$(addprefix $(PYTHON_WHEELS_PATH)/,$$($$*_DEPENDS))) +$(addprefix $(PYTHON_WHEELS_PATH)/, $(SONIC_PYTHON_WHEELS)) : $(PYTHON_WHEELS_PATH)/% : .platform $$(addsuffix -install,$$(addprefix $(PYTHON_WHEELS_PATH)/,$$($$*_DEPENDS))) \ + $$(addsuffix -install,$$(addprefix $(DEBS_PATH)/,$$($$*_DEBS_DEPENDS))) $(HEADER) pushd $($*_SRC_PATH) $(LOG) # apply series of patches if exist @@ -626,7 +627,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_CONFIG_ENGINE)) \ $(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_PLATFORM_API_PY2)) \ + $(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY2)) $(HEADER) # Pass initramfs and linux kernel explicitly. They are used for all platforms export debs_path="$(STRETCH_DEBS_PATH)" @@ -652,6 +654,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ export platform_common_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_PLATFORM_COMMON_PY2))" 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_mgmt_py2_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY2))" $(foreach docker, $($*_DOCKERS),\ export docker_image="$(docker)" 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..31326b96b2d4 --- /dev/null +++ b/src/sonic-yang-mgmt/README.rst @@ -0,0 +1,5 @@ +This Package will contain YANG models for sonic which are written with guidelines mentioned in +https://github.com/Azure/SONiC/blob/master/doc/mgmt/SONiC_YANG_Model_Guidelines.md. + +This package will include python yang libraries which will be used with sonic utilities +pacakge to validate the config. diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py new file mode 100644 index 000000000000..f7c6a74eef62 --- /dev/null +++ b/src/sonic-yang-mgmt/setup.py @@ -0,0 +1,84 @@ +#!/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 +import pytest + +# 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) + + # Continue usual build steps + # run pytest for libyang python APIs + self.pytest_args = [] + errno = pytest.main(self.pytest_args) + if (errno): + exit(errno) + +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/sonic_yang.py b/src/sonic-yang-mgmt/sonic_yang.py new file mode 100644 index 000000000000..a54a0240ea45 --- /dev/null +++ b/src/sonic-yang-mgmt/sonic_yang.py @@ -0,0 +1,489 @@ +import yang as ly +import sys +import json +import glob + +""" +Yang schema and data tree python APIs based on libyang python +""" +class sonic_yang: + def __init__(self, yang_dir): + self.yang_dir = yang_dir + self.ctx = None + self.module = None + self.root = None + + try: + self.ctx = ly.Context(yang_dir) + except Exception as e: + self.fail(e) + + def fail(self, e): + print(e) + raise e + + """ + 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: + 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.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.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: + 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 = data_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(1, 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) + + print("======================= print data =================") + print(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: + json.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_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): + if (self.root is None): + print("data not loaded") + return None + try: + 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 data_node is not None: + return data_node.parent() + + return None + + """ + get_parent_data_xpath(): find the parent data node's xpath + input: data_xpath - xpathof the data node + returns: - xpath of parent data node + - Exception if error + """ + def get_parent_data_xpath (self, data_xpath): + path="" + try: + 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): + path = data_node.path() + return path + + """ + 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_data_node(self, xpath, value): + val = str(value) + try: + 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 data_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 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 + 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 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 schema_node in schema_set.schema(): + if schema_xapth == schema_node.path(): + return schema_node + return None + """ + 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_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 data_node in set.data(): + if data_xpath == data_node.path(): + return data_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_data_node(self, data_xpath, value): + try: + data_node = self.new_data_node(data_xpath, value) + #check if the node added to the data tree + 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) + + """ + 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_data_node(): delete a data node from the data tree + input: xpath of the data node + returns: True - success False - failed + """ + def delete_data_node(self, data_xpath): + try: + data_node = self.find_data_node(data_xpath) + except Exception as e: + print("Failed to delete data node for xpath: " + str(data_xpath)) + self.fail(e) + else: + if (data_node): + data_node.unlink() + + """ + 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_data_node_value(self, data_xpath): + output = "" + try: + 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) + else: + if (data_node is not None): + subtype = data_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_data_node_value(self, data_xpath, value): + try: + data_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 + + 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())) + 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 + + value = str(self.find_data_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()) + + return ref_list 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/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/test-sonic-acl.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang new file mode 100644 index 000000000000..79f2cdded177 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-acl.yang @@ -0,0 +1,274 @@ +module test-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 test-sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import test-sonic-port { + prefix port; + revision-date 2019-07-01; + } + + import test-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/test-sonic-head.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang new file mode 100644 index 000000000000..547ac8ac1cb3 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-head.yang @@ -0,0 +1,72 @@ +module test-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; + } + } +} 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-sonic-interface.yang new file mode 100644 index 000000000000..fd573b17707d --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-interface.yang @@ -0,0 +1,78 @@ +module test-sonic-interface { + + namespace "http://github.com/Azure/sonic-interface"; + prefix intf; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import test-sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import test-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/test-sonic-port.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang new file mode 100644 index 000000000000..494205ce4d92 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-port.yang @@ -0,0 +1,84 @@ +module test-sonic-port{ + + namespace "http://github.com/Azure/sonic-port"; + prefix port; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import test-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/test-sonic-portchannel.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang new file mode 100644 index 000000000000..9b664c8289f0 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-portchannel.yang @@ -0,0 +1,85 @@ +module test-sonic-portchannel { + + namespace "http://github.com/Azure/sonic-portchannel"; + prefix lag; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import test-sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import test-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/test-sonic-vlan.yang b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang new file mode 100644 index 000000000000..227c3c6dbe6b --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sample-yang-models/test-sonic-vlan.yang @@ -0,0 +1,158 @@ +module test-sonic-vlan { + + namespace "http://github.com/Azure/sonic-vlan"; + prefix vlan; + + import ietf-yang-types { + prefix yang; + } + + import ietf-inet-types { + prefix inet; + } + + import test-sonic-head { + prefix head; + revision-date 2019-07-01; + } + + import test-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/sonic_config_data.json b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json new file mode 100644 index 000000000000..35857fd6609c --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data.json @@ -0,0 +1,276 @@ +{ + "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.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" + } + ] + }, + + "test-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" + ] + } + ] + }, + + "test-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" + } + ] + } + }, + "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" + } + ] + } + }, + + "test-sonic-acl:sonic-acl": { + "test-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" + } + ] + }, + + "test-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"] + } + ] + } + }, + + "test-sonic-interface:sonic-interface": { + "test-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..ae668566d9a3 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/sonic_config_data_merge.json @@ -0,0 +1,177 @@ +{ + "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 new file mode 100644 index 000000000000..d0361b349be6 --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json @@ -0,0 +1,109 @@ +{ + "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":"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":"/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":"/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":"/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":"/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":"/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":"/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":"/test-sonic-port:sonic-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"]} + ], + "schema_dependencies":[ + {"xpath":"/test-sonic-port:sonic-port/test-sonic-port:PORT/test-sonic-port:PORT_LIST/test-sonic-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"]} + ], + "members":[ + {"xpath":"/test-sonic-port:sonic-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']"]} + ], + + "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']"} + ] +} 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..d9e135b0792b --- /dev/null +++ b/src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py @@ -0,0 +1,201 @@ +import sys +import os +import pytest +import yang as ly +import sonic_yang as sy +import json +import getopt +import subprocess +import glob + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +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" + 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 + + 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) + + #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']) + 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_data_node(xpath, str(value)) + + data_node = yang_s.find_data_node(xpath) + assert data_node is not None + + #test find node value + 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_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']: + expected = node['valid'] + xpath = str(node['xpath']) + yang_s.delete_data_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_data_node_value(xpath, value) + + val = yang_s.find_data_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_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) + assert path == expected_xpath + + #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_data_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) + + def teardown_class(cls): + pass 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