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
11from tally_sheet import *
12
13from robot.libraries.BuiltIn import BuiltIn
14try:
15    from robot.utils import DotDict
16except ImportError:
17    import collections
18
19import gen_print as gp
20import gen_robot_print as grp
21import gen_valid as gv
22import gen_misc as gm
23import gen_cmd as gc
24
25# The code base directory will be one level up from the directory containing
26# this module.
27code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
28
29
30###############################################################################
31def create_boot_table(file_path=None):
32
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.iteritems():
78        enhanced_boot_table[key] = value
79        enhanced_boot_table[key + " (mfg)"] = value
80
81    return enhanced_boot_table
82
83###############################################################################
84
85
86###############################################################################
87def create_valid_boot_list(boot_table):
88
89    r"""
90    Return a list of all of the valid boot types (e.g. ['BMC Power On',
91    'BMC Power Off', ....]
92
93    Description of arguments:
94    boot_table  A boot table such as is returned by the create_boot_table
95    function.
96    """
97
98    return list(boot_table.keys())
99
100###############################################################################
101
102
103###############################################################################
104def read_boot_lists(dir_path="data/boot_lists/"):
105
106    r"""
107    Read the contents of all the boot lists files found in the given boot lists
108    directory and return dictionary of the lists.
109
110    Boot lists are simply files containing a boot test name on each line.
111    These files are useful for categorizing and organizing boot tests.  For
112    example, there may be a "Power_on" list, a "Power_off" list, etc.
113
114    The names of the boot list files will be the keys to the top level
115    dictionary.  Each dictionary entry is a list of all the boot tests found
116    in the corresponding file.
117
118    Here is an abbreviated look at the resulting boot_lists dictionary.
119
120    boot_lists:
121      boot_lists[All]:
122        boot_lists[All][0]:                           BMC Power On
123        boot_lists[All][1]:                           BMC Power Off
124    ...
125      boot_lists[Code_update]:
126        boot_lists[Code_update][0]:                   BMC oob hpm
127        boot_lists[Code_update][1]:                   BMC ib hpm
128    ...
129
130    Description of arguments:
131    dir_path  The path to the directory containing the boot list files.  If
132              this value is a relative path, this function will use the
133              code_base_dir_path as the base directory (see definition above).
134    """
135
136    if not dir_path.startswith("/"):
137        # Dir path is relative.
138        dir_path = code_base_dir_path + dir_path
139
140    # Get a list of all file names in the directory.
141    boot_file_names = os.listdir(dir_path)
142
143    boot_lists = DotDict()
144    for boot_category in boot_file_names:
145        file_path = gm.which(dir_path + boot_category)
146        boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1)
147        boot_lists[boot_category] = boot_list
148
149    return boot_lists
150
151###############################################################################
152
153
154###############################################################################
155def valid_boot_list(boot_list,
156                    valid_boot_types):
157
158    r"""
159    Verify that each entry in boot_list is a supported boot test.
160
161    Description of arguments:
162    boot_list         An array (i.e. list) of boot test types
163                      (e.g. "BMC Power On").
164    valid_boot_types  A list of valid boot types such as that returned by
165                      create_valid_boot_list.
166    """
167
168    for boot_name in boot_list:
169        boot_name = boot_name.strip(" ")
170        error_message = gv.svalid_value(boot_name,
171                                        valid_values=valid_boot_types,
172                                        var_name="boot_name")
173        if error_message != "":
174            BuiltIn().fail(gp.sprint_error(error_message))
175
176###############################################################################
177
178
179###############################################################################
180class boot_results:
181
182    r"""
183    This class defines a boot_results table.
184    """
185
186    def __init__(self,
187                 boot_table,
188                 boot_pass=0,
189                 boot_fail=0,
190                 obj_name='boot_results'):
191
192        r"""
193        Initialize the boot results object.
194
195        Description of arguments:
196        boot_table  Boot table object (see definition above).  The boot table
197                    contains all of the valid boot test types.  It can be
198                    created with the create_boot_table function.
199        boot_pass   An initial boot_pass value.  This program may be called
200                    as part of a larger test suite.  As such there may already
201                    have been some successful boot tests that we need to
202                    keep track of.
203        boot_fail   An initial boot_fail value.  This program may be called
204                    as part of a larger test suite.  As such there may already
205                    have been some unsuccessful boot tests that we need to
206                    keep track of.
207        obj_name    The name of this object.
208        """
209
210        # Store the method parms as class data.
211        self.__obj_name = obj_name
212        self.__initial_boot_pass = boot_pass
213        self.__initial_boot_fail = boot_fail
214
215        # Create boot_results_fields for use in creating boot_results table.
216        boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)])
217        # Create boot_results table.
218        self.__boot_results = tally_sheet('boot type',
219                                          boot_results_fields,
220                                          'boot_test_results')
221        self.__boot_results.set_sum_fields(['total', 'pass', 'fail'])
222        self.__boot_results.set_calc_fields(['total=pass+fail'])
223        # Create one row in the result table for each kind of boot test
224        # in the boot_table (i.e. for all supported boot tests).
225        for boot_name in list(boot_table.keys()):
226            self.__boot_results.add_row(boot_name)
227
228    def return_total_pass_fail(self):
229
230        r"""
231        Return the total boot_pass and boot_fail values.  This information is
232        comprised of the pass/fail values from the table plus the initial
233        pass/fail values.
234        """
235
236        totals_line = self.__boot_results.calc()
237        return totals_line['pass'] + self.__initial_boot_pass,\
238            totals_line['fail'] + self.__initial_boot_fail
239
240    def update(self,
241               boot_type,
242               boot_status):
243
244        r"""
245        Update our boot_results_table.  This includes:
246        - Updating the record for the given boot_type by incrementing the pass
247          or fail field.
248        - Calling the calc method to have the totals calculated.
249
250        Description of arguments:
251        boot_type    The type of boot test just done (e.g. "BMC Power On").
252        boot_status  The status of the boot just done.  This should be equal to
253                     either "pass" or "fail" (case-insensitive).
254        """
255
256        self.__boot_results.inc_row_field(boot_type, boot_status.lower())
257        self.__boot_results.calc()
258
259    def sprint_report(self,
260                      header_footer="\n"):
261
262        r"""
263        String-print the formatted boot_resuls_table and return them.
264
265        Description of arguments:
266        header_footer  This indicates whether a header and footer are to be
267                       included in the report.
268        """
269
270        buffer = ""
271
272        buffer += gp.sprint(header_footer)
273        buffer += self.__boot_results.sprint_report()
274        buffer += gp.sprint(header_footer)
275
276        return buffer
277
278    def print_report(self,
279                     header_footer="\n"):
280
281        r"""
282        Print the formatted boot_resuls_table to the console.
283
284        See sprint_report for details.
285        """
286
287        grp.rqprint(self.sprint_report(header_footer))
288
289    def sprint_obj(self):
290
291        r"""
292        sprint the fields of this object.  This would normally be for debug
293        purposes only.
294        """
295
296        buffer = ""
297
298        buffer += "class name: " + self.__class__.__name__ + "\n"
299        buffer += gp.sprint_var(self.__obj_name)
300        buffer += self.__boot_results.sprint_obj()
301        buffer += gp.sprint_var(self.__initial_boot_pass)
302        buffer += gp.sprint_var(self.__initial_boot_fail)
303
304        return buffer
305
306    def print_obj(self):
307
308        r"""
309        Print the fields of this object to stdout.  This would normally be for
310        debug purposes.
311        """
312
313        grp.rprint(self.sprint_obj())
314
315###############################################################################
316