1#!/usr/bin/env python 2 3r""" 4BMC redfish utility functions. 5""" 6 7import json 8from robot.libraries.BuiltIn import BuiltIn 9import gen_print as gp 10 11 12class bmc_redfish_utils(object): 13 14 def __init__(self): 15 r""" 16 Initialize the bmc_redfish_utils object. 17 """ 18 # Obtain a reference to the global redfish object. 19 self._redfish_ = BuiltIn().get_library_instance('redfish') 20 21 def get_redfish_session_info(self): 22 r""" 23 Returns redfish sessions info dictionary. 24 25 { 26 'key': 'yLXotJnrh5nDhXj5lLiH' , 27 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0' 28 } 29 """ 30 session_dict = { 31 "key": self._redfish_.get_session_key(), 32 "location": self._redfish_.get_session_location() 33 } 34 return session_dict 35 36 def get_attribute(self, resource_path, attribute, verify=None): 37 r""" 38 Get resource attribute. 39 40 Description of argument(s): 41 resource_path URI resource absolute path (e.g. 42 "/redfish/v1/Systems/1"). 43 attribute Name of the attribute (e.g. 'PowerState'). 44 """ 45 46 resp = self._redfish_.get(resource_path) 47 48 if verify: 49 if resp.dict[attribute] == verify: 50 return resp.dict[attribute] 51 else: 52 raise ValueError("Attribute value is not equal") 53 elif attribute in resp.dict: 54 return resp.dict[attribute] 55 56 return None 57 58 def get_properties(self, resource_path): 59 r""" 60 Returns dictionary of attributes for the resource. 61 62 Description of argument(s): 63 resource_path URI resource absolute path (e.g. 64 /redfish/v1/Systems/1"). 65 """ 66 67 resp = self._redfish_.get(resource_path) 68 return resp.dict 69 70 def get_target_actions(self, resource_path, target_attribute): 71 r""" 72 Returns resource target entry of the searched target attribute. 73 74 Description of argument(s): 75 resource_path URI resource absolute path 76 (e.g. "/redfish/v1/Systems/system"). 77 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset'). 78 79 Example: 80 "Actions": { 81 "#ComputerSystem.Reset": { 82 "ResetType@Redfish.AllowableValues": [ 83 "On", 84 "ForceOff", 85 "GracefulRestart", 86 "GracefulShutdown" 87 ], 88 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset" 89 } 90 } 91 """ 92 93 global target_list 94 target_list = [] 95 96 resp_dict = self.get_attribute(resource_path, "Actions") 97 if resp_dict is None: 98 return None 99 100 # Recursively search the "target" key in the nested dictionary. 101 # Populate the target_list of target entries. 102 self.get_key_value_nested_dict(resp_dict, "target") 103 # Return the matching target URL entry. 104 for target in target_list: 105 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset" 106 if target_attribute in target: 107 return target 108 109 return None 110 111 def get_member_list(self, resource_path): 112 r""" 113 Perform a GET list request and return available members entries. 114 115 Description of argument(s): 116 resource_path URI resource absolute path 117 (e.g. "/redfish/v1/SessionService/Sessions"). 118 119 "Members": [ 120 { 121 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7" 122 } 123 { 124 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H" 125 } 126 ], 127 """ 128 129 member_list = [] 130 resp_list_dict = self.get_attribute(resource_path, "Members") 131 if resp_list_dict is None: 132 return member_list 133 134 for member_id in range(0, len(resp_list_dict)): 135 member_list.append(resp_list_dict[member_id]["@odata.id"]) 136 137 return member_list 138 139 def list_request(self, resource_path): 140 r""" 141 Perform a GET list request and return available resource paths. 142 Description of argument(s): 143 resource_path URI resource absolute path 144 (e.g. "/redfish/v1/SessionService/Sessions"). 145 """ 146 gp.qprint_executing(style=gp.func_line_style_short) 147 # Set quiet variable to keep subordinate get() calls quiet. 148 quiet = 1 149 self.__pending_enumeration = set() 150 self._rest_response_ = \ 151 self._redfish_.get(resource_path, 152 valid_status_codes=[200, 404, 500]) 153 154 # Return empty list. 155 if self._rest_response_.status != 200: 156 return self.__pending_enumeration 157 self.walk_nested_dict(self._rest_response_.dict) 158 if not self.__pending_enumeration: 159 return resource_path 160 for resource in self.__pending_enumeration.copy(): 161 self._rest_response_ = \ 162 self._redfish_.get(resource, 163 valid_status_codes=[200, 404, 500]) 164 165 if self._rest_response_.status != 200: 166 continue 167 self.walk_nested_dict(self._rest_response_.dict) 168 return list(sorted(self.__pending_enumeration)) 169 170 def enumerate_request(self, resource_path, return_json=1, 171 include_dead_resources=False): 172 r""" 173 Perform a GET enumerate request and return available resource paths. 174 175 Description of argument(s): 176 resource_path URI resource absolute path (e.g. 177 "/redfish/v1/SessionService/Sessions"). 178 return_json Indicates whether the result should be 179 returned as a json string or as a 180 dictionary. 181 include_dead_resources Check and return a list of dead/broken URI 182 resources. 183 """ 184 185 gp.qprint_executing(style=gp.func_line_style_short) 186 187 return_json = int(return_json) 188 189 # Set quiet variable to keep subordinate get() calls quiet. 190 quiet = 1 191 192 # Variable to hold enumerated data. 193 self.__result = {} 194 195 # Variable to hold the pending list of resources for which enumeration. 196 # is yet to be obtained. 197 self.__pending_enumeration = set() 198 199 self.__pending_enumeration.add(resource_path) 200 201 # Variable having resources for which enumeration is completed. 202 enumerated_resources = set() 203 204 if include_dead_resources: 205 dead_resources = {} 206 207 resources_to_be_enumerated = (resource_path,) 208 209 while resources_to_be_enumerated: 210 for resource in resources_to_be_enumerated: 211 # JsonSchemas, SessionService or URLs containing # are not 212 # required in enumeration. 213 # Example: '/redfish/v1/JsonSchemas/' and sub resources. 214 # '/redfish/v1/SessionService' 215 # '/redfish/v1/Managers/bmc#/Oem' 216 if ('JsonSchemas' in resource) or ('SessionService' in resource)\ 217 or ('#' in resource): 218 continue 219 220 self._rest_response_ = \ 221 self._redfish_.get(resource, valid_status_codes=[200, 404, 500]) 222 # Enumeration is done for available resources ignoring the 223 # ones for which response is not obtained. 224 if self._rest_response_.status != 200: 225 if include_dead_resources: 226 try: 227 dead_resources[self._rest_response_.status].append( 228 resource) 229 except KeyError: 230 dead_resources[self._rest_response_.status] = \ 231 [resource] 232 continue 233 234 self.walk_nested_dict(self._rest_response_.dict, url=resource) 235 236 enumerated_resources.update(set(resources_to_be_enumerated)) 237 resources_to_be_enumerated = \ 238 tuple(self.__pending_enumeration - enumerated_resources) 239 240 if return_json: 241 if include_dead_resources: 242 return json.dumps(self.__result, sort_keys=True, 243 indent=4, separators=(',', ': ')), dead_resources 244 else: 245 return json.dumps(self.__result, sort_keys=True, 246 indent=4, separators=(',', ': ')) 247 else: 248 if include_dead_resources: 249 return self.__result, dead_resources 250 else: 251 return self.__result 252 253 def walk_nested_dict(self, data, url=''): 254 r""" 255 Parse through the nested dictionary and get the resource id paths. 256 Description of argument(s): 257 data Nested dictionary data from response message. 258 url Resource for which the response is obtained in data. 259 """ 260 url = url.rstrip('/') 261 262 for key, value in data.items(): 263 264 # Recursion if nested dictionary found. 265 if isinstance(value, dict): 266 self.walk_nested_dict(value) 267 else: 268 # Value contains a list of dictionaries having member data. 269 if 'Members' == key: 270 if isinstance(value, list): 271 for memberDict in value: 272 self.__pending_enumeration.add(memberDict['@odata.id']) 273 if '@odata.id' == key: 274 value = value.rstrip('/') 275 # Data for the given url. 276 if value == url: 277 self.__result[url] = data 278 # Data still needs to be looked up, 279 else: 280 self.__pending_enumeration.add(value) 281 282 def get_key_value_nested_dict(self, data, key): 283 r""" 284 Parse through the nested dictionary and get the searched key value. 285 286 Description of argument(s): 287 data Nested dictionary data from response message. 288 key Search dictionary key element. 289 """ 290 291 for k, v in data.items(): 292 if isinstance(v, dict): 293 self.get_key_value_nested_dict(v, key) 294 295 if k == key: 296 target_list.append(v) 297