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