88logger = logging .getLogger (__name__ )
99
1010
11- def trim_inventory (inv_files , tbinfo ):
11+ def trim_inventory (inv_files , tbinfo , target_hostname ):
1212 """
1313 Trim the useless topology neighbor the inv_files according to testbed to speed up ansible inventory initialization.
1414
@@ -21,21 +21,30 @@ def trim_inventory(inv_files, tbinfo):
2121 The useless ansible_host extremely slow down the initialization.
2222
2323 Hence, will trim and generate a temporary inventory file, for example:
24- ['../ansible/veos', '../ansible/inv1'] -> ['../ansible/veos_kvm-t0_trim_tmp', '../ansible/inv1']
25- Then pytest will use the light inventory file '../ansible/veos_kvm-t0_trim_tmp' to initialize the ansible_hosts.
24+ ['../ansible/veos', '../ansible/lab1'] -> ['../ansible/veos_kvm-t0_trim_tmp', '../ansible/lab1_kvm-t0_trim_tmp']
25+ Then pytest will use the light inventory files '../ansible/veos_kvm-t0_trim_tmp' and
26+ '../ansible/lab1_kvm-t0_trim_tmp' to initialize the ansible_hosts.
2627
2728 Args:
28- inv_files: inventory file list, sample: ['../ansible/veos', '../ansible/inv1 ']
29+ inv_files: inventory file list, sample: ['../ansible/veos', '../ansible/lab1 ']
2930 tbinfo: tbinfo
31+ target_hostname: target_hostname, will be None if parallel run is disabled
3032
3133 Returns:
3234 Direct update inv_files and return nothing
3335 """
3436
3537 vm_base = tbinfo .get ("vm_base" , None )
3638 tb_name = tbinfo .get ("conf-name" , None )
39+ ptf = tbinfo .get ("ptf" , None )
40+ inv_name = tbinfo .get ("inv_name" , None )
41+ duts = tbinfo .get ("duts" , None )
42+
3743 pattern = r'VM\d{3,7}'
38- logger .info (f"Start to trim inventory, tb[{ tb_name } ] vm_base [{ vm_base } ], inv file { inv_files } " )
44+
45+ # TODO: Trim fanout hosts and PDU hosts. Currently blocked by https://github.com/sonic-net/sonic-mgmt/issues/17347
46+ logger .info (f"Start to trim inventory, inv_files: { inv_files } , vm_base: { vm_base } , tb_name: { tb_name } , "
47+ f"ptf: { ptf } , inv_name: { inv_name } , duts: { duts } " )
3948
4049 # Find a key in all the levels of a dict,
4150 # Return the val of the key and the vm_base_path of the key
@@ -55,36 +64,104 @@ def find_key(d: dict, target_key: str = None, regex: str = None):
5564 stack .append ((value , path + [key ]))
5665 return None , []
5766
58- # Remove all the matched
67+ def get_value_by_key_path (d : dict , key_path : list ):
68+ for k in key_path :
69+ d = d [k ]
70+
71+ return d
72+
73+ def dump_trimmed_inv_to_file (trimmed_inv , trimmed_inventory_file_name ):
74+ with BlankNone (), open (trimmed_inventory_file_name , 'w' ) as f :
75+ yaml .dump (trimmed_inv , f )
76+
77+ # Remove all the unnecessary nodes in the inventory file(s)
5978 for idx in range (len (inv_files )):
6079 inv_file = inv_files [idx ]
80+ if target_hostname is not None :
81+ trimmed_inv_file_name = f"{ inv_file } _{ tb_name } _{ target_hostname } _trim_tmp"
82+ else :
83+ trimmed_inv_file_name = f"{ inv_file } _{ tb_name } _trim_tmp"
84+
6185 with open (inv_file , 'r' ) as file :
6286 inv = yaml .safe_load (file )
63- # If the vm_base is found in the inv_file,
64- # then other useless topology neighbor definition are in it,
65- # that's the inventory to be trimmed
66- _ , vm_base_path = find_key (d = inv , target_key = vm_base )
67- if vm_base_path :
68- keys_to_del = set ()
69- logger .info (f"Find vm_base { vm_base } in inv file { inv_file } , path: { vm_base_path } " )
70-
71- for root_key in inv .keys ():
72- neighbor_val , neighbor_path = find_key (d = inv [root_key ], regex = pattern )
73- if neighbor_path :
74- # Keep the neighbor server for the testbed
75- if root_key == vm_base_path [0 ]:
76- logger .info (f"vm_base[{ vm_base } ] located in { root_key } , inv file { inv_file } , keep it" )
77- # Remove all the useless neighbor server ansible_hosts
78- else :
79- logger .info (f"Attempt to remove { root_key } in inv file { inv_file } " )
80- keys_to_del .add (root_key )
81-
82- for key_to_del in keys_to_del :
83- del inv [key_to_del ]
84-
85- # dump and replace trimmed inventory file
86- trimmed_inventory_file_name = f"{ inv_file } _{ tb_name } _trim_tmp"
87- with BlankNone (), open (trimmed_inventory_file_name , 'w' ) as f :
88- yaml .dump (inv , f )
89-
90- inv_files [idx ] = trimmed_inventory_file_name
87+ if inv_file .split ('/' )[- 1 ] == inv_name :
88+
89+ def trim_value_of_key_path (key_path , keys_to_keep ):
90+ original_val = get_value_by_key_path (d = inv , key_path = key_path )
91+ all_keys = set (original_val .keys ())
92+ for key in all_keys :
93+ if key not in keys_to_keep :
94+ del original_val [key ]
95+
96+ vars_to_trim = ["ptf" , "hwsku" ]
97+ for var in vars_to_trim :
98+ if var == "ptf" and ptf :
99+ _ , ptf_key_path = find_key (d = inv , target_key = ptf )
100+ if ptf_key_path :
101+ logger .info (f"Keep PTF host { ptf } and trim the rest PTF hosts" )
102+ trim_value_of_key_path (ptf_key_path [:- 1 ], {ptf })
103+ elif var == "hwsku" and duts :
104+ # We have all the user-defined HwSKUs as the root keys of this inv file, and these
105+ # HwSKUs combined should be a subset of the keys under the sonic.children node.
106+ # We want to keep the active HwSKU(s) based on the DUTs info and trim the rest.
107+ # Besides, we should also trim the DUT hosts under active HwSKU(s) based on DUTs info.
108+ # Note: if we find an active DUT's HwSKU is somehow not a value under sonic.children node,
109+ # we will skip the HwSKU trimming to avoid potential data loss.
110+ all_hwskus = set (inv ["sonic" ]["children" ].keys ())
111+ should_trim_hwsku = True
112+ hwskus_to_keep = dict ()
113+ for dut in duts :
114+ _ , dut_key_path = find_key (d = inv , target_key = dut )
115+ curr_hwksu = dut_key_path [0 ] if dut_key_path else None
116+ if not curr_hwksu or curr_hwksu in hwskus_to_keep :
117+ continue
118+
119+ if curr_hwksu in all_hwskus :
120+ hwskus_to_keep [curr_hwksu ] = dut_key_path
121+ else :
122+ logger .warning (
123+ f"DUT { dut } 's hwsku is not a value under sonic.children node, skip trimming hwsku"
124+ )
125+
126+ should_trim_hwsku = False
127+ break
128+
129+ if should_trim_hwsku :
130+ for hwsku in all_hwskus :
131+ if hwsku in hwskus_to_keep :
132+ trim_value_of_key_path (hwskus_to_keep [hwsku ][:- 1 ], set (duts ))
133+ else :
134+ del inv ["sonic" ]["children" ][hwsku ]
135+ if hwsku in inv :
136+ logger .info (f"Attempt to delete { hwsku } in inv file { inv_file } " )
137+ del inv [hwsku ]
138+ else :
139+ logger .warning (f"Unknown or invalid var { var } to trim" )
140+
141+ dump_trimmed_inv_to_file (inv , trimmed_inv_file_name )
142+ inv_files [idx ] = trimmed_inv_file_name
143+ else :
144+ # If the vm_base is found in the inv_file,
145+ # then other useless topology neighbor definition are in it,
146+ # that's the inventory to be trimmed
147+ _ , vm_base_path = find_key (d = inv , target_key = vm_base )
148+ if vm_base_path :
149+ keys_to_del = set ()
150+ logger .info (f"Find vm_base { vm_base } in inv file { inv_file } , path: { vm_base_path } " )
151+
152+ for root_key in inv .keys ():
153+ neighbor_val , neighbor_path = find_key (d = inv [root_key ], regex = pattern )
154+ if neighbor_path :
155+ # Keep the neighbor server for the testbed
156+ if root_key == vm_base_path [0 ]:
157+ logger .info (f"vm_base[{ vm_base } ] located in { root_key } , inv file { inv_file } , keep it" )
158+ # Remove all the useless neighbor server ansible_hosts
159+ else :
160+ logger .info (f"Attempt to remove { root_key } in inv file { inv_file } " )
161+ keys_to_del .add (root_key )
162+
163+ for key_to_del in keys_to_del :
164+ del inv [key_to_del ]
165+
166+ dump_trimmed_inv_to_file (inv , trimmed_inv_file_name )
167+ inv_files [idx ] = trimmed_inv_file_name
0 commit comments