1#!/usr/bin/env python 2 3r""" 4See class prolog below for details. 5""" 6 7import sys 8import re 9import json 10from redfish_plus import redfish_plus 11from robot.libraries.BuiltIn import BuiltIn 12 13import func_args as fa 14import gen_print as gp 15 16 17class bmc_redfish(redfish_plus): 18 r""" 19 bmc_redfish is a child class of redfish_plus that is designed to provide 20 benefits specifically for using redfish to communicate with an OpenBMC. 21 22 See the prologs of the methods below for details. 23 """ 24 25 def __init__(self, *args, **kwargs): 26 r""" 27 Do BMC-related redfish initialization. 28 29 Presently, older versions of BMC code may not support redfish 30 requests. This can lead to unsightly error text being printed out for 31 programs that may use lib/bmc_redfish_resource.robot even though they 32 don't necessarily intend to make redfish requests. 33 34 This class method will make an attempt to tolerate this situation. At 35 some future point, when all BMCs can be expected to support redfish, 36 this class method may be considered for deletion. If it is deleted, 37 the self.__inited__ test code in the login() class method below should 38 likewise be deleted. 39 """ 40 self.__inited__ = False 41 try: 42 super(bmc_redfish, self).__init__(*args, **kwargs) 43 self.__inited__ = True 44 except ValueError as get_exception: 45 except_type, except_value, except_traceback = sys.exc_info() 46 regex = r"The HTTP status code was not valid:[\r\n]+status:[ ]+502" 47 result = re.match(regex, str(except_value), flags=re.MULTILINE) 48 if not result: 49 gp.lprint_var(except_type) 50 gp.lprint_varx("except_value", str(except_value)) 51 raise(get_exception) 52 BuiltIn().set_global_variable("${REDFISH_SUPPORTED}", self.__inited__) 53 BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", True) 54 55 def login(self, *args, **kwargs): 56 r""" 57 Assign BMC default values for username, password and auth arguments 58 and call parent class login method. 59 60 Description of argument(s): 61 args See parent class method prolog for details. 62 kwargs See parent class method prolog for details. 63 """ 64 65 if not self.__inited__: 66 message = "bmc_redfish.__init__() was never successfully run. It " 67 message += "is likely that the target BMC firmware code level " 68 message += "does not support redfish.\n" 69 raise ValueError(message) 70 # Assign default values for username, password, auth where necessary. 71 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") 72 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") 73 username, args, kwargs = fa.pop_arg(openbmc_username, *args, **kwargs) 74 password, args, kwargs = fa.pop_arg(openbmc_password, *args, **kwargs) 75 auth, args, kwargs = fa.pop_arg('session', *args, **kwargs) 76 77 super(bmc_redfish, self).login(username, password, auth, 78 *args, **kwargs) 79 80 def get_properties(self, *args, **kwargs): 81 r""" 82 Return dictionary of attributes for a given path. 83 84 The difference between calling this function and calling get() 85 directly is that this function returns ONLY the dictionary portion of 86 the response object. 87 88 Example robot code: 89 90 ${properties}= Get Properties /redfish/v1/Systems/system/ 91 Rprint Vars properties 92 93 Output: 94 95 properties: 96 [PowerState]: Off 97 [Processors]: 98 [@odata.id]: /redfish/v1/Systems/system/Processors 99 [SerialNumber]: 1234567 100 ... 101 102 Description of argument(s): 103 args See parent class get() prolog for details. 104 kwargs See parent class get() prolog for details. 105 """ 106 107 resp = self.get(*args, **kwargs) 108 return resp.dict if hasattr(resp, 'dict') else {} 109 110 def get_attribute(self, path, attribute, default=None, *args, **kwargs): 111 r""" 112 Get and return the named attribute from the properties for a given 113 path. 114 115 This method has the following advantages over calling get_properties 116 directly: 117 - The caller can specify a default value to be returned if the 118 attribute does not exist. 119 120 Example robot code: 121 122 ${attribute}= Get Attribute /redfish/v1/AccountService 123 ... MaxPasswordLength default=600 124 Rprint Vars attribute 125 126 Output: 127 128 attribute: 31 129 130 Description of argument(s): 131 path The path (e.g. 132 "/redfish/v1/AccountService"). 133 attribute The name of the attribute to be retrieved 134 (e.g. "MaxPasswordLength"). 135 default The default value to be returned if the 136 attribute does not exist (e.g. ""). 137 args See parent class get() prolog for details. 138 kwargs See parent class get() prolog for details. 139 """ 140 141 return self.get_properties(path, *args, **kwargs).get(attribute, 142 default) 143 144 def get_session_info(self): 145 r""" 146 Get and return session info as a tuple consisting of session_key and 147 session_location. 148 """ 149 150 return self.get_session_key(), self.get_session_location() 151 152 def enumerate(self, resource_path, return_json=1, include_dead_resources=False): 153 r""" 154 Perform a GET enumerate request and return available resource paths. 155 156 Description of argument(s): 157 resource_path URI resource absolute path (e.g. "/redfish/v1/SessionService/Sessions"). 158 return_json Indicates whether the result should be returned as a json string or as a 159 dictionary. 160 include_dead_resources Check and return a list of dead/broken URI resources. 161 """ 162 163 gp.qprint_executing(style=gp.func_line_style_short) 164 # Set quiet variable to keep subordinate get() calls quiet. 165 quiet = 1 166 167 self.__result = {} 168 # Variable to hold the pending list of resources for which enumeration is yet to be obtained. 169 self.__pending_enumeration = set() 170 self.__pending_enumeration.add(resource_path) 171 172 # Variable having resources for which enumeration is completed. 173 enumerated_resources = set() 174 dead_resources = {} 175 resources_to_be_enumerated = (resource_path,) 176 while resources_to_be_enumerated: 177 for resource in resources_to_be_enumerated: 178 # JsonSchemas, SessionService or URLs containing # are not required in enumeration. 179 # Example: '/redfish/v1/JsonSchemas/' and sub resources. 180 # '/redfish/v1/SessionService' 181 # '/redfish/v1/Managers/bmc#/Oem' 182 if ('JsonSchemas' in resource) or ('SessionService' in resource) or ('#' in resource): 183 continue 184 185 self._rest_response_ = self.get(resource, valid_status_codes=[200, 404, 500]) 186 # Enumeration is done for available resources ignoring the ones for which response is not 187 # obtained. 188 if self._rest_response_.status != 200: 189 if include_dead_resources: 190 try: 191 dead_resources[self._rest_response_.status].append(resource) 192 except KeyError: 193 dead_resources[self._rest_response_.status] = [resource] 194 continue 195 self.walk_nested_dict(self._rest_response_.dict, url=resource) 196 197 enumerated_resources.update(set(resources_to_be_enumerated)) 198 resources_to_be_enumerated = tuple(self.__pending_enumeration - enumerated_resources) 199 200 if return_json: 201 if include_dead_resources: 202 return json.dumps(self.__result, sort_keys=True, 203 indent=4, separators=(',', ': ')), dead_resources 204 else: 205 return json.dumps(self.__result, sort_keys=True, 206 indent=4, separators=(',', ': ')) 207 else: 208 if include_dead_resources: 209 return self.__result, dead_resources 210 else: 211 return self.__result 212 213 def walk_nested_dict(self, data, url=''): 214 r""" 215 Parse through the nested dictionary and get the resource id paths. 216 217 Description of argument(s): 218 data Nested dictionary data from response message. 219 url Resource for which the response is obtained in data. 220 """ 221 url = url.rstrip('/') 222 223 for key, value in data.items(): 224 225 # Recursion if nested dictionary found. 226 if isinstance(value, dict): 227 self.walk_nested_dict(value) 228 else: 229 # Value contains a list of dictionaries having member data. 230 if 'Members' == key: 231 if isinstance(value, list): 232 for memberDict in value: 233 self.__pending_enumeration.add(memberDict['@odata.id']) 234 if '@odata.id' == key: 235 value = value.rstrip('/') 236 # Data for the given url. 237 if value == url: 238 self.__result[url] = data 239 # Data still needs to be looked up, 240 else: 241 self.__pending_enumeration.add(value) 242