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