Changes: — Remove unnecessary file libyang-python-tests/sample_config_db.json — Keep only config in format of sonic_yang.json in yang_model_tests/yangTest.json. — Keep config in format of config_db.json in file tests/files/sample_config_db.json — Add a test case to validate yang models. **- Why I did it** As per discussion in sonic Yang workgroup, arranging files better so that Yang model can be in force for new config, also adding a test case to validate young models upto some extent. **- How I did it** Changes: — Remove unnecessary file libyang-python-tests/sample_config_db.json — Keep only config in format of sonic_yang.json in yang_model_tests/yangTest.json. — Keep config in format of config_db.json in file tests/files/sample_config_db.json — Add a test case to validate yang models. **- How to verify it** Build PKGs----Building-----
366 lines
12 KiB
Python
366 lines
12 KiB
Python
import sys
|
|
import os
|
|
import pytest
|
|
import sonic_yang as sy
|
|
import json
|
|
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 = "./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'])
|
|
yang_s = sy.SonicYang(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(self):
|
|
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 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']
|
|
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']:
|
|
xpath = str(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)
|
|
|
|
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)
|
|
#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
|
|
|
|
"""
|
|
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/files/sample_config_db.json"
|
|
|
|
syc = sy.SonicYang(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_validate_yang_models(self, sonic_yang_data):
|
|
'''
|
|
In this test, we validate yang models
|
|
a.) by converting the config as per RFC 7951 using YANG Models,
|
|
b.) by creating data tree using new YANG models and
|
|
c.) by validating config against YANG models.
|
|
Successful execution of these steps can be treated as
|
|
validation of new Yang models.
|
|
'''
|
|
test_file = sonic_yang_data['test_file']
|
|
syc = sonic_yang_data['syc']
|
|
# Currently only 2 YANG files are not directly related to config
|
|
# which are: sonic-extension.yang and sonic-types.yang. Hard coding
|
|
# it right now.
|
|
# If any more such helper yang files are added, we need to update here.
|
|
NON_CONFIG_YANG_FILES = 2
|
|
# read config
|
|
jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON')
|
|
jIn = json.loads(jIn)
|
|
numTables = len(jIn)
|
|
# load config and create Data tree
|
|
syc.loadData(jIn, debug=True)
|
|
# check all tables are loaded and config related to all Yang Models is
|
|
# loaded in Data tree.
|
|
assert len(syc.jIn) == numTables
|
|
print("{}:{}".format(len(syc.xlateJson), len(syc.yangFiles)))
|
|
assert len(syc.xlateJson) == len(syc.yangFiles) - NON_CONFIG_YANG_FILES
|
|
# Validate data tree
|
|
validTree = False
|
|
try:
|
|
syc.validate_data_tree()
|
|
validTree = True
|
|
except Exception as e:
|
|
pass
|
|
assert validTree == True
|
|
|
|
return
|
|
|
|
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']
|
|
|
|
jIn = self.readIjsonInput(test_file, 'SAMPLE_CONFIG_DB_JSON')
|
|
jIn = json.loads(jIn)
|
|
numTables = len(jIn)
|
|
|
|
syc.loadData(jIn, debug=True)
|
|
# check all tables are loaded and no tables is without Yang Models
|
|
assert len(syc.jIn) == numTables
|
|
assert len(syc.tablesWithOutYang) == 0
|
|
|
|
syc.getData(debug=True)
|
|
|
|
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 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_UNKNOWN')
|
|
|
|
syc.loadData(json.loads(jIn))
|
|
|
|
ty = syc.tablesWithOutYang
|
|
|
|
assert (len(ty) and "UNKNOWN_TABLE" in ty)
|
|
|
|
return
|
|
|
|
def teardown_class(self):
|
|
pass
|