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