blob: eaa7a521b19a962052b4ba197f3d673e3c12ddf2 [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
8import os
9import tempfile
10import json
Michael Walshb6e3aac2017-09-19 16:57:27 -050011import glob
Michael Walshac29d062017-02-20 16:13:10 -060012from 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
Michael Walshac29d062017-02-20 16:13:10 -060021import gen_valid as gv
22import gen_misc as gm
23import gen_cmd as gc
Michael Walshb6e3aac2017-09-19 16:57:27 -050024import var_funcs as vf
Michael Walshac29d062017-02-20 16:13:10 -060025
Michael Walsh410b1782019-10-22 15:56:18 -050026# The code base directory will be one level up from the directory containing this module.
Michael Walshac29d062017-02-20 16:13:10 -060027code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
28
Michael Sheposda40c1d2020-12-01 22:30:00 -060029redfish_support_trans_state = int(os.environ.get('REDFISH_SUPPORT_TRANS_STATE', 0)) or \
30 int(BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0))
31
Konstantin Aladyshevc056c2b2021-03-25 11:59:39 +030032platform_arch_type = os.environ.get('PLATFORM_ARCH_TYPE', '') or \
33 BuiltIn().get_variable_value("${PLATFORM_ARCH_TYPE}", default="power")
George Keishing1e2fbee2021-03-19 11:19:29 -050034
Michael Walshac29d062017-02-20 16:13:10 -060035
Michael Walsh6c4520c2019-07-16 16:40:00 -050036def create_boot_table(file_path=None,
37 os_host=""):
Michael Walshac29d062017-02-20 16:13:10 -060038 r"""
39 Read the boot table JSON file, convert it to an object and return it.
40
Michael Walsh410b1782019-10-22 15:56:18 -050041 Note that if the user is running without a global OS_HOST robot variable specified, this function will
42 remove all of the "os_" start and end state requirements from the JSON data.
Michael Walshac29d062017-02-20 16:13:10 -060043
Michael Walsh6c4520c2019-07-16 16:40:00 -050044 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050045 file_path The path to the boot_table file. If this value is not specified, it will
46 be obtained from the "BOOT_TABLE_PATH" environment variable, if set.
47 Otherwise, it will default to "data/boot_table.json". If this value is a
48 relative path, this function will use the code_base_dir_path as the base
49 directory (see definition above).
50 os_host The host name or IP address of the host associated with the machine being
51 tested. If the user is running without an OS_HOST (i.e. if this argument
52 is blank), we remove os starting and ending state requirements from the
53 boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060054 """
55 if file_path is None:
George Keishingb51d1502021-03-25 03:30:33 -050056 if redfish_support_trans_state and platform_arch_type != "x86":
Michael Sheposda40c1d2020-12-01 22:30:00 -060057 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table_redfish.json')
George Keishing1e2fbee2021-03-19 11:19:29 -050058 elif platform_arch_type == "x86":
59 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table_x86.json')
Michael Sheposda40c1d2020-12-01 22:30:00 -060060 else:
61 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json')
Michael Walshac29d062017-02-20 16:13:10 -060062
63 if not file_path.startswith("/"):
64 file_path = code_base_dir_path + file_path
65
66 # Pre-process the file by removing blank lines and comment lines.
67 temp = tempfile.NamedTemporaryFile()
68 temp_file_path = temp.name
69
70 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path
71 gc.cmd_fnc_u(cmd_buf, quiet=1)
72
73 boot_file = open(temp_file_path)
74 boot_table = json.load(boot_file, object_hook=DotDict)
75
Michael Walsh410b1782019-10-22 15:56:18 -050076 # If the user is running without an OS_HOST, we remove os starting and ending state requirements from
77 # the boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060078 if os_host == "":
79 for boot in boot_table:
80 state_keys = ['start', 'end']
81 for state_key in state_keys:
Michael Walsh37f833d2019-03-04 17:09:12 -060082 for sub_state in list(boot_table[boot][state_key]):
Michael Walshac29d062017-02-20 16:13:10 -060083 if sub_state.startswith("os_"):
84 boot_table[boot][state_key].pop(sub_state, None)
85
Michael Walsh07a01ef2017-02-27 14:20:22 -060086 # For every boot_type we should have a corresponding mfg mode boot type.
87 enhanced_boot_table = DotDict()
George Keishing36efbc02018-12-12 10:18:23 -060088 for key, value in boot_table.items():
Michael Walsh07a01ef2017-02-27 14:20:22 -060089 enhanced_boot_table[key] = value
90 enhanced_boot_table[key + " (mfg)"] = value
91
92 return enhanced_boot_table
Michael Walshac29d062017-02-20 16:13:10 -060093
Michael Walshac29d062017-02-20 16:13:10 -060094
Michael Walshac29d062017-02-20 16:13:10 -060095def create_valid_boot_list(boot_table):
Michael Walshac29d062017-02-20 16:13:10 -060096 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050097 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 -060098
Michael Walsh6c4520c2019-07-16 16:40:00 -050099 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500100 boot_table A boot table such as is returned by the create_boot_table function.
Michael Walshac29d062017-02-20 16:13:10 -0600101 """
102
103 return list(boot_table.keys())
104
Michael Walshac29d062017-02-20 16:13:10 -0600105
Michael Walshac29d062017-02-20 16:13:10 -0600106def read_boot_lists(dir_path="data/boot_lists/"):
Michael Walshac29d062017-02-20 16:13:10 -0600107 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500108 Read the contents of all the boot lists files found in the given boot lists directory and return
109 dictionary of the lists.
Michael Walshac29d062017-02-20 16:13:10 -0600110
Michael Walsh410b1782019-10-22 15:56:18 -0500111 Boot lists are simply files containing a boot test name on each line. These files are useful for
112 categorizing and organizing boot tests. For example, there may be a "Power_on" list, a "Power_off" list,
113 etc.
Michael Walshac29d062017-02-20 16:13:10 -0600114
Michael Walsh410b1782019-10-22 15:56:18 -0500115 The names of the boot list files will be the keys to the top level dictionary. Each dictionary entry is
116 a list of all the boot tests found in the corresponding file.
Michael Walshac29d062017-02-20 16:13:10 -0600117
118 Here is an abbreviated look at the resulting boot_lists dictionary.
119
120 boot_lists:
121 boot_lists[All]:
Michael Walsh6c4520c2019-07-16 16:40:00 -0500122 boot_lists[All][0]: REST Power On
123 boot_lists[All][1]: REST Power Off
Michael Walshac29d062017-02-20 16:13:10 -0600124 ...
125 boot_lists[Code_update]:
126 boot_lists[Code_update][0]: BMC oob hpm
127 boot_lists[Code_update][1]: BMC ib hpm
128 ...
129
Michael Walsh6c4520c2019-07-16 16:40:00 -0500130 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500131 dir_path The path to the directory containing the boot list files. If this value
132 is a relative path, this function will use the code_base_dir_path as the
133 base directory (see definition above).
Michael Walshac29d062017-02-20 16:13:10 -0600134 """
135
136 if not dir_path.startswith("/"):
137 # Dir path is relative.
138 dir_path = code_base_dir_path + dir_path
139
140 # Get a list of all file names in the directory.
141 boot_file_names = os.listdir(dir_path)
142
143 boot_lists = DotDict()
144 for boot_category in boot_file_names:
145 file_path = gm.which(dir_path + boot_category)
146 boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1)
147 boot_lists[boot_category] = boot_list
148
149 return boot_lists
150
Michael Walshac29d062017-02-20 16:13:10 -0600151
Michael Walshac29d062017-02-20 16:13:10 -0600152def valid_boot_list(boot_list,
153 valid_boot_types):
Michael Walshac29d062017-02-20 16:13:10 -0600154 r"""
155 Verify that each entry in boot_list is a supported boot test.
156
Michael Walsh6c4520c2019-07-16 16:40:00 -0500157 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500158 boot_list An array (i.e. list) of boot test types (e.g. "REST Power On").
159 valid_boot_types A list of valid boot types such as that returned by
160 create_valid_boot_list.
Michael Walshac29d062017-02-20 16:13:10 -0600161 """
162
163 for boot_name in boot_list:
164 boot_name = boot_name.strip(" ")
Michael Walshec01a6f2019-08-01 12:43:20 -0500165 error_message = gv.valid_value(boot_name,
166 valid_values=valid_boot_types,
167 var_name="boot_name")
Michael Walshac29d062017-02-20 16:13:10 -0600168 if error_message != "":
169 BuiltIn().fail(gp.sprint_error(error_message))
170
Michael Walshac29d062017-02-20 16:13:10 -0600171
Michael Walshac29d062017-02-20 16:13:10 -0600172class boot_results:
173
174 r"""
175 This class defines a boot_results table.
176 """
177
178 def __init__(self,
179 boot_table,
180 boot_pass=0,
181 boot_fail=0,
182 obj_name='boot_results'):
Michael Walshac29d062017-02-20 16:13:10 -0600183 r"""
184 Initialize the boot results object.
185
Michael Walsh6c4520c2019-07-16 16:40:00 -0500186 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500187 boot_table Boot table object (see definition above). The boot table contains all of
188 the valid boot test types. It can be created with the create_boot_table
189 function.
190 boot_pass An initial boot_pass value. This program may be called as part of a
191 larger test suite. As such there may already have been some successful
192 boot tests that we need to keep track of.
193 boot_fail An initial boot_fail value. This program may be called as part of a
194 larger test suite. As such there may already have been some unsuccessful
195 boot tests that we need to keep track of.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500196 obj_name The name of this object.
Michael Walshac29d062017-02-20 16:13:10 -0600197 """
198
199 # Store the method parms as class data.
200 self.__obj_name = obj_name
201 self.__initial_boot_pass = boot_pass
202 self.__initial_boot_fail = boot_fail
203
204 # Create boot_results_fields for use in creating boot_results table.
205 boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)])
206 # Create boot_results table.
207 self.__boot_results = tally_sheet('boot type',
208 boot_results_fields,
209 'boot_test_results')
210 self.__boot_results.set_sum_fields(['total', 'pass', 'fail'])
211 self.__boot_results.set_calc_fields(['total=pass+fail'])
Michael Walsh410b1782019-10-22 15:56:18 -0500212 # Create one row in the result table for each kind of boot test in the boot_table (i.e. for all
213 # supported boot tests).
Michael Walshac29d062017-02-20 16:13:10 -0600214 for boot_name in list(boot_table.keys()):
215 self.__boot_results.add_row(boot_name)
216
Michael Walsh6c4520c2019-07-16 16:40:00 -0500217 def add_row(self, *args, **kwargs):
218 r"""
219 Add row to tally_sheet class object.
220
221 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500222 See add_row method in tally_sheet.py for a description of all arguments.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500223 """
224 self.__boot_results.add_row(*args, **kwargs)
225
Michael Walshac29d062017-02-20 16:13:10 -0600226 def return_total_pass_fail(self):
Michael Walshac29d062017-02-20 16:13:10 -0600227 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500228 Return the total boot_pass and boot_fail values. This information is comprised of the pass/fail
229 values from the table plus the initial pass/fail values.
Michael Walshac29d062017-02-20 16:13:10 -0600230 """
231
232 totals_line = self.__boot_results.calc()
233 return totals_line['pass'] + self.__initial_boot_pass,\
234 totals_line['fail'] + self.__initial_boot_fail
235
236 def update(self,
237 boot_type,
238 boot_status):
Michael Walshac29d062017-02-20 16:13:10 -0600239 r"""
240 Update our boot_results_table. This includes:
Michael Walsh410b1782019-10-22 15:56:18 -0500241 - Updating the record for the given boot_type by incrementing the pass or fail field.
Michael Walshac29d062017-02-20 16:13:10 -0600242 - Calling the calc method to have the totals calculated.
243
Michael Walsh6c4520c2019-07-16 16:40:00 -0500244 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500245 boot_type The type of boot test just done (e.g. "REST Power On").
246 boot_status The status of the boot just done. This should be equal to either "pass"
247 or "fail" (case-insensitive).
Michael Walshac29d062017-02-20 16:13:10 -0600248 """
249
250 self.__boot_results.inc_row_field(boot_type, boot_status.lower())
Michael Walsh8f1ef9e2017-03-02 14:31:24 -0600251 self.__boot_results.calc()
Michael Walshac29d062017-02-20 16:13:10 -0600252
253 def sprint_report(self,
254 header_footer="\n"):
Michael Walshac29d062017-02-20 16:13:10 -0600255 r"""
256 String-print the formatted boot_resuls_table and return them.
257
Michael Walsh6c4520c2019-07-16 16:40:00 -0500258 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500259 header_footer This indicates whether a header and footer are to be included in the
260 report.
Michael Walshac29d062017-02-20 16:13:10 -0600261 """
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,
Michael Walsh6c4520c2019-07-16 16:40:00 -0500272 header_footer="\n",
273 quiet=None):
Michael Walshac29d062017-02-20 16:13:10 -0600274 r"""
275 Print the formatted boot_resuls_table to the console.
276
Michael Walsh6c4520c2019-07-16 16:40:00 -0500277 Description of argument(s):
Michael Walshac29d062017-02-20 16:13:10 -0600278 See sprint_report for details.
Michael Walsh410b1782019-10-22 15:56:18 -0500279 quiet Only print if this value is 0. This function will search upward in the
280 stack to get the default value.
Michael Walshac29d062017-02-20 16:13:10 -0600281 """
282
Michael Walsh6c4520c2019-07-16 16:40:00 -0500283 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
284
Michael Walshc108e422019-03-28 12:27:18 -0500285 gp.qprint(self.sprint_report(header_footer))
Michael Walshac29d062017-02-20 16:13:10 -0600286
287 def sprint_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600288 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500289 sprint the fields of this object. This would normally be for debug purposes only.
Michael Walshac29d062017-02-20 16:13:10 -0600290 """
291
292 buffer = ""
293
294 buffer += "class name: " + self.__class__.__name__ + "\n"
295 buffer += gp.sprint_var(self.__obj_name)
296 buffer += self.__boot_results.sprint_obj()
297 buffer += gp.sprint_var(self.__initial_boot_pass)
298 buffer += gp.sprint_var(self.__initial_boot_fail)
299
300 return buffer
301
302 def print_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600303 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500304 Print the fields of this object to stdout. This would normally be for debug purposes.
Michael Walshac29d062017-02-20 16:13:10 -0600305 """
306
Michael Walshc108e422019-03-28 12:27:18 -0500307 gp.gp_print(self.sprint_obj())
Michael Walshac29d062017-02-20 16:13:10 -0600308
Michael Walshb6e3aac2017-09-19 16:57:27 -0500309
Michael Walshb6e3aac2017-09-19 16:57:27 -0500310def create_boot_results_file_path(pgm_name,
311 openbmc_nickname,
312 master_pid):
Michael Walshb6e3aac2017-09-19 16:57:27 -0500313 r"""
314 Create a file path to be used to store a boot_results object.
315
316 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500317 pgm_name The name of the program. This will form part of the resulting file name.
318 openbmc_nickname The name of the system. This could be a nickname, a hostname, an IP,
319 etc. This will form part of the resulting file name.
320 master_pid The master process id which will form part of the file name.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500321 """
Michael Walshb6e3aac2017-09-19 16:57:27 -0500322
Michael Walsh8d7b7382017-09-27 16:00:25 -0500323 USER = os.environ.get("USER", "")
324 dir_path = "/tmp/" + USER + "/"
325 if not os.path.exists(dir_path):
326 os.makedirs(dir_path)
327
Michael Walshb6e3aac2017-09-19 16:57:27 -0500328 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
Michael Walsh8d7b7382017-09-27 16:00:25 -0500329 return vf.create_file_path(file_name_dict, dir_path=dir_path,
330 file_suffix=":boot_results")
Michael Walshb6e3aac2017-09-19 16:57:27 -0500331
Michael Walshb6e3aac2017-09-19 16:57:27 -0500332
Michael Walshb6e3aac2017-09-19 16:57:27 -0500333def cleanup_boot_results_file():
Michael Walshb6e3aac2017-09-19 16:57:27 -0500334 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500335 Delete all boot results files whose corresponding pids are no longer active.
Michael Walshb6e3aac2017-09-19 16:57:27 -0500336 """
337
Michael Walsh410b1782019-10-22 15:56:18 -0500338 # 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 -0500339 globex = create_boot_results_file_path("*", "*", "*")
340 file_list = sorted(glob.glob(globex))
341 for file_path in file_list:
342 # Use parse_file_path to extract info from the file path.
343 file_dict = vf.parse_file_path(file_path)
344 if gm.pid_active(file_dict['master_pid']):
345 gp.qprint_timen("Preserving " + file_path + ".")
346 else:
347 gc.cmd_fnc("rm -f " + file_path)
Michael Walsh6c4520c2019-07-16 16:40:00 -0500348
349
350def update_boot_history(boot_history, boot_start_message, max_boot_history=10):
351 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500352 Update the boot_history list by appending the boot_start_message and by removing all but the last n
353 entries.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500354
355 Description of argument(s):
356 boot_history A list of boot start messages.
Michael Walsh410b1782019-10-22 15:56:18 -0500357 boot_start_message This is typically a time-stamped line of text announcing the start of a
358 boot test.
359 max_boot_history The max number of entries to be kept in the boot_history list. The
360 oldest entries are deleted to achieve this list size.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500361 """
362
363 boot_history.append(boot_start_message)
364
365 # Trim list to max number of entries.
366 del boot_history[:max(0, len(boot_history) - max_boot_history)]
367
368
369def print_boot_history(boot_history, quiet=None):
370 r"""
371 Print the last ten boots done with their time stamps.
372
373 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500374 quiet Only print if this value is 0. This function will search upward in the
375 stack to get the default value.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500376 """
377
378 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
379
380 # indent 0, 90 chars wide, linefeed, char is "="
381 gp.qprint_dashes(0, 90)
382 gp.qprintn("Last 10 boots:\n")
383
384 for boot_entry in boot_history:
385 gp.qprint(boot_entry)
386 gp.qprint_dashes(0, 90)