Skip to content

Commit 5b5cd89

Browse files
find_data_dependencies() should be recursive
The only caller to find_data_dependencies() is in sonic-utilities and it is called in a recursive manner by calling into libyang functions directly. This should be properly abstracted within sonic-yang-mgmt to ensure it is not dependent on any particular libyang version. Test cases have been updated to reflect the behavior while still maintaining compatibility with the existing sonic-utilities until it is updated.
1 parent 394aad1 commit 5b5cd89

File tree

2 files changed

+136
-28
lines changed

2 files changed

+136
-28
lines changed

src/sonic-yang-mgmt/sonic_yang.py

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def _get_module_tree(self, module_name, format):
245245
try:
246246
module = self.ctx.get_module(str(module_name))
247247
except Exception as e:
248-
self.sysLog(msg="Cound not get module: " + str(module_name), debug=syslog.LOG_ERR, doPrint=True)
248+
self.sysLog(msg="Could not get module: " + str(module_name), debug=syslog.LOG_ERR, doPrint=True)
249249
self.fail(e)
250250
else:
251251
if (module is not None):
@@ -380,7 +380,7 @@ def _find_schema_node(self, schema_xpath):
380380
return None
381381
else:
382382
for schema_node in schema_set.schema():
383-
if schema_xapth == schema_node.path():
383+
if schema_xpath == schema_node.path():
384384
return schema_node
385385
return None
386386
"""
@@ -533,7 +533,7 @@ def find_schema_must_count(self, schema_xpath, match_ancestors: bool=False):
533533
try:
534534
schema_node = self._find_schema_node(schema_xpath)
535535
except Exception as e:
536-
self.sysLog(msg="Cound not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
536+
self.sysLog(msg="Could not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
537537
self.fail(e)
538538
return 0
539539

@@ -598,10 +598,28 @@ def find_schema_dependencies(self, schema_xpath, match_ancestors: bool=False):
598598
return result
599599

600600
ref_list = []
601+
if schema_xpath is None or len(schema_xpath) == 0 or schema_xpath == "/":
602+
if not match_ancestors:
603+
return ref_list
604+
605+
# Iterate across all modules, can't use "/"
606+
for module in self.ctx.get_module_iter():
607+
if module.data() is None:
608+
continue
609+
610+
module_list = []
611+
try:
612+
module_list = self.find_schema_dependencies(module.data().path(), match_ancestors=match_ancestors)
613+
except Exception as e:
614+
self.sysLog(msg=f"Exception while finding schema dependencies for module {module.name()}: {str(e)}", debug=syslog.LOG_ERR, doPrint=True)
615+
616+
ref_list.extend(module_list)
617+
return ref_list
618+
601619
try:
602620
schema_node = self._find_schema_node(schema_xpath)
603621
except Exception as e:
604-
self.sysLog(msg="Cound not find the schema node from xpath: " + str(schema_xpath), debug=syslog.LOG_ERR, doPrint=True)
622+
self.sysLog(msg=f"Could not find the schema node from xpath: {str(schema_xpath)}: {str(e)}", debug=syslog.LOG_ERR, doPrint=True)
605623
self.fail(e)
606624
return ref_list
607625

@@ -648,36 +666,80 @@ def __find_schema_dependencies_only(self, schema_node):
648666
return ref_list
649667

650668
"""
651-
find_data_dependencies(): find the data dependencies from data xpath
652-
input: data_xpath - xpath of data node. (Public)
653-
returns: - list of xpath
654-
- Exception if error
669+
find_data_dependencies(): find the data dependencies from data xpath (Public)
670+
input: data_xpath - xpath to search. If it references an exact data node
671+
only the references to that data node will be returned.
672+
If a path contains multiple data nodes, then all references
673+
to all child nodes will be returned. If set to None (or "" or "/"),
674+
will return all references, globally.
655675
"""
656676
def find_data_dependencies(self, data_xpath):
657677
ref_list = []
658-
node = self.root
659-
try:
660-
data_node = self._find_data_node(data_xpath)
661-
except Exception as e:
662-
self.sysLog(msg="find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xapth), debug=syslog.LOG_ERR, doPrint=True)
663-
return ref_list
678+
required_value = None
679+
base_dnode = None
680+
search_xpath = None
681+
682+
if data_xpath is None or len(data_xpath) == 0 or data_xpath == "/":
683+
data_xpath = None
684+
search_xpath = "/"
685+
686+
if data_xpath is not None:
687+
dnode_list = []
688+
try:
689+
dnode_list = list(self.root.find_path(data_xpath).data())
690+
except Exception as e:
691+
# We don't care the reason for the failure, this is caught in
692+
# the next statement.
693+
pass
694+
695+
if len(dnode_list) == 0:
696+
self.sysLog(msg="find_data_dependencies(): Failed to find data node from xpath: {}".format(data_xpath), debug=syslog.LOG_ERR, doPrint=True)
697+
return ref_list
698+
699+
base_dnode = dnode_list[0]
700+
if base_dnode.schema() is None:
701+
return ref_list
702+
703+
search_xpath = base_dnode.schema().path()
704+
705+
# If exactly 1 node and it's a data node, we need to match the value.
706+
if len(dnode_list) == 1:
707+
try:
708+
required_value = self._find_data_node_value(data_xpath)
709+
except Exception as e:
710+
# Might not be a data node, ignore
711+
pass
712+
713+
# Get a list of all schema leafrefs pointing to this node (or these data nodes).
714+
lreflist = []
664715

665716
try:
666-
value = str(self._find_data_node_value(data_xpath))
667-
668-
backlinks = self.find_schema_dependencies(data_node.schema().path(), False)
669-
if backlinks is not None and len(backlinks) > 0:
670-
for link in backlinks:
671-
node_set = node.find_path(link)
672-
for data_set in node_set.data():
673-
data_set.schema()
674-
casted = data_set.subtype()
675-
if value == casted.value_str():
676-
ref_list.append(data_set.path())
717+
match_ancestors = True
718+
if required_value is not None:
719+
match_ancestors = False
720+
721+
lreflist = self.find_schema_dependencies(search_xpath, match_ancestors=match_ancestors)
722+
if lreflist is None:
723+
raise Exception("no schema backlinks found")
677724
except Exception as e:
678-
self.sysLog(msg='Failed to find node or dependencies for {}'.format(data_xpath), debug=syslog.LOG_ERR, doPrint=True)
679-
raise SonicYangException("Failed to find node or dependencies for \
680-
{}\n{}".format(data_xpath, str(e)))
725+
self.sysLog(msg='Failed to find node or dependencies for {}: {}'.format(data_xpath, str(e)), debug=syslog.LOG_ERR, doPrint=True)
726+
lreflist = []
727+
# Exception not expected by existing tests if backlinks not found, so don't raise.
728+
# raise SonicYangException("Failed to find node or dependencies for {}\n{}".format(data_xpath, str(e)))
729+
730+
# For all found data nodes, emit the path to the data node. If we need to
731+
# restrict to a value, do so.
732+
for lref in lreflist:
733+
try:
734+
data_set = self.root.find_path(lref).data()
735+
for dnode in data_set:
736+
if required_value is None or (
737+
dnode.subtype() is not None and dnode.subtype().value_str() == required_value
738+
):
739+
ref_list.append(dnode.path())
740+
except Exception as e:
741+
# Possible no data paths matched, ignore
742+
pass
681743

682744
return ref_list
683745

src/sonic-yang-mgmt/tests/libyang-python-tests/test_SonicYang.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,52 @@
5656
],
5757
"xpath" : "/test-port:test-port/PORT/PORT_LIST[port_name='Ethernet8']/port_name"
5858
},
59+
{
60+
"dependencies" : [
61+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V6'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
62+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/ACL_TABLE_NAME",
63+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
64+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_40']/ACL_TABLE_NAME"
65+
],
66+
"xpath" : "/test-acl:test-acl/ACL_TABLE"
67+
},
68+
{
69+
"dependencies" : [
70+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet8']",
71+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet2']/port",
72+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet3']/vlanid",
73+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='fe80::/10']/vlanid",
74+
"/test-interface:test-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='10.1.1.64/26']/interface",
75+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='2000:f500:45:6709::/64']/vlanid",
76+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet5']/vlanid",
77+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet1']/port",
78+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet0']",
79+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet6']/port",
80+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet2']",
81+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet7']",
82+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet3']/port",
83+
"/test-interface:test-interface/INTERFACE/INTERFACE_LIST[interface='Ethernet8'][ip-prefix='2000:f500:40:a749::/126']/interface",
84+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-test'][RULE_NAME='rule_20']/ACL_TABLE_NAME",
85+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/port",
86+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet5']/port",
87+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet1']/vlanid",
88+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='fe80::/10']/vlanid",
89+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet4']/port",
90+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
91+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet2']/vlanid",
92+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V6']/ports[.='Ethernet9']",
93+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet6']/vlanid",
94+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V4'][RULE_NAME='Rule_40']/ACL_TABLE_NAME",
95+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet0']/vlanid",
96+
"/test-acl:test-acl/ACL_RULE/ACL_RULE_LIST[ACL_TABLE_NAME='PACL-V6'][RULE_NAME='Rule_20']/ACL_TABLE_NAME",
97+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='111'][ip-prefix='10.1.1.64/26']/vlanid",
98+
"/test-vlan:test-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[vlanid='111'][port='Ethernet4']/vlanid",
99+
"/test-acl:test-acl/ACL_TABLE/ACL_TABLE_LIST[ACL_TABLE_NAME='PACL-V4']/ports[.='Ethernet1']",
100+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='2000:f500:41:4e9::/64']/vlanid",
101+
"/test-vlan:test-vlan/VLAN_INTERFACE/VLAN_INTERFACE_LIST[vlanid='555'][ip-prefix='10.1.5.64/26']/vlanid"
102+
],
103+
"xpath" : "/"
104+
},
59105
{
60106
"dependencies" : [],
61107
"xpath" : "/test-port:test-port/PORT/PORT_LIST[port_name='Ethernet8']/alias"

0 commit comments

Comments
 (0)