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