blob: 4bc4229d500cb3595e13e707be520b1aafb942d8 [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
21import gen_robot_print as grp
22import gen_valid as gv
23import gen_misc as gm
24import gen_cmd as gc
Michael Walshb6e3aac2017-09-19 16:57:27 -050025import var_funcs as vf
Michael Walshac29d062017-02-20 16:13:10 -060026
27# The code base directory will be one level up from the directory containing
28# this module.
29code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
30
31
32###############################################################################
33def create_boot_table(file_path=None):
34
35 r"""
36 Read the boot table JSON file, convert it to an object and return it.
37
38 Note that if the user is running without a global OS_HOST robot variable
39 specified, this function will remove all of the "os_" start and end state
40 requirements from the JSON data.
41
42 Description of arguments:
43 file_path The path to the boot_table file. If this value is not
44 specified, it will be obtained from the "BOOT_TABLE_PATH"
45 environment variable, if set. Otherwise, it will default to
46 "data/boot_table.json". If this value is a relative path,
47 this function will use the code_base_dir_path as the base
48 directory (see definition above).
49 """
50 if file_path is None:
51 file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json')
52
53 if not file_path.startswith("/"):
54 file_path = code_base_dir_path + file_path
55
56 # Pre-process the file by removing blank lines and comment lines.
57 temp = tempfile.NamedTemporaryFile()
58 temp_file_path = temp.name
59
60 cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path
61 gc.cmd_fnc_u(cmd_buf, quiet=1)
62
63 boot_file = open(temp_file_path)
64 boot_table = json.load(boot_file, object_hook=DotDict)
65
66 # If the user is running without an OS_HOST, we remove os starting and
67 # ending state requirements from the boot entries.
68 os_host = BuiltIn().get_variable_value("${OS_HOST}", default="")
69 if os_host == "":
70 for boot in boot_table:
71 state_keys = ['start', 'end']
72 for state_key in state_keys:
73 for sub_state in boot_table[boot][state_key]:
74 if sub_state.startswith("os_"):
75 boot_table[boot][state_key].pop(sub_state, None)
76
Michael Walsh07a01ef2017-02-27 14:20:22 -060077 # For every boot_type we should have a corresponding mfg mode boot type.
78 enhanced_boot_table = DotDict()
79 for key, value in boot_table.iteritems():
80 enhanced_boot_table[key] = value
81 enhanced_boot_table[key + " (mfg)"] = value
82
83 return enhanced_boot_table
Michael Walshac29d062017-02-20 16:13:10 -060084
85###############################################################################
86
87
88###############################################################################
89def create_valid_boot_list(boot_table):
90
91 r"""
92 Return a list of all of the valid boot types (e.g. ['BMC Power On',
93 'BMC Power Off', ....]
94
95 Description of arguments:
96 boot_table A boot table such as is returned by the create_boot_table
97 function.
98 """
99
100 return list(boot_table.keys())
101
102###############################################################################
103
104
105###############################################################################
106def read_boot_lists(dir_path="data/boot_lists/"):
107
108 r"""
109 Read the contents of all the boot lists files found in the given boot lists
110 directory and return dictionary of the lists.
111
112 Boot lists are simply files containing a boot test name on each line.
113 These files are useful for categorizing and organizing boot tests. For
114 example, there may be a "Power_on" list, a "Power_off" list, etc.
115
116 The names of the boot list files will be the keys to the top level
117 dictionary. Each dictionary entry is a list of all the boot tests found
118 in the corresponding file.
119
120 Here is an abbreviated look at the resulting boot_lists dictionary.
121
122 boot_lists:
123 boot_lists[All]:
124 boot_lists[All][0]: BMC Power On
125 boot_lists[All][1]: BMC Power Off
126 ...
127 boot_lists[Code_update]:
128 boot_lists[Code_update][0]: BMC oob hpm
129 boot_lists[Code_update][1]: BMC ib hpm
130 ...
131
132 Description of arguments:
133 dir_path The path to the directory containing the boot list files. If
134 this value is a relative path, this function will use the
135 code_base_dir_path as the base directory (see definition above).
136 """
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
153###############################################################################
154
155
156###############################################################################
157def valid_boot_list(boot_list,
158 valid_boot_types):
159
160 r"""
161 Verify that each entry in boot_list is a supported boot test.
162
163 Description of arguments:
164 boot_list An array (i.e. list) of boot test types
165 (e.g. "BMC Power On").
166 valid_boot_types A list of valid boot types such as that returned by
167 create_valid_boot_list.
168 """
169
170 for boot_name in boot_list:
171 boot_name = boot_name.strip(" ")
172 error_message = gv.svalid_value(boot_name,
173 valid_values=valid_boot_types,
174 var_name="boot_name")
175 if error_message != "":
176 BuiltIn().fail(gp.sprint_error(error_message))
177
178###############################################################################
179
180
181###############################################################################
182class boot_results:
183
184 r"""
185 This class defines a boot_results table.
186 """
187
188 def __init__(self,
189 boot_table,
190 boot_pass=0,
191 boot_fail=0,
192 obj_name='boot_results'):
193
194 r"""
195 Initialize the boot results object.
196
197 Description of arguments:
198 boot_table Boot table object (see definition above). The boot table
199 contains all of the valid boot test types. It can be
200 created with the create_boot_table function.
201 boot_pass An initial boot_pass value. This program may be called
202 as part of a larger test suite. As such there may already
203 have been some successful boot tests that we need to
204 keep track of.
205 boot_fail An initial boot_fail value. This program may be called
206 as part of a larger test suite. As such there may already
207 have been some unsuccessful boot tests that we need to
208 keep track of.
209 obj_name The name of this object.
210 """
211
212 # Store the method parms as class data.
213 self.__obj_name = obj_name
214 self.__initial_boot_pass = boot_pass
215 self.__initial_boot_fail = boot_fail
216
217 # Create boot_results_fields for use in creating boot_results table.
218 boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)])
219 # Create boot_results table.
220 self.__boot_results = tally_sheet('boot type',
221 boot_results_fields,
222 'boot_test_results')
223 self.__boot_results.set_sum_fields(['total', 'pass', 'fail'])
224 self.__boot_results.set_calc_fields(['total=pass+fail'])
225 # Create one row in the result table for each kind of boot test
226 # in the boot_table (i.e. for all supported boot tests).
227 for boot_name in list(boot_table.keys()):
228 self.__boot_results.add_row(boot_name)
229
230 def return_total_pass_fail(self):
231
232 r"""
233 Return the total boot_pass and boot_fail values. This information is
234 comprised of the pass/fail values from the table plus the initial
235 pass/fail values.
236 """
237
238 totals_line = self.__boot_results.calc()
239 return totals_line['pass'] + self.__initial_boot_pass,\
240 totals_line['fail'] + self.__initial_boot_fail
241
242 def update(self,
243 boot_type,
244 boot_status):
245
246 r"""
247 Update our boot_results_table. This includes:
248 - Updating the record for the given boot_type by incrementing the pass
249 or fail field.
250 - Calling the calc method to have the totals calculated.
251
252 Description of arguments:
253 boot_type The type of boot test just done (e.g. "BMC Power On").
254 boot_status The status of the boot just done. This should be equal to
255 either "pass" or "fail" (case-insensitive).
256 """
257
258 self.__boot_results.inc_row_field(boot_type, boot_status.lower())
Michael Walsh8f1ef9e2017-03-02 14:31:24 -0600259 self.__boot_results.calc()
Michael Walshac29d062017-02-20 16:13:10 -0600260
261 def sprint_report(self,
262 header_footer="\n"):
263
264 r"""
265 String-print the formatted boot_resuls_table and return them.
266
267 Description of arguments:
268 header_footer This indicates whether a header and footer are to be
269 included in the report.
270 """
271
272 buffer = ""
273
274 buffer += gp.sprint(header_footer)
275 buffer += self.__boot_results.sprint_report()
276 buffer += gp.sprint(header_footer)
277
278 return buffer
279
280 def print_report(self,
281 header_footer="\n"):
282
283 r"""
284 Print the formatted boot_resuls_table to the console.
285
286 See sprint_report for details.
287 """
288
289 grp.rqprint(self.sprint_report(header_footer))
290
291 def sprint_obj(self):
292
293 r"""
294 sprint the fields of this object. This would normally be for debug
295 purposes only.
296 """
297
298 buffer = ""
299
300 buffer += "class name: " + self.__class__.__name__ + "\n"
301 buffer += gp.sprint_var(self.__obj_name)
302 buffer += self.__boot_results.sprint_obj()
303 buffer += gp.sprint_var(self.__initial_boot_pass)
304 buffer += gp.sprint_var(self.__initial_boot_fail)
305
306 return buffer
307
308 def print_obj(self):
309
310 r"""
311 Print the fields of this object to stdout. This would normally be for
312 debug purposes.
313 """
314
315 grp.rprint(self.sprint_obj())
316
317###############################################################################
Michael Walshb6e3aac2017-09-19 16:57:27 -0500318
319
320###############################################################################
321def create_boot_results_file_path(pgm_name,
322 openbmc_nickname,
323 master_pid):
324
325 r"""
326 Create a file path to be used to store a boot_results object.
327
328 Description of argument(s):
329 pgm_name The name of the program. This will form part of the
330 resulting file name.
331 openbmc_nickname The name of the system. This could be a nickname, a
332 hostname, an IP, etc. This will form part of the
333 resulting file name.
334 master_pid The master process id which will form part of the file
335 name.
336 """
337
Michael Walsh8d7b7382017-09-27 16:00:25 -0500338 USER = os.environ.get("USER", "")
339 dir_path = "/tmp/" + USER + "/"
340 if not os.path.exists(dir_path):
341 os.makedirs(dir_path)
342
Michael Walshb6e3aac2017-09-19 16:57:27 -0500343 file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
Michael Walsh8d7b7382017-09-27 16:00:25 -0500344 return vf.create_file_path(file_name_dict, dir_path=dir_path,
345 file_suffix=":boot_results")
Michael Walshb6e3aac2017-09-19 16:57:27 -0500346
347###############################################################################
348
349
350###############################################################################
351def cleanup_boot_results_file():
352
353 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)
369
370###############################################################################