blob: 8eade5f3b12e129f7d64fd003d8b84b79e3c3a7e [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 Keishingf2613b72019-02-13 12:45:59 -06009from robot.libraries.BuiltIn import BuiltIn
Michael Walshc86a2f72019-03-19 13:24:37 -050010import gen_print as gp
George Keishingf2613b72019-02-13 12:45:59 -060011
Tony Lee05aa70b2021-01-28 19:18:27 +080012MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
13
George Keishingf2613b72019-02-13 12:45:59 -060014
15class bmc_redfish_utils(object):
16
George Keishingeb1fe352020-06-19 03:02:22 -050017 ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
18
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
George Keishingf2613b72019-02-13 12:45:59 -060025 self._redfish_ = BuiltIn().get_library_instance('redfish')
26
Tony Lee05aa70b2021-01-28 19:18:27 +080027 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()
33 self._rest_response_ = \
34 self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
35
36 # If REST URL /xyz/openbmc_project/ is supported.
37 if self._rest_response_.status == 200:
38 self.__inited__ = True
George Keishingeb1fe352020-06-19 03:02:22 -050039
40 BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", self.__inited__)
41
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(),
53 "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 """
66
67 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050068
69 if verify:
70 if resp.dict[attribute] == verify:
71 return resp.dict[attribute]
72 else:
73 raise ValueError("Attribute value is not equal")
74 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060075 return resp.dict[attribute]
76
77 return None
78
George Keishingc3c05c22019-02-19 01:04:54 -060079 def get_properties(self, resource_path):
80 r"""
81 Returns dictionary of attributes for the resource.
82
83 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050084 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050085 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060086 """
87
88 resp = self._redfish_.get(resource_path)
89 return resp.dict
90
George Keishing789c3b42020-07-14 08:44:47 -050091 def get_members_uri(self, resource_path, attribute):
92 r"""
93 Returns the list of valid path which has a given attribute.
94
95 Description of argument(s):
96 resource_path URI resource base path (e.g.
97 '/redfish/v1/Systems/',
98 '/redfish/v1/Chassis/').
99 attribute Name of the attribute (e.g. 'PowerSupplies').
100 """
101
George Keishingd5f179e2020-07-14 16:07:31 -0500102 # Set quiet variable to keep subordinate get() calls quiet.
103 quiet = 1
104
George Keishing789c3b42020-07-14 08:44:47 -0500105 # Get the member id list.
106 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
107 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500108
109 valid_path_list = []
110
111 for path_idx in resource_path_list:
112 # Get all the child object path under the member id e.g.
113 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
114 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500115
116 # Iterate and check if path object has the attribute.
117 for child_path_idx in child_path_list:
George Keishing6396bc62020-07-15 06:56:46 -0500118 if ('JsonSchemas' in child_path_idx)\
119 or ('SessionService' in child_path_idx)\
120 or ('#' in child_path_idx):
121 continue
George Keishing789c3b42020-07-14 08:44:47 -0500122 if self.get_attribute(child_path_idx, attribute):
123 valid_path_list.append(child_path_idx)
124
George Keishingd5f179e2020-07-14 16:07:31 -0500125 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500126 return valid_path_list
127
George Keishing3a6f0732020-07-13 14:21:23 -0500128 def get_endpoint_path_list(self, resource_path, end_point_prefix):
129 r"""
130 Returns list with entries ending in "/endpoint".
131
132 Description of argument(s):
133 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500134 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500135
136 Find all list entries ending in "/endpoint" combination such as
137 /redfish/v1/Chassis/<foo>/Power
138 /redfish/v1/Chassis/<bar>/Power
139 """
140
141 end_point_list = self.list_request(resource_path)
142
143 # Regex to match entries ending in "/prefix" with optional underscore.
George Keishingd2f210a2022-02-23 10:44:10 -0600144 regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
George Keishing3a6f0732020-07-13 14:21:23 -0500145 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
146
George Keishing7ec45932019-02-27 14:02:16 -0600147 def get_target_actions(self, resource_path, target_attribute):
148 r"""
149 Returns resource target entry of the searched target attribute.
150
151 Description of argument(s):
152 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000153 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600154 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
155
156 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500157 "Actions": {
158 "#ComputerSystem.Reset": {
159 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600160 "On",
161 "ForceOff",
162 "GracefulRestart",
163 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500164 ],
165 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
166 }
167 }
George Keishing7ec45932019-02-27 14:02:16 -0600168 """
169
170 global target_list
171 target_list = []
172
173 resp_dict = self.get_attribute(resource_path, "Actions")
174 if resp_dict is None:
175 return None
176
177 # Recursively search the "target" key in the nested dictionary.
178 # Populate the target_list of target entries.
179 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600180 # Return the matching target URL entry.
181 for target in target_list:
182 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
Anusha Dathatriadfdb602021-04-07 01:50:24 -0500183 attribute_in_uri = target.rsplit('/', 1)[-1]
184 # attribute_in_uri "ComputerSystem.Reset"
185 if target_attribute == attribute_in_uri:
George Keishing7ec45932019-02-27 14:02:16 -0600186 return target
187
188 return None
189
George Keishingdabf38f2019-03-10 09:52:40 -0500190 def get_member_list(self, resource_path):
191 r"""
192 Perform a GET list request and return available members entries.
193
194 Description of argument(s):
195 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000196 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500197
198 "Members": [
199 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500200 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500201 }
202 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500203 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500204 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500205 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500206 """
207
208 member_list = []
209 resp_list_dict = self.get_attribute(resource_path, "Members")
210 if resp_list_dict is None:
211 return member_list
212
213 for member_id in range(0, len(resp_list_dict)):
214 member_list.append(resp_list_dict[member_id]["@odata.id"])
215
216 return member_list
217
George Keishingf2613b72019-02-13 12:45:59 -0600218 def list_request(self, resource_path):
219 r"""
220 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600221 Description of argument(s):
222 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000223 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600224 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500225 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500226 # Set quiet variable to keep subordinate get() calls quiet.
227 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500228 self.__pending_enumeration = set()
Michael Walshc86a2f72019-03-19 13:24:37 -0500229 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000230 self._redfish_.get(resource_path,
231 valid_status_codes=[200, 404, 500])
George Keishingf2613b72019-02-13 12:45:59 -0600232
233 # Return empty list.
234 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500235 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600236 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500237 if not self.__pending_enumeration:
238 return resource_path
239 for resource in self.__pending_enumeration.copy():
Michael Walshc86a2f72019-03-19 13:24:37 -0500240 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000241 self._redfish_.get(resource,
242 valid_status_codes=[200, 404, 500])
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500243
George Keishingf2613b72019-02-13 12:45:59 -0600244 if self._rest_response_.status != 200:
245 continue
246 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500247 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600248
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600249 def enumerate_request(self, resource_path, return_json=1,
250 include_dead_resources=False):
George Keishingf2613b72019-02-13 12:45:59 -0600251 r"""
252 Perform a GET enumerate request and return available resource paths.
253
254 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500255 resource_path URI resource absolute path (e.g.
256 "/redfish/v1/SessionService/Sessions").
257 return_json Indicates whether the result should be
258 returned as a json string or as a
259 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600260 include_dead_resources Check and return a list of dead/broken URI
261 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600262 """
263
Michael Walshc86a2f72019-03-19 13:24:37 -0500264 gp.qprint_executing(style=gp.func_line_style_short)
265
Michael Walsh37e028f2019-05-22 16:16:32 -0500266 return_json = int(return_json)
267
Michael Walshc86a2f72019-03-19 13:24:37 -0500268 # Set quiet variable to keep subordinate get() calls quiet.
269 quiet = 1
270
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500271 # Variable to hold enumerated data.
272 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600273
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500274 # Variable to hold the pending list of resources for which enumeration.
275 # is yet to be obtained.
276 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600277
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500278 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600279
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500280 # Variable having resources for which enumeration is completed.
281 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600282
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600283 if include_dead_resources:
284 dead_resources = {}
285
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500286 resources_to_be_enumerated = (resource_path,)
287
288 while resources_to_be_enumerated:
289 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600290 # JsonSchemas, SessionService or URLs containing # are not
291 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500292 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500293 # '/redfish/v1/SessionService'
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600294 # '/redfish/v1/Managers/bmc#/Oem'
George Keishing76407612021-08-04 13:17:42 +0000295 if ('JsonSchemas' in resource) or ('SessionService' in resource)\
George Keishingd5f57222021-08-10 23:52:30 -0500296 or ('PostCodes' in resource) or ('Registries' in resource)\
Tim Leeaf145b62021-12-15 08:56:41 +0800297 or ('Journal' in resource)\
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600298 or ('#' in resource):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500299 continue
300
301 self._rest_response_ = \
George Keishing985659a2021-06-23 02:52:13 -0500302 self._redfish_.get(resource, valid_status_codes=[200, 404, 405, 500])
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500303 # Enumeration is done for available resources ignoring the
304 # ones for which response is not obtained.
305 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600306 if include_dead_resources:
307 try:
308 dead_resources[self._rest_response_.status].append(
309 resource)
310 except KeyError:
311 dead_resources[self._rest_response_.status] = \
312 [resource]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500313 continue
314
315 self.walk_nested_dict(self._rest_response_.dict, url=resource)
316
317 enumerated_resources.update(set(resources_to_be_enumerated))
318 resources_to_be_enumerated = \
319 tuple(self.__pending_enumeration - enumerated_resources)
320
Michael Walsh37e028f2019-05-22 16:16:32 -0500321 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600322 if include_dead_resources:
323 return json.dumps(self.__result, sort_keys=True,
324 indent=4, separators=(',', ': ')), dead_resources
325 else:
326 return json.dumps(self.__result, sort_keys=True,
327 indent=4, separators=(',', ': '))
Michael Walsh37e028f2019-05-22 16:16:32 -0500328 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600329 if include_dead_resources:
330 return self.__result, dead_resources
331 else:
332 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600333
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500334 def walk_nested_dict(self, data, url=''):
George Keishingf2613b72019-02-13 12:45:59 -0600335 r"""
336 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600337 Description of argument(s):
338 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500339 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600340 """
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500341 url = url.rstrip('/')
George Keishingf2613b72019-02-13 12:45:59 -0600342
343 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500344
345 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600346 if isinstance(value, dict):
347 self.walk_nested_dict(value)
348 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500349 # Value contains a list of dictionaries having member data.
George Keishingf2613b72019-02-13 12:45:59 -0600350 if 'Members' == key:
351 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500352 for memberDict in value:
Anusha Dathatri0d305d32021-07-08 00:56:11 -0500353 if isinstance(memberDict, str):
354 self.__pending_enumeration.add(memberDict)
355 else:
356 self.__pending_enumeration.add(memberDict['@odata.id'])
357
George Keishingf2613b72019-02-13 12:45:59 -0600358 if '@odata.id' == key:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500359 value = value.rstrip('/')
360 # Data for the given url.
361 if value == url:
362 self.__result[url] = data
363 # Data still needs to be looked up,
364 else:
365 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600366
367 def get_key_value_nested_dict(self, data, key):
368 r"""
369 Parse through the nested dictionary and get the searched key value.
370
371 Description of argument(s):
372 data Nested dictionary data from response message.
373 key Search dictionary key element.
374 """
375
376 for k, v in data.items():
377 if isinstance(v, dict):
378 self.get_key_value_nested_dict(v, key)
379
380 if k == key:
381 target_list.append(v)