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