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 os_host=""): 33 r""" 34 Read the boot table JSON file, convert it to an object and return it. 35 36 Note that if the user is running without a global OS_HOST robot variable 37 specified, this function will remove all of the "os_" start and end state 38 requirements from the JSON data. 39 40 Description of argument(s): 41 file_path The path to the boot_table file. If this 42 value is not specified, it will be 43 obtained from the "BOOT_TABLE_PATH" 44 environment variable, if set. Otherwise, 45 it will default to "data/boot_table.json". 46 If this value is a relative path, this 47 function will use the code_base_dir_path 48 as the base directory (see definition 49 above). 50 os_host The host name or IP address of the host 51 associated with the machine being tested. 52 If the user is running without an OS_HOST 53 (i.e. if this argument is blank), we 54 remove os starting and ending state 55 requirements from the boot entries. 56 """ 57 if file_path is None: 58 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json') 59 60 if not file_path.startswith("/"): 61 file_path = code_base_dir_path + file_path 62 63 # Pre-process the file by removing blank lines and comment lines. 64 temp = tempfile.NamedTemporaryFile() 65 temp_file_path = temp.name 66 67 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path 68 gc.cmd_fnc_u(cmd_buf, quiet=1) 69 70 boot_file = open(temp_file_path) 71 boot_table = json.load(boot_file, object_hook=DotDict) 72 73 # If the user is running without an OS_HOST, we remove os starting and 74 # ending state requirements from the boot entries. 75 if os_host == "": 76 for boot in boot_table: 77 state_keys = ['start', 'end'] 78 for state_key in state_keys: 79 for sub_state in list(boot_table[boot][state_key]): 80 if sub_state.startswith("os_"): 81 boot_table[boot][state_key].pop(sub_state, None) 82 83 # For every boot_type we should have a corresponding mfg mode boot type. 84 enhanced_boot_table = DotDict() 85 for key, value in boot_table.items(): 86 enhanced_boot_table[key] = value 87 enhanced_boot_table[key + " (mfg)"] = value 88 89 return enhanced_boot_table 90 91 92def create_valid_boot_list(boot_table): 93 r""" 94 Return a list of all of the valid boot types (e.g. ['REST Power On', 'REST 95 Power Off', ...]). 96 97 Description of argument(s): 98 boot_table A boot table such as is returned by the 99 create_boot_table function. 100 """ 101 102 return list(boot_table.keys()) 103 104 105def read_boot_lists(dir_path="data/boot_lists/"): 106 r""" 107 Read the contents of all the boot lists files found in the given boot 108 lists directory and return dictionary of the lists. 109 110 Boot lists are simply files containing a boot test name on each line. 111 These files are useful for categorizing and organizing boot tests. For 112 example, there may be a "Power_on" list, a "Power_off" list, etc. 113 114 The names of the boot list files will be the keys to the top level 115 dictionary. Each dictionary entry is a list of all the boot tests found 116 in the corresponding file. 117 118 Here is an abbreviated look at the resulting boot_lists dictionary. 119 120 boot_lists: 121 boot_lists[All]: 122 boot_lists[All][0]: REST Power On 123 boot_lists[All][1]: REST Power Off 124 ... 125 boot_lists[Code_update]: 126 boot_lists[Code_update][0]: BMC oob hpm 127 boot_lists[Code_update][1]: BMC ib hpm 128 ... 129 130 Description of argument(s): 131 dir_path The path to the directory containing the 132 boot list files. If this value is a 133 relative path, this function will use the 134 code_base_dir_path as the base directory 135 (see definition above). 136 """ 137 138 if not dir_path.startswith("/"): 139 # Dir path is relative. 140 dir_path = code_base_dir_path + dir_path 141 142 # Get a list of all file names in the directory. 143 boot_file_names = os.listdir(dir_path) 144 145 boot_lists = DotDict() 146 for boot_category in boot_file_names: 147 file_path = gm.which(dir_path + boot_category) 148 boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1) 149 boot_lists[boot_category] = boot_list 150 151 return boot_lists 152 153 154def valid_boot_list(boot_list, 155 valid_boot_types): 156 r""" 157 Verify that each entry in boot_list is a supported boot test. 158 159 Description of argument(s): 160 boot_list An array (i.e. list) of boot test types 161 (e.g. "REST Power On"). 162 valid_boot_types A list of valid boot types such as that 163 returned by create_valid_boot_list. 164 """ 165 166 for boot_name in boot_list: 167 boot_name = boot_name.strip(" ") 168 error_message = gv.valid_value(boot_name, 169 valid_values=valid_boot_types, 170 var_name="boot_name") 171 if error_message != "": 172 BuiltIn().fail(gp.sprint_error(error_message)) 173 174 175class boot_results: 176 177 r""" 178 This class defines a boot_results table. 179 """ 180 181 def __init__(self, 182 boot_table, 183 boot_pass=0, 184 boot_fail=0, 185 obj_name='boot_results'): 186 r""" 187 Initialize the boot results object. 188 189 Description of argument(s): 190 boot_table Boot table object (see definition above). 191 The boot table contains all of the valid 192 boot test types. It can be created with 193 the create_boot_table function. 194 boot_pass An initial boot_pass value. This program 195 may be called as part of a larger test 196 suite. As such there may already have 197 been some successful boot tests that we 198 need to keep track of. 199 boot_fail An initial boot_fail value. This program 200 may be called as part of a larger test 201 suite. As such there may already have 202 been some unsuccessful boot tests that we 203 need to keep track of. 204 obj_name The name of this object. 205 """ 206 207 # Store the method parms as class data. 208 self.__obj_name = obj_name 209 self.__initial_boot_pass = boot_pass 210 self.__initial_boot_fail = boot_fail 211 212 # Create boot_results_fields for use in creating boot_results table. 213 boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)]) 214 # Create boot_results table. 215 self.__boot_results = tally_sheet('boot type', 216 boot_results_fields, 217 'boot_test_results') 218 self.__boot_results.set_sum_fields(['total', 'pass', 'fail']) 219 self.__boot_results.set_calc_fields(['total=pass+fail']) 220 # Create one row in the result table for each kind of boot test in 221 # the boot_table (i.e. for all supported boot tests). 222 for boot_name in list(boot_table.keys()): 223 self.__boot_results.add_row(boot_name) 224 225 def add_row(self, *args, **kwargs): 226 r""" 227 Add row to tally_sheet class object. 228 229 Description of argument(s): 230 See add_row method in tally_sheet.py for a description of all 231 arguments. 232 """ 233 self.__boot_results.add_row(*args, **kwargs) 234 235 def return_total_pass_fail(self): 236 r""" 237 Return the total boot_pass and boot_fail values. This information is 238 comprised of the pass/fail values from the table plus the initial 239 pass/fail values. 240 """ 241 242 totals_line = self.__boot_results.calc() 243 return totals_line['pass'] + self.__initial_boot_pass,\ 244 totals_line['fail'] + self.__initial_boot_fail 245 246 def update(self, 247 boot_type, 248 boot_status): 249 r""" 250 Update our boot_results_table. This includes: 251 - Updating the record for the given boot_type by incrementing the pass 252 or fail field. 253 - Calling the calc method to have the totals calculated. 254 255 Description of argument(s): 256 boot_type The type of boot test just done (e.g. 257 "REST Power On"). 258 boot_status The status of the boot just done. This 259 should be equal to either "pass" or "fail" 260 (case-insensitive). 261 """ 262 263 self.__boot_results.inc_row_field(boot_type, boot_status.lower()) 264 self.__boot_results.calc() 265 266 def sprint_report(self, 267 header_footer="\n"): 268 r""" 269 String-print the formatted boot_resuls_table and return them. 270 271 Description of argument(s): 272 header_footer This indicates whether a header and footer 273 are to be included in the report. 274 """ 275 276 buffer = "" 277 278 buffer += gp.sprint(header_footer) 279 buffer += self.__boot_results.sprint_report() 280 buffer += gp.sprint(header_footer) 281 282 return buffer 283 284 def print_report(self, 285 header_footer="\n", 286 quiet=None): 287 r""" 288 Print the formatted boot_resuls_table to the console. 289 290 Description of argument(s): 291 See sprint_report for details. 292 quiet Only print if this value is 0. This 293 function will search upward in the stack 294 to get the default value. 295 """ 296 297 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) 298 299 gp.qprint(self.sprint_report(header_footer)) 300 301 def sprint_obj(self): 302 r""" 303 sprint the fields of this object. This would normally be for debug 304 purposes only. 305 """ 306 307 buffer = "" 308 309 buffer += "class name: " + self.__class__.__name__ + "\n" 310 buffer += gp.sprint_var(self.__obj_name) 311 buffer += self.__boot_results.sprint_obj() 312 buffer += gp.sprint_var(self.__initial_boot_pass) 313 buffer += gp.sprint_var(self.__initial_boot_fail) 314 315 return buffer 316 317 def print_obj(self): 318 r""" 319 Print the fields of this object to stdout. This would normally be for 320 debug purposes. 321 """ 322 323 gp.gp_print(self.sprint_obj()) 324 325 326def create_boot_results_file_path(pgm_name, 327 openbmc_nickname, 328 master_pid): 329 r""" 330 Create a file path to be used to store a boot_results object. 331 332 Description of argument(s): 333 pgm_name The name of the program. This will form 334 part of the resulting file name. 335 openbmc_nickname The name of the system. This could be a 336 nickname, a hostname, an IP, etc. This 337 will form part of the resulting file name. 338 master_pid The master process id which will form part 339 of the file name. 340 """ 341 342 USER = os.environ.get("USER", "") 343 dir_path = "/tmp/" + USER + "/" 344 if not os.path.exists(dir_path): 345 os.makedirs(dir_path) 346 347 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid) 348 return vf.create_file_path(file_name_dict, dir_path=dir_path, 349 file_suffix=":boot_results") 350 351 352def cleanup_boot_results_file(): 353 r""" 354 Delete all boot results files whose corresponding pids are no longer 355 active. 356 """ 357 358 # Use create_boot_results_file_path to create a globex to find all of the 359 # existing boot results files. 360 globex = create_boot_results_file_path("*", "*", "*") 361 file_list = sorted(glob.glob(globex)) 362 for file_path in file_list: 363 # Use parse_file_path to extract info from the file path. 364 file_dict = vf.parse_file_path(file_path) 365 if gm.pid_active(file_dict['master_pid']): 366 gp.qprint_timen("Preserving " + file_path + ".") 367 else: 368 gc.cmd_fnc("rm -f " + file_path) 369 370 371def update_boot_history(boot_history, boot_start_message, max_boot_history=10): 372 r""" 373 Update the boot_history list by appending the boot_start_message and by 374 removing all but the last n entries. 375 376 Description of argument(s): 377 boot_history A list of boot start messages. 378 boot_start_message This is typically a time-stamped line of 379 text announcing the start of a boot test. 380 max_boot_history The max number of entries to be kept in 381 the boot_history list. The oldest entries 382 are deleted to achieve this list size. 383 """ 384 385 boot_history.append(boot_start_message) 386 387 # Trim list to max number of entries. 388 del boot_history[:max(0, len(boot_history) - max_boot_history)] 389 390 391def print_boot_history(boot_history, quiet=None): 392 r""" 393 Print the last ten boots done with their time stamps. 394 395 Description of argument(s): 396 quiet Only print if this value is 0. This 397 function will search upward in the stack 398 to get the default value. 399 """ 400 401 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) 402 403 # indent 0, 90 chars wide, linefeed, char is "=" 404 gp.qprint_dashes(0, 90) 405 gp.qprintn("Last 10 boots:\n") 406 407 for boot_entry in boot_history: 408 gp.qprint(boot_entry) 409 gp.qprint_dashes(0, 90) 410