Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 106 additions & 28 deletions src/sonic-yang-mgmt/sonic_yang.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ def _load_data_model(self, yang_dir, yang_files, data_files, output=None):

return (self.ctx, self.root)

"""
load_module_str_name(): load a module based on the provided string and return
the loaded module name. This is needed by
sonic-utilities to prevent direct dependency on
libyang.
input: yang_module_str yang-formatted module
returns: module name on success, exception on failure
"""
def load_module_str_name(self, yang_module_str):
try:
module = self.ctx.parse_module_mem(yang_module_str, ly.LYS_IN_YANG)
except Exception as e:
self.fail(e)

return module.name()

"""
print_data_mem(): print the data tree
input: option: "JSON" or "XML"
Expand Down Expand Up @@ -229,7 +245,7 @@ def _get_module_tree(self, module_name, format):
try:
module = self.ctx.get_module(str(module_name))
except Exception as e:
self.sysLog(msg="Cound not get module: " + str(module_name), debug=syslog.LOG_ERR, doPrint=True)
self.sysLog(msg="Could not get module: " + str(module_name), debug=syslog.LOG_ERR, doPrint=True)
self.fail(e)
else:
if (module is not None):
Expand Down Expand Up @@ -364,7 +380,7 @@ def _find_schema_node(self, schema_xpath):
return None
else:
for schema_node in schema_set.schema():
if schema_xapth == schema_node.path():
if schema_xpath == schema_node.path():
return schema_node
return None
"""
Expand Down Expand Up @@ -517,7 +533,7 @@ def find_schema_must_count(self, schema_xpath, match_ancestors: bool=False):
try:
schema_node = self._find_schema_node(schema_xpath)
except Exception as e:
self.sysLog(msg="Cound not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
self.sysLog(msg="Could not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
self.fail(e)
return 0

Expand Down Expand Up @@ -582,10 +598,28 @@ def find_schema_dependencies(self, schema_xpath, match_ancestors: bool=False):
return result

ref_list = []
if schema_xpath is None or len(schema_xpath) == 0 or schema_xpath == "/":
if not match_ancestors:
return ref_list

# Iterate across all modules, can't use "/"
for module in self.ctx.get_module_iter():
if module.data() is None:
continue

module_list = []
try:
module_list = self.find_schema_dependencies(module.data().path(), match_ancestors=match_ancestors)
except Exception as e:
self.sysLog(msg=f"Exception while finding schema dependencies for module {module.name()}: {str(e)}", debug=syslog.LOG_ERR, doPrint=True)

ref_list.extend(module_list)
return ref_list

try:
schema_node = self._find_schema_node(schema_xpath)
except Exception as e:
self.sysLog(msg="Cound not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
self.sysLog(msg=f"Could not find the schema node from xpath: {str(schema_xpath)}: {str(e)}", debug=syslog.LOG_ERR, doPrint=True)
self.fail(e)
return ref_list

Expand Down Expand Up @@ -632,36 +666,80 @@ def __find_schema_dependencies_only(self, schema_node):
return ref_list

"""
find_data_dependencies(): find the data dependencies from data xpath
input: data_xpath - xpath of data node. (Public)
returns: - list of xpath
- Exception if error
find_data_dependencies(): find the data dependencies from data xpath (Public)
input: data_xpath - xpath to search. If it references an exact data node
only the references to that data node will be returned.
If a path contains multiple data nodes, then all references
to all child nodes will be returned. If set to None (or "" or "/"),
will return all references, globally.
"""
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:
self.sysLog(msg="find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xapth), debug=syslog.LOG_ERR, doPrint=True)
return ref_list
required_value = None
base_dnode = None
search_xpath = None

if data_xpath is None or len(data_xpath) == 0 or data_xpath == "/":
data_xpath = None
search_xpath = "/"

if data_xpath is not None:
dnode_list = []
try:
dnode_list = list(self.root.find_path(data_xpath).data())
except Exception as e:
# We don't care the reason for the failure, this is caught in
# the next statement.
pass

if len(dnode_list) == 0:
self.sysLog(msg="find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xpath), debug=syslog.LOG_ERR, doPrint=True)
return ref_list

base_dnode = dnode_list[0]
if base_dnode.schema() is None:
return ref_list

search_xpath = base_dnode.schema().path()

# If exactly 1 node and it's a data node, we need to match the value.
if len(dnode_list) == 1:
try:
required_value = self._find_data_node_value(data_xpath)
except Exception as e:
# Might not be a data node, ignore
pass

# Get a list of all schema leafrefs pointing to this node (or these data nodes).
lreflist = []

try:
value = str(self._find_data_node_value(data_xpath))

backlinks = self.find_schema_dependencies(data_node.schema().path(), False)
if backlinks is not None and len(backlinks) > 0:
for link in backlinks:
node_set = node.find_path(link)
for data_set in node_set.data():
data_set.schema()
casted = data_set.subtype()
if value == casted.value_str():
ref_list.append(data_set.path())
match_ancestors = True
if required_value is not None:
match_ancestors = False

lreflist = self.find_schema_dependencies(search_xpath, match_ancestors=match_ancestors)
if lreflist is None:
raise Exception("no schema backlinks found")
except Exception as e:
self.sysLog(msg='Failed to find node or dependencies for {}'.format(data_xpath), debug=syslog.LOG_ERR, doPrint=True)
raise SonicYangException("Failed to find node or dependencies for \
{}\n{}".format(data_xpath, str(e)))
self.sysLog(msg='Failed to find node or dependencies for {}: {}'.format(data_xpath, str(e)), debug=syslog.LOG_ERR, doPrint=True)
lreflist = []
# Exception not expected by existing tests if backlinks not found, so don't raise.
# raise SonicYangException("Failed to find node or dependencies for {}\n{}".format(data_xpath, str(e)))

# For all found data nodes, emit the path to the data node. If we need to
# restrict to a value, do so.
for lref in lreflist:
try:
data_set = self.root.find_path(lref).data()
for dnode in data_set:
if required_value is None or (
dnode.subtype() is not None and dnode.subtype().value_str() == required_value
):
ref_list.append(dnode.path())
except Exception as e:
# Possible no data paths matched, ignore
pass

return ref_list

Expand Down
46 changes: 46 additions & 0 deletions src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,52 @@
],
"xpath" : "/test-port:test-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name"
},
{
"dependencies" : [
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V6'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/ACL_TABLE_NAME",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_40']/ACL_TABLE_NAME"
],
"xpath" : "/test-acl:test-acl/ACL_TABLE"
},
{
"dependencies" : [
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet2']/port",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet3']/vlanid",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='fe80::/10']/vlanid",
"/test-interface:test-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet5']/vlanid",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet1']/port",
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet0']",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet6']/port",
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet2']",
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet7']",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet3']/port",
"/test-interface:test-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/ACL_TABLE_NAME",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet5']/port",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet1']/vlanid",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='fe80::/10']/vlanid",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet4']/port",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet2']/vlanid",
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet9']",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet6']/vlanid",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_40']/ACL_TABLE_NAME",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid",
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V6'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid",
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet4']/vlanid",
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet1']",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='2000:f500:41:4e9::/64']/vlanid",
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='10.1.5.64/26']/vlanid"
],
"xpath" : "/"
},
{
"dependencies" : [],
"xpath" : "/test-port:test-port/PORT/PORT_LIST[port_name='Ethernet8']/alias"
Expand Down
16 changes: 16 additions & 0 deletions src/sonic-yang-mgmt/tests/libyang-python-tests/test_sonic_yang.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ def test_load_invalid_model_files(self, data, yang_s):
with pytest.raises(Exception):
assert self.load_yang_model_file(yang_s, yang_dir, file, module)

# test load_module_str_name on test-acl.yang
def test_load_module_str_name(self, data, yang_s):
yang_dir = data['yang_dir']
file = "test-acl.yang"

try:
with open(f'{yang_dir}/test-acl.yang', 'r') as f:
content = f.read()

name = yang_s.load_module_str_name(content)
except Exception as e:
print(e)
raise

assert name == "test-acl"

#test load yang modules in directory
def test_load_yang_model_dir(self, data, yang_s):
yang_dir = data['yang_dir']
Expand Down
Loading