blob: 8299abd51d0a5806ada2699406ad93ba8744143e [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
Patrick Williams20f38712022-12-08 06:18:26 -060026 self._redfish_ = BuiltIn().get_library_instance("redfish")
George Keishingf2613b72019-02-13 12:45:59 -060027
Patrick Williams20f38712022-12-08 06:18:26 -060028 if MTLS_ENABLED == "True":
George Keishingeb1fe352020-06-19 03:02:22 -050029 self.__inited__ = True
Tony Lee05aa70b2021-01-28 19:18:27 +080030 else:
31 # There is a possibility that a given driver support both redfish and
32 # legacy REST.
33 self._redfish_.login()
Patrick Williams20f38712022-12-08 06:18:26 -060034 self._rest_response_ = self._redfish_.get(
35 "/xyz/openbmc_project/", valid_status_codes=[200, 404]
36 )
Tony Lee05aa70b2021-01-28 19:18:27 +080037
38 # If REST URL /xyz/openbmc_project/ is supported.
39 if self._rest_response_.status == 200:
40 self.__inited__ = True
George Keishingeb1fe352020-06-19 03:02:22 -050041
Patrick Williams20f38712022-12-08 06:18:26 -060042 BuiltIn().set_global_variable(
43 "${REDFISH_REST_SUPPORTED}", self.__inited__
44 )
George Keishingeb1fe352020-06-19 03:02:22 -050045
George Keishing374e6842019-02-20 08:57:18 -060046 def get_redfish_session_info(self):
47 r"""
48 Returns redfish sessions info dictionary.
49
50 {
51 'key': 'yLXotJnrh5nDhXj5lLiH' ,
52 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
53 }
54 """
55 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060056 "key": self._redfish_.get_session_key(),
Patrick Williams20f38712022-12-08 06:18:26 -060057 "location": self._redfish_.get_session_location(),
George Keishing374e6842019-02-20 08:57:18 -060058 }
59 return session_dict
60
Sandhya Somashekar37122b62019-06-18 06:02:02 -050061 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060062 r"""
63 Get resource attribute.
64
65 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050066 resource_path URI resource absolute path (e.g.
67 "/redfish/v1/Systems/1").
68 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060069 """
George Keishing43909f32024-08-20 22:08:02 +053070 try:
71 resp = self._redfish_.get(resource_path)
72 except JSONDecodeError as e:
73 BuiltIn().log_to_console(
74 "get_attribute: JSONDecodeError, re-trying"
75 )
76 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050077
78 if verify:
79 if resp.dict[attribute] == verify:
80 return resp.dict[attribute]
81 else:
82 raise ValueError("Attribute value is not equal")
83 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060084 return resp.dict[attribute]
85
86 return None
87
George Keishingc3c05c22019-02-19 01:04:54 -060088 def get_properties(self, resource_path):
89 r"""
90 Returns dictionary of attributes for the resource.
91
92 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050093 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050094 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060095 """
96
97 resp = self._redfish_.get(resource_path)
98 return resp.dict
99
George Keishing789c3b42020-07-14 08:44:47 -0500100 def get_members_uri(self, resource_path, attribute):
101 r"""
102 Returns the list of valid path which has a given attribute.
103
104 Description of argument(s):
105 resource_path URI resource base path (e.g.
106 '/redfish/v1/Systems/',
107 '/redfish/v1/Chassis/').
108 attribute Name of the attribute (e.g. 'PowerSupplies').
109 """
110
George Keishingd5f179e2020-07-14 16:07:31 -0500111 # Set quiet variable to keep subordinate get() calls quiet.
112 quiet = 1
113
George Keishing789c3b42020-07-14 08:44:47 -0500114 # Get the member id list.
115 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
116 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500117
118 valid_path_list = []
119
120 for path_idx in resource_path_list:
121 # Get all the child object path under the member id e.g.
122 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
123 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500124
125 # Iterate and check if path object has the attribute.
126 for child_path_idx in child_path_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600127 if (
128 ("JsonSchemas" in child_path_idx)
129 or ("SessionService" in child_path_idx)
130 or ("#" in child_path_idx)
131 ):
George Keishing6396bc62020-07-15 06:56:46 -0500132 continue
George Keishing789c3b42020-07-14 08:44:47 -0500133 if self.get_attribute(child_path_idx, attribute):
134 valid_path_list.append(child_path_idx)
135
George Keishingd5f179e2020-07-14 16:07:31 -0500136 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500137 return valid_path_list
138
George Keishing3a6f0732020-07-13 14:21:23 -0500139 def get_endpoint_path_list(self, resource_path, end_point_prefix):
140 r"""
141 Returns list with entries ending in "/endpoint".
142
143 Description of argument(s):
144 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500145 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500146
147 Find all list entries ending in "/endpoint" combination such as
148 /redfish/v1/Chassis/<foo>/Power
149 /redfish/v1/Chassis/<bar>/Power
150 """
151
152 end_point_list = self.list_request(resource_path)
153
154 # Regex to match entries ending in "/prefix" with optional underscore.
George Keishingd2f210a2022-02-23 10:44:10 -0600155 regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
George Keishing3a6f0732020-07-13 14:21:23 -0500156 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
157
George Keishing7ec45932019-02-27 14:02:16 -0600158 def get_target_actions(self, resource_path, target_attribute):
159 r"""
160 Returns resource target entry of the searched target attribute.
161
162 Description of argument(s):
163 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000164 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600165 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
166
167 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500168 "Actions": {
169 "#ComputerSystem.Reset": {
170 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600171 "On",
172 "ForceOff",
173 "GracefulRestart",
174 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500175 ],
176 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
177 }
178 }
George Keishing7ec45932019-02-27 14:02:16 -0600179 """
180
181 global target_list
182 target_list = []
183
184 resp_dict = self.get_attribute(resource_path, "Actions")
185 if resp_dict is None:
186 return None
187
188 # Recursively search the "target" key in the nested dictionary.
189 # Populate the target_list of target entries.
190 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600191 # Return the matching target URL entry.
192 for target in target_list:
193 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
Patrick Williams20f38712022-12-08 06:18:26 -0600194 attribute_in_uri = target.rsplit("/", 1)[-1]
Anusha Dathatriadfdb602021-04-07 01:50:24 -0500195 # attribute_in_uri "ComputerSystem.Reset"
196 if target_attribute == attribute_in_uri:
George Keishing7ec45932019-02-27 14:02:16 -0600197 return target
198
199 return None
200
George Keishingdabf38f2019-03-10 09:52:40 -0500201 def get_member_list(self, resource_path):
202 r"""
203 Perform a GET list request and return available members entries.
204
205 Description of argument(s):
206 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000207 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500208
209 "Members": [
210 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500211 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500212 }
213 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500214 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500215 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500216 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500217 """
218
219 member_list = []
220 resp_list_dict = self.get_attribute(resource_path, "Members")
221 if resp_list_dict is None:
222 return member_list
223
224 for member_id in range(0, len(resp_list_dict)):
225 member_list.append(resp_list_dict[member_id]["@odata.id"])
226
227 return member_list
228
George Keishingf2613b72019-02-13 12:45:59 -0600229 def list_request(self, resource_path):
230 r"""
231 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600232 Description of argument(s):
233 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000234 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600235 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500236 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500237 # Set quiet variable to keep subordinate get() calls quiet.
238 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500239 self.__pending_enumeration = set()
Patrick Williams20f38712022-12-08 06:18:26 -0600240 self._rest_response_ = self._redfish_.get(
241 resource_path, valid_status_codes=[200, 404, 500]
242 )
George Keishingf2613b72019-02-13 12:45:59 -0600243
244 # Return empty list.
245 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500246 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600247 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500248 if not self.__pending_enumeration:
249 return resource_path
250 for resource in self.__pending_enumeration.copy():
Patrick Williams20f38712022-12-08 06:18:26 -0600251 self._rest_response_ = self._redfish_.get(
252 resource, valid_status_codes=[200, 404, 500]
253 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500254
George Keishingf2613b72019-02-13 12:45:59 -0600255 if self._rest_response_.status != 200:
256 continue
257 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500258 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600259
Patrick Williams20f38712022-12-08 06:18:26 -0600260 def enumerate_request(
261 self, resource_path, return_json=1, include_dead_resources=False
262 ):
George Keishingf2613b72019-02-13 12:45:59 -0600263 r"""
264 Perform a GET enumerate request and return available resource paths.
265
266 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500267 resource_path URI resource absolute path (e.g.
268 "/redfish/v1/SessionService/Sessions").
269 return_json Indicates whether the result should be
270 returned as a json string or as a
271 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600272 include_dead_resources Check and return a list of dead/broken URI
273 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600274 """
275
Michael Walshc86a2f72019-03-19 13:24:37 -0500276 gp.qprint_executing(style=gp.func_line_style_short)
277
Michael Walsh37e028f2019-05-22 16:16:32 -0500278 return_json = int(return_json)
279
Michael Walshc86a2f72019-03-19 13:24:37 -0500280 # Set quiet variable to keep subordinate get() calls quiet.
281 quiet = 1
282
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500283 # Variable to hold enumerated data.
284 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600285
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500286 # Variable to hold the pending list of resources for which enumeration.
287 # is yet to be obtained.
288 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600289
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500290 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600291
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500292 # Variable having resources for which enumeration is completed.
293 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600294
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600295 if include_dead_resources:
296 dead_resources = {}
297
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500298 resources_to_be_enumerated = (resource_path,)
299
300 while resources_to_be_enumerated:
301 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600302 # JsonSchemas, SessionService or URLs containing # are not
303 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500304 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500305 # '/redfish/v1/SessionService'
ganesanb4d430282023-04-27 14:33:23 +0000306 # '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
Patrick Williams20f38712022-12-08 06:18:26 -0600307 if (
308 ("JsonSchemas" in resource)
309 or ("SessionService" in resource)
310 or ("PostCodes" in resource)
311 or ("Registries" in resource)
312 or ("Journal" in resource)
313 or ("#" in resource)
314 ):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500315 continue
316
George Keishing43909f32024-08-20 22:08:02 +0530317 try:
318 self._rest_response_ = self._redfish_.get(
319 resource, valid_status_codes=[200, 404, 405, 500]
320 )
321 except JSONDecodeError as e:
322 BuiltIn().log_to_console(
323 "enumerate_request: JSONDecodeError, re-trying"
324 )
325 self._rest_response_ = self._redfish_.get(
326 resource, valid_status_codes=[200, 404, 405, 500]
327 )
328
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500329 # Enumeration is done for available resources ignoring the
330 # ones for which response is not obtained.
331 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600332 if include_dead_resources:
333 try:
334 dead_resources[self._rest_response_.status].append(
Patrick Williams20f38712022-12-08 06:18:26 -0600335 resource
336 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600337 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600338 dead_resources[self._rest_response_.status] = [
339 resource
340 ]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500341 continue
342
343 self.walk_nested_dict(self._rest_response_.dict, url=resource)
344
345 enumerated_resources.update(set(resources_to_be_enumerated))
Patrick Williams20f38712022-12-08 06:18:26 -0600346 resources_to_be_enumerated = tuple(
347 self.__pending_enumeration - enumerated_resources
348 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500349
Michael Walsh37e028f2019-05-22 16:16:32 -0500350 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600351 if include_dead_resources:
Patrick Williams20f38712022-12-08 06:18:26 -0600352 return (
353 json.dumps(
354 self.__result,
355 sort_keys=True,
356 indent=4,
357 separators=(",", ": "),
358 ),
359 dead_resources,
360 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600361 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600362 return json.dumps(
363 self.__result,
364 sort_keys=True,
365 indent=4,
366 separators=(",", ": "),
367 )
Michael Walsh37e028f2019-05-22 16:16:32 -0500368 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600369 if include_dead_resources:
370 return self.__result, dead_resources
371 else:
372 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600373
Patrick Williams20f38712022-12-08 06:18:26 -0600374 def walk_nested_dict(self, data, url=""):
George Keishingf2613b72019-02-13 12:45:59 -0600375 r"""
376 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600377 Description of argument(s):
378 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500379 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600380 """
Patrick Williams20f38712022-12-08 06:18:26 -0600381 url = url.rstrip("/")
George Keishingf2613b72019-02-13 12:45:59 -0600382
383 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500384 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600385 if isinstance(value, dict):
386 self.walk_nested_dict(value)
387 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500388 # Value contains a list of dictionaries having member data.
Patrick Williams20f38712022-12-08 06:18:26 -0600389 if "Members" == key:
George Keishingf2613b72019-02-13 12:45:59 -0600390 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500391 for memberDict in value:
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500392 if isinstance(memberDict, str):
393 self.__pending_enumeration.add(memberDict)
394 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600395 self.__pending_enumeration.add(
396 memberDict["@odata.id"]
397 )
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500398
Patrick Williams20f38712022-12-08 06:18:26 -0600399 if "@odata.id" == key:
400 value = value.rstrip("/")
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500401 # Data for the given url.
402 if value == url:
403 self.__result[url] = data
404 # Data still needs to be looked up,
405 else:
406 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600407
408 def get_key_value_nested_dict(self, data, key):
409 r"""
410 Parse through the nested dictionary and get the searched key value.
411
412 Description of argument(s):
413 data Nested dictionary data from response message.
414 key Search dictionary key element.
415 """
416
417 for k, v in data.items():
418 if isinstance(v, dict):
419 self.get_key_value_nested_dict(v, key)
420
421 if k == key:
422 target_list.append(v)