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