1#!/usr/bin/env python3 2 3r""" 4See redfish_plus class prolog below for details. 5""" 6 7import json 8 9import func_args as fa 10import gen_print as gp 11import requests 12from redfish.rest.v1 import HttpClient 13from robot.libraries.BuiltIn import BuiltIn 14 15host = BuiltIn().get_variable_value("${OPENBMC_HOST}") 16MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}") 17CERT_DIR_PATH = BuiltIn().get_variable_value("${CERT_DIR_PATH}") 18VALID_CERT = BuiltIn().get_variable_value("${VALID_CERT}") 19 20 21def valid_http_status_code(status, valid_status_codes): 22 r""" 23 Raise exception if status is not found in the valid_status_codes list. 24 25 Description of argument(s): 26 status An HTTP status code (e.g. 200, 400, etc.). 27 valid_status_codes A list of status codes that the caller considers acceptable. If this is 28 a null list, then any status code is considered acceptable. Note that 29 for the convenience of the caller, valid_status_codes may be either a 30 python list or a string which can be evaluated to become a python list 31 (e.g. "[200]"). 32 """ 33 34 if type(valid_status_codes) is not list: 35 valid_status_codes = eval(valid_status_codes) 36 if len(valid_status_codes) == 0: 37 return 38 if status in valid_status_codes: 39 return 40 41 message = "The HTTP status code was not valid:\n" 42 message += gp.sprint_vars(status, valid_status_codes) 43 raise ValueError(message) 44 45 46class redfish_plus(HttpClient): 47 r""" 48 redfish_plus is a wrapper for redfish rest that provides the following benefits vs. using redfish 49 directly: 50 51 For rest_request functions (e.g. get, put, post, etc.): 52 - Function-call logging to stdout. 53 - Automatic valid_status_codes processing (i.e. an exception will be raised if the rest response 54 status code is not as expected. 55 - Easily used from robot programs. 56 """ 57 58 ROBOT_LIBRARY_SCOPE = "TEST SUITE" 59 60 def rest_request(self, func, *args, **kwargs): 61 r""" 62 Perform redfish rest request and return response. 63 64 This function provides the following additional functionality. 65 - The calling function's call line is logged to standard out (provided that global variable "quiet" 66 is not set). 67 - The caller may include a valid_status_codes argument. 68 - Callers may include inline python code strings to define arguments. This predominantly benefits 69 robot callers. 70 71 For example, instead of calling like this: 72 ${data}= Create Dictionary HostName=${hostname} 73 Redfish.patch ${REDFISH_NW_PROTOCOL_URI} body=&{data} 74 75 Callers may do this: 76 77 Redfish.patch ${REDFISH_NW_PROTOCOL_URI} 78 ... body=[('HostName', '${hostname}')] 79 80 Description of argument(s): 81 func A reference to the parent class function which is to be called (e.g. get, 82 put, etc.). 83 args This is passed directly to the function referenced by the func argument 84 (see the documentation for the corresponding redfish HttpClient method 85 for details). 86 kwargs This is passed directly to the function referenced by the func argument 87 (see the documentation for the corresponding redfish HttpClient method 88 for details) with the following exception: If kwargs contains a 89 valid_status_codes key, it will be removed from kwargs and processed by 90 this function. This allows the caller to indicate what rest status codes 91 are acceptable. The default value is [200]. See the 92 valid_http_status_code function above for details. 93 94 Example uses: 95 96 From a python program: 97 98 response = bmc_redfish.get("/redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces", [200, 201]) 99 100 If this call to the get method generates a response.status equal to anything other than 200 or 201, 101 an exception will be raised. 102 103 From a robot program: 104 105 BMC_Redfish.logout 106 ${response}= BMC_Redfish.Get /redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces valid_status_codes=[401] 107 108 As part of a robot test, the programmer has logged out to verify that the get request will generate a 109 status code of 401 (i.e. "Unauthorized"). 110 111 Timeout for GET/POST/PATCH/DELETE operations. By default 30 seconds, else user defined value. 112 Similarly, Max retry by default 10 attempt for the operation, else user defined value. 113 """ 114 gp.qprint_executing(stack_frame_ix=3, style=gp.func_line_style_short) 115 # Convert python string object definitions to objects (mostly useful for robot callers). 116 args = fa.args_to_objects(args) 117 kwargs = fa.args_to_objects(kwargs) 118 timeout = kwargs.pop("timeout", 30) 119 self._timeout = timeout 120 max_retry = kwargs.pop("max_retry", 10) 121 self._max_retry = max_retry 122 valid_status_codes = kwargs.pop("valid_status_codes", [200]) 123 124 try: 125 response = func(*args, **kwargs) 126 except Exception as e: 127 error_response = type(e).__name__ + " from redfish_plus class" 128 BuiltIn().log_to_console(error_response) 129 return 130 131 valid_http_status_code(response.status, valid_status_codes) 132 return response 133 134 # Define rest function wrappers. 135 def get(self, *args, **kwargs): 136 if MTLS_ENABLED == "True": 137 return self.rest_request(self.get_with_mtls, *args, **kwargs) 138 else: 139 return self.rest_request( 140 super(redfish_plus, self).get, *args, **kwargs 141 ) 142 143 def head(self, *args, **kwargs): 144 if MTLS_ENABLED == "True": 145 return self.rest_request(self.head_with_mtls, *args, **kwargs) 146 else: 147 return self.rest_request( 148 super(redfish_plus, self).head, *args, **kwargs 149 ) 150 151 def post(self, *args, **kwargs): 152 if MTLS_ENABLED == "True": 153 return self.rest_request(self.post_with_mtls, *args, **kwargs) 154 else: 155 return self.rest_request( 156 super(redfish_plus, self).post, *args, **kwargs 157 ) 158 159 def put(self, *args, **kwargs): 160 if MTLS_ENABLED == "True": 161 return self.rest_request(self.put_with_mtls, *args, **kwargs) 162 else: 163 return self.rest_request( 164 super(redfish_plus, self).put, *args, **kwargs 165 ) 166 167 def patch(self, *args, **kwargs): 168 if MTLS_ENABLED == "True": 169 return self.rest_request(self.patch_with_mtls, *args, **kwargs) 170 else: 171 return self.rest_request( 172 super(redfish_plus, self).patch, *args, **kwargs 173 ) 174 175 def delete(self, *args, **kwargs): 176 if MTLS_ENABLED == "True": 177 return self.rest_request(self.delete_with_mtls, *args, **kwargs) 178 else: 179 return self.rest_request( 180 super(redfish_plus, self).delete, *args, **kwargs 181 ) 182 183 def __del__(self): 184 del self 185 186 def get_with_mtls(self, *args, **kwargs): 187 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 188 response = requests.get( 189 url="https://" + host + args[0], 190 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 191 verify=False, 192 headers={"Cache-Control": "no-cache"}, 193 ) 194 195 response.status = response.status_code 196 if response.status == 200: 197 response.dict = json.loads(response.text) 198 199 return response 200 201 def post_with_mtls(self, *args, **kwargs): 202 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 203 body = kwargs.pop("body", {}) 204 response = requests.post( 205 url="https://" + host + args[0], 206 json=body, 207 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 208 verify=False, 209 headers={"Content-Type": "application/json"}, 210 ) 211 212 response.status = response.status_code 213 214 return response 215 216 def patch_with_mtls(self, *args, **kwargs): 217 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 218 body = kwargs.pop("body", {}) 219 response = requests.patch( 220 url="https://" + host + args[0], 221 json=body, 222 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 223 verify=False, 224 headers={"Content-Type": "application/json"}, 225 ) 226 227 response.status = response.status_code 228 229 return response 230 231 def delete_with_mtls(self, *args, **kwargs): 232 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 233 response = requests.delete( 234 url="https://" + host + args[0], 235 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 236 verify=False, 237 headers={"Content-Type": "application/json"}, 238 ) 239 240 response.status = response.status_code 241 242 return response 243 244 def put_with_mtls(self, *args, **kwargs): 245 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 246 body = kwargs.pop("body", {}) 247 response = requests.put( 248 url="https://" + host + args[0], 249 json=body, 250 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 251 verify=False, 252 headers={"Content-Type": "application/json"}, 253 ) 254 255 response.status = response.status_code 256 257 return response 258 259 def head_with_mtls(self, *args, **kwargs): 260 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 261 body = kwargs.pop("body", {}) 262 response = requests.head( 263 url="https://" + host + args[0], 264 json=body, 265 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 266 verify=False, 267 headers={"Content-Type": "application/json"}, 268 ) 269 270 response.status = response.status_code 271 272 return response 273