xref: /openbmc/openbmc-test-automation/lib/boot_data.py (revision 97ecb270b7e6afb058f8353d7bed4be9e727f9b8)
1#!/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
11import glob
12from 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
25import var_funcs as vf
26
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
77    # 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
84
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())
259        self.__boot_results.calc()
260
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###############################################################################
318
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
338    file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
339    return vf.create_file_path(file_name_dict, file_suffix=":boot_results")
340
341###############################################################################
342
343
344###############################################################################
345def cleanup_boot_results_file():
346
347    r"""
348    Delete all boot results files whose corresponding pids are no longer
349    active.
350    """
351
352    # Use create_boot_results_file_path to create a globex to find all of the
353    # existing boot results files.
354    globex = create_boot_results_file_path("*", "*", "*")
355    file_list = sorted(glob.glob(globex))
356    for file_path in file_list:
357        # Use parse_file_path to extract info from the file path.
358        file_dict = vf.parse_file_path(file_path)
359        if gm.pid_active(file_dict['master_pid']):
360            gp.qprint_timen("Preserving " + file_path + ".")
361        else:
362            gc.cmd_fnc("rm -f " + file_path)
363
364###############################################################################
365