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