|  | #!/usr/bin/env python | 
|  |  | 
|  | r""" | 
|  | This module has functions to support various data structures such as the | 
|  | boot_table, valid_boot_list and boot_results_table. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import tempfile | 
|  | import json | 
|  | import glob | 
|  | from tally_sheet import * | 
|  |  | 
|  | from robot.libraries.BuiltIn import BuiltIn | 
|  | try: | 
|  | from robot.utils import DotDict | 
|  | except ImportError: | 
|  | import collections | 
|  |  | 
|  | import gen_print as gp | 
|  | import gen_valid as gv | 
|  | import gen_misc as gm | 
|  | import gen_cmd as gc | 
|  | import var_funcs as vf | 
|  |  | 
|  | # The code base directory will be one level up from the directory containing | 
|  | # this module. | 
|  | code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep | 
|  |  | 
|  |  | 
|  | def create_boot_table(file_path=None, | 
|  | os_host=""): | 
|  | r""" | 
|  | Read the boot table JSON file, convert it to an object and return it. | 
|  |  | 
|  | Note that if the user is running without a global OS_HOST robot variable | 
|  | specified, this function will remove all of the "os_" start and end state | 
|  | requirements from the JSON data. | 
|  |  | 
|  | Description of argument(s): | 
|  | file_path                       The path to the boot_table file.  If this | 
|  | value is not specified, it will be | 
|  | obtained from the "BOOT_TABLE_PATH" | 
|  | environment variable, if set.  Otherwise, | 
|  | it will default to "data/boot_table.json". | 
|  | If this value is a relative path, this | 
|  | function will use the code_base_dir_path | 
|  | as the base directory (see definition | 
|  | above). | 
|  | os_host                         The host name or IP address of the host | 
|  | associated with the machine being tested. | 
|  | If the user is running without an OS_HOST | 
|  | (i.e. if this argument is blank), we | 
|  | remove os starting and ending state | 
|  | requirements from the boot entries. | 
|  | """ | 
|  | if file_path is None: | 
|  | file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json') | 
|  |  | 
|  | if not file_path.startswith("/"): | 
|  | file_path = code_base_dir_path + file_path | 
|  |  | 
|  | # Pre-process the file by removing blank lines and comment lines. | 
|  | temp = tempfile.NamedTemporaryFile() | 
|  | temp_file_path = temp.name | 
|  |  | 
|  | cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path | 
|  | gc.cmd_fnc_u(cmd_buf, quiet=1) | 
|  |  | 
|  | boot_file = open(temp_file_path) | 
|  | boot_table = json.load(boot_file, object_hook=DotDict) | 
|  |  | 
|  | # If the user is running without an OS_HOST, we remove os starting and | 
|  | # ending state requirements from the boot entries. | 
|  | if os_host == "": | 
|  | for boot in boot_table: | 
|  | state_keys = ['start', 'end'] | 
|  | for state_key in state_keys: | 
|  | for sub_state in list(boot_table[boot][state_key]): | 
|  | if sub_state.startswith("os_"): | 
|  | boot_table[boot][state_key].pop(sub_state, None) | 
|  |  | 
|  | # For every boot_type we should have a corresponding mfg mode boot type. | 
|  | enhanced_boot_table = DotDict() | 
|  | for key, value in boot_table.items(): | 
|  | enhanced_boot_table[key] = value | 
|  | enhanced_boot_table[key + " (mfg)"] = value | 
|  |  | 
|  | return enhanced_boot_table | 
|  |  | 
|  |  | 
|  | def create_valid_boot_list(boot_table): | 
|  | r""" | 
|  | Return a list of all of the valid boot types (e.g. ['REST Power On', 'REST | 
|  | Power Off', ...]). | 
|  |  | 
|  | Description of argument(s): | 
|  | boot_table                      A boot table such as is returned by the | 
|  | create_boot_table function. | 
|  | """ | 
|  |  | 
|  | return list(boot_table.keys()) | 
|  |  | 
|  |  | 
|  | def read_boot_lists(dir_path="data/boot_lists/"): | 
|  | r""" | 
|  | Read the contents of all the boot lists files found in the given boot | 
|  | lists directory and return dictionary of the lists. | 
|  |  | 
|  | Boot lists are simply files containing a boot test name on each line. | 
|  | These files are useful for categorizing and organizing boot tests.  For | 
|  | example, there may be a "Power_on" list, a "Power_off" list, etc. | 
|  |  | 
|  | The names of the boot list files will be the keys to the top level | 
|  | dictionary.  Each dictionary entry is a list of all the boot tests found | 
|  | in the corresponding file. | 
|  |  | 
|  | Here is an abbreviated look at the resulting boot_lists dictionary. | 
|  |  | 
|  | boot_lists: | 
|  | boot_lists[All]: | 
|  | boot_lists[All][0]:                           REST Power On | 
|  | boot_lists[All][1]:                           REST Power Off | 
|  | ... | 
|  | boot_lists[Code_update]: | 
|  | boot_lists[Code_update][0]:                   BMC oob hpm | 
|  | boot_lists[Code_update][1]:                   BMC ib hpm | 
|  | ... | 
|  |  | 
|  | Description of argument(s): | 
|  | dir_path                        The path to the directory containing the | 
|  | boot list files.  If this value is a | 
|  | relative path, this function will use the | 
|  | code_base_dir_path as the base directory | 
|  | (see definition above). | 
|  | """ | 
|  |  | 
|  | if not dir_path.startswith("/"): | 
|  | # Dir path is relative. | 
|  | dir_path = code_base_dir_path + dir_path | 
|  |  | 
|  | # Get a list of all file names in the directory. | 
|  | boot_file_names = os.listdir(dir_path) | 
|  |  | 
|  | boot_lists = DotDict() | 
|  | for boot_category in boot_file_names: | 
|  | file_path = gm.which(dir_path + boot_category) | 
|  | boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1) | 
|  | boot_lists[boot_category] = boot_list | 
|  |  | 
|  | return boot_lists | 
|  |  | 
|  |  | 
|  | def valid_boot_list(boot_list, | 
|  | valid_boot_types): | 
|  | r""" | 
|  | Verify that each entry in boot_list is a supported boot test. | 
|  |  | 
|  | Description of argument(s): | 
|  | boot_list                       An array (i.e. list) of boot test types | 
|  | (e.g. "REST Power On"). | 
|  | valid_boot_types                A list of valid boot types such as that | 
|  | returned by create_valid_boot_list. | 
|  | """ | 
|  |  | 
|  | for boot_name in boot_list: | 
|  | boot_name = boot_name.strip(" ") | 
|  | error_message = gv.svalid_value(boot_name, | 
|  | valid_values=valid_boot_types, | 
|  | var_name="boot_name") | 
|  | if error_message != "": | 
|  | BuiltIn().fail(gp.sprint_error(error_message)) | 
|  |  | 
|  |  | 
|  | class boot_results: | 
|  |  | 
|  | r""" | 
|  | This class defines a boot_results table. | 
|  | """ | 
|  |  | 
|  | def __init__(self, | 
|  | boot_table, | 
|  | boot_pass=0, | 
|  | boot_fail=0, | 
|  | obj_name='boot_results'): | 
|  | r""" | 
|  | Initialize the boot results object. | 
|  |  | 
|  | Description of argument(s): | 
|  | boot_table                  Boot table object (see definition above). | 
|  | The boot table contains all of the valid | 
|  | boot test types.  It can be created with | 
|  | the create_boot_table function. | 
|  | boot_pass                   An initial boot_pass value.  This program | 
|  | may be called as part of a larger test | 
|  | suite.  As such there may already have | 
|  | been some successful boot tests that we | 
|  | need to keep track of. | 
|  | boot_fail                   An initial boot_fail value.  This program | 
|  | may be called as part of a larger test | 
|  | suite.  As such there may already have | 
|  | been some unsuccessful boot tests that we | 
|  | need to keep track of. | 
|  | obj_name                    The name of this object. | 
|  | """ | 
|  |  | 
|  | # Store the method parms as class data. | 
|  | self.__obj_name = obj_name | 
|  | self.__initial_boot_pass = boot_pass | 
|  | self.__initial_boot_fail = boot_fail | 
|  |  | 
|  | # Create boot_results_fields for use in creating boot_results table. | 
|  | boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)]) | 
|  | # Create boot_results table. | 
|  | self.__boot_results = tally_sheet('boot type', | 
|  | boot_results_fields, | 
|  | 'boot_test_results') | 
|  | self.__boot_results.set_sum_fields(['total', 'pass', 'fail']) | 
|  | self.__boot_results.set_calc_fields(['total=pass+fail']) | 
|  | # Create one row in the result table for each kind of boot test in | 
|  | # the boot_table (i.e. for all supported boot tests). | 
|  | for boot_name in list(boot_table.keys()): | 
|  | self.__boot_results.add_row(boot_name) | 
|  |  | 
|  | def add_row(self, *args, **kwargs): | 
|  | r""" | 
|  | Add row to tally_sheet class object. | 
|  |  | 
|  | Description of argument(s): | 
|  | See add_row method in tally_sheet.py for a description of all | 
|  | arguments. | 
|  | """ | 
|  | self.__boot_results.add_row(*args, **kwargs) | 
|  |  | 
|  | def return_total_pass_fail(self): | 
|  | r""" | 
|  | Return the total boot_pass and boot_fail values.  This information is | 
|  | comprised of the pass/fail values from the table plus the initial | 
|  | pass/fail values. | 
|  | """ | 
|  |  | 
|  | totals_line = self.__boot_results.calc() | 
|  | return totals_line['pass'] + self.__initial_boot_pass,\ | 
|  | totals_line['fail'] + self.__initial_boot_fail | 
|  |  | 
|  | def update(self, | 
|  | boot_type, | 
|  | boot_status): | 
|  | r""" | 
|  | Update our boot_results_table.  This includes: | 
|  | - Updating the record for the given boot_type by incrementing the pass | 
|  | or fail field. | 
|  | - Calling the calc method to have the totals calculated. | 
|  |  | 
|  | Description of argument(s): | 
|  | boot_type                   The type of boot test just done (e.g. | 
|  | "REST Power On"). | 
|  | boot_status                 The status of the boot just done.  This | 
|  | should be equal to either "pass" or "fail" | 
|  | (case-insensitive). | 
|  | """ | 
|  |  | 
|  | self.__boot_results.inc_row_field(boot_type, boot_status.lower()) | 
|  | self.__boot_results.calc() | 
|  |  | 
|  | def sprint_report(self, | 
|  | header_footer="\n"): | 
|  | r""" | 
|  | String-print the formatted boot_resuls_table and return them. | 
|  |  | 
|  | Description of argument(s): | 
|  | header_footer               This indicates whether a header and footer | 
|  | are to be included in the report. | 
|  | """ | 
|  |  | 
|  | buffer = "" | 
|  |  | 
|  | buffer += gp.sprint(header_footer) | 
|  | buffer += self.__boot_results.sprint_report() | 
|  | buffer += gp.sprint(header_footer) | 
|  |  | 
|  | return buffer | 
|  |  | 
|  | def print_report(self, | 
|  | header_footer="\n", | 
|  | quiet=None): | 
|  | r""" | 
|  | Print the formatted boot_resuls_table to the console. | 
|  |  | 
|  | Description of argument(s): | 
|  | See sprint_report for details. | 
|  | quiet                       Only print if this value is 0.  This | 
|  | function will search upward in the stack | 
|  | to get the default value. | 
|  | """ | 
|  |  | 
|  | quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) | 
|  |  | 
|  | gp.qprint(self.sprint_report(header_footer)) | 
|  |  | 
|  | def sprint_obj(self): | 
|  | r""" | 
|  | sprint the fields of this object.  This would normally be for debug | 
|  | purposes only. | 
|  | """ | 
|  |  | 
|  | buffer = "" | 
|  |  | 
|  | buffer += "class name: " + self.__class__.__name__ + "\n" | 
|  | buffer += gp.sprint_var(self.__obj_name) | 
|  | buffer += self.__boot_results.sprint_obj() | 
|  | buffer += gp.sprint_var(self.__initial_boot_pass) | 
|  | buffer += gp.sprint_var(self.__initial_boot_fail) | 
|  |  | 
|  | return buffer | 
|  |  | 
|  | def print_obj(self): | 
|  | r""" | 
|  | Print the fields of this object to stdout.  This would normally be for | 
|  | debug purposes. | 
|  | """ | 
|  |  | 
|  | gp.gp_print(self.sprint_obj()) | 
|  |  | 
|  |  | 
|  | def create_boot_results_file_path(pgm_name, | 
|  | openbmc_nickname, | 
|  | master_pid): | 
|  | r""" | 
|  | Create a file path to be used to store a boot_results object. | 
|  |  | 
|  | Description of argument(s): | 
|  | pgm_name                        The name of the program.  This will form | 
|  | part of the resulting file name. | 
|  | openbmc_nickname                The name of the system.  This could be a | 
|  | nickname, a hostname, an IP, etc.  This | 
|  | will form part of the resulting file name. | 
|  | master_pid                      The master process id which will form part | 
|  | of the file name. | 
|  | """ | 
|  |  | 
|  | USER = os.environ.get("USER", "") | 
|  | dir_path = "/tmp/" + USER + "/" | 
|  | if not os.path.exists(dir_path): | 
|  | os.makedirs(dir_path) | 
|  |  | 
|  | file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid) | 
|  | return vf.create_file_path(file_name_dict, dir_path=dir_path, | 
|  | file_suffix=":boot_results") | 
|  |  | 
|  |  | 
|  | def cleanup_boot_results_file(): | 
|  | r""" | 
|  | Delete all boot results files whose corresponding pids are no longer | 
|  | active. | 
|  | """ | 
|  |  | 
|  | # Use create_boot_results_file_path to create a globex to find all of the | 
|  | # existing boot results files. | 
|  | globex = create_boot_results_file_path("*", "*", "*") | 
|  | file_list = sorted(glob.glob(globex)) | 
|  | for file_path in file_list: | 
|  | # Use parse_file_path to extract info from the file path. | 
|  | file_dict = vf.parse_file_path(file_path) | 
|  | if gm.pid_active(file_dict['master_pid']): | 
|  | gp.qprint_timen("Preserving " + file_path + ".") | 
|  | else: | 
|  | gc.cmd_fnc("rm -f " + file_path) | 
|  |  | 
|  |  | 
|  | def update_boot_history(boot_history, boot_start_message, max_boot_history=10): | 
|  | r""" | 
|  | Update the boot_history list by appending the boot_start_message and by | 
|  | removing all but the last n entries. | 
|  |  | 
|  | Description of argument(s): | 
|  | boot_history                    A list of boot start messages. | 
|  | boot_start_message              This is typically a time-stamped line of | 
|  | text announcing the start of a boot test. | 
|  | max_boot_history                The max number of entries to be kept in | 
|  | the boot_history list.  The oldest entries | 
|  | are deleted to achieve this list size. | 
|  | """ | 
|  |  | 
|  | boot_history.append(boot_start_message) | 
|  |  | 
|  | # Trim list to max number of entries. | 
|  | del boot_history[:max(0, len(boot_history) - max_boot_history)] | 
|  |  | 
|  |  | 
|  | def print_boot_history(boot_history, quiet=None): | 
|  | r""" | 
|  | Print the last ten boots done with their time stamps. | 
|  |  | 
|  | Description of argument(s): | 
|  | quiet                           Only print if this value is 0.  This | 
|  | function will search upward in the stack | 
|  | to get the default value. | 
|  | """ | 
|  |  | 
|  | quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) | 
|  |  | 
|  | # indent 0, 90 chars wide, linefeed, char is "=" | 
|  | gp.qprint_dashes(0, 90) | 
|  | gp.qprintn("Last 10 boots:\n") | 
|  |  | 
|  | for boot_entry in boot_history: | 
|  | gp.qprint(boot_entry) | 
|  | gp.qprint_dashes(0, 90) |