1e7e9171eSGeorge Keishing#!/usr/bin/env python3 235139f97SMichael Walsh 335139f97SMichael Walshr""" 435139f97SMichael WalshSee redfish_plus class prolog below for details. 535139f97SMichael Walsh""" 635139f97SMichael Walsh 7e635ddc0SGeorge Keishingimport json 805aa70bcSTony Lee 920f38712SPatrick Williamsimport func_args as fa 1020f38712SPatrick Williamsimport gen_print as gp 1120f38712SPatrick Williamsimport requests 1220f38712SPatrick Williamsfrom redfish.rest.v1 import HttpClient 1320f38712SPatrick Williamsfrom robot.libraries.BuiltIn import BuiltIn 14e635ddc0SGeorge Keishing 1505aa70bcSTony Leehost = BuiltIn().get_variable_value("${OPENBMC_HOST}") 1605aa70bcSTony LeeMTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}") 1705aa70bcSTony LeeCERT_DIR_PATH = BuiltIn().get_variable_value("${CERT_DIR_PATH}") 1805aa70bcSTony LeeVALID_CERT = BuiltIn().get_variable_value("${VALID_CERT}") 1935139f97SMichael Walsh 2035139f97SMichael Walsh 2135139f97SMichael Walshdef valid_http_status_code(status, valid_status_codes): 2235139f97SMichael Walsh r""" 2335139f97SMichael Walsh Raise exception if status is not found in the valid_status_codes list. 2435139f97SMichael Walsh 2535139f97SMichael Walsh Description of argument(s): 2635139f97SMichael Walsh status An HTTP status code (e.g. 200, 400, etc.). 27410b1787SMichael Walsh valid_status_codes A list of status codes that the caller considers acceptable. If this is 28410b1787SMichael Walsh a null list, then any status code is considered acceptable. Note that 29410b1787SMichael Walsh for the convenience of the caller, valid_status_codes may be either a 30410b1787SMichael Walsh python list or a string which can be evaluated to become a python list 31410b1787SMichael Walsh (e.g. "[200]"). 3235139f97SMichael Walsh """ 3335139f97SMichael Walsh 3435139f97SMichael Walsh if type(valid_status_codes) is not list: 3535139f97SMichael Walsh valid_status_codes = eval(valid_status_codes) 3635139f97SMichael Walsh if len(valid_status_codes) == 0: 3735139f97SMichael Walsh return 3835139f97SMichael Walsh if status in valid_status_codes: 3935139f97SMichael Walsh return 4035139f97SMichael Walsh 4135139f97SMichael Walsh message = "The HTTP status code was not valid:\n" 4235139f97SMichael Walsh message += gp.sprint_vars(status, valid_status_codes) 4335139f97SMichael Walsh raise ValueError(message) 4435139f97SMichael Walsh 4535139f97SMichael Walsh 4635139f97SMichael Walshclass redfish_plus(HttpClient): 4735139f97SMichael Walsh r""" 48410b1787SMichael Walsh redfish_plus is a wrapper for redfish rest that provides the following benefits vs. using redfish 49410b1787SMichael Walsh directly: 5035139f97SMichael Walsh 5135139f97SMichael Walsh For rest_request functions (e.g. get, put, post, etc.): 5235139f97SMichael Walsh - Function-call logging to stdout. 53410b1787SMichael Walsh - Automatic valid_status_codes processing (i.e. an exception will be raised if the rest response 54410b1787SMichael Walsh status code is not as expected. 5535139f97SMichael Walsh - Easily used from robot programs. 5635139f97SMichael Walsh """ 5735139f97SMichael Walsh 5820f38712SPatrick Williams ROBOT_LIBRARY_SCOPE = "TEST SUITE" 5935139f97SMichael Walsh 6035139f97SMichael Walsh def rest_request(self, func, *args, **kwargs): 6135139f97SMichael Walsh r""" 6235139f97SMichael Walsh Perform redfish rest request and return response. 6335139f97SMichael Walsh 6435139f97SMichael Walsh This function provides the following additional functionality. 65410b1787SMichael Walsh - The calling function's call line is logged to standard out (provided that global variable "quiet" 66410b1787SMichael Walsh is not set). 6735139f97SMichael Walsh - The caller may include a valid_status_codes argument. 68410b1787SMichael Walsh - Callers may include inline python code strings to define arguments. This predominantly benefits 69410b1787SMichael Walsh robot callers. 702477e097SMichael Walsh 712477e097SMichael Walsh For example, instead of calling like this: 722477e097SMichael Walsh ${data}= Create Dictionary HostName=${hostname} 732477e097SMichael Walsh Redfish.patch ${REDFISH_NW_PROTOCOL_URI} body=&{data} 742477e097SMichael Walsh 752477e097SMichael Walsh Callers may do this: 762477e097SMichael Walsh 772477e097SMichael Walsh Redfish.patch ${REDFISH_NW_PROTOCOL_URI} 782477e097SMichael Walsh ... body=[('HostName', '${hostname}')] 7935139f97SMichael Walsh 8035139f97SMichael Walsh Description of argument(s): 81410b1787SMichael Walsh func A reference to the parent class function which is to be called (e.g. get, 82410b1787SMichael Walsh put, etc.). 83410b1787SMichael Walsh args This is passed directly to the function referenced by the func argument 84410b1787SMichael Walsh (see the documentation for the corresponding redfish HttpClient method 85410b1787SMichael Walsh for details). 86410b1787SMichael Walsh kwargs This is passed directly to the function referenced by the func argument 87410b1787SMichael Walsh (see the documentation for the corresponding redfish HttpClient method 88410b1787SMichael Walsh for details) with the following exception: If kwargs contains a 89410b1787SMichael Walsh valid_status_codes key, it will be removed from kwargs and processed by 90410b1787SMichael Walsh this function. This allows the caller to indicate what rest status codes 91410b1787SMichael Walsh are acceptable. The default value is [200]. See the 92410b1787SMichael Walsh valid_http_status_code function above for details. 9335139f97SMichael Walsh 9435139f97SMichael Walsh Example uses: 9535139f97SMichael Walsh 9635139f97SMichael Walsh From a python program: 9735139f97SMichael Walsh 98*4d430283Sganesanb response = bmc_redfish.get("/redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces", [200, 201]) 9935139f97SMichael Walsh 100410b1787SMichael Walsh If this call to the get method generates a response.status equal to anything other than 200 or 201, 101410b1787SMichael Walsh an exception will be raised. 10235139f97SMichael Walsh 10335139f97SMichael Walsh From a robot program: 10435139f97SMichael Walsh 10535139f97SMichael Walsh BMC_Redfish.logout 106*4d430283Sganesanb ${response}= BMC_Redfish.Get /redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces valid_status_codes=[401] 10735139f97SMichael Walsh 108410b1787SMichael Walsh As part of a robot test, the programmer has logged out to verify that the get request will generate a 109410b1787SMichael Walsh status code of 401 (i.e. "Unauthorized"). 110c7c771e7SGeorge Keishing 111c7c771e7SGeorge Keishing Timeout for GET/POST/PATCH/DELETE operations. By default 30 seconds, else user defined value. 112c7c771e7SGeorge Keishing Similarly, Max retry by default 10 attempt for the operation, else user defined value. 11335139f97SMichael Walsh """ 11435139f97SMichael Walsh gp.qprint_executing(stack_frame_ix=3, style=gp.func_line_style_short) 115410b1787SMichael Walsh # Convert python string object definitions to objects (mostly useful for robot callers). 1162477e097SMichael Walsh args = fa.args_to_objects(args) 1172477e097SMichael Walsh kwargs = fa.args_to_objects(kwargs) 11820f38712SPatrick Williams timeout = kwargs.pop("timeout", 30) 119c7c771e7SGeorge Keishing self._timeout = timeout 12020f38712SPatrick Williams max_retry = kwargs.pop("max_retry", 10) 121c7c771e7SGeorge Keishing self._max_retry = max_retry 12220f38712SPatrick Williams valid_status_codes = kwargs.pop("valid_status_codes", [200]) 12335139f97SMichael Walsh response = func(*args, **kwargs) 12435139f97SMichael Walsh valid_http_status_code(response.status, valid_status_codes) 12535139f97SMichael Walsh return response 12635139f97SMichael Walsh 12735139f97SMichael Walsh # Define rest function wrappers. 12835139f97SMichael Walsh def get(self, *args, **kwargs): 12920f38712SPatrick Williams if MTLS_ENABLED == "True": 13005aa70bcSTony Lee return self.rest_request(self.get_with_mtls, *args, **kwargs) 13105aa70bcSTony Lee else: 13220f38712SPatrick Williams return self.rest_request( 13320f38712SPatrick Williams super(redfish_plus, self).get, *args, **kwargs 13420f38712SPatrick Williams ) 13535139f97SMichael Walsh 13635139f97SMichael Walsh def head(self, *args, **kwargs): 13720f38712SPatrick Williams if MTLS_ENABLED == "True": 13805aa70bcSTony Lee return self.rest_request(self.head_with_mtls, *args, **kwargs) 13905aa70bcSTony Lee else: 14020f38712SPatrick Williams return self.rest_request( 14120f38712SPatrick Williams super(redfish_plus, self).head, *args, **kwargs 14220f38712SPatrick Williams ) 14335139f97SMichael Walsh 14435139f97SMichael Walsh def post(self, *args, **kwargs): 14520f38712SPatrick Williams if MTLS_ENABLED == "True": 14605aa70bcSTony Lee return self.rest_request(self.post_with_mtls, *args, **kwargs) 14705aa70bcSTony Lee else: 14820f38712SPatrick Williams return self.rest_request( 14920f38712SPatrick Williams super(redfish_plus, self).post, *args, **kwargs 15020f38712SPatrick Williams ) 15135139f97SMichael Walsh 15235139f97SMichael Walsh def put(self, *args, **kwargs): 15320f38712SPatrick Williams if MTLS_ENABLED == "True": 15405aa70bcSTony Lee return self.rest_request(self.put_with_mtls, *args, **kwargs) 15505aa70bcSTony Lee else: 15620f38712SPatrick Williams return self.rest_request( 15720f38712SPatrick Williams super(redfish_plus, self).put, *args, **kwargs 15820f38712SPatrick Williams ) 15935139f97SMichael Walsh 16035139f97SMichael Walsh def patch(self, *args, **kwargs): 16120f38712SPatrick Williams if MTLS_ENABLED == "True": 16205aa70bcSTony Lee return self.rest_request(self.patch_with_mtls, *args, **kwargs) 16305aa70bcSTony Lee else: 16420f38712SPatrick Williams return self.rest_request( 16520f38712SPatrick Williams super(redfish_plus, self).patch, *args, **kwargs 16620f38712SPatrick Williams ) 16735139f97SMichael Walsh 16835139f97SMichael Walsh def delete(self, *args, **kwargs): 16920f38712SPatrick Williams if MTLS_ENABLED == "True": 17005aa70bcSTony Lee return self.rest_request(self.delete_with_mtls, *args, **kwargs) 17105aa70bcSTony Lee else: 17220f38712SPatrick Williams return self.rest_request( 17320f38712SPatrick Williams super(redfish_plus, self).delete, *args, **kwargs 17420f38712SPatrick Williams ) 17535139f97SMichael Walsh 17635139f97SMichael Walsh def __del__(self): 17735139f97SMichael Walsh del self 17805aa70bcSTony Lee 17905aa70bcSTony Lee def get_with_mtls(self, *args, **kwargs): 18020f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 18120f38712SPatrick Williams response = requests.get( 18220f38712SPatrick Williams url="https://" + host + args[0], 18320f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 18405aa70bcSTony Lee verify=False, 18520f38712SPatrick Williams headers={"Cache-Control": "no-cache"}, 18620f38712SPatrick Williams ) 18705aa70bcSTony Lee 18805aa70bcSTony Lee response.status = response.status_code 18905aa70bcSTony Lee if response.status == 200: 19005aa70bcSTony Lee response.dict = json.loads(response.text) 19105aa70bcSTony Lee 19205aa70bcSTony Lee return response 19305aa70bcSTony Lee 19405aa70bcSTony Lee def post_with_mtls(self, *args, **kwargs): 19520f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 19620f38712SPatrick Williams body = kwargs.pop("body", {}) 19720f38712SPatrick Williams response = requests.post( 19820f38712SPatrick Williams url="https://" + host + args[0], 19905aa70bcSTony Lee json=body, 20020f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 20105aa70bcSTony Lee verify=False, 20220f38712SPatrick Williams headers={"Content-Type": "application/json"}, 20320f38712SPatrick Williams ) 20405aa70bcSTony Lee 20505aa70bcSTony Lee response.status = response.status_code 20605aa70bcSTony Lee 20705aa70bcSTony Lee return response 20805aa70bcSTony Lee 20905aa70bcSTony Lee def patch_with_mtls(self, *args, **kwargs): 21020f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 21120f38712SPatrick Williams body = kwargs.pop("body", {}) 21220f38712SPatrick Williams response = requests.patch( 21320f38712SPatrick Williams url="https://" + host + args[0], 21405aa70bcSTony Lee json=body, 21520f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 21605aa70bcSTony Lee verify=False, 21720f38712SPatrick Williams headers={"Content-Type": "application/json"}, 21820f38712SPatrick Williams ) 21905aa70bcSTony Lee 22005aa70bcSTony Lee response.status = response.status_code 22105aa70bcSTony Lee 22205aa70bcSTony Lee return response 22305aa70bcSTony Lee 22405aa70bcSTony Lee def delete_with_mtls(self, *args, **kwargs): 22520f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 22620f38712SPatrick Williams response = requests.delete( 22720f38712SPatrick Williams url="https://" + host + args[0], 22820f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 22905aa70bcSTony Lee verify=False, 23020f38712SPatrick Williams headers={"Content-Type": "application/json"}, 23120f38712SPatrick Williams ) 23205aa70bcSTony Lee 23305aa70bcSTony Lee response.status = response.status_code 23405aa70bcSTony Lee 23505aa70bcSTony Lee return response 23605aa70bcSTony Lee 23705aa70bcSTony Lee def put_with_mtls(self, *args, **kwargs): 23820f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 23920f38712SPatrick Williams body = kwargs.pop("body", {}) 24020f38712SPatrick Williams response = requests.put( 24120f38712SPatrick Williams url="https://" + host + args[0], 24205aa70bcSTony Lee json=body, 24320f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 24405aa70bcSTony Lee verify=False, 24520f38712SPatrick Williams headers={"Content-Type": "application/json"}, 24620f38712SPatrick Williams ) 24705aa70bcSTony Lee 24805aa70bcSTony Lee response.status = response.status_code 24905aa70bcSTony Lee 25005aa70bcSTony Lee return response 25105aa70bcSTony Lee 25205aa70bcSTony Lee def head_with_mtls(self, *args, **kwargs): 25320f38712SPatrick Williams cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT}) 25420f38712SPatrick Williams body = kwargs.pop("body", {}) 25520f38712SPatrick Williams response = requests.head( 25620f38712SPatrick Williams url="https://" + host + args[0], 25705aa70bcSTony Lee json=body, 25820f38712SPatrick Williams cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"], 25905aa70bcSTony Lee verify=False, 26020f38712SPatrick Williams headers={"Content-Type": "application/json"}, 26120f38712SPatrick Williams ) 26205aa70bcSTony Lee 26305aa70bcSTony Lee response.status = response.status_code 26405aa70bcSTony Lee 26505aa70bcSTony Lee return response 266