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