blob: d6e1cb055860fd438a15805991e7d9151929a7ac [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walsh35139f92019-03-01 15:58:05 -06002
3r"""
4See redfish_plus class prolog below for details.
5"""
6
George Keishinge635ddc2022-12-08 07:38:02 -06007import json
Tony Lee05aa70b2021-01-28 19:18:27 +08008
Patrick Williams20f38712022-12-08 06:18:26 -06009import func_args as fa
10import gen_print as gp
11import requests
12from redfish.rest.v1 import HttpClient
13from robot.libraries.BuiltIn import BuiltIn
George Keishinge635ddc2022-12-08 07:38:02 -060014
Tony Lee05aa70b2021-01-28 19:18:27 +080015host = 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}")
Michael Walsh35139f92019-03-01 15:58:05 -060019
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.).
Michael Walsh410b1782019-10-22 15:56:18 -050027 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]").
Michael Walsh35139f92019-03-01 15:58:05 -060032 """
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"""
Michael Walsh410b1782019-10-22 15:56:18 -050048 redfish_plus is a wrapper for redfish rest that provides the following benefits vs. using redfish
49 directly:
Michael Walsh35139f92019-03-01 15:58:05 -060050
51 For rest_request functions (e.g. get, put, post, etc.):
52 - Function-call logging to stdout.
Michael Walsh410b1782019-10-22 15:56:18 -050053 - Automatic valid_status_codes processing (i.e. an exception will be raised if the rest response
54 status code is not as expected.
Michael Walsh35139f92019-03-01 15:58:05 -060055 - Easily used from robot programs.
56 """
57
Patrick Williams20f38712022-12-08 06:18:26 -060058 ROBOT_LIBRARY_SCOPE = "TEST SUITE"
Michael Walsh35139f92019-03-01 15:58:05 -060059
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.
Michael Walsh410b1782019-10-22 15:56:18 -050065 - The calling function's call line is logged to standard out (provided that global variable "quiet"
66 is not set).
Michael Walsh35139f92019-03-01 15:58:05 -060067 - The caller may include a valid_status_codes argument.
Michael Walsh410b1782019-10-22 15:56:18 -050068 - Callers may include inline python code strings to define arguments. This predominantly benefits
69 robot callers.
Michael Walsh2477e092019-05-17 15:48:56 -050070
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}')]
Michael Walsh35139f92019-03-01 15:58:05 -060079
80 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050081 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.
Michael Walsh35139f92019-03-01 15:58:05 -060093
94 Example uses:
95
96 From a python program:
97
ganesanb4d430282023-04-27 14:33:23 +000098 response = bmc_redfish.get("/redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces", [200, 201])
Michael Walsh35139f92019-03-01 15:58:05 -060099
Michael Walsh410b1782019-10-22 15:56:18 -0500100 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.
Michael Walsh35139f92019-03-01 15:58:05 -0600102
103 From a robot program:
104
105 BMC_Redfish.logout
ganesanb4d430282023-04-27 14:33:23 +0000106 ${response}= BMC_Redfish.Get /redfish/v1/Managers/${MANAGER_ID}/EthernetInterfaces valid_status_codes=[401]
Michael Walsh35139f92019-03-01 15:58:05 -0600107
Michael Walsh410b1782019-10-22 15:56:18 -0500108 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").
George Keishingc7c771e2021-03-30 13:27:27 -0500110
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.
Michael Walsh35139f92019-03-01 15:58:05 -0600113 """
Michael Walsh410b1782019-10-22 15:56:18 -0500114 # Convert python string object definitions to objects (mostly useful for robot callers).
Michael Walsh2477e092019-05-17 15:48:56 -0500115 args = fa.args_to_objects(args)
116 kwargs = fa.args_to_objects(kwargs)
Patrick Williams20f38712022-12-08 06:18:26 -0600117 timeout = kwargs.pop("timeout", 30)
George Keishingc7c771e2021-03-30 13:27:27 -0500118 self._timeout = timeout
Patrick Williams20f38712022-12-08 06:18:26 -0600119 max_retry = kwargs.pop("max_retry", 10)
George Keishingc7c771e2021-03-30 13:27:27 -0500120 self._max_retry = max_retry
Patrick Williams20f38712022-12-08 06:18:26 -0600121 valid_status_codes = kwargs.pop("valid_status_codes", [200])
George Keishing7049fba2025-01-16 10:44:46 +0530122
George Keishing01a503c2025-03-27 17:40:53 +0530123 # /redfish/v1/ does not require authentication and start of the test
124 # is dumping 3 entries at the beginning which is excessive.
125 # args[0] position is always the URI for redfish request
126 # Example:
127 # ('/redfish/v1/',)
128 # ('/redfish/v1/Managers/bmc',)
129 # Skip logging if matches /redfish/v1/
130 if args[0] != "/redfish/v1/":
131 gp.qprint_executing(
132 stack_frame_ix=3, style=gp.func_line_style_short
133 )
134
George Keishing7049fba2025-01-16 10:44:46 +0530135 try:
136 response = func(*args, **kwargs)
137 except Exception as e:
138 error_response = type(e).__name__ + " from redfish_plus class"
139 BuiltIn().log_to_console(error_response)
140 return
141
Michael Walsh35139f92019-03-01 15:58:05 -0600142 valid_http_status_code(response.status, valid_status_codes)
143 return response
144
145 # Define rest function wrappers.
146 def get(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600147 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800148 return self.rest_request(self.get_with_mtls, *args, **kwargs)
149 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600150 return self.rest_request(
151 super(redfish_plus, self).get, *args, **kwargs
152 )
Michael Walsh35139f92019-03-01 15:58:05 -0600153
154 def head(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600155 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800156 return self.rest_request(self.head_with_mtls, *args, **kwargs)
157 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600158 return self.rest_request(
159 super(redfish_plus, self).head, *args, **kwargs
160 )
Michael Walsh35139f92019-03-01 15:58:05 -0600161
162 def post(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600163 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800164 return self.rest_request(self.post_with_mtls, *args, **kwargs)
165 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600166 return self.rest_request(
167 super(redfish_plus, self).post, *args, **kwargs
168 )
Michael Walsh35139f92019-03-01 15:58:05 -0600169
170 def put(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600171 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800172 return self.rest_request(self.put_with_mtls, *args, **kwargs)
173 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600174 return self.rest_request(
175 super(redfish_plus, self).put, *args, **kwargs
176 )
Michael Walsh35139f92019-03-01 15:58:05 -0600177
178 def patch(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600179 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800180 return self.rest_request(self.patch_with_mtls, *args, **kwargs)
181 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600182 return self.rest_request(
183 super(redfish_plus, self).patch, *args, **kwargs
184 )
Michael Walsh35139f92019-03-01 15:58:05 -0600185
186 def delete(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600187 if MTLS_ENABLED == "True":
Tony Lee05aa70b2021-01-28 19:18:27 +0800188 return self.rest_request(self.delete_with_mtls, *args, **kwargs)
189 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600190 return self.rest_request(
191 super(redfish_plus, self).delete, *args, **kwargs
192 )
Michael Walsh35139f92019-03-01 15:58:05 -0600193
194 def __del__(self):
195 del self
Tony Lee05aa70b2021-01-28 19:18:27 +0800196
197 def get_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600198 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
199 response = requests.get(
200 url="https://" + host + args[0],
201 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
202 verify=False,
203 headers={"Cache-Control": "no-cache"},
204 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800205
206 response.status = response.status_code
207 if response.status == 200:
208 response.dict = json.loads(response.text)
209
210 return response
211
212 def post_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600213 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
214 body = kwargs.pop("body", {})
215 response = requests.post(
216 url="https://" + host + args[0],
217 json=body,
218 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
219 verify=False,
220 headers={"Content-Type": "application/json"},
221 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800222
223 response.status = response.status_code
224
225 return response
226
227 def patch_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600228 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
229 body = kwargs.pop("body", {})
230 response = requests.patch(
231 url="https://" + host + args[0],
232 json=body,
233 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
234 verify=False,
235 headers={"Content-Type": "application/json"},
236 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800237
238 response.status = response.status_code
239
240 return response
241
242 def delete_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600243 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
244 response = requests.delete(
245 url="https://" + host + args[0],
246 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
247 verify=False,
248 headers={"Content-Type": "application/json"},
249 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800250
251 response.status = response.status_code
252
253 return response
254
255 def put_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600256 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
257 body = kwargs.pop("body", {})
258 response = requests.put(
259 url="https://" + host + args[0],
260 json=body,
261 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
262 verify=False,
263 headers={"Content-Type": "application/json"},
264 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800265
266 response.status = response.status_code
267
268 return response
269
270 def head_with_mtls(self, *args, **kwargs):
Patrick Williams20f38712022-12-08 06:18:26 -0600271 cert_dict = kwargs.pop("certificate", {"certificate_name": VALID_CERT})
272 body = kwargs.pop("body", {})
273 response = requests.head(
274 url="https://" + host + args[0],
275 json=body,
276 cert=CERT_DIR_PATH + "/" + cert_dict["certificate_name"],
277 verify=False,
278 headers={"Content-Type": "application/json"},
279 )
Tony Lee05aa70b2021-01-28 19:18:27 +0800280
281 response.status = response.status_code
282
283 return response