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 and SessionService data are not required in enumeration. 212 # Example: '/redfish/v1/JsonSchemas/' and sub resources. 213 # '/redfish/v1/SessionService' 214 if ('JsonSchemas' in resource) or ('SessionService' in resource): 215 continue 216 217 self._rest_response_ = \ 218 self._redfish_.get(resource, valid_status_codes=[200, 404, 500]) 219 # Enumeration is done for available resources ignoring the 220 # ones for which response is not obtained. 221 if self._rest_response_.status != 200: 222 if include_dead_resources: 223 try: 224 dead_resources[self._rest_response_.status].append( 225 resource) 226 except KeyError: 227 dead_resources[self._rest_response_.status] = \ 228 [resource] 229 continue 230 231 self.walk_nested_dict(self._rest_response_.dict, url=resource) 232 233 enumerated_resources.update(set(resources_to_be_enumerated)) 234 resources_to_be_enumerated = \ 235 tuple(self.__pending_enumeration - enumerated_resources) 236 237 if return_json: 238 if include_dead_resources: 239 return json.dumps(self.__result, sort_keys=True, 240 indent=4, separators=(',', ': ')), dead_resources 241 else: 242 return json.dumps(self.__result, sort_keys=True, 243 indent=4, separators=(',', ': ')) 244 else: 245 if include_dead_resources: 246 return self.__result, dead_resources 247 else: 248 return self.__result 249 250 def walk_nested_dict(self, data, url=''): 251 r""" 252 Parse through the nested dictionary and get the resource id paths. 253 Description of argument(s): 254 data Nested dictionary data from response message. 255 url Resource for which the response is obtained in data. 256 """ 257 url = url.rstrip('/') 258 259 for key, value in data.items(): 260 261 # Recursion if nested dictionary found. 262 if isinstance(value, dict): 263 self.walk_nested_dict(value) 264 else: 265 # Value contains a list of dictionaries having member data. 266 if 'Members' == key: 267 if isinstance(value, list): 268 for memberDict in value: 269 self.__pending_enumeration.add(memberDict['@odata.id']) 270 if '@odata.id' == key: 271 value = value.rstrip('/') 272 # Data for the given url. 273 if value == url: 274 self.__result[url] = data 275 # Data still needs to be looked up, 276 else: 277 self.__pending_enumeration.add(value) 278 279 def get_key_value_nested_dict(self, data, key): 280 r""" 281 Parse through the nested dictionary and get the searched key value. 282 283 Description of argument(s): 284 data Nested dictionary data from response message. 285 key Search dictionary key element. 286 """ 287 288 for k, v in data.items(): 289 if isinstance(v, dict): 290 self.get_key_value_nested_dict(v, key) 291 292 if k == key: 293 target_list.append(v) 294