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