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