blob: b767e2128e875103f17268627ef0adcfbad52574 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshac29d062017-02-20 16:13:10 -06002
3r"""
Michael Walsh410b1782019-10-22 15:56:18 -05004This module has functions to support various data structures such as the boot_table, valid_boot_list and
5boot_results_table.
Michael Walshac29d062017-02-20 16:13:10 -06006"""
7
Patrick Williams20f38712022-12-08 06:18:26 -06008import glob
9import json
Michael Walshac29d062017-02-20 16:13:10 -060010import os
11import tempfile
Patrick Williams57318182022-12-08 06:18:26 -060012
George Keishinge635ddc2022-12-08 07:38:02 -060013from robot.libraries.BuiltIn import BuiltIn
Patrick Williams20f38712022-12-08 06:18:26 -060014from tally_sheet import *
15
Michael Walshac29d062017-02-20 16:13:10 -060016try:
17 from robot.utils import DotDict
18except ImportError:
19 import collections
20
Patrick Williams20f38712022-12-08 06:18:26 -060021import gen_cmd as gc
22import gen_misc as gm
Michael Walshac29d062017-02-20 16:13:10 -060023import gen_print as gp
Michael Walshac29d062017-02-20 16:13:10 -060024import gen_valid as gv
Michael Walshb6e3aac2017-09-19 16:57:27 -050025import var_funcs as vf
Michael Walshac29d062017-02-20 16:13:10 -060026
Michael Walsh410b1782019-10-22 15:56:18 -050027# The code base directory will be one level up from the directory containing this module.
Michael Walshac29d062017-02-20 16:13:10 -060028code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
29
Patrick Williams20f38712022-12-08 06:18:26 -060030redfish_support_trans_state = int(
31 os.environ.get("REDFISH_SUPPORT_TRANS_STATE", 0)
32) or int(
33 BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0)
34)
Michael Sheposda40c1d2020-12-01 22:30:00 -060035
Patrick Williams20f38712022-12-08 06:18:26 -060036platform_arch_type = os.environ.get(
37 "PLATFORM_ARCH_TYPE", ""
38) or BuiltIn().get_variable_value("${PLATFORM_ARCH_TYPE}", default="power")
George Keishing1e2fbee2021-03-19 11:19:29 -050039
Michael Walshac29d062017-02-20 16:13:10 -060040
Patrick Williams20f38712022-12-08 06:18:26 -060041def create_boot_table(file_path=None, os_host=""):
Michael Walshac29d062017-02-20 16:13:10 -060042 r"""
43 Read the boot table JSON file, convert it to an object and return it.
44
Michael Walsh410b1782019-10-22 15:56:18 -050045 Note that if the user is running without a global OS_HOST robot variable specified, this function will
46 remove all of the "os_" start and end state requirements from the JSON data.
Michael Walshac29d062017-02-20 16:13:10 -060047
Michael Walsh6c4520c2019-07-16 16:40:00 -050048 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050049 file_path The path to the boot_table file. If this value is not specified, it will
50 be obtained from the "BOOT_TABLE_PATH" environment variable, if set.
51 Otherwise, it will default to "data/boot_table.json". If this value is a
52 relative path, this function will use the code_base_dir_path as the base
53 directory (see definition above).
54 os_host The host name or IP address of the host associated with the machine being
55 tested. If the user is running without an OS_HOST (i.e. if this argument
56 is blank), we remove os starting and ending state requirements from the
57 boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060058 """
59 if file_path is None:
George Keishingb51d1502021-03-25 03:30:33 -050060 if redfish_support_trans_state and platform_arch_type != "x86":
Patrick Williams20f38712022-12-08 06:18:26 -060061 file_path = os.environ.get(
62 "BOOT_TABLE_PATH", "data/boot_table_redfish.json"
63 )
George Keishing1e2fbee2021-03-19 11:19:29 -050064 elif platform_arch_type == "x86":
Patrick Williams20f38712022-12-08 06:18:26 -060065 file_path = os.environ.get(
66 "BOOT_TABLE_PATH", "data/boot_table_x86.json"
67 )
Michael Sheposda40c1d2020-12-01 22:30:00 -060068 else:
Patrick Williams20f38712022-12-08 06:18:26 -060069 file_path = os.environ.get(
70 "BOOT_TABLE_PATH", "data/boot_table.json"
71 )
Michael Walshac29d062017-02-20 16:13:10 -060072
73 if not file_path.startswith("/"):
74 file_path = code_base_dir_path + file_path
75
76 # Pre-process the file by removing blank lines and comment lines.
77 temp = tempfile.NamedTemporaryFile()
78 temp_file_path = temp.name
79
80 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path
81 gc.cmd_fnc_u(cmd_buf, quiet=1)
82
83 boot_file = open(temp_file_path)
84 boot_table = json.load(boot_file, object_hook=DotDict)
85
Michael Walsh410b1782019-10-22 15:56:18 -050086 # If the user is running without an OS_HOST, we remove os starting and ending state requirements from
87 # the boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060088 if os_host == "":
89 for boot in boot_table:
Patrick Williams20f38712022-12-08 06:18:26 -060090 state_keys = ["start", "end"]
Michael Walshac29d062017-02-20 16:13:10 -060091 for state_key in state_keys:
Michael Walsh37f833d2019-03-04 17:09:12 -060092 for sub_state in list(boot_table[boot][state_key]):
Michael Walshac29d062017-02-20 16:13:10 -060093 if sub_state.startswith("os_"):
94 boot_table[boot][state_key].pop(sub_state, None)
95
Michael Walsh07a01ef2017-02-27 14:20:22 -060096 # For every boot_type we should have a corresponding mfg mode boot type.
97 enhanced_boot_table = DotDict()
George Keishing36efbc02018-12-12 10:18:23 -060098 for key, value in boot_table.items():
Michael Walsh07a01ef2017-02-27 14:20:22 -060099 enhanced_boot_table[key] = value
100 enhanced_boot_table[key + " (mfg)"] = value
101
102 return enhanced_boot_table
Michael Walshac29d062017-02-20 16:13:10 -0600103
Michael Walshac29d062017-02-20 16:13:10 -0600104
Michael Walshac29d062017-02-20 16:13:10 -0600105def create_valid_boot_list(boot_table):
Michael Walshac29d062017-02-20 16:13:10 -0600106 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500107 Return a list of all of the valid boot types (e.g. ['REST Power On', 'REST Power Off', ...]).
Michael Walshac29d062017-02-20 16:13:10 -0600108
Michael Walsh6c4520c2019-07-16 16:40:00 -0500109 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500110 boot_table A boot table such as is returned by the create_boot_table function.
Michael Walshac29d062017-02-20 16:13:10 -0600111 """
112
113 return list(boot_table.keys())
114
Michael Walshac29d062017-02-20 16:13:10 -0600115
Michael Walshac29d062017-02-20 16:13:10 -0600116def read_boot_lists(dir_path="data/boot_lists/"):
Michael Walshac29d062017-02-20 16:13:10 -0600117 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500118 Read the contents of all the boot lists files found in the given boot lists directory and return
119 dictionary of the lists.
Michael Walshac29d062017-02-20 16:13:10 -0600120
Michael Walsh410b1782019-10-22 15:56:18 -0500121 Boot lists are simply files containing a boot test name on each line. These files are useful for
122 categorizing and organizing boot tests. For example, there may be a "Power_on" list, a "Power_off" list,
123 etc.
Michael Walshac29d062017-02-20 16:13:10 -0600124
Michael Walsh410b1782019-10-22 15:56:18 -0500125 The names of the boot list files will be the keys to the top level dictionary. Each dictionary entry is
126 a list of all the boot tests found in the corresponding file.
Michael Walshac29d062017-02-20 16:13:10 -0600127
128 Here is an abbreviated look at the resulting boot_lists dictionary.
129
130 boot_lists:
131 boot_lists[All]:
Michael Walsh6c4520c2019-07-16 16:40:00 -0500132 boot_lists[All][0]: REST Power On
133 boot_lists[All][1]: REST Power Off
Michael Walshac29d062017-02-20 16:13:10 -0600134 ...
135 boot_lists[Code_update]:
136 boot_lists[Code_update][0]: BMC oob hpm
137 boot_lists[Code_update][1]: BMC ib hpm
138 ...
139
Michael Walsh6c4520c2019-07-16 16:40:00 -0500140 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500141 dir_path The path to the directory containing the boot list files. If this value
142 is a relative path, this function will use the code_base_dir_path as the
143 base directory (see definition above).
Michael Walshac29d062017-02-20 16:13:10 -0600144 """
145
146 if not dir_path.startswith("/"):
147 # Dir path is relative.
148 dir_path = code_base_dir_path + dir_path
149
150 # Get a list of all file names in the directory.
151 boot_file_names = os.listdir(dir_path)
152
153 boot_lists = DotDict()
154 for boot_category in boot_file_names:
155 file_path = gm.which(dir_path + boot_category)
156 boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1)
157 boot_lists[boot_category] = boot_list
158
159 return boot_lists
160
Michael Walshac29d062017-02-20 16:13:10 -0600161
Patrick Williams20f38712022-12-08 06:18:26 -0600162def valid_boot_list(boot_list, valid_boot_types):
Michael Walshac29d062017-02-20 16:13:10 -0600163 r"""
164 Verify that each entry in boot_list is a supported boot test.
165
Michael Walsh6c4520c2019-07-16 16:40:00 -0500166 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500167 boot_list An array (i.e. list) of boot test types (e.g. "REST Power On").
168 valid_boot_types A list of valid boot types such as that returned by
169 create_valid_boot_list.
Michael Walshac29d062017-02-20 16:13:10 -0600170 """
171
172 for boot_name in boot_list:
173 boot_name = boot_name.strip(" ")
Patrick Williams20f38712022-12-08 06:18:26 -0600174 error_message = gv.valid_value(
175 boot_name, valid_values=valid_boot_types, var_name="boot_name"
176 )
Michael Walshac29d062017-02-20 16:13:10 -0600177 if error_message != "":
178 BuiltIn().fail(gp.sprint_error(error_message))
179
Michael Walshac29d062017-02-20 16:13:10 -0600180
Michael Walshac29d062017-02-20 16:13:10 -0600181class boot_results:
Michael Walshac29d062017-02-20 16:13:10 -0600182 r"""
183 This class defines a boot_results table.
184 """
185
Patrick Williams20f38712022-12-08 06:18:26 -0600186 def __init__(
187 self, boot_table, boot_pass=0, boot_fail=0, obj_name="boot_results"
188 ):
Michael Walshac29d062017-02-20 16:13:10 -0600189 r"""
190 Initialize the boot results object.
191
Michael Walsh6c4520c2019-07-16 16:40:00 -0500192 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500193 boot_table Boot table object (see definition above). The boot table contains all of
194 the valid boot test types. It can be created with the create_boot_table
195 function.
196 boot_pass An initial boot_pass value. This program may be called as part of a
197 larger test suite. As such there may already have been some successful
198 boot tests that we need to keep track of.
199 boot_fail An initial boot_fail value. This program may be called as part of a
200 larger test suite. As such there may already have been some unsuccessful
201 boot tests that we need to keep track of.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500202 obj_name The name of this object.
Michael Walshac29d062017-02-20 16:13:10 -0600203 """
204
205 # Store the method parms as class data.
206 self.__obj_name = obj_name
207 self.__initial_boot_pass = boot_pass
208 self.__initial_boot_fail = boot_fail
209
210 # Create boot_results_fields for use in creating boot_results table.
Patrick Williams20f38712022-12-08 06:18:26 -0600211 boot_results_fields = DotDict([("total", 0), ("pass", 0), ("fail", 0)])
Michael Walshac29d062017-02-20 16:13:10 -0600212 # Create boot_results table.
Patrick Williams20f38712022-12-08 06:18:26 -0600213 self.__boot_results = tally_sheet(
214 "boot type", boot_results_fields, "boot_test_results"
215 )
216 self.__boot_results.set_sum_fields(["total", "pass", "fail"])
217 self.__boot_results.set_calc_fields(["total=pass+fail"])
Michael Walsh410b1782019-10-22 15:56:18 -0500218 # Create one row in the result table for each kind of boot test in the boot_table (i.e. for all
219 # supported boot tests).
Michael Walshac29d062017-02-20 16:13:10 -0600220 for boot_name in list(boot_table.keys()):
221 self.__boot_results.add_row(boot_name)
222
Michael Walsh6c4520c2019-07-16 16:40:00 -0500223 def add_row(self, *args, **kwargs):
224 r"""
225 Add row to tally_sheet class object.
226
227 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500228 See add_row method in tally_sheet.py for a description of all arguments.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500229 """
230 self.__boot_results.add_row(*args, **kwargs)
231
Michael Walshac29d062017-02-20 16:13:10 -0600232 def return_total_pass_fail(self):
Michael Walshac29d062017-02-20 16:13:10 -0600233 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500234 Return the total boot_pass and boot_fail values. This information is comprised of the pass/fail
235 values from the table plus the initial pass/fail values.
Michael Walshac29d062017-02-20 16:13:10 -0600236 """
237
238 totals_line = self.__boot_results.calc()
Patrick Williams20f38712022-12-08 06:18:26 -0600239 return (
240 totals_line["pass"] + self.__initial_boot_pass,
241 totals_line["fail"] + self.__initial_boot_fail,
242 )
Michael Walshac29d062017-02-20 16:13:10 -0600243
Patrick Williams20f38712022-12-08 06:18:26 -0600244 def update(self, boot_type, boot_status):
Michael Walshac29d062017-02-20 16:13:10 -0600245 r"""
246 Update our boot_results_table. This includes:
Michael Walsh410b1782019-10-22 15:56:18 -0500247 - Updating the record for the given boot_type by incrementing the pass or fail field.
Michael Walshac29d062017-02-20 16:13:10 -0600248 - Calling the calc method to have the totals calculated.
249
Michael Walsh6c4520c2019-07-16 16:40:00 -0500250 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500251 boot_type The type of boot test just done (e.g. "REST Power On").
252 boot_status The status of the boot just done. This should be equal to either "pass"
253 or "fail" (case-insensitive).
Michael Walshac29d062017-02-20 16:13:10 -0600254 """
255
256 self.__boot_results.inc_row_field(boot_type, boot_status.lower())
Michael Walsh8f1ef9e2017-03-02 14:31:24 -0600257 self.__boot_results.calc()
Michael Walshac29d062017-02-20 16:13:10 -0600258
Patrick Williams20f38712022-12-08 06:18:26 -0600259 def sprint_report(self, header_footer="\n"):
Michael Walshac29d062017-02-20 16:13:10 -0600260 r"""
261 String-print the formatted boot_resuls_table and return them.
262
Michael Walsh6c4520c2019-07-16 16:40:00 -0500263 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500264 header_footer This indicates whether a header and footer are to be included in the
265 report.
Michael Walshac29d062017-02-20 16:13:10 -0600266 """
267
268 buffer = ""
269
270 buffer += gp.sprint(header_footer)
271 buffer += self.__boot_results.sprint_report()
272 buffer += gp.sprint(header_footer)
273
274 return buffer
275
Patrick Williams20f38712022-12-08 06:18:26 -0600276 def print_report(self, header_footer="\n", quiet=None):
Michael Walshac29d062017-02-20 16:13:10 -0600277 r"""
278 Print the formatted boot_resuls_table to the console.
279
Michael Walsh6c4520c2019-07-16 16:40:00 -0500280 Description of argument(s):
Michael Walshac29d062017-02-20 16:13:10 -0600281 See sprint_report for details.
Michael Walsh410b1782019-10-22 15:56:18 -0500282 quiet Only print if this value is 0. This function will search upward in the
283 stack to get the default value.
Michael Walshac29d062017-02-20 16:13:10 -0600284 """
285
Patrick Williams20f38712022-12-08 06:18:26 -0600286 quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walsh6c4520c2019-07-16 16:40:00 -0500287
Michael Walshc108e422019-03-28 12:27:18 -0500288 gp.qprint(self.sprint_report(header_footer))
Michael Walshac29d062017-02-20 16:13:10 -0600289
290 def sprint_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600291 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500292 sprint the fields of this object. This would normally be for debug purposes only.
Michael Walshac29d062017-02-20 16:13:10 -0600293 """
294
295 buffer = ""
296
297 buffer += "class name: " + self.__class__.__name__ + "\n"
298 buffer += gp.sprint_var(self.__obj_name)
299 buffer += self.__boot_results.sprint_obj()
300 buffer += gp.sprint_var(self.__initial_boot_pass)
301 buffer += gp.sprint_var(self.__initial_boot_fail)
302
303 return buffer
304
305 def print_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600306 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500307 Print the fields of this object to stdout. This would normally be for debug purposes.
Michael Walshac29d062017-02-20 16:13:10 -0600308 """
309
Michael Walshc108e422019-03-28 12:27:18 -0500310 gp.gp_print(self.sprint_obj())
Michael Walshac29d062017-02-20 16:13:10 -0600311
Michael Walshb6e3aac2017-09-19 16:57:27 -0500312
Patrick Williams20f38712022-12-08 06:18:26 -0600313def create_boot_results_file_path(pgm_name, openbmc_nickname, master_pid):
Michael Walshb6e3aac2017-09-19 16:57:27 -0500314 r"""
315 Create a file path to be used to store a boot_results object.
316
317 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500318 pgm_name The name of the program. This will form part of the resulting file name.
319 openbmc_nickname The name of the system. This could be a nickname, a hostname, an IP,
320 etc. This will form part of the resulting file name.
321 master_pid The master process id which will form part of the file name.
Patrick Williams20f38712022-12-08 06:18:26 -0600322 """
Michael Walshb6e3aac2017-09-19 16:57:27 -0500323
Michael Walsh8d7b7382017-09-27 16:00:25 -0500324 USER = os.environ.get("USER", "")
325 dir_path = "/tmp/" + USER + "/"
326 if not os.path.exists(dir_path):
327 os.makedirs(dir_path)
328
Michael Walshb6e3aac2017-09-19 16:57:27 -0500329 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
Patrick Williams20f38712022-12-08 06:18:26 -0600330 return vf.create_file_path(
331 file_name_dict, dir_path=dir_path, file_suffix=":boot_results"
332 )
Michael Walshb6e3aac2017-09-19 16:57:27 -0500333
Michael Walshb6e3aac2017-09-19 16:57:27 -0500334
Michael Walshb6e3aac2017-09-19 16:57:27 -0500335def cleanup_boot_results_file():
Michael Walshb6e3aac2017-09-19 16:57:27 -0500336 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500337 Delete all boot results files whose corresponding pids are no longer active.
Michael Walshb6e3aac2017-09-19 16:57:27 -0500338 """
339
Michael Walsh410b1782019-10-22 15:56:18 -0500340 # Use create_boot_results_file_path to create a globex to find all of the existing boot results files.
Michael Walshb6e3aac2017-09-19 16:57:27 -0500341 globex = create_boot_results_file_path("*", "*", "*")
342 file_list = sorted(glob.glob(globex))
343 for file_path in file_list:
344 # Use parse_file_path to extract info from the file path.
345 file_dict = vf.parse_file_path(file_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600346 if gm.pid_active(file_dict["master_pid"]):
Michael Walshb6e3aac2017-09-19 16:57:27 -0500347 gp.qprint_timen("Preserving " + file_path + ".")
348 else:
349 gc.cmd_fnc("rm -f " + file_path)
Michael Walsh6c4520c2019-07-16 16:40:00 -0500350
351
352def update_boot_history(boot_history, boot_start_message, max_boot_history=10):
353 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500354 Update the boot_history list by appending the boot_start_message and by removing all but the last n
355 entries.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500356
357 Description of argument(s):
358 boot_history A list of boot start messages.
Michael Walsh410b1782019-10-22 15:56:18 -0500359 boot_start_message This is typically a time-stamped line of text announcing the start of a
360 boot test.
361 max_boot_history The max number of entries to be kept in the boot_history list. The
362 oldest entries are deleted to achieve this list size.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500363 """
364
365 boot_history.append(boot_start_message)
366
367 # Trim list to max number of entries.
Patrick Williams20f38712022-12-08 06:18:26 -0600368 del boot_history[: max(0, len(boot_history) - max_boot_history)]
Michael Walsh6c4520c2019-07-16 16:40:00 -0500369
370
371def print_boot_history(boot_history, quiet=None):
372 r"""
373 Print the last ten boots done with their time stamps.
374
375 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500376 quiet Only print if this value is 0. This function will search upward in the
377 stack to get the default value.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500378 """
379
Patrick Williams20f38712022-12-08 06:18:26 -0600380 quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walsh6c4520c2019-07-16 16:40:00 -0500381
382 # indent 0, 90 chars wide, linefeed, char is "="
383 gp.qprint_dashes(0, 90)
384 gp.qprintn("Last 10 boots:\n")
385
386 for boot_entry in boot_history:
387 gp.qprint(boot_entry)
388 gp.qprint_dashes(0, 90)