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 response = func(*args, **kwargs) 124 valid_http_status_code(response.status, valid_status_codes) 125 return response 126 127 # Define rest function wrappers. 128 def get(self, *args, **kwargs): 129 if MTLS_ENABLED == "True": 130 return self.rest_request(self.get_with_mtls, *args, **kwargs) 131 else: 132 return self.rest_request( 133 super(redfish_plus, self).get, *args, **kwargs 134 ) 135 136 def head(self, *args, **kwargs): 137 if MTLS_ENABLED == "True": 138 return self.rest_request(self.head_with_mtls, *args, **kwargs) 139 else: 140 return self.rest_request( 141 super(redfish_plus, self).head, *args, **kwargs 142 ) 143 144 def post(self, *args, **kwargs): 145 if MTLS_ENABLED == "True": 146 return self.rest_request(self.post_with_mtls, *args, **kwargs) 147 else: 148 return self.rest_request( 149 super(redfish_plus, self).post, *args, **kwargs 150 ) 151 152 def put(self, *args, **kwargs): 153 if MTLS_ENABLED == "True": 154 return self.rest_request(self.put_with_mtls, *args, **kwargs) 155 else: 156 return self.rest_request( 157 super(redfish_plus, self).put, *args, **kwargs 158 ) 159 160 def patch(self, *args, **kwargs): 161 if MTLS_ENABLED == "True": 162 return self.rest_request(self.patch_with_mtls, *args, **kwargs) 163 else: 164 return self.rest_request( 165 super(redfish_plus, self).patch, *args, **kwargs 166 ) 167 168 def delete(self, *args, **kwargs): 169 if MTLS_ENABLED == "True": 170 return self.rest_request(self.delete_with_mtls, *args, **kwargs) 171 else: 172 return self.rest_request( 173 super(redfish_plus, self).delete, *args, **kwargs 174 ) 175 176 def __del__(self): 177 del self 178 179 def get_with_mtls(self, *args, **kwargs): 180 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 181 response = requests.get( 182 url="https://" + host + args[0], 183 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 184 verify=False, 185 headers={"Cache-Control": "no-cache"}, 186 ) 187 188 response.status = response.status_code 189 if response.status == 200: 190 response.dict = json.loads(response.text) 191 192 return response 193 194 def post_with_mtls(self, *args, **kwargs): 195 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 196 body = kwargs.pop("body", {}) 197 response = requests.post( 198 url="https://" + host + args[0], 199 json=body, 200 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 201 verify=False, 202 headers={"Content-Type": "application/json"}, 203 ) 204 205 response.status = response.status_code 206 207 return response 208 209 def patch_with_mtls(self, *args, **kwargs): 210 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 211 body = kwargs.pop("body", {}) 212 response = requests.patch( 213 url="https://" + host + args[0], 214 json=body, 215 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 216 verify=False, 217 headers={"Content-Type": "application/json"}, 218 ) 219 220 response.status = response.status_code 221 222 return response 223 224 def delete_with_mtls(self, *args, **kwargs): 225 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 226 response = requests.delete( 227 url="https://" + host + args[0], 228 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 229 verify=False, 230 headers={"Content-Type": "application/json"}, 231 ) 232 233 response.status = response.status_code 234 235 return response 236 237 def put_with_mtls(self, *args, **kwargs): 238 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 239 body = kwargs.pop("body", {}) 240 response = requests.put( 241 url="https://" + host + args[0], 242 json=body, 243 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 244 verify=False, 245 headers={"Content-Type": "application/json"}, 246 ) 247 248 response.status = response.status_code 249 250 return response 251 252 def head_with_mtls(self, *args, **kwargs): 253 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 254 body = kwargs.pop("body", {}) 255 response = requests.head( 256 url="https://" + host + args[0], 257 json=body, 258 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 259 verify=False, 260 headers={"Content-Type": "application/json"}, 261 ) 262 263 response.status = response.status_code 264 265 return response 266