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