#!/usr/bin/env python3 r""" This module contains functions having to do with redfish path walking. """ import os import subprocess import json ERROR_RESPONSE = { "404": 'Response Error: status_code: 404 -- Not Found', "500": 'Response Error: status_code: 500 -- Internal Server Error', } # Variable to hold enumerated data. result = {} # Variable to hold the pending list of resources for which enumeration. # is yet to be obtained. pending_enumeration = set() def execute_redfish_cmd(parms, json_type="json"): r""" Run CLI standard redfish tool. Description of variable: parms_string Command to execute from the current SHELL. quiet do not print tool error message if True """ resp = subprocess.run([parms], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) if resp.stderr: print('\n\t\tERROR with %s ' % parms) print('\t\t' + resp.stderr) return resp.stderr elif json_type == "json": json_data = json.loads(resp.stdout) return json_data else: return resp.stdout def enumerate_request(hostname, username, password, url, return_json="json"): r""" Perform a GET enumerate request and return available resource paths. Description of argument(s): url URI resource absolute path (e.g. "/redfish/v1/SessionService/Sessions"). return_json Indicates whether the result should be returned as a json string or as a dictionary. """ parms = 'redfishtool -u ' + username + ' -p ' + password + ' -r ' + \ hostname + ' -S Always raw GET ' pending_enumeration.add(url) # Variable having resources for which enumeration is completed. enumerated_resources = set() resources_to_be_enumerated = (url,) while resources_to_be_enumerated: for resource in resources_to_be_enumerated: # JsonSchemas, SessionService or URLs containing # are not # required in enumeration. # Example: '/redfish/v1/JsonSchemas/' and sub resources. # '/redfish/v1/SessionService' # '/redfish/v1/Managers/bmc#/Oem' if ('JsonSchemas' in resource) or ('SessionService' in resource)\ or ('PostCodes' in resource) or ('Registries' in resource)\ or ('#' in resource): continue response = execute_redfish_cmd(parms + resource) # Enumeration is done for available resources ignoring the # ones for which response is not obtained. if 'Error getting response' in response: continue walk_nested_dict(response, url=resource) enumerated_resources.update(set(resources_to_be_enumerated)) resources_to_be_enumerated = \ tuple(pending_enumeration - enumerated_resources) if return_json == "json": return json.dumps(result, sort_keys=True, indent=4, separators=(',', ': ')) else: return result def walk_nested_dict(data, url=''): r""" Parse through the nested dictionary and get the resource id paths. Description of argument(s): data Nested dictionary data from response message. url Resource for which the response is obtained in data. """ url = url.rstrip('/') for key, value in data.items(): # Recursion if nested dictionary found. if isinstance(value, dict): walk_nested_dict(value) else: # Value contains a list of dictionaries having member data. if 'Members' == key: if isinstance(value, list): for memberDict in value: if isinstance(memberDict, str): pending_enumeration.add(memberDict) else: pending_enumeration.add(memberDict['@odata.id']) if '@odata.id' == key: value = value.rstrip('/') # Data for the given url. if value == url: result[url] = data # Data still needs to be looked up, else: pending_enumeration.add(value) def get_key_value_nested_dict(data, key): r""" Parse through the nested dictionary and get the searched key value. Description of argument(s): data Nested dictionary data from response message. key Search dictionary key element. """ for k, v in data.items(): if isinstance(v, dict): get_key_value_nested_dict(v, key) if k == key: target_list.append(v)