Created new lib/boot_data.py module.

Includes the following:
 - create_boot_table
 - create_valid_boot_list
 - read_boot_lists
 - valid_boot_list
 - boot_results class.

Change-Id: Id1a0b95a5aad93bc8541963430d67c6423125aab
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/boot_data.py b/lib/boot_data.py
new file mode 100755
index 0000000..c036dc8
--- /dev/null
+++ b/lib/boot_data.py
@@ -0,0 +1,308 @@
+#!/usr/bin/env python
+
+r"""
+This module has functions to support various data structures such as the
+boot_table, valid_boot_list and boot_results_table.
+"""
+
+import os
+import tempfile
+import json
+from tally_sheet import *
+
+from robot.libraries.BuiltIn import BuiltIn
+try:
+    from robot.utils import DotDict
+except ImportError:
+    import collections
+
+import gen_print as gp
+import gen_robot_print as grp
+import gen_valid as gv
+import gen_misc as gm
+import gen_cmd as gc
+
+# The code base directory will be one level up from the directory containing
+# this module.
+code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
+
+
+###############################################################################
+def create_boot_table(file_path=None):
+
+    r"""
+    Read the boot table JSON file, convert it to an object and return it.
+
+    Note that if the user is running without a global OS_HOST robot variable
+    specified, this function will remove all of the "os_" start and end state
+    requirements from the JSON data.
+
+    Description of arguments:
+    file_path  The path to the boot_table file.  If this value is not
+               specified, it will be obtained from the "BOOT_TABLE_PATH"
+               environment variable, if set.  Otherwise, it will default to
+               "data/boot_table.json".  If this value is a relative path,
+               this function will use the code_base_dir_path as the base
+               directory (see definition above).
+    """
+    if file_path is None:
+        file_path = os.environ.get('BOOT_TABLE_PATH', 'data/boot_table.json')
+
+    if not file_path.startswith("/"):
+        file_path = code_base_dir_path + file_path
+
+    # Pre-process the file by removing blank lines and comment lines.
+    temp = tempfile.NamedTemporaryFile()
+    temp_file_path = temp.name
+
+    cmd_buf = "egrep -v '^[ ]*$|^[ ]*#' " + file_path + " > " + temp_file_path
+    gc.cmd_fnc_u(cmd_buf, quiet=1)
+
+    boot_file = open(temp_file_path)
+    boot_table = json.load(boot_file, object_hook=DotDict)
+
+    # If the user is running without an OS_HOST, we remove os starting and
+    # ending state requirements from the boot entries.
+    os_host = BuiltIn().get_variable_value("${OS_HOST}", default="")
+    if os_host == "":
+        for boot in boot_table:
+            state_keys = ['start', 'end']
+            for state_key in state_keys:
+                for sub_state in boot_table[boot][state_key]:
+                    if sub_state.startswith("os_"):
+                        boot_table[boot][state_key].pop(sub_state, None)
+
+    return boot_table
+
+###############################################################################
+
+
+###############################################################################
+def create_valid_boot_list(boot_table):
+
+    r"""
+    Return a list of all of the valid boot types (e.g. ['BMC Power On',
+    'BMC Power Off', ....]
+
+    Description of arguments:
+    boot_table  A boot table such as is returned by the create_boot_table
+    function.
+    """
+
+    return list(boot_table.keys())
+
+###############################################################################
+
+
+###############################################################################
+def read_boot_lists(dir_path="data/boot_lists/"):
+
+    r"""
+    Read the contents of all the boot lists files found in the given boot lists
+    directory and return dictionary of the lists.
+
+    Boot lists are simply files containing a boot test name on each line.
+    These files are useful for categorizing and organizing boot tests.  For
+    example, there may be a "Power_on" list, a "Power_off" list, etc.
+
+    The names of the boot list files will be the keys to the top level
+    dictionary.  Each dictionary entry is a list of all the boot tests found
+    in the corresponding file.
+
+    Here is an abbreviated look at the resulting boot_lists dictionary.
+
+    boot_lists:
+      boot_lists[All]:
+        boot_lists[All][0]:                           BMC Power On
+        boot_lists[All][1]:                           BMC Power Off
+    ...
+      boot_lists[Code_update]:
+        boot_lists[Code_update][0]:                   BMC oob hpm
+        boot_lists[Code_update][1]:                   BMC ib hpm
+    ...
+
+    Description of arguments:
+    dir_path  The path to the directory containing the boot list files.  If
+              this value is a relative path, this function will use the
+              code_base_dir_path as the base directory (see definition above).
+    """
+
+    if not dir_path.startswith("/"):
+        # Dir path is relative.
+        dir_path = code_base_dir_path + dir_path
+
+    # Get a list of all file names in the directory.
+    boot_file_names = os.listdir(dir_path)
+
+    boot_lists = DotDict()
+    for boot_category in boot_file_names:
+        file_path = gm.which(dir_path + boot_category)
+        boot_list = gm.file_to_list(file_path, newlines=0, comments=0, trim=1)
+        boot_lists[boot_category] = boot_list
+
+    return boot_lists
+
+###############################################################################
+
+
+###############################################################################
+def valid_boot_list(boot_list,
+                    valid_boot_types):
+
+    r"""
+    Verify that each entry in boot_list is a supported boot test.
+
+    Description of arguments:
+    boot_list         An array (i.e. list) of boot test types
+                      (e.g. "BMC Power On").
+    valid_boot_types  A list of valid boot types such as that returned by
+                      create_valid_boot_list.
+    """
+
+    for boot_name in boot_list:
+        boot_name = boot_name.strip(" ")
+        error_message = gv.svalid_value(boot_name,
+                                        valid_values=valid_boot_types,
+                                        var_name="boot_name")
+        if error_message != "":
+            BuiltIn().fail(gp.sprint_error(error_message))
+
+###############################################################################
+
+
+###############################################################################
+class boot_results:
+
+    r"""
+    This class defines a boot_results table.
+    """
+
+    def __init__(self,
+                 boot_table,
+                 boot_pass=0,
+                 boot_fail=0,
+                 obj_name='boot_results'):
+
+        r"""
+        Initialize the boot results object.
+
+        Description of arguments:
+        boot_table  Boot table object (see definition above).  The boot table
+                    contains all of the valid boot test types.  It can be
+                    created with the create_boot_table function.
+        boot_pass   An initial boot_pass value.  This program may be called
+                    as part of a larger test suite.  As such there may already
+                    have been some successful boot tests that we need to
+                    keep track of.
+        boot_fail   An initial boot_fail value.  This program may be called
+                    as part of a larger test suite.  As such there may already
+                    have been some unsuccessful boot tests that we need to
+                    keep track of.
+        obj_name    The name of this object.
+        """
+
+        # Store the method parms as class data.
+        self.__obj_name = obj_name
+        self.__initial_boot_pass = boot_pass
+        self.__initial_boot_fail = boot_fail
+
+        # Create boot_results_fields for use in creating boot_results table.
+        boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)])
+        # Create boot_results table.
+        self.__boot_results = tally_sheet('boot type',
+                                          boot_results_fields,
+                                          'boot_test_results')
+        self.__boot_results.set_sum_fields(['total', 'pass', 'fail'])
+        self.__boot_results.set_calc_fields(['total=pass+fail'])
+        # Create one row in the result table for each kind of boot test
+        # in the boot_table (i.e. for all supported boot tests).
+        for boot_name in list(boot_table.keys()):
+            self.__boot_results.add_row(boot_name)
+
+    def return_total_pass_fail(self):
+
+        r"""
+        Return the total boot_pass and boot_fail values.  This information is
+        comprised of the pass/fail values from the table plus the initial
+        pass/fail values.
+        """
+
+        totals_line = self.__boot_results.calc()
+        return totals_line['pass'] + self.__initial_boot_pass,\
+            totals_line['fail'] + self.__initial_boot_fail
+
+    def update(self,
+               boot_type,
+               boot_status):
+
+        r"""
+        Update our boot_results_table.  This includes:
+        - Updating the record for the given boot_type by incrementing the pass
+          or fail field.
+        - Calling the calc method to have the totals calculated.
+
+        Description of arguments:
+        boot_type    The type of boot test just done (e.g. "BMC Power On").
+        boot_status  The status of the boot just done.  This should be equal to
+                     either "pass" or "fail" (case-insensitive).
+        """
+
+        self.__boot_results.inc_row_field(boot_type, boot_status.lower())
+
+    def sprint_report(self,
+                      header_footer="\n"):
+
+        r"""
+        String-print the formatted boot_resuls_table and return them.
+
+        Description of arguments:
+        header_footer  This indicates whether a header and footer are to be
+                       included in the report.
+        """
+
+        buffer = ""
+
+        buffer += gp.sprint(header_footer)
+        buffer += self.__boot_results.sprint_report()
+        buffer += gp.sprint(header_footer)
+
+        return buffer
+
+    def print_report(self,
+                     header_footer="\n"):
+
+        r"""
+        Print the formatted boot_resuls_table to the console.
+
+        See sprint_report for details.
+        """
+
+        grp.rqprint(self.sprint_report(header_footer))
+
+    def sprint_obj(self):
+
+        r"""
+        sprint the fields of this object.  This would normally be for debug
+        purposes only.
+        """
+
+        buffer = ""
+
+        buffer += "class name: " + self.__class__.__name__ + "\n"
+        buffer += gp.sprint_var(self.__obj_name)
+        buffer += self.__boot_results.sprint_obj()
+        buffer += gp.sprint_var(self.__initial_boot_pass)
+        buffer += gp.sprint_var(self.__initial_boot_fail)
+
+        return buffer
+
+    def print_obj(self):
+
+        r"""
+        Print the fields of this object to stdout.  This would normally be for
+        debug purposes.
+        """
+
+        grp.rprint(self.sprint_obj())
+
+###############################################################################