blob: e240d943b69ce0a2bb5047c398f180658ef59b66 [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
Patrick Williams20f38712022-12-08 06:18:26 -06009
George Keishinge635ddc2022-12-08 07:38:02 -060010import gen_print as gp
Patrick Williams20f38712022-12-08 06:18:26 -060011from robot.libraries.BuiltIn import BuiltIn
George Keishingf2613b72019-02-13 12:45:59 -060012
Tony Lee05aa70b2021-01-28 19:18:27 +080013MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
14
George Keishingf2613b72019-02-13 12:45:59 -060015
16class bmc_redfish_utils(object):
Patrick Williams20f38712022-12-08 06:18:26 -060017 ROBOT_LIBRARY_SCOPE = "TEST SUITE"
George Keishingeb1fe352020-06-19 03:02:22 -050018
George Keishingf2613b72019-02-13 12:45:59 -060019 def __init__(self):
20 r"""
21 Initialize the bmc_redfish_utils object.
22 """
23 # Obtain a reference to the global redfish object.
George Keishingeb1fe352020-06-19 03:02:22 -050024 self.__inited__ = False
Patrick Williams20f38712022-12-08 06:18:26 -060025 self._redfish_ = BuiltIn().get_library_instance("redfish")
George Keishingf2613b72019-02-13 12:45:59 -060026
Patrick Williams20f38712022-12-08 06:18:26 -060027 if MTLS_ENABLED == "True":
George Keishingeb1fe352020-06-19 03:02:22 -050028 self.__inited__ = True
Tony Lee05aa70b2021-01-28 19:18:27 +080029 else:
30 # There is a possibility that a given driver support both redfish and
31 # legacy REST.
32 self._redfish_.login()
Patrick Williams20f38712022-12-08 06:18:26 -060033 self._rest_response_ = self._redfish_.get(
34 "/xyz/openbmc_project/", valid_status_codes=[200, 404]
35 )
Tony Lee05aa70b2021-01-28 19:18:27 +080036
37 # If REST URL /xyz/openbmc_project/ is supported.
38 if self._rest_response_.status == 200:
39 self.__inited__ = True
George Keishingeb1fe352020-06-19 03:02:22 -050040
Patrick Williams20f38712022-12-08 06:18:26 -060041 BuiltIn().set_global_variable(
42 "${REDFISH_REST_SUPPORTED}", self.__inited__
43 )
George Keishingeb1fe352020-06-19 03:02:22 -050044
George Keishing374e6842019-02-20 08:57:18 -060045 def get_redfish_session_info(self):
46 r"""
47 Returns redfish sessions info dictionary.
48
49 {
50 'key': 'yLXotJnrh5nDhXj5lLiH' ,
51 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
52 }
53 """
54 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060055 "key": self._redfish_.get_session_key(),
Patrick Williams20f38712022-12-08 06:18:26 -060056 "location": self._redfish_.get_session_location(),
George Keishing374e6842019-02-20 08:57:18 -060057 }
58 return session_dict
59
Sandhya Somashekar37122b62019-06-18 06:02:02 -050060 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060061 r"""
62 Get resource attribute.
63
64 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050065 resource_path URI resource absolute path (e.g.
66 "/redfish/v1/Systems/1").
67 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060068 """
69
70 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050071
72 if verify:
73 if resp.dict[attribute] == verify:
74 return resp.dict[attribute]
75 else:
76 raise ValueError("Attribute value is not equal")
77 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060078 return resp.dict[attribute]
79
80 return None
81
George Keishingc3c05c22019-02-19 01:04:54 -060082 def get_properties(self, resource_path):
83 r"""
84 Returns dictionary of attributes for the resource.
85
86 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050087 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050088 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060089 """
90
91 resp = self._redfish_.get(resource_path)
92 return resp.dict
93
George Keishing789c3b42020-07-14 08:44:47 -050094 def get_members_uri(self, resource_path, attribute):
95 r"""
96 Returns the list of valid path which has a given attribute.
97
98 Description of argument(s):
99 resource_path URI resource base path (e.g.
100 '/redfish/v1/Systems/',
101 '/redfish/v1/Chassis/').
102 attribute Name of the attribute (e.g. 'PowerSupplies').
103 """
104
George Keishingd5f179e2020-07-14 16:07:31 -0500105 # Set quiet variable to keep subordinate get() calls quiet.
106 quiet = 1
107
George Keishing789c3b42020-07-14 08:44:47 -0500108 # Get the member id list.
109 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
110 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500111
112 valid_path_list = []
113
114 for path_idx in resource_path_list:
115 # Get all the child object path under the member id e.g.
116 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
117 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500118
119 # Iterate and check if path object has the attribute.
120 for child_path_idx in child_path_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600121 if (
122 ("JsonSchemas" in child_path_idx)
123 or ("SessionService" in child_path_idx)
124 or ("#" in child_path_idx)
125 ):
George Keishing6396bc62020-07-15 06:56:46 -0500126 continue
George Keishing789c3b42020-07-14 08:44:47 -0500127 if self.get_attribute(child_path_idx, attribute):
128 valid_path_list.append(child_path_idx)
129
George Keishingd5f179e2020-07-14 16:07:31 -0500130 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500131 return valid_path_list
132
George Keishing3a6f0732020-07-13 14:21:23 -0500133 def get_endpoint_path_list(self, resource_path, end_point_prefix):
134 r"""
135 Returns list with entries ending in "/endpoint".
136
137 Description of argument(s):
138 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500139 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500140
141 Find all list entries ending in "/endpoint" combination such as
142 /redfish/v1/Chassis/<foo>/Power
143 /redfish/v1/Chassis/<bar>/Power
144 """
145
146 end_point_list = self.list_request(resource_path)
147
148 # Regex to match entries ending in "/prefix" with optional underscore.
George Keishingd2f210a2022-02-23 10:44:10 -0600149 regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
George Keishing3a6f0732020-07-13 14:21:23 -0500150 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
151
George Keishing7ec45932019-02-27 14:02:16 -0600152 def get_target_actions(self, resource_path, target_attribute):
153 r"""
154 Returns resource target entry of the searched target attribute.
155
156 Description of argument(s):
157 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000158 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600159 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
160
161 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500162 "Actions": {
163 "#ComputerSystem.Reset": {
164 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600165 "On",
166 "ForceOff",
167 "GracefulRestart",
168 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500169 ],
170 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
171 }
172 }
George Keishing7ec45932019-02-27 14:02:16 -0600173 """
174
175 global target_list
176 target_list = []
177
178 resp_dict = self.get_attribute(resource_path, "Actions")
179 if resp_dict is None:
180 return None
181
182 # Recursively search the "target" key in the nested dictionary.
183 # Populate the target_list of target entries.
184 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600185 # Return the matching target URL entry.
186 for target in target_list:
187 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
Patrick Williams20f38712022-12-08 06:18:26 -0600188 attribute_in_uri = target.rsplit("/", 1)[-1]
Anusha Dathatriadfdb602021-04-07 01:50:24 -0500189 # attribute_in_uri "ComputerSystem.Reset"
190 if target_attribute == attribute_in_uri:
George Keishing7ec45932019-02-27 14:02:16 -0600191 return target
192
193 return None
194
George Keishingdabf38f2019-03-10 09:52:40 -0500195 def get_member_list(self, resource_path):
196 r"""
197 Perform a GET list request and return available members entries.
198
199 Description of argument(s):
200 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000201 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500202
203 "Members": [
204 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500205 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500206 }
207 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500208 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500209 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500210 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500211 """
212
213 member_list = []
214 resp_list_dict = self.get_attribute(resource_path, "Members")
215 if resp_list_dict is None:
216 return member_list
217
218 for member_id in range(0, len(resp_list_dict)):
219 member_list.append(resp_list_dict[member_id]["@odata.id"])
220
221 return member_list
222
George Keishingf2613b72019-02-13 12:45:59 -0600223 def list_request(self, resource_path):
224 r"""
225 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600226 Description of argument(s):
227 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000228 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600229 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500230 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500231 # Set quiet variable to keep subordinate get() calls quiet.
232 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500233 self.__pending_enumeration = set()
Patrick Williams20f38712022-12-08 06:18:26 -0600234 self._rest_response_ = self._redfish_.get(
235 resource_path, valid_status_codes=[200, 404, 500]
236 )
George Keishingf2613b72019-02-13 12:45:59 -0600237
238 # Return empty list.
239 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500240 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600241 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500242 if not self.__pending_enumeration:
243 return resource_path
244 for resource in self.__pending_enumeration.copy():
Patrick Williams20f38712022-12-08 06:18:26 -0600245 self._rest_response_ = self._redfish_.get(
246 resource, valid_status_codes=[200, 404, 500]
247 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500248
George Keishingf2613b72019-02-13 12:45:59 -0600249 if self._rest_response_.status != 200:
250 continue
251 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500252 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600253
Patrick Williams20f38712022-12-08 06:18:26 -0600254 def enumerate_request(
255 self, resource_path, return_json=1, include_dead_resources=False
256 ):
George Keishingf2613b72019-02-13 12:45:59 -0600257 r"""
258 Perform a GET enumerate request and return available resource paths.
259
260 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500261 resource_path URI resource absolute path (e.g.
262 "/redfish/v1/SessionService/Sessions").
263 return_json Indicates whether the result should be
264 returned as a json string or as a
265 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600266 include_dead_resources Check and return a list of dead/broken URI
267 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600268 """
269
Michael Walshc86a2f72019-03-19 13:24:37 -0500270 gp.qprint_executing(style=gp.func_line_style_short)
271
Michael Walsh37e028f2019-05-22 16:16:32 -0500272 return_json = int(return_json)
273
Michael Walshc86a2f72019-03-19 13:24:37 -0500274 # Set quiet variable to keep subordinate get() calls quiet.
275 quiet = 1
276
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500277 # Variable to hold enumerated data.
278 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600279
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500280 # Variable to hold the pending list of resources for which enumeration.
281 # is yet to be obtained.
282 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600283
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500284 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600285
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500286 # Variable having resources for which enumeration is completed.
287 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600288
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600289 if include_dead_resources:
290 dead_resources = {}
291
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500292 resources_to_be_enumerated = (resource_path,)
293
294 while resources_to_be_enumerated:
295 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600296 # JsonSchemas, SessionService or URLs containing # are not
297 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500298 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500299 # '/redfish/v1/SessionService'
ganesanb4d430282023-04-27 14:33:23 +0000300 # '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
Patrick Williams20f38712022-12-08 06:18:26 -0600301 if (
302 ("JsonSchemas" in resource)
303 or ("SessionService" in resource)
304 or ("PostCodes" in resource)
305 or ("Registries" in resource)
306 or ("Journal" in resource)
307 or ("#" in resource)
308 ):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500309 continue
310
Patrick Williams20f38712022-12-08 06:18:26 -0600311 self._rest_response_ = self._redfish_.get(
312 resource, valid_status_codes=[200, 404, 405, 500]
313 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500314 # Enumeration is done for available resources ignoring the
315 # ones for which response is not obtained.
316 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600317 if include_dead_resources:
318 try:
319 dead_resources[self._rest_response_.status].append(
Patrick Williams20f38712022-12-08 06:18:26 -0600320 resource
321 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600322 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600323 dead_resources[self._rest_response_.status] = [
324 resource
325 ]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500326 continue
327
328 self.walk_nested_dict(self._rest_response_.dict, url=resource)
329
330 enumerated_resources.update(set(resources_to_be_enumerated))
Patrick Williams20f38712022-12-08 06:18:26 -0600331 resources_to_be_enumerated = tuple(
332 self.__pending_enumeration - enumerated_resources
333 )
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500334
Michael Walsh37e028f2019-05-22 16:16:32 -0500335 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600336 if include_dead_resources:
Patrick Williams20f38712022-12-08 06:18:26 -0600337 return (
338 json.dumps(
339 self.__result,
340 sort_keys=True,
341 indent=4,
342 separators=(",", ": "),
343 ),
344 dead_resources,
345 )
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600346 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600347 return json.dumps(
348 self.__result,
349 sort_keys=True,
350 indent=4,
351 separators=(",", ": "),
352 )
Michael Walsh37e028f2019-05-22 16:16:32 -0500353 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600354 if include_dead_resources:
355 return self.__result, dead_resources
356 else:
357 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600358
Patrick Williams20f38712022-12-08 06:18:26 -0600359 def walk_nested_dict(self, data, url=""):
George Keishingf2613b72019-02-13 12:45:59 -0600360 r"""
361 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600362 Description of argument(s):
363 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500364 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600365 """
Patrick Williams20f38712022-12-08 06:18:26 -0600366 url = url.rstrip("/")
George Keishingf2613b72019-02-13 12:45:59 -0600367
368 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500369 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600370 if isinstance(value, dict):
371 self.walk_nested_dict(value)
372 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500373 # Value contains a list of dictionaries having member data.
Patrick Williams20f38712022-12-08 06:18:26 -0600374 if "Members" == key:
George Keishingf2613b72019-02-13 12:45:59 -0600375 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500376 for memberDict in value:
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500377 if isinstance(memberDict, str):
378 self.__pending_enumeration.add(memberDict)
379 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600380 self.__pending_enumeration.add(
381 memberDict["@odata.id"]
382 )
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500383
Patrick Williams20f38712022-12-08 06:18:26 -0600384 if "@odata.id" == key:
385 value = value.rstrip("/")
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500386 # Data for the given url.
387 if value == url:
388 self.__result[url] = data
389 # Data still needs to be looked up,
390 else:
391 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600392
393 def get_key_value_nested_dict(self, data, key):
394 r"""
395 Parse through the nested dictionary and get the searched key value.
396
397 Description of argument(s):
398 data Nested dictionary data from response message.
399 key Search dictionary key element.
400 """
401
402 for k, v in data.items():
403 if isinstance(v, dict):
404 self.get_key_value_nested_dict(v, key)
405
406 if k == key:
407 target_list.append(v)