blob: 5a0400efefe3cfe639221e17634d2376c060b8f4 [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:
George Keishingb4e3ad42025-03-20 10:32:40 +053038 # initialized if only login succeed.
Tony Lee05aa70b2021-01-28 19:18:27 +080039 self._redfish_.login()
George Keishingb4e3ad42025-03-20 10:32:40 +053040 self.__inited__ = True
George Keishingeb1fe352020-06-19 03:02:22 -050041
George Keishing374e6842019-02-20 08:57:18 -060042 def get_redfish_session_info(self):
43 r"""
44 Returns redfish sessions info dictionary.
45
46 {
47 'key': 'yLXotJnrh5nDhXj5lLiH' ,
48 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
49 }
50 """
51 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060052 "key": self._redfish_.get_session_key(),
Patrick Williams20f38712022-12-08 06:18:26 -060053 "location": self._redfish_.get_session_location(),
George Keishing374e6842019-02-20 08:57:18 -060054 }
55 return session_dict
56
Sandhya Somashekar37122b62019-06-18 06:02:02 -050057 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060058 r"""
59 Get resource attribute.
60
61 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050062 resource_path URI resource absolute path (e.g.
63 "/redfish/v1/Systems/1").
64 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060065 """
George Keishing43909f32024-08-20 22:08:02 +053066 try:
67 resp = self._redfish_.get(resource_path)
68 except JSONDecodeError as e:
69 BuiltIn().log_to_console(
70 "get_attribute: JSONDecodeError, re-trying"
71 )
72 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050073
74 if verify:
75 if resp.dict[attribute] == verify:
76 return resp.dict[attribute]
77 else:
78 raise ValueError("Attribute value is not equal")
79 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060080 return resp.dict[attribute]
81
82 return None
83
George Keishingc3c05c22019-02-19 01:04:54 -060084 def get_properties(self, resource_path):
85 r"""
86 Returns dictionary of attributes for the resource.
87
88 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050089 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050090 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060091 """
92
93 resp = self._redfish_.get(resource_path)
94 return resp.dict
95
George Keishing789c3b42020-07-14 08:44:47 -050096 def get_members_uri(self, resource_path, attribute):
97 r"""
98 Returns the list of valid path which has a given attribute.
99
100 Description of argument(s):
101 resource_path URI resource base path (e.g.
102 '/redfish/v1/Systems/',
103 '/redfish/v1/Chassis/').
104 attribute Name of the attribute (e.g. 'PowerSupplies').
105 """
106
George Keishingd5f179e2020-07-14 16:07:31 -0500107 # Set quiet variable to keep subordinate get() calls quiet.
108 quiet = 1
109
George Keishing789c3b42020-07-14 08:44:47 -0500110 # Get the member id list.
111 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
112 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500113
114 valid_path_list = []
115
116 for path_idx in resource_path_list:
117 # Get all the child object path under the member id e.g.
118 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
119 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500120
121 # Iterate and check if path object has the attribute.
122 for child_path_idx in child_path_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600123 if (
124 ("JsonSchemas" in child_path_idx)
125 or ("SessionService" in child_path_idx)
126 or ("#" in child_path_idx)
127 ):
George Keishing6396bc62020-07-15 06:56:46 -0500128 continue
George Keishing789c3b42020-07-14 08:44:47 -0500129 if self.get_attribute(child_path_idx, attribute):
130 valid_path_list.append(child_path_idx)
131
George Keishingd5f179e2020-07-14 16:07:31 -0500132 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500133 return valid_path_list
134
George Keishing3a6f0732020-07-13 14:21:23 -0500135 def get_endpoint_path_list(self, resource_path, end_point_prefix):
136 r"""
137 Returns list with entries ending in "/endpoint".
138
139 Description of argument(s):
140 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500141 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500142
143 Find all list entries ending in "/endpoint" combination such as
144 /redfish/v1/Chassis/<foo>/Power
145 /redfish/v1/Chassis/<bar>/Power
146 """
147
148 end_point_list = self.list_request(resource_path)
149
150 # Regex to match entries ending in "/prefix" with optional underscore.
George Keishingd2f210a2022-02-23 10:44:10 -0600151 regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
George Keishing3a6f0732020-07-13 14:21:23 -0500152 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
153
George Keishing7ec45932019-02-27 14:02:16 -0600154 def get_target_actions(self, resource_path, target_attribute):
155 r"""
156 Returns resource target entry of the searched target attribute.
157
158 Description of argument(s):
159 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000160 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600161 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
162
163 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500164 "Actions": {
165 "#ComputerSystem.Reset": {
166 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600167 "On",
168 "ForceOff",
169 "GracefulRestart",
170 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500171 ],
172 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
173 }
174 }
George Keishing7ec45932019-02-27 14:02:16 -0600175 """
176
177 global target_list
178 target_list = []
179
180 resp_dict = self.get_attribute(resource_path, "Actions")
181 if resp_dict is None:
182 return None
183
184 # Recursively search the "target" key in the nested dictionary.
185 # Populate the target_list of target entries.
186 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600187 # Return the matching target URL entry.
188 for target in target_list:
189 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
Patrick Williams20f38712022-12-08 06:18:26 -0600190 attribute_in_uri = target.rsplit("/", 1)[-1]
Anusha Dathatriadfdb602021-04-07 01:50:24 -0500191 # attribute_in_uri "ComputerSystem.Reset"
192 if target_attribute == attribute_in_uri:
George Keishing7ec45932019-02-27 14:02:16 -0600193 return target
194
195 return None
196
George Keishingdabf38f2019-03-10 09:52:40 -0500197 def get_member_list(self, resource_path):
198 r"""
199 Perform a GET list request and return available members entries.
200
201 Description of argument(s):
202 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000203 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500204
205 "Members": [
206 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500207 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500208 }
209 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500210 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500211 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500212 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500213 """
214
215 member_list = []
216 resp_list_dict = self.get_attribute(resource_path, "Members")
217 if resp_list_dict is None:
218 return member_list
219
220 for member_id in range(0, len(resp_list_dict)):
221 member_list.append(resp_list_dict[member_id]["@odata.id"])
222
223 return member_list
224
George Keishingf2613b72019-02-13 12:45:59 -0600225 def list_request(self, resource_path):
226 r"""
227 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600228 Description of argument(s):
229 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000230 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600231 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500232 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500233 # Set quiet variable to keep subordinate get() calls quiet.
234 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500235 self.__pending_enumeration = set()
Patrick Williams20f38712022-12-08 06:18:26 -0600236 self._rest_response_ = self._redfish_.get(
237 resource_path, valid_status_codes=[200, 404, 500]
238 )
George Keishingf2613b72019-02-13 12:45:59 -0600239
240 # Return empty list.
241 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500242 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600243 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500244 if not self.__pending_enumeration:
245 return resource_path
246 for resource in self.__pending_enumeration.copy():
Patrick Williams20f38712022-12-08 06:18:26 -0600247 self._rest_response_ = self._redfish_.get(
248 resource, valid_status_codes=[200, 404, 500]
249 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500250
George Keishingf2613b72019-02-13 12:45:59 -0600251 if self._rest_response_.status != 200:
252 continue
253 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500254 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600255
Patrick Williams20f38712022-12-08 06:18:26 -0600256 def enumerate_request(
257 self, resource_path, return_json=1, include_dead_resources=False
258 ):
George Keishingf2613b72019-02-13 12:45:59 -0600259 r"""
260 Perform a GET enumerate request and return available resource paths.
261
262 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500263 resource_path URI resource absolute path (e.g.
264 "/redfish/v1/SessionService/Sessions").
265 return_json Indicates whether the result should be
266 returned as a json string or as a
267 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600268 include_dead_resources Check and return a list of dead/broken URI
269 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600270 """
271
Michael Walshc86a2f72019-03-19 13:24:37 -0500272 gp.qprint_executing(style=gp.func_line_style_short)
273
Michael Walsh37e028f2019-05-22 16:16:32 -0500274 return_json = int(return_json)
275
Michael Walshc86a2f72019-03-19 13:24:37 -0500276 # Set quiet variable to keep subordinate get() calls quiet.
277 quiet = 1
278
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500279 # Variable to hold enumerated data.
280 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600281
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500282 # Variable to hold the pending list of resources for which enumeration.
283 # is yet to be obtained.
284 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600285
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500286 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600287
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500288 # Variable having resources for which enumeration is completed.
289 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600290
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600291 if include_dead_resources:
292 dead_resources = {}
293
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500294 resources_to_be_enumerated = (resource_path,)
295
296 while resources_to_be_enumerated:
297 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600298 # JsonSchemas, SessionService or URLs containing # are not
299 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500300 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500301 # '/redfish/v1/SessionService'
ganesanb4d430282023-04-27 14:33:23 +0000302 # '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
Patrick Williams20f38712022-12-08 06:18:26 -0600303 if (
304 ("JsonSchemas" in resource)
305 or ("SessionService" in resource)
306 or ("PostCodes" in resource)
307 or ("Registries" in resource)
308 or ("Journal" in resource)
309 or ("#" in resource)
310 ):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500311 continue
312
George Keishing43909f32024-08-20 22:08:02 +0530313 try:
314 self._rest_response_ = self._redfish_.get(
315 resource, valid_status_codes=[200, 404, 405, 500]
316 )
317 except JSONDecodeError as e:
318 BuiltIn().log_to_console(
319 "enumerate_request: JSONDecodeError, re-trying"
320 )
321 self._rest_response_ = self._redfish_.get(
322 resource, valid_status_codes=[200, 404, 405, 500]
323 )
324
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500325 # Enumeration is done for available resources ignoring the
326 # ones for which response is not obtained.
327 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600328 if include_dead_resources:
329 try:
330 dead_resources[self._rest_response_.status].append(
Patrick Williams20f38712022-12-08 06:18:26 -0600331 resource
332 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600333 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600334 dead_resources[self._rest_response_.status] = [
335 resource
336 ]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500337 continue
338
339 self.walk_nested_dict(self._rest_response_.dict, url=resource)
340
341 enumerated_resources.update(set(resources_to_be_enumerated))
Patrick Williams20f38712022-12-08 06:18:26 -0600342 resources_to_be_enumerated = tuple(
343 self.__pending_enumeration - enumerated_resources
344 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500345
Michael Walsh37e028f2019-05-22 16:16:32 -0500346 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600347 if include_dead_resources:
Patrick Williams20f38712022-12-08 06:18:26 -0600348 return (
349 json.dumps(
350 self.__result,
351 sort_keys=True,
352 indent=4,
353 separators=(",", ": "),
354 ),
355 dead_resources,
356 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600357 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600358 return json.dumps(
359 self.__result,
360 sort_keys=True,
361 indent=4,
362 separators=(",", ": "),
363 )
Michael Walsh37e028f2019-05-22 16:16:32 -0500364 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600365 if include_dead_resources:
366 return self.__result, dead_resources
367 else:
368 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600369
Patrick Williams20f38712022-12-08 06:18:26 -0600370 def walk_nested_dict(self, data, url=""):
George Keishingf2613b72019-02-13 12:45:59 -0600371 r"""
372 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600373 Description of argument(s):
374 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500375 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600376 """
Patrick Williams20f38712022-12-08 06:18:26 -0600377 url = url.rstrip("/")
George Keishingf2613b72019-02-13 12:45:59 -0600378
379 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500380 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600381 if isinstance(value, dict):
382 self.walk_nested_dict(value)
383 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500384 # Value contains a list of dictionaries having member data.
Patrick Williams20f38712022-12-08 06:18:26 -0600385 if "Members" == key:
George Keishingf2613b72019-02-13 12:45:59 -0600386 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500387 for memberDict in value:
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500388 if isinstance(memberDict, str):
389 self.__pending_enumeration.add(memberDict)
Reshma Sc4873922024-11-27 15:32:15 +0530390 elif (
391 isinstance(memberDict, dict)
392 and "@odata.id" in memberDict
393 ):
Patrick Williams20f38712022-12-08 06:18:26 -0600394 self.__pending_enumeration.add(
395 memberDict["@odata.id"]
396 )
Reshma Sc4873922024-11-27 15:32:15 +0530397 else:
398 self.__pending_enumeration.add(memberDict[1])
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500399
Patrick Williams20f38712022-12-08 06:18:26 -0600400 if "@odata.id" == key:
401 value = value.rstrip("/")
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500402 # Data for the given url.
403 if value == url:
404 self.__result[url] = data
405 # Data still needs to be looked up,
406 else:
407 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600408
409 def get_key_value_nested_dict(self, data, key):
410 r"""
411 Parse through the nested dictionary and get the searched key value.
412
413 Description of argument(s):
414 data Nested dictionary data from response message.
415 key Search dictionary key element.
416 """
417
418 for k, v in data.items():
419 if isinstance(v, dict):
420 self.get_key_value_nested_dict(v, key)
421
422 if k == key:
423 target_list.append(v)