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