1#!/usr/bin/env python 2 3r""" 4This module has functions to support various data structures such as the 5boot_table, valid_boot_list and boot_results_table. 6""" 7 8import os 9import tempfile 10import json 11import glob 12from tally_sheet import * 13 14from robot.libraries.BuiltIn import BuiltIn 15try: 16 from robot.utils import DotDict 17except ImportError: 18 import collections 19 20import gen_print as gp 21import gen_valid as gv 22import gen_misc as gm 23import gen_cmd as gc 24import var_funcs as vf 25 26# The code base directory will be one level up from the directory containing 27# this module. 28code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep 29 30 31def create_boot_table(file_path=None): 32 r""" 33 Read the boot table JSON file, convert it to an object and return it. 34 35 Note that if the user is running without a global OS_HOST robot variable 36 specified, this function will remove all of the "os_" start and end state 37 requirements from the JSON data. 38 39 Description of arguments: 40 file_path The path to the boot_table file. If this value is not 41 specified, it will be obtained from the "BOOT_TABLE_PATH" 42 environment variable, if set. Otherwise, it will default to 43 "data/boot_table.json". If this value is a relative path, 44 this function will use the code_base_dir_path as the base 45 directory (see definition above). 46 """ 47 if file_path is None: 48 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json') 49 50 if not file_path.startswith("/"): 51 file_path = code_base_dir_path + file_path 52 53 # Pre-process the file by removing blank lines and comment lines. 54 temp = tempfile.NamedTemporaryFile() 55 temp_file_path = temp.name 56 57 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path 58 gc.cmd_fnc_u(cmd_buf, quiet=1) 59 60 boot_file = open(temp_file_path) 61 boot_table = json.load(boot_file, object_hook=DotDict) 62 63 # If the user is running without an OS_HOST, we remove os starting and 64 # ending state requirements from the boot entries. 65 os_host = BuiltIn().get_variable_value("${OS_HOST}", default="") 66 if os_host == "": 67 for boot in boot_table: 68 state_keys = ['start', 'end'] 69 for state_key in state_keys: 70 for sub_state in list(boot_table[boot][state_key]): 71 if sub_state.startswith("os_"): 72 boot_table[boot][state_key].pop(sub_state, None) 73 74 # For every boot_type we should have a corresponding mfg mode boot type. 75 enhanced_boot_table = DotDict() 76 for key, value in boot_table.items(): 77 enhanced_boot_table[key] = value 78 enhanced_boot_table[key + " (mfg)"] = value 79 80 return enhanced_boot_table 81 82 83def create_valid_boot_list(boot_table): 84 r""" 85 Return a list of all of the valid boot types (e.g. ['BMC Power On', 86 'BMC Power Off', ....] 87 88 Description of arguments: 89 boot_table A boot table such as is returned by the create_boot_table 90 function. 91 """ 92 93 return list(boot_table.keys()) 94 95 96def read_boot_lists(dir_path="data/boot_lists/"): 97 r""" 98 Read the contents of all the boot lists files found in the given boot lists 99 directory and return dictionary of the lists. 100 101 Boot lists are simply files containing a boot test name on each line. 102 These files are useful for categorizing and organizing boot tests. For 103 example, there may be a "Power_on" list, a "Power_off" list, etc. 104 105 The names of the boot list files will be the keys to the top level 106 dictionary. Each dictionary entry is a list of all the boot tests found 107 in the corresponding file. 108 109 Here is an abbreviated look at the resulting boot_lists dictionary. 110 111 boot_lists: 112 boot_lists[All]: 113 boot_lists[All][0]: BMC Power On 114 boot_lists[All][1]: BMC Power Off 115 ... 116 boot_lists[Code_update]: 117 boot_lists[Code_update][0]: BMC oob hpm 118 boot_lists[Code_update][1]: BMC ib hpm 119 ... 120 121 Description of arguments: 122 dir_path The path to the directory containing the boot list files. If 123 this value is a relative path, this function will use the 124 code_base_dir_path as the base directory (see definition above). 125 """ 126 127 if not dir_path.startswith("/"): 128 # Dir path is relative. 129 dir_path = code_base_dir_path + dir_path 130 131 # Get a list of all file names in the directory. 132 boot_file_names = os.listdir(dir_path) 133 134 boot_lists = DotDict() 135 for boot_category in boot_file_names: 136 file_path = gm.which(dir_path + boot_category) 137 boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1) 138 boot_lists[boot_category] = boot_list 139 140 return boot_lists 141 142 143def valid_boot_list(boot_list, 144 valid_boot_types): 145 r""" 146 Verify that each entry in boot_list is a supported boot test. 147 148 Description of arguments: 149 boot_list An array (i.e. list) of boot test types 150 (e.g. "BMC Power On"). 151 valid_boot_types A list of valid boot types such as that returned by 152 create_valid_boot_list. 153 """ 154 155 for boot_name in boot_list: 156 boot_name = boot_name.strip(" ") 157 error_message = gv.svalid_value(boot_name, 158 valid_values=valid_boot_types, 159 var_name="boot_name") 160 if error_message != "": 161 BuiltIn().fail(gp.sprint_error(error_message)) 162 163 164class boot_results: 165 166 r""" 167 This class defines a boot_results table. 168 """ 169 170 def __init__(self, 171 boot_table, 172 boot_pass=0, 173 boot_fail=0, 174 obj_name='boot_results'): 175 r""" 176 Initialize the boot results object. 177 178 Description of arguments: 179 boot_table Boot table object (see definition above). The boot table 180 contains all of the valid boot test types. It can be 181 created with the create_boot_table function. 182 boot_pass An initial boot_pass value. This program may be called 183 as part of a larger test suite. As such there may already 184 have been some successful boot tests that we need to 185 keep track of. 186 boot_fail An initial boot_fail value. This program may be called 187 as part of a larger test suite. As such there may already 188 have been some unsuccessful boot tests that we need to 189 keep track of. 190 obj_name The name of this object. 191 """ 192 193 # Store the method parms as class data. 194 self.__obj_name = obj_name 195 self.__initial_boot_pass = boot_pass 196 self.__initial_boot_fail = boot_fail 197 198 # Create boot_results_fields for use in creating boot_results table. 199 boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)]) 200 # Create boot_results table. 201 self.__boot_results = tally_sheet('boot type', 202 boot_results_fields, 203 'boot_test_results') 204 self.__boot_results.set_sum_fields(['total', 'pass', 'fail']) 205 self.__boot_results.set_calc_fields(['total=pass+fail']) 206 # Create one row in the result table for each kind of boot test 207 # in the boot_table (i.e. for all supported boot tests). 208 for boot_name in list(boot_table.keys()): 209 self.__boot_results.add_row(boot_name) 210 211 def return_total_pass_fail(self): 212 r""" 213 Return the total boot_pass and boot_fail values. This information is 214 comprised of the pass/fail values from the table plus the initial 215 pass/fail values. 216 """ 217 218 totals_line = self.__boot_results.calc() 219 return totals_line['pass'] + self.__initial_boot_pass,\ 220 totals_line['fail'] + self.__initial_boot_fail 221 222 def update(self, 223 boot_type, 224 boot_status): 225 r""" 226 Update our boot_results_table. This includes: 227 - Updating the record for the given boot_type by incrementing the pass 228 or fail field. 229 - Calling the calc method to have the totals calculated. 230 231 Description of arguments: 232 boot_type The type of boot test just done (e.g. "BMC Power On"). 233 boot_status The status of the boot just done. This should be equal to 234 either "pass" or "fail" (case-insensitive). 235 """ 236 237 self.__boot_results.inc_row_field(boot_type, boot_status.lower()) 238 self.__boot_results.calc() 239 240 def sprint_report(self, 241 header_footer="\n"): 242 r""" 243 String-print the formatted boot_resuls_table and return them. 244 245 Description of arguments: 246 header_footer This indicates whether a header and footer are to be 247 included in the report. 248 """ 249 250 buffer = "" 251 252 buffer += gp.sprint(header_footer) 253 buffer += self.__boot_results.sprint_report() 254 buffer += gp.sprint(header_footer) 255 256 return buffer 257 258 def print_report(self, 259 header_footer="\n"): 260 r""" 261 Print the formatted boot_resuls_table to the console. 262 263 See sprint_report for details. 264 """ 265 266 gp.qprint(self.sprint_report(header_footer)) 267 268 def sprint_obj(self): 269 r""" 270 sprint the fields of this object. This would normally be for debug 271 purposes only. 272 """ 273 274 buffer = "" 275 276 buffer += "class name: " + self.__class__.__name__ + "\n" 277 buffer += gp.sprint_var(self.__obj_name) 278 buffer += self.__boot_results.sprint_obj() 279 buffer += gp.sprint_var(self.__initial_boot_pass) 280 buffer += gp.sprint_var(self.__initial_boot_fail) 281 282 return buffer 283 284 def print_obj(self): 285 r""" 286 Print the fields of this object to stdout. This would normally be for 287 debug purposes. 288 """ 289 290 gp.gp_print(self.sprint_obj()) 291 292 293def create_boot_results_file_path(pgm_name, 294 openbmc_nickname, 295 master_pid): 296 r""" 297 Create a file path to be used to store a boot_results object. 298 299 Description of argument(s): 300 pgm_name The name of the program. This will form part of the 301 resulting file name. 302 openbmc_nickname The name of the system. This could be a nickname, a 303 hostname, an IP, etc. This will form part of the 304 resulting file name. 305 master_pid The master process id which will form part of the file 306 name. 307 """ 308 309 USER = os.environ.get("USER", "") 310 dir_path = "/tmp/" + USER + "/" 311 if not os.path.exists(dir_path): 312 os.makedirs(dir_path) 313 314 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid) 315 return vf.create_file_path(file_name_dict, dir_path=dir_path, 316 file_suffix=":boot_results") 317 318 319def cleanup_boot_results_file(): 320 r""" 321 Delete all boot results files whose corresponding pids are no longer 322 active. 323 """ 324 325 # Use create_boot_results_file_path to create a globex to find all of the 326 # existing boot results files. 327 globex = create_boot_results_file_path("*", "*", "*") 328 file_list = sorted(glob.glob(globex)) 329 for file_path in file_list: 330 # Use parse_file_path to extract info from the file path. 331 file_dict = vf.parse_file_path(file_path) 332 if gm.pid_active(file_dict['master_pid']): 333 gp.qprint_timen("Preserving " + file_path + ".") 334 else: 335 gc.cmd_fnc("rm -f " + file_path) 336