blob: 5264efb1a22a5cccb80428b9a26e984a5473a0db [file] [log] [blame]
Michael Walshac29d062017-02-20 16:13:10 -06001#!/usr/bin/env python
2
3r"""
4This module has functions to support various data structures such as the
5boot_table, valid_boot_list and boot_results_table.
6"""
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
26# The code base directory will be one level up from the directory containing
27# this module.
28code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
29
30
Michael Walsh6c4520c2019-07-16 16:40:00 -050031def create_boot_table(file_path=None,
32 os_host=""):
Michael Walshac29d062017-02-20 16:13:10 -060033 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
Michael Walsh6c4520c2019-07-16 16:40:00 -050040 Description of argument(s):
41 file_path The path to the boot_table file. If this
42 value is not specified, it will be
43 obtained from the "BOOT_TABLE_PATH"
44 environment variable, if set. Otherwise,
45 it will default to "data/boot_table.json".
46 If this value is a relative path, this
47 function will use the code_base_dir_path
48 as the base directory (see definition
49 above).
50 os_host The host name or IP address of the host
51 associated with the machine being tested.
52 If the user is running without an OS_HOST
53 (i.e. if this argument is blank), we
54 remove os starting and ending state
55 requirements from the boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060056 """
57 if file_path is None:
58 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json')
59
60 if not file_path.startswith("/"):
61 file_path = code_base_dir_path + file_path
62
63 # Pre-process the file by removing blank lines and comment lines.
64 temp = tempfile.NamedTemporaryFile()
65 temp_file_path = temp.name
66
67 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path
68 gc.cmd_fnc_u(cmd_buf, quiet=1)
69
70 boot_file = open(temp_file_path)
71 boot_table = json.load(boot_file, object_hook=DotDict)
72
73 # If the user is running without an OS_HOST, we remove os starting and
74 # ending state requirements from the boot entries.
Michael Walshac29d062017-02-20 16:13:10 -060075 if os_host == "":
76 for boot in boot_table:
77 state_keys = ['start', 'end']
78 for state_key in state_keys:
Michael Walsh37f833d2019-03-04 17:09:12 -060079 for sub_state in list(boot_table[boot][state_key]):
Michael Walshac29d062017-02-20 16:13:10 -060080 if sub_state.startswith("os_"):
81 boot_table[boot][state_key].pop(sub_state, None)
82
Michael Walsh07a01ef2017-02-27 14:20:22 -060083 # For every boot_type we should have a corresponding mfg mode boot type.
84 enhanced_boot_table = DotDict()
George Keishing36efbc02018-12-12 10:18:23 -060085 for key, value in boot_table.items():
Michael Walsh07a01ef2017-02-27 14:20:22 -060086 enhanced_boot_table[key] = value
87 enhanced_boot_table[key + " (mfg)"] = value
88
89 return enhanced_boot_table
Michael Walshac29d062017-02-20 16:13:10 -060090
Michael Walshac29d062017-02-20 16:13:10 -060091
Michael Walshac29d062017-02-20 16:13:10 -060092def create_valid_boot_list(boot_table):
Michael Walshac29d062017-02-20 16:13:10 -060093 r"""
Michael Walsh6c4520c2019-07-16 16:40:00 -050094 Return a list of all of the valid boot types (e.g. ['REST Power On', 'REST
95 Power Off', ...]).
Michael Walshac29d062017-02-20 16:13:10 -060096
Michael Walsh6c4520c2019-07-16 16:40:00 -050097 Description of argument(s):
98 boot_table A boot table such as is returned by the
99 create_boot_table function.
Michael Walshac29d062017-02-20 16:13:10 -0600100 """
101
102 return list(boot_table.keys())
103
Michael Walshac29d062017-02-20 16:13:10 -0600104
Michael Walshac29d062017-02-20 16:13:10 -0600105def read_boot_lists(dir_path="data/boot_lists/"):
Michael Walshac29d062017-02-20 16:13:10 -0600106 r"""
Michael Walsh6c4520c2019-07-16 16:40:00 -0500107 Read the contents of all the boot lists files found in the given boot
108 lists directory and return dictionary of the lists.
Michael Walshac29d062017-02-20 16:13:10 -0600109
110 Boot lists are simply files containing a boot test name on each line.
111 These files are useful for categorizing and organizing boot tests. For
112 example, there may be a "Power_on" list, a "Power_off" list, etc.
113
114 The names of the boot list files will be the keys to the top level
115 dictionary. Each dictionary entry is a list of all the boot tests found
116 in the corresponding file.
117
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):
131 dir_path The path to the directory containing the
132 boot list files. If this value is a
133 relative path, this function will use the
134 code_base_dir_path as the base directory
135 (see definition above).
Michael Walshac29d062017-02-20 16:13:10 -0600136 """
137
138 if not dir_path.startswith("/"):
139 # Dir path is relative.
140 dir_path = code_base_dir_path + dir_path
141
142 # Get a list of all file names in the directory.
143 boot_file_names = os.listdir(dir_path)
144
145 boot_lists = DotDict()
146 for boot_category in boot_file_names:
147 file_path = gm.which(dir_path + boot_category)
148 boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1)
149 boot_lists[boot_category] = boot_list
150
151 return boot_lists
152
Michael Walshac29d062017-02-20 16:13:10 -0600153
Michael Walshac29d062017-02-20 16:13:10 -0600154def valid_boot_list(boot_list,
155 valid_boot_types):
Michael Walshac29d062017-02-20 16:13:10 -0600156 r"""
157 Verify that each entry in boot_list is a supported boot test.
158
Michael Walsh6c4520c2019-07-16 16:40:00 -0500159 Description of argument(s):
160 boot_list An array (i.e. list) of boot test types
161 (e.g. "REST Power On").
162 valid_boot_types A list of valid boot types such as that
163 returned by create_valid_boot_list.
Michael Walshac29d062017-02-20 16:13:10 -0600164 """
165
166 for boot_name in boot_list:
167 boot_name = boot_name.strip(" ")
Michael Walshec01a6f2019-08-01 12:43:20 -0500168 error_message = gv.valid_value(boot_name,
169 valid_values=valid_boot_types,
170 var_name="boot_name")
Michael Walshac29d062017-02-20 16:13:10 -0600171 if error_message != "":
172 BuiltIn().fail(gp.sprint_error(error_message))
173
Michael Walshac29d062017-02-20 16:13:10 -0600174
Michael Walshac29d062017-02-20 16:13:10 -0600175class boot_results:
176
177 r"""
178 This class defines a boot_results table.
179 """
180
181 def __init__(self,
182 boot_table,
183 boot_pass=0,
184 boot_fail=0,
185 obj_name='boot_results'):
Michael Walshac29d062017-02-20 16:13:10 -0600186 r"""
187 Initialize the boot results object.
188
Michael Walsh6c4520c2019-07-16 16:40:00 -0500189 Description of argument(s):
190 boot_table Boot table object (see definition above).
191 The boot table contains all of the valid
192 boot test types. It can be created with
193 the create_boot_table function.
194 boot_pass An initial boot_pass value. This program
195 may be called as part of a larger test
196 suite. As such there may already have
197 been some successful boot tests that we
198 need to keep track of.
199 boot_fail An initial boot_fail value. This program
200 may be called as part of a larger test
201 suite. As such there may already have
202 been some unsuccessful boot tests that we
203 need to keep track of.
204 obj_name The name of this object.
Michael Walshac29d062017-02-20 16:13:10 -0600205 """
206
207 # Store the method parms as class data.
208 self.__obj_name = obj_name
209 self.__initial_boot_pass = boot_pass
210 self.__initial_boot_fail = boot_fail
211
212 # Create boot_results_fields for use in creating boot_results table.
213 boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)])
214 # Create boot_results table.
215 self.__boot_results = tally_sheet('boot type',
216 boot_results_fields,
217 'boot_test_results')
218 self.__boot_results.set_sum_fields(['total', 'pass', 'fail'])
219 self.__boot_results.set_calc_fields(['total=pass+fail'])
Michael Walsh6c4520c2019-07-16 16:40:00 -0500220 # Create one row in the result table for each kind of boot test in
221 # the boot_table (i.e. for all supported boot tests).
Michael Walshac29d062017-02-20 16:13:10 -0600222 for boot_name in list(boot_table.keys()):
223 self.__boot_results.add_row(boot_name)
224
Michael Walsh6c4520c2019-07-16 16:40:00 -0500225 def add_row(self, *args, **kwargs):
226 r"""
227 Add row to tally_sheet class object.
228
229 Description of argument(s):
230 See add_row method in tally_sheet.py for a description of all
231 arguments.
232 """
233 self.__boot_results.add_row(*args, **kwargs)
234
Michael Walshac29d062017-02-20 16:13:10 -0600235 def return_total_pass_fail(self):
Michael Walshac29d062017-02-20 16:13:10 -0600236 r"""
237 Return the total boot_pass and boot_fail values. This information is
238 comprised of the pass/fail values from the table plus the initial
239 pass/fail values.
240 """
241
242 totals_line = self.__boot_results.calc()
243 return totals_line['pass'] + self.__initial_boot_pass,\
244 totals_line['fail'] + self.__initial_boot_fail
245
246 def update(self,
247 boot_type,
248 boot_status):
Michael Walshac29d062017-02-20 16:13:10 -0600249 r"""
250 Update our boot_results_table. This includes:
251 - Updating the record for the given boot_type by incrementing the pass
252 or fail field.
253 - Calling the calc method to have the totals calculated.
254
Michael Walsh6c4520c2019-07-16 16:40:00 -0500255 Description of argument(s):
256 boot_type The type of boot test just done (e.g.
257 "REST Power On").
258 boot_status The status of the boot just done. This
259 should be equal to either "pass" or "fail"
260 (case-insensitive).
Michael Walshac29d062017-02-20 16:13:10 -0600261 """
262
263 self.__boot_results.inc_row_field(boot_type, boot_status.lower())
Michael Walsh8f1ef9e2017-03-02 14:31:24 -0600264 self.__boot_results.calc()
Michael Walshac29d062017-02-20 16:13:10 -0600265
266 def sprint_report(self,
267 header_footer="\n"):
Michael Walshac29d062017-02-20 16:13:10 -0600268 r"""
269 String-print the formatted boot_resuls_table and return them.
270
Michael Walsh6c4520c2019-07-16 16:40:00 -0500271 Description of argument(s):
272 header_footer This indicates whether a header and footer
273 are to be included in the report.
Michael Walshac29d062017-02-20 16:13:10 -0600274 """
275
276 buffer = ""
277
278 buffer += gp.sprint(header_footer)
279 buffer += self.__boot_results.sprint_report()
280 buffer += gp.sprint(header_footer)
281
282 return buffer
283
284 def print_report(self,
Michael Walsh6c4520c2019-07-16 16:40:00 -0500285 header_footer="\n",
286 quiet=None):
Michael Walshac29d062017-02-20 16:13:10 -0600287 r"""
288 Print the formatted boot_resuls_table to the console.
289
Michael Walsh6c4520c2019-07-16 16:40:00 -0500290 Description of argument(s):
Michael Walshac29d062017-02-20 16:13:10 -0600291 See sprint_report for details.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500292 quiet Only print if this value is 0. This
293 function will search upward in the stack
294 to get the default value.
Michael Walshac29d062017-02-20 16:13:10 -0600295 """
296
Michael Walsh6c4520c2019-07-16 16:40:00 -0500297 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
298
Michael Walshc108e422019-03-28 12:27:18 -0500299 gp.qprint(self.sprint_report(header_footer))
Michael Walshac29d062017-02-20 16:13:10 -0600300
301 def sprint_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600302 r"""
303 sprint the fields of this object. This would normally be for debug
304 purposes only.
305 """
306
307 buffer = ""
308
309 buffer += "class name: " + self.__class__.__name__ + "\n"
310 buffer += gp.sprint_var(self.__obj_name)
311 buffer += self.__boot_results.sprint_obj()
312 buffer += gp.sprint_var(self.__initial_boot_pass)
313 buffer += gp.sprint_var(self.__initial_boot_fail)
314
315 return buffer
316
317 def print_obj(self):
Michael Walshac29d062017-02-20 16:13:10 -0600318 r"""
319 Print the fields of this object to stdout. This would normally be for
320 debug purposes.
321 """
322
Michael Walshc108e422019-03-28 12:27:18 -0500323 gp.gp_print(self.sprint_obj())
Michael Walshac29d062017-02-20 16:13:10 -0600324
Michael Walshb6e3aac2017-09-19 16:57:27 -0500325
Michael Walshb6e3aac2017-09-19 16:57:27 -0500326def create_boot_results_file_path(pgm_name,
327 openbmc_nickname,
328 master_pid):
Michael Walshb6e3aac2017-09-19 16:57:27 -0500329 r"""
330 Create a file path to be used to store a boot_results object.
331
332 Description of argument(s):
Michael Walsh6c4520c2019-07-16 16:40:00 -0500333 pgm_name The name of the program. This will form
334 part of the resulting file name.
335 openbmc_nickname The name of the system. This could be a
336 nickname, a hostname, an IP, etc. This
337 will form part of the resulting file name.
338 master_pid The master process id which will form part
339 of the file name.
340 """
Michael Walshb6e3aac2017-09-19 16:57:27 -0500341
Michael Walsh8d7b7382017-09-27 16:00:25 -0500342 USER = os.environ.get("USER", "")
343 dir_path = "/tmp/" + USER + "/"
344 if not os.path.exists(dir_path):
345 os.makedirs(dir_path)
346
Michael Walshb6e3aac2017-09-19 16:57:27 -0500347 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
Michael Walsh8d7b7382017-09-27 16:00:25 -0500348 return vf.create_file_path(file_name_dict, dir_path=dir_path,
349 file_suffix=":boot_results")
Michael Walshb6e3aac2017-09-19 16:57:27 -0500350
Michael Walshb6e3aac2017-09-19 16:57:27 -0500351
Michael Walshb6e3aac2017-09-19 16:57:27 -0500352def cleanup_boot_results_file():
Michael Walshb6e3aac2017-09-19 16:57:27 -0500353 r"""
354 Delete all boot results files whose corresponding pids are no longer
355 active.
356 """
357
358 # Use create_boot_results_file_path to create a globex to find all of the
359 # existing boot results files.
360 globex = create_boot_results_file_path("*", "*", "*")
361 file_list = sorted(glob.glob(globex))
362 for file_path in file_list:
363 # Use parse_file_path to extract info from the file path.
364 file_dict = vf.parse_file_path(file_path)
365 if gm.pid_active(file_dict['master_pid']):
366 gp.qprint_timen("Preserving " + file_path + ".")
367 else:
368 gc.cmd_fnc("rm -f " + file_path)
Michael Walsh6c4520c2019-07-16 16:40:00 -0500369
370
371def update_boot_history(boot_history, boot_start_message, max_boot_history=10):
372 r"""
373 Update the boot_history list by appending the boot_start_message and by
374 removing all but the last n entries.
375
376 Description of argument(s):
377 boot_history A list of boot start messages.
378 boot_start_message This is typically a time-stamped line of
379 text announcing the start of a boot test.
380 max_boot_history The max number of entries to be kept in
381 the boot_history list. The oldest entries
George Keishingd97ddcc2019-07-18 13:38:03 -0500382 are deleted to achieve this list size.
Michael Walsh6c4520c2019-07-16 16:40:00 -0500383 """
384
385 boot_history.append(boot_start_message)
386
387 # Trim list to max number of entries.
388 del boot_history[:max(0, len(boot_history) - max_boot_history)]
389
390
391def print_boot_history(boot_history, quiet=None):
392 r"""
393 Print the last ten boots done with their time stamps.
394
395 Description of argument(s):
396 quiet Only print if this value is 0. This
397 function will search upward in the stack
398 to get the default value.
399 """
400
401 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
402
403 # indent 0, 90 chars wide, linefeed, char is "="
404 gp.qprint_dashes(0, 90)
405 gp.qprintn("Last 10 boots:\n")
406
407 for boot_entry in boot_history:
408 gp.qprint(boot_entry)
409 gp.qprint_dashes(0, 90)