1#!/usr/bin/env python3 2 3r""" 4See redfish_plus class prolog below for details. 5""" 6 7from redfish.rest.v1 import HttpClient 8import gen_print as gp 9import func_args as fa 10import requests 11import json 12from robot.libraries.BuiltIn import BuiltIn 13 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/bmc/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/bmc/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 130 if MTLS_ENABLED == 'True': 131 return self.rest_request(self.get_with_mtls, *args, **kwargs) 132 else: 133 return self.rest_request(super(redfish_plus, self).get, *args, 134 **kwargs) 135 136 def head(self, *args, **kwargs): 137 138 if MTLS_ENABLED == 'True': 139 return self.rest_request(self.head_with_mtls, *args, **kwargs) 140 else: 141 return self.rest_request(super(redfish_plus, self).head, *args, 142 **kwargs) 143 144 def post(self, *args, **kwargs): 145 146 if MTLS_ENABLED == 'True': 147 return self.rest_request(self.post_with_mtls, *args, **kwargs) 148 else: 149 return self.rest_request(super(redfish_plus, self).post, *args, 150 **kwargs) 151 152 def put(self, *args, **kwargs): 153 154 if MTLS_ENABLED == 'True': 155 return self.rest_request(self.put_with_mtls, *args, **kwargs) 156 else: 157 return self.rest_request(super(redfish_plus, self).put, *args, 158 **kwargs) 159 160 def patch(self, *args, **kwargs): 161 162 if MTLS_ENABLED == 'True': 163 return self.rest_request(self.patch_with_mtls, *args, **kwargs) 164 else: 165 return self.rest_request(super(redfish_plus, self).patch, *args, 166 **kwargs) 167 168 def delete(self, *args, **kwargs): 169 170 if MTLS_ENABLED == 'True': 171 return self.rest_request(self.delete_with_mtls, *args, **kwargs) 172 else: 173 return self.rest_request(super(redfish_plus, self).delete, *args, 174 **kwargs) 175 176 def __del__(self): 177 del self 178 179 def get_with_mtls(self, *args, **kwargs): 180 181 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 182 response = requests.get(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 response.status = response.status_code 188 if response.status == 200: 189 response.dict = json.loads(response.text) 190 191 return response 192 193 def post_with_mtls(self, *args, **kwargs): 194 195 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 196 body = kwargs.pop('body', {}) 197 response = requests.post(url='https://' + host + args[0], 198 json=body, 199 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'], 200 verify=False, 201 headers={"Content-Type": "application/json"}) 202 203 response.status = response.status_code 204 205 return response 206 207 def patch_with_mtls(self, *args, **kwargs): 208 209 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 210 body = kwargs.pop('body', {}) 211 response = requests.patch(url='https://' + host + args[0], 212 json=body, 213 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'], 214 verify=False, 215 headers={"Content-Type": "application/json"}) 216 217 response.status = response.status_code 218 219 return response 220 221 def delete_with_mtls(self, *args, **kwargs): 222 223 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 224 response = requests.delete(url='https://' + host + args[0], 225 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'], 226 verify=False, 227 headers={"Content-Type": "application/json"}) 228 229 response.status = response.status_code 230 231 return response 232 233 def put_with_mtls(self, *args, **kwargs): 234 235 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 236 body = kwargs.pop('body', {}) 237 response = requests.put(url='https://' + host + args[0], 238 json=body, 239 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'], 240 verify=False, 241 headers={"Content-Type": "application/json"}) 242 243 response.status = response.status_code 244 245 return response 246 247 def head_with_mtls(self, *args, **kwargs): 248 249 cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT}) 250 body = kwargs.pop('body', {}) 251 response = requests.head(url='https://' + host + args[0], 252 json=body, 253 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'], 254 verify=False, 255 headers={"Content-Type": "application/json"}) 256 257 response.status = response.status_code 258 259 return response 260