#!/usr/bin/env python3 r""" This module contains functions having to do with redfish path walking. """ import json import os import subprocess 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)