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