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