From 1db50e54c830bd8539075eb36a7bb91f47115998 Mon Sep 17 00:00:00 2001 From: ganglv <88995770+ganglyu@users.noreply.github.com> Date: Mon, 16 May 2022 11:42:20 +0800 Subject: [PATCH] [sonic-cfggen]: Update UT to run yang validation (#9700) Why I did it Config db schema generated by minigraph should run yang validation. How I did it Modify run_script to add yang validation. How to verify it Run sonic-config-engine unit test. Signed-off-by: Gang Lv ganglv@microsoft.com --- src/sonic-config-engine/tests/common_utils.py | 51 ++++++++++++++++++- src/sonic-config-engine/tests/test_cfggen.py | 3 ++ .../tests/test_minigraph_case.py | 3 ++ .../tests/test_multinpu_cfggen.py | 3 ++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/sonic-config-engine/tests/common_utils.py b/src/sonic-config-engine/tests/common_utils.py index 47fe9f37f2a..7b8d2b4e27a 100644 --- a/src/sonic-config-engine/tests/common_utils.py +++ b/src/sonic-config-engine/tests/common_utils.py @@ -3,10 +3,14 @@ import os import re import sys +import subprocess +import argparse +import shlex PY3x = sys.version_info >= (3, 0) PYvX_DIR = "py3" if PY3x else "py2" PYTHON_INTERPRETTER = "python3" if PY3x else "python2" +YANG_MODELS_DIR = "/usr/local/yang-models" def tuple_to_str(tuplestr): """ Convert Python tuple '('elem1', 'elem2')' representation into string on the for "elem1|elem2" """ @@ -33,6 +37,52 @@ def liststr_to_dict(liststr): return list_obj +class YangWrapper(object): + def __init__(self, path=YANG_MODELS_DIR): + """ + sonic_yang only supports python3 + """ + if PY3x: + import sonic_yang + self.yang_parser = sonic_yang.SonicYang(path) + self.yang_parser.loadYangModel() + self.test_dir = os.path.dirname(os.path.realpath(__file__)) + self.script_file = PYTHON_INTERPRETTER + ' ' + os.path.join(self.test_dir, '..', 'sonic-cfggen') + + def validate(self, argument): + """ + Raise exception when yang validation failed + """ + if PY3x and "-m" in argument: + import sonic_yang + parser=argparse.ArgumentParser(description="Render configuration file from minigraph data and jinja2 template.") + parser.add_argument("-m", "--minigraph", help="minigraph xml file", nargs='?', const='/etc/sonic/minigraph.xml') + parser.add_argument("-k", "--hwsku", help="HwSKU") + parser.add_argument("-n", "--namespace", help="namespace name", nargs='?', const=None, default=None) + parser.add_argument("-p", "--port-config", help="port config file, used with -m or -k", nargs='?', const=None) + parser.add_argument("-S", "--hwsku-config", help="hwsku config file, used with -p and -m or -k", nargs='?', const=None) + args, unknown = parser.parse_known_args(shlex.split(argument)) + + print('\n Validating yang schema') + cmd = self.script_file + ' -m ' + args.minigraph + if args.hwsku is not None: + cmd += ' -k ' + args.hwsku + if args.hwsku_config is not None: + cmd += ' -S ' + args.hwsku_config + if args.port_config is not None: + cmd += ' -p ' + args.port_config + if args.namespace is not None: + cmd += ' -n ' + args.namespace + cmd += ' --print-data' + output = subprocess.check_output(cmd, shell=True).decode() + try: + self.yang_parser.loadData(configdbJson=json.loads(output)) + self.yang_parser.validate_data_tree() + except sonic_yang.SonicYangException as e: + print("yang data generated from %s is not valid: %s"%(args.minigraph, str(e))) + return False + return True + def cmp(file1, file2): """ compare files """ try: @@ -43,4 +93,3 @@ def cmp(file1, file2): return obj1 == obj2 except: return filecmp.cmp(file1, file2) - diff --git a/src/sonic-config-engine/tests/test_cfggen.py b/src/sonic-config-engine/tests/test_cfggen.py index 023f13c2f23..22ce5671ee8 100644 --- a/src/sonic-config-engine/tests/test_cfggen.py +++ b/src/sonic-config-engine/tests/test_cfggen.py @@ -14,6 +14,7 @@ class TestCfgGen(TestCase): def setUp(self): + self.yang = utils.YangWrapper() self.test_dir = os.path.dirname(os.path.realpath(__file__)) self.script_file = utils.PYTHON_INTERPRETTER + ' ' + os.path.join(self.test_dir, '..', 'sonic-cfggen') self.sample_graph = os.path.join(self.test_dir, 'sample_graph.xml') @@ -50,6 +51,8 @@ def tearDown(self): def run_script(self, argument, check_stderr=False, verbose=False): print('\n Running sonic-cfggen ' + argument) + self.assertTrue(self.yang.validate(argument)) + if check_stderr: output = subprocess.check_output(self.script_file + ' ' + argument, stderr=subprocess.STDOUT, shell=True) else: diff --git a/src/sonic-config-engine/tests/test_minigraph_case.py b/src/sonic-config-engine/tests/test_minigraph_case.py index d18dd0499da..a6db6f37c59 100644 --- a/src/sonic-config-engine/tests/test_minigraph_case.py +++ b/src/sonic-config-engine/tests/test_minigraph_case.py @@ -14,6 +14,7 @@ class TestCfgGenCaseInsensitive(TestCase): def setUp(self): + self.yang = utils.YangWrapper() self.test_dir = os.path.dirname(os.path.realpath(__file__)) self.script_file = utils.PYTHON_INTERPRETTER + ' ' + os.path.join(self.test_dir, '..', 'sonic-cfggen') self.sample_graph = os.path.join(self.test_dir, 'simple-sample-graph-case.xml') @@ -24,6 +25,8 @@ def setUp(self): def run_script(self, argument, check_stderr=False): print('\n Running sonic-cfggen ' + argument) + self.assertTrue(self.yang.validate(argument)) + if check_stderr: output = subprocess.check_output(self.script_file + ' ' + argument, stderr=subprocess.STDOUT, shell=True) else: diff --git a/src/sonic-config-engine/tests/test_multinpu_cfggen.py b/src/sonic-config-engine/tests/test_multinpu_cfggen.py index f39cc9dce1b..35c2000c0e4 100644 --- a/src/sonic-config-engine/tests/test_multinpu_cfggen.py +++ b/src/sonic-config-engine/tests/test_multinpu_cfggen.py @@ -20,6 +20,7 @@ class TestMultiNpuCfgGen(TestCase): def setUp(self): + self.yang = utils.YangWrapper() self.test_dir = os.path.dirname(os.path.realpath(__file__)) self.test_data_dir = os.path.join(self.test_dir, 'multi_npu_data') self.script_file = utils.PYTHON_INTERPRETTER + ' ' + os.path.join(self.test_dir, '..', 'sonic-cfggen') @@ -34,6 +35,8 @@ def setUp(self): def run_script(self, argument, check_stderr=False): print('\n Running sonic-cfggen ' + argument) + self.assertTrue(self.yang.validate(argument)) + if check_stderr: output = subprocess.check_output(self.script_file + ' ' + argument, stderr=subprocess.STDOUT, shell=True) else: