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