xref: /openbmc/openbmc-test-automation/ffdc/plugins/redfish.py (revision a64fa5fa6ba06a50b5ee9f193279a7bc8e5b20e6)
1#!/usr/bin/env python3
2
3r"""
4This module contains functions having to do with redfish path walking.
5"""
6
7import json
8import subprocess
9
10ERROR_RESPONSE = {
11    "404": "Response Error: status_code: 404 -- Not Found",
12    "500": "Response Error: status_code: 500 -- Internal Server Error",
13}
14
15# Variable to hold enumerated data.
16result = {}
17
18# Variable to hold the pending list of resources for which enumeration.
19# is yet to be obtained.
20pending_enumeration = set()
21
22
23def execute_redfish_cmd(parms, json_type="json"):
24    r"""
25    Execute a Redfish command and return the output in the specified JSON
26    format.
27
28    This function executes a provided Redfish command using the redfishtool
29    and returns the output in the specified JSON format. The function takes
30    the parms argument, which is expected to be a qualified string containing
31    the redfishtool command line URI and required parameters.
32
33    The function also accepts an optional json_type parameter, which specifies
34    the desired JSON format for the output (either "json" or "yaml").
35
36    The function returns the output of the executed command as a string in the
37    specified JSON format.
38
39    Parameters:
40        parms (str):               A qualified Redfish command line string.
41        json_type (str, optional): The desired JSON format for the output.
42                                   Defaults to "json".
43
44    Returns:
45        str: The output of the executed command as a string in the specified
46             JSON format.
47    """
48    resp = subprocess.run(
49        [parms],
50        stdout=subprocess.PIPE,
51        stderr=subprocess.PIPE,
52        shell=True,
53        universal_newlines=True,
54    )
55
56    if resp.stderr:
57        print("\n\t\tERROR with %s " % parms)
58        print("\t\t" + resp.stderr)
59        return resp.stderr
60    elif json_type == "json":
61        json_data = json.loads(resp.stdout)
62        return json_data
63    else:
64        return resp.stdout
65
66
67def enumerate_request(hostname, username, password, url, return_json="json"):
68    r"""
69    Perform a GET enumerate request and return available resource paths.
70
71    This function performs a GET enumerate request on the specified URI
72    resource and returns the available resource paths.
73
74    The function takes the remote host details (hostname, username, password)
75    and the URI resource absolute path as arguments. The function also accepts
76    an optional return_json parameter, which specifies whether the result
77    should be returned as a JSON string or as a dictionary.
78
79    The function returns the available resource paths as a list of strings.
80
81    Parameters:
82        hostname (str):              Name or IP address of the remote host.
83        username (str):              User on the remote host with access to
84                                     files.
85        password (str):              Password for the user on the remote host.
86        url (str):                   URI resource absolute path e.g.
87                                     /redfish/v1/SessionService/Sessions
88        return_json (str, optional): Indicates whether the result should be
89                                     returned as a JSON string or as a
90                                     dictionary. Defaults to "json".
91
92    Returns:
93        list: A list of available resource paths as strings.
94    """
95    parms = (
96        "redfishtool -u "
97        + username
98        + " -p "
99        + password
100        + " -r "
101        + hostname
102        + " -S Always raw GET "
103    )
104
105    pending_enumeration.add(url)
106
107    # Variable having resources for which enumeration is completed.
108    enumerated_resources = set()
109
110    resources_to_be_enumerated = (url,)
111
112    while resources_to_be_enumerated:
113        for resource in resources_to_be_enumerated:
114            # JsonSchemas, SessionService or URLs containing # are not
115            # required in enumeration.
116            # Example: '/redfish/v1/JsonSchemas/' and sub resources.
117            #          '/redfish/v1/SessionService'
118            #          '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
119            if (
120                ("JsonSchemas" in resource)
121                or ("SessionService" in resource)
122                or ("PostCodes" in resource)
123                or ("Registries" in resource)
124                or ("#" in resource)
125            ):
126                continue
127
128            response = execute_redfish_cmd(parms + resource)
129            # Enumeration is done for available resources ignoring the
130            # ones for which response is not obtained.
131            if "Error getting response" in response:
132                continue
133
134            walk_nested_dict(response, url=resource)
135
136        enumerated_resources.update(set(resources_to_be_enumerated))
137        resources_to_be_enumerated = tuple(
138            pending_enumeration - enumerated_resources
139        )
140
141    if return_json == "json":
142        return json.dumps(
143            result, sort_keys=True, indent=4, separators=(",", ": ")
144        )
145    else:
146        return result
147
148
149def walk_nested_dict(data, url=""):
150    r"""
151    Parse through the nested dictionary and extract resource ID paths.
152
153    This function traverses a nested dictionary and extracts resource ID paths.
154    The function takes the data argument, which is expected to be a nested
155    dictionary containing resource information.
156
157    The function also accepts an optional url argument, which specifies the
158    resource for which the response is obtained in the data dictionary.
159
160    The function returns a list of resource ID paths as strings.
161
162    Parameters:
163        data (dict):         A nested dictionary containing resource
164                             information.
165        url (str, optional): The resource for which the response is obtained
166                             in the data dictionary. Defaults to an empty
167                             string.
168
169    Returns:
170        list: A list of resource ID paths as strings.
171    """
172    url = url.rstrip("/")
173
174    for key, value in data.items():
175        # Recursion if nested dictionary found.
176        if isinstance(value, dict):
177            walk_nested_dict(value)
178        else:
179            # Value contains a list of dictionaries having member data.
180            if "Members" == key:
181                if isinstance(value, list):
182                    for memberDict in value:
183                        if isinstance(memberDict, str):
184                            pending_enumeration.add(memberDict)
185                        else:
186                            pending_enumeration.add(memberDict["@odata.id"])
187
188            if "@odata.id" == key:
189                value = value.rstrip("/")
190                # Data for the given url.
191                if value == url:
192                    result[url] = data
193                # Data still needs to be looked up,
194                else:
195                    pending_enumeration.add(value)
196
197
198def get_key_value_nested_dict(data, key):
199    r"""
200    Parse through the nested dictionary and retrieve the value associated with
201    the searched key.
202
203    This function traverses a nested dictionary and retrieves the value
204    associated with the searched key. The function takes the data argument,
205    which is expected to be a nested dictionary containing resource
206    information.
207
208    The function also accepts a key argument, which specifies the key to
209    search for in the nested dictionary.
210
211    The function returns the value associated with the searched key as a
212    string.
213
214    Parameters:
215        data (dict): A nested dictionary containing resource information.
216        key (str):   The key to search for in the nested dictionary.
217
218    Returns:
219        str: The value associated with the searched key as a string.
220    """
221    for k, v in data.items():
222        if isinstance(v, dict):
223            get_key_value_nested_dict(v, key)
224