blob: a39c5d962b8e37eb81159891ef0975a0805e82b5 [file] [log] [blame]
George Keishingf2613b72019-02-13 12:45:59 -06001#!/usr/bin/env python
2
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
12
13class bmc_redfish_utils(object):
14
George Keishingeb1fe352020-06-19 03:02:22 -050015 ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
16
George Keishingf2613b72019-02-13 12:45:59 -060017 def __init__(self):
18 r"""
19 Initialize the bmc_redfish_utils object.
20 """
21 # Obtain a reference to the global redfish object.
George Keishingeb1fe352020-06-19 03:02:22 -050022 self.__inited__ = False
George Keishingf2613b72019-02-13 12:45:59 -060023 self._redfish_ = BuiltIn().get_library_instance('redfish')
24
George Keishingeb1fe352020-06-19 03:02:22 -050025 # There is a possibility that a given driver support both redfish and
26 # legacy REST.
27 self._redfish_.login()
28 self._rest_response_ = \
29 self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
30
31 # If REST URL /xyz/openbmc_project/ is supported.
32 if self._rest_response_.status == 200:
33 self.__inited__ = True
34
35 BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", self.__inited__)
36
George Keishing374e6842019-02-20 08:57:18 -060037 def get_redfish_session_info(self):
38 r"""
39 Returns redfish sessions info dictionary.
40
41 {
42 'key': 'yLXotJnrh5nDhXj5lLiH' ,
43 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
44 }
45 """
46 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060047 "key": self._redfish_.get_session_key(),
48 "location": self._redfish_.get_session_location()
George Keishing374e6842019-02-20 08:57:18 -060049 }
50 return session_dict
51
Sandhya Somashekar37122b62019-06-18 06:02:02 -050052 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060053 r"""
54 Get resource attribute.
55
56 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050057 resource_path URI resource absolute path (e.g.
58 "/redfish/v1/Systems/1").
59 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060060 """
61
62 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050063
64 if verify:
65 if resp.dict[attribute] == verify:
66 return resp.dict[attribute]
67 else:
68 raise ValueError("Attribute value is not equal")
69 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060070 return resp.dict[attribute]
71
72 return None
73
George Keishingc3c05c22019-02-19 01:04:54 -060074 def get_properties(self, resource_path):
75 r"""
76 Returns dictionary of attributes for the resource.
77
78 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050079 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050080 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060081 """
82
83 resp = self._redfish_.get(resource_path)
84 return resp.dict
85
George Keishing789c3b42020-07-14 08:44:47 -050086 def get_members_uri(self, resource_path, attribute):
87 r"""
88 Returns the list of valid path which has a given attribute.
89
90 Description of argument(s):
91 resource_path URI resource base path (e.g.
92 '/redfish/v1/Systems/',
93 '/redfish/v1/Chassis/').
94 attribute Name of the attribute (e.g. 'PowerSupplies').
95 """
96
George Keishingd5f179e2020-07-14 16:07:31 -050097 # Set quiet variable to keep subordinate get() calls quiet.
98 quiet = 1
99
George Keishing789c3b42020-07-14 08:44:47 -0500100 # Get the member id list.
101 # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
102 resource_path_list = self.get_member_list(resource_path)
George Keishing789c3b42020-07-14 08:44:47 -0500103
104 valid_path_list = []
105
106 for path_idx in resource_path_list:
107 # Get all the child object path under the member id e.g.
108 # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
109 child_path_list = self.list_request(path_idx)
George Keishing789c3b42020-07-14 08:44:47 -0500110
111 # Iterate and check if path object has the attribute.
112 for child_path_idx in child_path_list:
George Keishing6396bc62020-07-15 06:56:46 -0500113 if ('JsonSchemas' in child_path_idx)\
114 or ('SessionService' in child_path_idx)\
115 or ('#' in child_path_idx):
116 continue
George Keishing789c3b42020-07-14 08:44:47 -0500117 if self.get_attribute(child_path_idx, attribute):
118 valid_path_list.append(child_path_idx)
119
George Keishingd5f179e2020-07-14 16:07:31 -0500120 BuiltIn().log_to_console(valid_path_list)
George Keishing789c3b42020-07-14 08:44:47 -0500121 return valid_path_list
122
George Keishing3a6f0732020-07-13 14:21:23 -0500123 def get_endpoint_path_list(self, resource_path, end_point_prefix):
124 r"""
125 Returns list with entries ending in "/endpoint".
126
127 Description of argument(s):
128 resource_path URI resource base path (e.g. "/redfish/v1/Chassis/").
George Keishinge68cbfb2020-08-12 11:11:58 -0500129 end_point_prefix Name of the endpoint (e.g. 'Power').
George Keishing3a6f0732020-07-13 14:21:23 -0500130
131 Find all list entries ending in "/endpoint" combination such as
132 /redfish/v1/Chassis/<foo>/Power
133 /redfish/v1/Chassis/<bar>/Power
134 """
135
136 end_point_list = self.list_request(resource_path)
137
138 # Regex to match entries ending in "/prefix" with optional underscore.
139 regex = ".*/" + end_point_prefix + "[_]?[0-9]*?"
140 return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
141
George Keishing7ec45932019-02-27 14:02:16 -0600142 def get_target_actions(self, resource_path, target_attribute):
143 r"""
144 Returns resource target entry of the searched target attribute.
145
146 Description of argument(s):
147 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000148 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -0600149 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
150
151 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500152 "Actions": {
153 "#ComputerSystem.Reset": {
154 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -0600155 "On",
156 "ForceOff",
157 "GracefulRestart",
158 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500159 ],
160 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
161 }
162 }
George Keishing7ec45932019-02-27 14:02:16 -0600163 """
164
165 global target_list
166 target_list = []
167
168 resp_dict = self.get_attribute(resource_path, "Actions")
169 if resp_dict is None:
170 return None
171
172 # Recursively search the "target" key in the nested dictionary.
173 # Populate the target_list of target entries.
174 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600175 # Return the matching target URL entry.
176 for target in target_list:
177 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
178 if target_attribute in target:
179 return target
180
181 return None
182
George Keishingdabf38f2019-03-10 09:52:40 -0500183 def get_member_list(self, resource_path):
184 r"""
185 Perform a GET list request and return available members entries.
186
187 Description of argument(s):
188 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000189 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500190
191 "Members": [
192 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500193 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500194 }
195 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500196 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500197 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500198 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500199 """
200
201 member_list = []
202 resp_list_dict = self.get_attribute(resource_path, "Members")
203 if resp_list_dict is None:
204 return member_list
205
206 for member_id in range(0, len(resp_list_dict)):
207 member_list.append(resp_list_dict[member_id]["@odata.id"])
208
209 return member_list
210
George Keishingf2613b72019-02-13 12:45:59 -0600211 def list_request(self, resource_path):
212 r"""
213 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600214 Description of argument(s):
215 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000216 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600217 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500218 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500219 # Set quiet variable to keep subordinate get() calls quiet.
220 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500221 self.__pending_enumeration = set()
Michael Walshc86a2f72019-03-19 13:24:37 -0500222 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000223 self._redfish_.get(resource_path,
224 valid_status_codes=[200, 404, 500])
George Keishingf2613b72019-02-13 12:45:59 -0600225
226 # Return empty list.
227 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500228 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600229 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500230 if not self.__pending_enumeration:
231 return resource_path
232 for resource in self.__pending_enumeration.copy():
Michael Walshc86a2f72019-03-19 13:24:37 -0500233 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000234 self._redfish_.get(resource,
235 valid_status_codes=[200, 404, 500])
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500236
George Keishingf2613b72019-02-13 12:45:59 -0600237 if self._rest_response_.status != 200:
238 continue
239 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500240 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600241
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600242 def enumerate_request(self, resource_path, return_json=1,
243 include_dead_resources=False):
George Keishingf2613b72019-02-13 12:45:59 -0600244 r"""
245 Perform a GET enumerate request and return available resource paths.
246
247 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500248 resource_path URI resource absolute path (e.g.
249 "/redfish/v1/SessionService/Sessions").
250 return_json Indicates whether the result should be
251 returned as a json string or as a
252 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600253 include_dead_resources Check and return a list of dead/broken URI
254 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600255 """
256
Michael Walshc86a2f72019-03-19 13:24:37 -0500257 gp.qprint_executing(style=gp.func_line_style_short)
258
Michael Walsh37e028f2019-05-22 16:16:32 -0500259 return_json = int(return_json)
260
Michael Walshc86a2f72019-03-19 13:24:37 -0500261 # Set quiet variable to keep subordinate get() calls quiet.
262 quiet = 1
263
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500264 # Variable to hold enumerated data.
265 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600266
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500267 # Variable to hold the pending list of resources for which enumeration.
268 # is yet to be obtained.
269 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600270
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500271 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600272
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500273 # Variable having resources for which enumeration is completed.
274 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600275
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600276 if include_dead_resources:
277 dead_resources = {}
278
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500279 resources_to_be_enumerated = (resource_path,)
280
281 while resources_to_be_enumerated:
282 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600283 # JsonSchemas, SessionService or URLs containing # are not
284 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500285 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500286 # '/redfish/v1/SessionService'
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600287 # '/redfish/v1/Managers/bmc#/Oem'
288 if ('JsonSchemas' in resource) or ('SessionService' in resource)\
289 or ('#' in resource):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500290 continue
291
292 self._rest_response_ = \
293 self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
294 # Enumeration is done for available resources ignoring the
295 # ones for which response is not obtained.
296 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600297 if include_dead_resources:
298 try:
299 dead_resources[self._rest_response_.status].append(
300 resource)
301 except KeyError:
302 dead_resources[self._rest_response_.status] = \
303 [resource]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500304 continue
305
306 self.walk_nested_dict(self._rest_response_.dict, url=resource)
307
308 enumerated_resources.update(set(resources_to_be_enumerated))
309 resources_to_be_enumerated = \
310 tuple(self.__pending_enumeration - enumerated_resources)
311
Michael Walsh37e028f2019-05-22 16:16:32 -0500312 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600313 if include_dead_resources:
314 return json.dumps(self.__result, sort_keys=True,
315 indent=4, separators=(',', ': ')), dead_resources
316 else:
317 return json.dumps(self.__result, sort_keys=True,
318 indent=4, separators=(',', ': '))
Michael Walsh37e028f2019-05-22 16:16:32 -0500319 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600320 if include_dead_resources:
321 return self.__result, dead_resources
322 else:
323 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600324
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500325 def walk_nested_dict(self, data, url=''):
George Keishingf2613b72019-02-13 12:45:59 -0600326 r"""
327 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600328 Description of argument(s):
329 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500330 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600331 """
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500332 url = url.rstrip('/')
George Keishingf2613b72019-02-13 12:45:59 -0600333
334 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500335
336 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600337 if isinstance(value, dict):
338 self.walk_nested_dict(value)
339 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500340 # Value contains a list of dictionaries having member data.
George Keishingf2613b72019-02-13 12:45:59 -0600341 if 'Members' == key:
342 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500343 for memberDict in value:
344 self.__pending_enumeration.add(memberDict['@odata.id'])
George Keishingf2613b72019-02-13 12:45:59 -0600345 if '@odata.id' == key:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500346 value = value.rstrip('/')
347 # Data for the given url.
348 if value == url:
349 self.__result[url] = data
350 # Data still needs to be looked up,
351 else:
352 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600353
354 def get_key_value_nested_dict(self, data, key):
355 r"""
356 Parse through the nested dictionary and get the searched key value.
357
358 Description of argument(s):
359 data Nested dictionary data from response message.
360 key Search dictionary key element.
361 """
362
363 for k, v in data.items():
364 if isinstance(v, dict):
365 self.get_key_value_nested_dict(v, key)
366
367 if k == key:
368 target_list.append(v)