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
32def create_boot_table(file_path=None):
33    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:
71                for sub_state in boot_table[boot][state_key]:
72                    if sub_state.startswith("os_"):
73                        boot_table[boot][state_key].pop(sub_state, None)
74
75    # For every boot_type we should have a corresponding mfg mode boot type.
76    enhanced_boot_table = DotDict()
77    for key, value in boot_table.items():
78        enhanced_boot_table[key] = value
79        enhanced_boot_table[key + " (mfg)"] = value
80
81    return enhanced_boot_table
82
83
84def create_valid_boot_list(boot_table):
85    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
96
97def read_boot_lists(dir_path="data/boot_lists/"):
98    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
143
144def valid_boot_list(boot_list,
145                    valid_boot_types):
146    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
164
165class 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'):
176        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):
213        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):
226        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())
239        self.__boot_results.calc()
240
241    def sprint_report(self,
242                      header_footer="\n"):
243        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"):
261        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):
270        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):
286        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
293
294def create_boot_results_file_path(pgm_name,
295                                  openbmc_nickname,
296                                  master_pid):
297    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
310    USER = os.environ.get("USER", "")
311    dir_path = "/tmp/" + USER + "/"
312    if not os.path.exists(dir_path):
313        os.makedirs(dir_path)
314
315    file_name_dict = vf.create_var_dict(pgm_name, openbmc_nickname, master_pid)
316    return vf.create_file_path(file_name_dict, dir_path=dir_path,
317                               file_suffix=":boot_results")
318
319
320def cleanup_boot_results_file():
321    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)
337