2
2
import jsonpatch
3
3
from jsonpointer import JsonPointer
4
4
import sonic_yang
5
+ import sonic_yang_ext
5
6
import subprocess
6
7
import yang as ly
7
8
import copy
@@ -155,14 +156,14 @@ def crop_tables_without_yang(self, config_db_as_json):
155
156
sy ._cropConfigDB ()
156
157
157
158
return sy .jIn
158
-
159
+
159
160
def get_empty_tables (self , config ):
160
161
empty_tables = []
161
162
for key in config .keys ():
162
163
if not (config [key ]):
163
164
empty_tables .append (key )
164
165
return empty_tables
165
-
166
+
166
167
def remove_empty_tables (self , config ):
167
168
config_with_non_empty_tables = {}
168
169
for table in config :
@@ -398,7 +399,7 @@ def find_ref_paths(self, path, config):
398
399
Finds the paths referencing any line under the given 'path' within the given 'config'.
399
400
Example:
400
401
path: /PORT
401
- config:
402
+ config:
402
403
{
403
404
"VLAN_MEMBER": {
404
405
"Vlan1000|Ethernet0": {},
@@ -543,10 +544,25 @@ def _get_xpath_tokens_from_list(self, model, token_index, path_tokens, config):
543
544
if len (path_tokens )- 1 == token_index :
544
545
return xpath_tokens
545
546
547
+ type_1_list_model = self ._get_type_1_list_model (model )
548
+ if type_1_list_model :
549
+ new_xpath_tokens = self ._get_xpath_tokens_from_type_1_list (type_1_list_model , token_index + 1 , path_tokens , config [path_tokens [token_index ]])
550
+ xpath_tokens .extend (new_xpath_tokens )
551
+ return xpath_tokens
552
+
546
553
new_xpath_tokens = self ._get_xpath_tokens_from_leaf (model , token_index + 1 , path_tokens ,config [path_tokens [token_index ]])
547
554
xpath_tokens .extend (new_xpath_tokens )
548
555
return xpath_tokens
549
556
557
+ def _get_xpath_tokens_from_type_1_list (self , model , token_index , path_tokens , config ):
558
+ type_1_list_name = model ['@name' ]
559
+ keyName = model ['key' ]['@value' ]
560
+ value = path_tokens [token_index ]
561
+ keyToken = f"[{ keyName } ='{ value } ']"
562
+ itemToken = f"{ type_1_list_name } { keyToken } "
563
+
564
+ return [itemToken ]
565
+
550
566
def _get_xpath_tokens_from_leaf (self , model , token_index , path_tokens , config ):
551
567
token = path_tokens [token_index ]
552
568
@@ -580,7 +596,7 @@ def _get_xpath_tokens_from_leaf(self, model, token_index, path_tokens, config):
580
596
# /module-name:container/leaf-list[.='val']
581
597
# Source: Check examples in https://netopeer.liberouter.org/doc/libyang/master/html/howto_x_path.html
582
598
return [f"{ token } [.='{ value } ']" ]
583
-
599
+
584
600
# checking 'uses' statement
585
601
if not isinstance (config [token ], list ): # leaf-list under uses is not supported yet in sonic_yang
586
602
table = path_tokens [0 ]
@@ -608,7 +624,7 @@ def _extractKey(self, tableKey, keys):
608
624
def _get_list_model (self , model , token_index , path_tokens ):
609
625
parent_container_name = path_tokens [token_index ]
610
626
clist = model .get ('list' )
611
- # Container contains a single list, just return it
627
+ # Container contains a single list, just return it
612
628
# TODO: check if matching also by name is necessary
613
629
if isinstance (clist , dict ):
614
630
return clist
@@ -630,6 +646,15 @@ def _get_list_model(self, model, token_index, path_tokens):
630
646
631
647
return None
632
648
649
+ def _get_type_1_list_model (self , model ):
650
+ list_name = model ['@name' ]
651
+ if list_name not in sonic_yang_ext .Type_1_list_maps_model :
652
+ return None
653
+
654
+ # Type 1 list is expected to have a single inner list model.
655
+ # No need to check if it is a dictionary of list models.
656
+ return model .get ('list' )
657
+
633
658
def convert_xpath_to_path (self , xpath , config , sy ):
634
659
"""
635
660
Converts the given XPATH to JsonPatch path (i.e. JsonPointer).
@@ -711,10 +736,66 @@ def _get_path_tokens_from_list(self, model, token_index, xpath_tokens, config):
711
736
if next_token in key_dict :
712
737
return path_tokens
713
738
739
+ type_1_list_model = self ._get_type_1_list_model (model )
740
+ if type_1_list_model :
741
+ new_path_tokens = self ._get_path_tokens_from_type_1_list (type_1_list_model , token_index + 1 , xpath_tokens , config [path_token ])
742
+ path_tokens .extend (new_path_tokens )
743
+ return path_tokens
744
+
714
745
new_path_tokens = self ._get_path_tokens_from_leaf (model , token_index + 1 , xpath_tokens , config [path_token ])
715
746
path_tokens .extend (new_path_tokens )
716
747
return path_tokens
717
748
749
+ def _get_path_tokens_from_type_1_list (self , model , token_index , xpath_tokens , config ):
750
+ type_1_inner_list_name = model ['@name' ]
751
+
752
+ token = xpath_tokens [token_index ]
753
+ list_tokens = token .split ("[" , 1 ) # split once on the first '[', first element will be the inner list name
754
+ inner_list_name = list_tokens [0 ]
755
+
756
+ if type_1_inner_list_name != inner_list_name :
757
+ raise GenericConfigUpdaterError (f"Type 1 inner list name '{ type_1_inner_list_name } ' does match xpath inner list name '{ inner_list_name } '." )
758
+
759
+ key_dict = self ._extract_key_dict (token )
760
+
761
+ # If no keys specified return empty tokens, as we are already inside the correct table.
762
+ # Also note that the type 1 inner list name in SonicYang has no correspondence in ConfigDb and is ignored.
763
+ # Example where VLAN_MEMBER_LIST has no specific key/value:
764
+ # xpath: /sonic-dot1p-tc-map:sonic-dot1p-tc-map/DOT1P_TO_TC_MAP/DOT1P_TO_TC_MAP_LIST[name='Dot1p_to_tc_map1']/DOT1P_TO_TC_MAP
765
+ # path: /DOT1P_TO_TC_MAP/Dot1p_to_tc_map1
766
+ if not (key_dict ):
767
+ return []
768
+
769
+ if len (key_dict ) > 1 :
770
+ raise GenericConfigUpdaterError (f"Type 1 inner list should have only 1 key in xpath, { len (key_dict )} specified. Key dictionary: { key_dict } " )
771
+
772
+ keyName = next (iter (key_dict .keys ()))
773
+ value = key_dict [keyName ]
774
+
775
+ path_tokens = [value ]
776
+
777
+ # If this is the last xpath token, return the path tokens we have built so far, no need for futher checks
778
+ # Example:
779
+ # xpath: /sonic-dot1p-tc-map:sonic-dot1p-tc-map/DOT1P_TO_TC_MAP/DOT1P_TO_TC_MAP_LIST[name='Dot1p_to_tc_map1']/DOT1P_TO_TC_MAP[dot1p='2']
780
+ # path: /DOT1P_TO_TC_MAP/Dot1p_to_tc_map1/2
781
+ if token_index + 1 >= len (xpath_tokens ):
782
+ return path_tokens
783
+
784
+ # Checking if the next_token is actually a child leaf of the inner type 1 list, for which case
785
+ # just ignore the token, and return the already created ConfigDb path pointing to the whole object
786
+ # Example where the leaf specified is the key:
787
+ # xpath: /sonic-dot1p-tc-map:sonic-dot1p-tc-map/DOT1P_TO_TC_MAP/DOT1P_TO_TC_MAP_LIST[name='Dot1p_to_tc_map1']/DOT1P_TO_TC_MAP[dot1p='2']/dot1p
788
+ # path: /DOT1P_TO_TC_MAP/Dot1p_to_tc_map1/2
789
+ # Example where the leaf specified is not the key:
790
+ # xpath: /sonic-dot1p-tc-map:sonic-dot1p-tc-map/DOT1P_TO_TC_MAP/DOT1P_TO_TC_MAP_LIST[name='Dot1p_to_tc_map1']/DOT1P_TO_TC_MAP[dot1p='2']/tc
791
+ # path: /DOT1P_TO_TC_MAP/Dot1p_to_tc_map1/2
792
+ next_token = xpath_tokens [token_index + 1 ]
793
+ leaf_model = self ._get_model (model .get ('leaf' ), next_token )
794
+ if leaf_model :
795
+ return path_tokens
796
+
797
+ raise GenericConfigUpdaterError (f"Type 1 inner list '{ type_1_inner_list_name } ' does not have a child leaf named '{ next_token } '" )
798
+
718
799
def _get_path_tokens_from_leaf (self , model , token_index , xpath_tokens , config ):
719
800
token = xpath_tokens [token_index ]
720
801
0 commit comments