xref: /openbmc/openbmc-test-automation/ffdc/plugins/redfish.py (revision 62b0c90e54c41276e2d41aff217f93c91d9df4a7)
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    Run CLI standard redfish tool.
26
27    Description of variable:
28    parms_string         Command to execute from the current SHELL.
29    quiet                do not print tool error message if True
30    """
31    resp = subprocess.run(
32        [parms],
33        stdout=subprocess.PIPE,
34        stderr=subprocess.PIPE,
35        shell=True,
36        universal_newlines=True,
37    )
38
39    if resp.stderr:
40        print("\n\t\tERROR with %s " % parms)
41        print("\t\t" + resp.stderr)
42        return resp.stderr
43    elif json_type == "json":
44        json_data = json.loads(resp.stdout)
45        return json_data
46    else:
47        return resp.stdout
48
49
50def enumerate_request(hostname, username, password, url, return_json="json"):
51    r"""
52    Perform a GET enumerate request and return available resource paths.
53
54    Description of argument(s):
55    url               URI resource absolute path (e.g.
56                      "/redfish/v1/SessionService/Sessions").
57    return_json       Indicates whether the result should be
58                      returned as a json string or as a
59                      dictionary.
60    """
61    parms = (
62        "redfishtool -u "
63        + username
64        + " -p "
65        + password
66        + " -r "
67        + hostname
68        + " -S Always raw GET "
69    )
70
71    pending_enumeration.add(url)
72
73    # Variable having resources for which enumeration is completed.
74    enumerated_resources = set()
75
76    resources_to_be_enumerated = (url,)
77
78    while resources_to_be_enumerated:
79        for resource in resources_to_be_enumerated:
80            # JsonSchemas, SessionService or URLs containing # are not
81            # required in enumeration.
82            # Example: '/redfish/v1/JsonSchemas/' and sub resources.
83            #          '/redfish/v1/SessionService'
84            #          '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
85            if (
86                ("JsonSchemas" in resource)
87                or ("SessionService" in resource)
88                or ("PostCodes" in resource)
89                or ("Registries" in resource)
90                or ("#" in resource)
91            ):
92                continue
93
94            response = execute_redfish_cmd(parms + resource)
95            # Enumeration is done for available resources ignoring the
96            # ones for which response is not obtained.
97            if "Error getting response" in response:
98                continue
99
100            walk_nested_dict(response, url=resource)
101
102        enumerated_resources.update(set(resources_to_be_enumerated))
103        resources_to_be_enumerated = tuple(
104            pending_enumeration - enumerated_resources
105        )
106
107    if return_json == "json":
108        return json.dumps(
109            result, sort_keys=True, indent=4, separators=(",", ": ")
110        )
111    else:
112        return result
113
114
115def walk_nested_dict(data, url=""):
116    r"""
117    Parse through the nested dictionary and get the resource id paths.
118
119    Description of argument(s):
120    data    Nested dictionary data from response message.
121    url     Resource for which the response is obtained in data.
122    """
123    url = url.rstrip("/")
124
125    for key, value in data.items():
126        # Recursion if nested dictionary found.
127        if isinstance(value, dict):
128            walk_nested_dict(value)
129        else:
130            # Value contains a list of dictionaries having member data.
131            if "Members" == key:
132                if isinstance(value, list):
133                    for memberDict in value:
134                        if isinstance(memberDict, str):
135                            pending_enumeration.add(memberDict)
136                        else:
137                            pending_enumeration.add(memberDict["@odata.id"])
138
139            if "@odata.id" == key:
140                value = value.rstrip("/")
141                # Data for the given url.
142                if value == url:
143                    result[url] = data
144                # Data still needs to be looked up,
145                else:
146                    pending_enumeration.add(value)
147
148
149def get_key_value_nested_dict(data, key):
150    r"""
151    Parse through the nested dictionary and get the searched key value.
152
153    Description of argument(s):
154    data    Nested dictionary data from response message.
155    key     Search dictionary key element.
156    """
157
158    for k, v in data.items():
159        if isinstance(v, dict):
160            get_key_value_nested_dict(v, key)
161