blob: eaa7a521b19a962052b4ba197f3d673e3c12ddf2 [file] [log] [blame]
#!/usr/bin/env python3
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
redfish_support_trans_state = int(os.environ.get('REDFISH_SUPPORT_TRANS_STATE', 0)) or \
int(BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0))
platform_arch_type = os.environ.get('PLATFORM_ARCH_TYPE', '') or \
BuiltIn().get_variable_value("${PLATFORM_ARCH_TYPE}", default="power")
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:
if redfish_support_trans_state and platform_arch_type != "x86":
file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table_redfish.json')
elif platform_arch_type == "x86":
file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table_x86.json')
else:
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.valid_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)