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_valid as gv
22import gen_misc as gm
23import gen_cmd as gc
24import var_funcs as vf
25
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
31def create_boot_table(file_path=None):
32    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:
70                for sub_state in list(boot_table[boot][state_key]):
71                    if sub_state.startswith("os_"):
72                        boot_table[boot][state_key].pop(sub_state, None)
73
74    # For every boot_type we should have a corresponding mfg mode boot type.
75    enhanced_boot_table = DotDict()
76    for key, value in boot_table.items():
77        enhanced_boot_table[key] = value
78        enhanced_boot_table[key + " (mfg)"] = value
79
80    return enhanced_boot_table
81
82
83def create_valid_boot_list(boot_table):
84    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
95
96def read_boot_lists(dir_path="data/boot_lists/"):
97    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
142
143def valid_boot_list(boot_list,
144                    valid_boot_types):
145    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
163
164class 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'):
175        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):
212        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):
225        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())
238        self.__boot_results.calc()
239
240    def sprint_report(self,
241                      header_footer="\n"):
242        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"):
260        r"""
261        Print the formatted boot_resuls_table to the console.
262
263        See sprint_report for details.
264        """
265
266        gp.qprint(self.sprint_report(header_footer))
267
268    def sprint_obj(self):
269        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):
285        r"""
286        Print the fields of this object to stdout.  This would normally be for
287        debug purposes.
288        """
289
290        gp.gp_print(self.sprint_obj())
291
292
293def create_boot_results_file_path(pgm_name,
294                                  openbmc_nickname,
295                                  master_pid):
296    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
309    USER = os.environ.get("USER", "")
310    dir_path = "/tmp/" + USER + "/"
311    if not os.path.exists(dir_path):
312        os.makedirs(dir_path)
313
314    file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
315    return vf.create_file_path(file_name_dict, dir_path=dir_path,
316                               file_suffix=":boot_results")
317
318
319def cleanup_boot_results_file():
320    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)
336