1#!/usr/bin/env python3
2
3r"""
4This module is the python counterpart to openbmc_ffdc.robot..
5"""
6
7import os
8
9import gen_print as gp
10import gen_robot_keyword as grk
11import gen_valid as gv
12import state as st
13from robot.libraries.BuiltIn import BuiltIn
14
15redfish_support_trans_state = int(
16    os.environ.get("REDFISH_SUPPORT_TRANS_STATE", 0)
17) or int(
18    BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0)
19)
20
21
22def ffdc(
23    ffdc_dir_path=None,
24    ffdc_prefix=None,
25    ffdc_function_list="",
26    comm_check=True,
27):
28    r"""
29    Gather First Failure Data Capture (FFDC).
30
31    This includes:
32    - Set global FFDC_TIME.
33    - Create FFDC work space directory.
34    - Write test info details.
35    - Call BMC methods to write/collect FFDC data.
36
37    Description of arguments:
38    ffdc_dir_path                   The dir path where FFDC data should be put.
39    ffdc_prefix                     The prefix to be given to each FFDC file name generated.
40    ffdc_function_list              A colon-delimited list of all the types of FFDC data you wish to have
41                                    collected.  A blank value means that all possible kinds of FFDC are to be
42                                    collected.  See FFDC_METHOD_CALL object in lib/openbmc_ffdc_list.py for
43                                    possible choices.
44    comm_check                      Do a communications check prior to collecting FFDC.  If commincation to
45                                    the BMC can't be established, abort the FFDC collection.
46    """
47
48    ffdc_file_list = []
49
50    # Check if Ping and SSH connection is alive
51    OPENBMC_HOST = BuiltIn().get_variable_value("${OPENBMC_HOST}")
52
53    if comm_check:
54        if not redfish_support_trans_state:
55            interface = "rest"
56        else:
57            interface = "redfish"
58
59        state = st.get_state(req_states=["ping", "uptime", interface])
60        gp.qprint_var(state)
61        if not int(state["ping"]):
62            gp.print_error(
63                "BMC is not ping-able.  Terminating FFDC collection.\n"
64            )
65            return ffdc_file_list
66
67        if not int(state[interface]):
68            gp.print_error("%s commands to the BMC are failing." % interface)
69
70        if state["uptime"] == "":
71            gp.print_error("BMC is not communicating via ssh.\n")
72
73        # If SSH and Redfish connection doesn't works, abort.
74        if not int(state[interface]) and state["uptime"] == "":
75            gp.print_error(
76                "BMC is not communicating via ssh or Redfish.  Terminating"
77                " FFDC"
78                + " collection.\n"
79            )
80            return ffdc_file_list
81
82    gp.qprint_timen("Collecting FFDC.")
83
84    # Get default values for arguments.
85    ffdc_dir_path, ffdc_prefix = set_ffdc_defaults(ffdc_dir_path, ffdc_prefix)
86    gp.qprint_var(ffdc_dir_path)
87    gp.qprint_var(ffdc_prefix)
88
89    # LOG_PREFIX is used by subordinate functions.
90    LOG_PREFIX = ffdc_dir_path + ffdc_prefix
91    BuiltIn().set_global_variable("${LOG_PREFIX}", LOG_PREFIX)
92
93    cmd_buf = ["Create Directory", ffdc_dir_path]
94    gp.qprint_issuing(cmd_buf)
95    status, output = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
96    if status != "PASS":
97        error_message = gp.sprint_error_report(
98            "Create Directory failed"
99            + " with the following"
100            + " error:\n"
101            + output
102        )
103        BuiltIn().fail(error_message)
104
105    # FFDC_FILE_PATH is used by Header Message.
106    FFDC_FILE_PATH = ffdc_dir_path + ffdc_prefix + "BMC_general.txt"
107    BuiltIn().set_global_variable("${FFDC_FILE_PATH}", FFDC_FILE_PATH)
108
109    status, ffdc_file_list = grk.run_key_u("Header Message")
110    status, ffdc_file_sub_list = grk.run_key_u(
111        "Call FFDC Methods  ffdc_function_list=" + ffdc_function_list
112    )
113
114    # Combine lists, remove duplicates and sort.
115    ffdc_file_list = sorted(set(ffdc_file_list + ffdc_file_sub_list))
116
117    gp.qprint_timen("Finished collecting FFDC.")
118
119    return ffdc_file_list
120
121
122def set_ffdc_defaults(ffdc_dir_path=None, ffdc_prefix=None):
123    r"""
124    Set a default value for ffdc_dir_path and ffdc_prefix if they don't
125    already have values.  Return both values.
126
127    Description of arguments:
128    ffdc_dir_path  The dir path where FFDC data should be put.
129    ffdc_prefix    The prefix to be given to each FFDC file name generated.
130
131    NOTE: If global variable ffdc_dir_path_style is set to ${1}, this function
132    will create default values in a newer way.  Otherwise, its behavior
133    will remain unchanged.
134    """
135
136    # Note: Several subordinate functions like 'Get Test Dir and Name' and
137    # 'Header Message' expect global variable FFDC_TIME to be set.
138    cmd_buf = ["Get Current Time Stamp"]
139    gp.dprint_issuing(cmd_buf)
140    FFDC_TIME = BuiltIn().run_keyword(*cmd_buf)
141    BuiltIn().set_global_variable("${FFDC_TIME}", FFDC_TIME)
142
143    ffdc_dir_path_style = BuiltIn().get_variable_value(
144        "${ffdc_dir_path_style}"
145    )
146
147    if ffdc_dir_path is None:
148        if ffdc_dir_path_style:
149            try:
150                ffdc_dir_path = os.environ["FFDC_DIR_PATH"]
151            except KeyError:
152                ffdc_dir_path = (
153                    os.path.dirname(
154                        BuiltIn().get_variable_value("${LOG_FILE}")
155                    )
156                    + "/"
157                )
158        else:
159            FFDC_LOG_PATH = os.getcwd() + "/logs/"
160            if FFDC_LOG_PATH is None:
161                FFDC_LOG_PATH = ""
162            if FFDC_LOG_PATH == "":
163                FFDC_LOG_PATH = (
164                    os.path.dirname(
165                        BuiltIn().get_variable_value("${LOG_FILE}")
166                    )
167                    + "/"
168                )
169            error_message = gv.valid_value(
170                FFDC_LOG_PATH, var_name="FFDC_LOG_PATH"
171            )
172            if error_message != "":
173                error_message = gp.sprint_error_report(error_message)
174                BuiltIn().fail(error_message)
175            FFDC_LOG_PATH = os.path.normpath(FFDC_LOG_PATH) + os.sep
176
177            cmd_buf = ["Get Test Dir and Name"]
178            gp.print_issuing(cmd_buf)
179            suitename, testname = BuiltIn().run_keyword(*cmd_buf)
180
181            ffdc_dir_path = FFDC_LOG_PATH + suitename + "/" + testname + "/"
182
183    # Add trailing slash.
184    ffdc_dir_path = os.path.normpath(ffdc_dir_path) + os.sep
185
186    if ffdc_prefix is None:
187        FFDC_TIME = BuiltIn().get_variable_value("${FFDC_TIME}")
188        if ffdc_prefix is None:
189            if ffdc_dir_path_style:
190                OPENBMC_HOST = BuiltIn().get_variable_value("${OPENBMC_HOST}")
191                OPENBMC_NICKNAME = BuiltIn().get_variable_value(
192                    "${OPENBMC_NICKNAME}", default=OPENBMC_HOST
193                )
194                ffdc_prefix = (
195                    OPENBMC_NICKNAME
196                    + "."
197                    + FFDC_TIME[2:8]
198                    + "."
199                    + FFDC_TIME[8:14]
200                    + "."
201                )
202            else:
203                ffdc_prefix = FFDC_TIME + "_"
204
205    BuiltIn().set_global_variable("${FFDC_DIR_PATH}", ffdc_dir_path)
206    BuiltIn().set_global_variable("${FFDC_PREFIX}", ffdc_prefix)
207
208    return ffdc_dir_path, ffdc_prefix
209