blob: 099345a7a63421d4cf7b8a1b549ef4595000e095 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
George Keishingf2613b72019-02-13 12:45:59 -06002
3r"""
4BMC redfish utility functions.
5"""
6
7import json
George Keishing3a6f0732020-07-13 14:21:23 -05008import re
George Keishing43909f32024-08-20 22:08:02 +05309from json.decoder import JSONDecodeError
Patrick Williams20f38712022-12-08 06:18:26 -060010
George Keishinge635ddc2022-12-08 07:38:02 -060011import gen_print as gp
Patrick Williams20f38712022-12-08 06:18:26 -060012from robot.libraries.BuiltIn import BuiltIn
George Keishingf2613b72019-02-13 12:45:59 -060013
Tony Lee05aa70b2021-01-28 19:18:27 +080014MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
15
George Keishingf2613b72019-02-13 12:45:59 -060016
17class bmc_redfish_utils(object):
Patrick Williams20f38712022-12-08 06:18:26 -060018 ROBOT_LIBRARY_SCOPE = "TEST SUITE"
George Keishingeb1fe352020-06-19 03:02:22 -050019
George Keishingf2613b72019-02-13 12:45:59 -060020 def __init__(self):
21 r"""
22 Initialize the bmc_redfish_utils object.
23 """
24 # Obtain a reference to the global redfish object.
George Keishingeb1fe352020-06-19 03:02:22 -050025 self.__inited__ = False
George Keishing7049fba2025-01-16 10:44:46 +053026 try:
27 self._redfish_ = BuiltIn().get_library_instance("redfish")
28 except RuntimeError as e:
29 BuiltIn().log_to_console(
30 "get_library_instance: No active redfish instance found."
31 )
32 # Handling init exception at worse to raise on error.
33 raise e
George Keishingf2613b72019-02-13 12:45:59 -060034
Patrick Williams20f38712022-12-08 06:18:26 -060035 if MTLS_ENABLED == "True":
George Keishingeb1fe352020-06-19 03:02:22 -050036 self.__inited__ = True
Tony Lee05aa70b2021-01-28 19:18:27 +080037 else:
38 # There is a possibility that a given driver support both redfish and
39 # legacy REST.
40 self._redfish_.login()
Patrick Williams20f38712022-12-08 06:18:26 -060041 self._rest_response_ = self._redfish_.get(
42 "/xyz/openbmc_project/", valid_status_codes=[200, 404]
43 )
Tony Lee05aa70b2021-01-28 19:18:27 +080044
45 # If REST URL /xyz/openbmc_project/ is supported.
46 if self._rest_response_.status == 200:
47 self.__inited__ = True
George Keishingeb1fe352020-06-19 03:02:22 -050048
Patrick Williams20f38712022-12-08 06:18:26 -060049 BuiltIn().set_global_variable(
50 "${REDFISH_REST_SUPPORTED}", self.__inited__
51 )
George Keishingeb1fe352020-06-19 03:02:22 -050052
George Keishing374e6842019-02-20 08:57:18 -060053 def get_redfish_session_info(self):
54 r"""
55 Returns redfish sessions info dictionary.
56
57 {
58 'key': 'yLXotJnrh5nDhXj5lLiH' ,
59 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
60 }
61 """
62 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060063 "key": self._redfish_.get_session_key(),
Patrick Williams20f38712022-12-08 06:18:26 -060064 "location": self._redfish_.get_session_location(),
George Keishing374e6842019-02-20 08:57:18 -060065 }
66 return session_dict
67
Sandhya Somashekar37122b62019-06-18 06:02:02 -050068 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060069 r"""
70 Get resource attribute.
71
72 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050073 resource_path URI resource absolute path (e.g.
74 "/redfish/v1/Systems/1").
75 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060076 """
George Keishing43909f32024-08-20 22:08:02 +053077 try:
78 resp = self._redfish_.get(resource_path)
79 except JSONDecodeError as e:
80 BuiltIn().log_to_console(
81 "get_attribute: JSONDecodeError, re-trying"
82 )
83 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050084
85 if verify:
86 if resp.dict[attribute] == verify:
87 return resp.dict[attribute]
88 else:
89 raise ValueError("Attribute value is not equal")
90 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060091 return resp.dict[attribute]
92
93 return None
94
George Keishingc3c05c22019-02-19 01:04:54 -060095 def get_properties(self, resource_path):
96 r"""
97 Returns dictionary of attributes for the resource.
98
99 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -0500100 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -0500101 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -0600102 """
103
104 resp = self._redfish_.get(resource_path)
105 return resp.dict
106
George Keishing789c3b42020-07-14 08:44:47 -0500107 def get_members_uri(self, resource_path, attribute):
108 r"""
109 Returns the list of valid path which has a given attribute.
110
111 Description of argument(s):
112 resource_path URI resource base path (e.g.
113 '/redfish/v1/Systems/',
114 '/redfish/v1/Chassis/').
115 attribute Name of the attribute (e.g. 'PowerSupplies').
116 """
117
George Keishingd5f179e2020-07-14 16:07:31 -0500118 # Set quiet variable to keep subordinate get() calls quiet.
119 quiet = 1
120
George Keishing789c3b42020-07-14 08:44:47 -0500121 # Get the member id list.
122 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
123 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500124
125 valid_path_list = []
126
127 for path_idx in resource_path_list:
128 # Get all the child object path under the member id e.g.
129 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
130 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500131
132 # Iterate and check if path object has the attribute.
133 for child_path_idx in child_path_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600134 if (
135 ("JsonSchemas" in child_path_idx)
136 or ("SessionService" in child_path_idx)
137 or ("#" in child_path_idx)
138 ):
George Keishing6396bc62020-07-15 06:56:46 -0500139 continue
George Keishing789c3b42020-07-14 08:44:47 -0500140 if self.get_attribute(child_path_idx, attribute):
141 valid_path_list.append(child_path_idx)
142
George Keishingd5f179e2020-07-14 16:07:31 -0500143 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500144 return valid_path_list
145
George Keishing3a6f0732020-07-13 14:21:23 -0500146 def get_endpoint_path_list(self, resource_path, end_point_prefix):
147 r"""
148 Returns list with entries ending in "/endpoint".
149
150 Description of argument(s):
151 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500152 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500153
154 Find all list entries ending in "/endpoint" combination such as
155 /redfish/v1/Chassis/<foo>/Power
156 /redfish/v1/Chassis/<bar>/Power
157 """
158
159 end_point_list = self.list_request(resource_path)
160
161 # Regex to match entries ending in "/prefix" with optional underscore.
George Keishingd2f210a2022-02-23 10:44:10 -0600162 regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
George Keishing3a6f0732020-07-13 14:21:23 -0500163 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
164
George Keishing7ec45932019-02-27 14:02:16 -0600165 def get_target_actions(self, resource_path, target_attribute):
166 r"""
167 Returns resource target entry of the searched target attribute.
168
169 Description of argument(s):
170 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000171 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600172 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
173
174 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500175 "Actions": {
176 "#ComputerSystem.Reset": {
177 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600178 "On",
179 "ForceOff",
180 "GracefulRestart",
181 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500182 ],
183 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
184 }
185 }
George Keishing7ec45932019-02-27 14:02:16 -0600186 """
187
188 global target_list
189 target_list = []
190
191 resp_dict = self.get_attribute(resource_path, "Actions")
192 if resp_dict is None:
193 return None
194
195 # Recursively search the "target" key in the nested dictionary.
196 # Populate the target_list of target entries.
197 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600198 # Return the matching target URL entry.
199 for target in target_list:
200 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
Patrick Williams20f38712022-12-08 06:18:26 -0600201 attribute_in_uri = target.rsplit("/", 1)[-1]
Anusha Dathatriadfdb602021-04-07 01:50:24 -0500202 # attribute_in_uri "ComputerSystem.Reset"
203 if target_attribute == attribute_in_uri:
George Keishing7ec45932019-02-27 14:02:16 -0600204 return target
205
206 return None
207
George Keishingdabf38f2019-03-10 09:52:40 -0500208 def get_member_list(self, resource_path):
209 r"""
210 Perform a GET list request and return available members entries.
211
212 Description of argument(s):
213 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000214 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500215
216 "Members": [
217 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500218 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500219 }
220 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500221 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500222 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500223 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500224 """
225
226 member_list = []
227 resp_list_dict = self.get_attribute(resource_path, "Members")
228 if resp_list_dict is None:
229 return member_list
230
231 for member_id in range(0, len(resp_list_dict)):
232 member_list.append(resp_list_dict[member_id]["@odata.id"])
233
234 return member_list
235
George Keishingf2613b72019-02-13 12:45:59 -0600236 def list_request(self, resource_path):
237 r"""
238 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600239 Description of argument(s):
240 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000241 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600242 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500243 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500244 # Set quiet variable to keep subordinate get() calls quiet.
245 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500246 self.__pending_enumeration = set()
Patrick Williams20f38712022-12-08 06:18:26 -0600247 self._rest_response_ = self._redfish_.get(
248 resource_path, valid_status_codes=[200, 404, 500]
249 )
George Keishingf2613b72019-02-13 12:45:59 -0600250
251 # Return empty list.
252 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500253 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600254 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500255 if not self.__pending_enumeration:
256 return resource_path
257 for resource in self.__pending_enumeration.copy():
Patrick Williams20f38712022-12-08 06:18:26 -0600258 self._rest_response_ = self._redfish_.get(
259 resource, valid_status_codes=[200, 404, 500]
260 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500261
George Keishingf2613b72019-02-13 12:45:59 -0600262 if self._rest_response_.status != 200:
263 continue
264 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500265 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600266
Patrick Williams20f38712022-12-08 06:18:26 -0600267 def enumerate_request(
268 self, resource_path, return_json=1, include_dead_resources=False
269 ):
George Keishingf2613b72019-02-13 12:45:59 -0600270 r"""
271 Perform a GET enumerate request and return available resource paths.
272
273 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500274 resource_path URI resource absolute path (e.g.
275 "/redfish/v1/SessionService/Sessions").
276 return_json Indicates whether the result should be
277 returned as a json string or as a
278 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600279 include_dead_resources Check and return a list of dead/broken URI
280 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600281 """
282
Michael Walshc86a2f72019-03-19 13:24:37 -0500283 gp.qprint_executing(style=gp.func_line_style_short)
284
Michael Walsh37e028f2019-05-22 16:16:32 -0500285 return_json = int(return_json)
286
Michael Walshc86a2f72019-03-19 13:24:37 -0500287 # Set quiet variable to keep subordinate get() calls quiet.
288 quiet = 1
289
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500290 # Variable to hold enumerated data.
291 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600292
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500293 # Variable to hold the pending list of resources for which enumeration.
294 # is yet to be obtained.
295 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600296
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500297 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600298
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500299 # Variable having resources for which enumeration is completed.
300 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600301
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600302 if include_dead_resources:
303 dead_resources = {}
304
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500305 resources_to_be_enumerated = (resource_path,)
306
307 while resources_to_be_enumerated:
308 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600309 # JsonSchemas, SessionService or URLs containing # are not
310 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500311 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500312 # '/redfish/v1/SessionService'
ganesanb4d430282023-04-27 14:33:23 +0000313 # '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
Patrick Williams20f38712022-12-08 06:18:26 -0600314 if (
315 ("JsonSchemas" in resource)
316 or ("SessionService" in resource)
317 or ("PostCodes" in resource)
318 or ("Registries" in resource)
319 or ("Journal" in resource)
320 or ("#" in resource)
321 ):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500322 continue
323
George Keishing43909f32024-08-20 22:08:02 +0530324 try:
325 self._rest_response_ = self._redfish_.get(
326 resource, valid_status_codes=[200, 404, 405, 500]
327 )
328 except JSONDecodeError as e:
329 BuiltIn().log_to_console(
330 "enumerate_request: JSONDecodeError, re-trying"
331 )
332 self._rest_response_ = self._redfish_.get(
333 resource, valid_status_codes=[200, 404, 405, 500]
334 )
335
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500336 # Enumeration is done for available resources ignoring the
337 # ones for which response is not obtained.
338 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600339 if include_dead_resources:
340 try:
341 dead_resources[self._rest_response_.status].append(
Patrick Williams20f38712022-12-08 06:18:26 -0600342 resource
343 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600344 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600345 dead_resources[self._rest_response_.status] = [
346 resource
347 ]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500348 continue
349
350 self.walk_nested_dict(self._rest_response_.dict, url=resource)
351
352 enumerated_resources.update(set(resources_to_be_enumerated))
Patrick Williams20f38712022-12-08 06:18:26 -0600353 resources_to_be_enumerated = tuple(
354 self.__pending_enumeration - enumerated_resources
355 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500356
Michael Walsh37e028f2019-05-22 16:16:32 -0500357 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600358 if include_dead_resources:
Patrick Williams20f38712022-12-08 06:18:26 -0600359 return (
360 json.dumps(
361 self.__result,
362 sort_keys=True,
363 indent=4,
364 separators=(",", ": "),
365 ),
366 dead_resources,
367 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600368 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600369 return json.dumps(
370 self.__result,
371 sort_keys=True,
372 indent=4,
373 separators=(",", ": "),
374 )
Michael Walsh37e028f2019-05-22 16:16:32 -0500375 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600376 if include_dead_resources:
377 return self.__result, dead_resources
378 else:
379 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600380
Patrick Williams20f38712022-12-08 06:18:26 -0600381 def walk_nested_dict(self, data, url=""):
George Keishingf2613b72019-02-13 12:45:59 -0600382 r"""
383 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600384 Description of argument(s):
385 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500386 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600387 """
Patrick Williams20f38712022-12-08 06:18:26 -0600388 url = url.rstrip("/")
George Keishingf2613b72019-02-13 12:45:59 -0600389
390 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500391 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600392 if isinstance(value, dict):
393 self.walk_nested_dict(value)
394 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500395 # Value contains a list of dictionaries having member data.
Patrick Williams20f38712022-12-08 06:18:26 -0600396 if "Members" == key:
George Keishingf2613b72019-02-13 12:45:59 -0600397 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500398 for memberDict in value:
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500399 if isinstance(memberDict, str):
400 self.__pending_enumeration.add(memberDict)
Reshma Sc4873922024-11-27 15:32:15 +0530401 elif (
402 isinstance(memberDict, dict)
403 and "@odata.id" in memberDict
404 ):
Patrick Williams20f38712022-12-08 06:18:26 -0600405 self.__pending_enumeration.add(
406 memberDict["@odata.id"]
407 )
Reshma Sc4873922024-11-27 15:32:15 +0530408 else:
409 self.__pending_enumeration.add(memberDict[1])
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500410
Patrick Williams20f38712022-12-08 06:18:26 -0600411 if "@odata.id" == key:
412 value = value.rstrip("/")
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500413 # Data for the given url.
414 if value == url:
415 self.__result[url] = data
416 # Data still needs to be looked up,
417 else:
418 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600419
420 def get_key_value_nested_dict(self, data, key):
421 r"""
422 Parse through the nested dictionary and get the searched key value.
423
424 Description of argument(s):
425 data Nested dictionary data from response message.
426 key Search dictionary key element.
427 """
428
429 for k, v in data.items():
430 if isinstance(v, dict):
431 self.get_key_value_nested_dict(v, key)
432
433 if k == key:
434 target_list.append(v)