blob: f8d2a94d80de0681fa3fff184925bc4faf5770e1 [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
8from robot.libraries.BuiltIn import BuiltIn
Michael Walshc86a2f72019-03-19 13:24:37 -05009import gen_print as gp
George Keishingf2613b72019-02-13 12:45:59 -060010
11
12class bmc_redfish_utils(object):
13
George Keishingeb1fe352020-06-19 03:02:22 -050014 ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
15
George Keishingf2613b72019-02-13 12:45:59 -060016 def __init__(self):
17 r"""
18 Initialize the bmc_redfish_utils object.
19 """
20 # Obtain a reference to the global redfish object.
George Keishingeb1fe352020-06-19 03:02:22 -050021 self.__inited__ = False
George Keishingf2613b72019-02-13 12:45:59 -060022 self._redfish_ = BuiltIn().get_library_instance('redfish')
23
George Keishingeb1fe352020-06-19 03:02:22 -050024 # There is a possibility that a given driver support both redfish and
25 # legacy REST.
26 self._redfish_.login()
27 self._rest_response_ = \
28 self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
29
30 # If REST URL /xyz/openbmc_project/ is supported.
31 if self._rest_response_.status == 200:
32 self.__inited__ = True
33
34 BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", self.__inited__)
35
George Keishing374e6842019-02-20 08:57:18 -060036 def get_redfish_session_info(self):
37 r"""
38 Returns redfish sessions info dictionary.
39
40 {
41 'key': 'yLXotJnrh5nDhXj5lLiH' ,
42 'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
43 }
44 """
45 session_dict = {
George Keishing97c93942019-03-04 12:45:07 -060046 "key": self._redfish_.get_session_key(),
47 "location": self._redfish_.get_session_location()
George Keishing374e6842019-02-20 08:57:18 -060048 }
49 return session_dict
50
Sandhya Somashekar37122b62019-06-18 06:02:02 -050051 def get_attribute(self, resource_path, attribute, verify=None):
George Keishingf2613b72019-02-13 12:45:59 -060052 r"""
53 Get resource attribute.
54
55 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050056 resource_path URI resource absolute path (e.g.
57 "/redfish/v1/Systems/1").
58 attribute Name of the attribute (e.g. 'PowerState').
George Keishingf2613b72019-02-13 12:45:59 -060059 """
60
61 resp = self._redfish_.get(resource_path)
Sandhya Somashekar37122b62019-06-18 06:02:02 -050062
63 if verify:
64 if resp.dict[attribute] == verify:
65 return resp.dict[attribute]
66 else:
67 raise ValueError("Attribute value is not equal")
68 elif attribute in resp.dict:
George Keishingf2613b72019-02-13 12:45:59 -060069 return resp.dict[attribute]
70
71 return None
72
George Keishingc3c05c22019-02-19 01:04:54 -060073 def get_properties(self, resource_path):
74 r"""
75 Returns dictionary of attributes for the resource.
76
77 Description of argument(s):
Michael Walshc86a2f72019-03-19 13:24:37 -050078 resource_path URI resource absolute path (e.g.
Sandhya Somashekar37122b62019-06-18 06:02:02 -050079 /redfish/v1/Systems/1").
George Keishingc3c05c22019-02-19 01:04:54 -060080 """
81
82 resp = self._redfish_.get(resource_path)
83 return resp.dict
84
George Keishing7ec45932019-02-27 14:02:16 -060085 def get_target_actions(self, resource_path, target_attribute):
86 r"""
87 Returns resource target entry of the searched target attribute.
88
89 Description of argument(s):
90 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +000091 (e.g. "/redfish/v1/Systems/system").
George Keishing7ec45932019-02-27 14:02:16 -060092 target_attribute Name of the attribute (e.g. 'ComputerSystem.Reset').
93
94 Example:
Anusha Dathatri62dfb862019-04-23 06:52:16 -050095 "Actions": {
96 "#ComputerSystem.Reset": {
97 "ResetType@Redfish.AllowableValues": [
George Keishing7ec45932019-02-27 14:02:16 -060098 "On",
99 "ForceOff",
100 "GracefulRestart",
101 "GracefulShutdown"
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500102 ],
103 "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
104 }
105 }
George Keishing7ec45932019-02-27 14:02:16 -0600106 """
107
108 global target_list
109 target_list = []
110
111 resp_dict = self.get_attribute(resource_path, "Actions")
112 if resp_dict is None:
113 return None
114
115 # Recursively search the "target" key in the nested dictionary.
116 # Populate the target_list of target entries.
117 self.get_key_value_nested_dict(resp_dict, "target")
George Keishing7ec45932019-02-27 14:02:16 -0600118 # Return the matching target URL entry.
119 for target in target_list:
120 # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
121 if target_attribute in target:
122 return target
123
124 return None
125
George Keishingdabf38f2019-03-10 09:52:40 -0500126 def get_member_list(self, resource_path):
127 r"""
128 Perform a GET list request and return available members entries.
129
130 Description of argument(s):
131 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000132 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingdabf38f2019-03-10 09:52:40 -0500133
134 "Members": [
135 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500136 "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
George Keishingdabf38f2019-03-10 09:52:40 -0500137 }
138 {
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500139 "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
George Keishingdabf38f2019-03-10 09:52:40 -0500140 }
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500141 ],
George Keishingdabf38f2019-03-10 09:52:40 -0500142 """
143
144 member_list = []
145 resp_list_dict = self.get_attribute(resource_path, "Members")
146 if resp_list_dict is None:
147 return member_list
148
149 for member_id in range(0, len(resp_list_dict)):
150 member_list.append(resp_list_dict[member_id]["@odata.id"])
151
152 return member_list
153
George Keishingf2613b72019-02-13 12:45:59 -0600154 def list_request(self, resource_path):
155 r"""
156 Perform a GET list request and return available resource paths.
George Keishingf2613b72019-02-13 12:45:59 -0600157 Description of argument(s):
158 resource_path URI resource absolute path
George Keishingf8acde92019-04-19 19:46:48 +0000159 (e.g. "/redfish/v1/SessionService/Sessions").
George Keishingf2613b72019-02-13 12:45:59 -0600160 """
Michael Walshc86a2f72019-03-19 13:24:37 -0500161 gp.qprint_executing(style=gp.func_line_style_short)
Michael Walshc86a2f72019-03-19 13:24:37 -0500162 # Set quiet variable to keep subordinate get() calls quiet.
163 quiet = 1
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500164 self.__pending_enumeration = set()
Michael Walshc86a2f72019-03-19 13:24:37 -0500165 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000166 self._redfish_.get(resource_path,
167 valid_status_codes=[200, 404, 500])
George Keishingf2613b72019-02-13 12:45:59 -0600168
169 # Return empty list.
170 if self._rest_response_.status != 200:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500171 return self.__pending_enumeration
George Keishingf2613b72019-02-13 12:45:59 -0600172 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500173 if not self.__pending_enumeration:
174 return resource_path
175 for resource in self.__pending_enumeration.copy():
Michael Walshc86a2f72019-03-19 13:24:37 -0500176 self._rest_response_ = \
George Keishingf8acde92019-04-19 19:46:48 +0000177 self._redfish_.get(resource,
178 valid_status_codes=[200, 404, 500])
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500179
George Keishingf2613b72019-02-13 12:45:59 -0600180 if self._rest_response_.status != 200:
181 continue
182 self.walk_nested_dict(self._rest_response_.dict)
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500183 return list(sorted(self.__pending_enumeration))
George Keishingf2613b72019-02-13 12:45:59 -0600184
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600185 def enumerate_request(self, resource_path, return_json=1,
186 include_dead_resources=False):
George Keishingf2613b72019-02-13 12:45:59 -0600187 r"""
188 Perform a GET enumerate request and return available resource paths.
189
190 Description of argument(s):
Michael Walsh37e028f2019-05-22 16:16:32 -0500191 resource_path URI resource absolute path (e.g.
192 "/redfish/v1/SessionService/Sessions").
193 return_json Indicates whether the result should be
194 returned as a json string or as a
195 dictionary.
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600196 include_dead_resources Check and return a list of dead/broken URI
197 resources.
George Keishingf2613b72019-02-13 12:45:59 -0600198 """
199
Michael Walshc86a2f72019-03-19 13:24:37 -0500200 gp.qprint_executing(style=gp.func_line_style_short)
201
Michael Walsh37e028f2019-05-22 16:16:32 -0500202 return_json = int(return_json)
203
Michael Walshc86a2f72019-03-19 13:24:37 -0500204 # Set quiet variable to keep subordinate get() calls quiet.
205 quiet = 1
206
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500207 # Variable to hold enumerated data.
208 self.__result = {}
George Keishingf2613b72019-02-13 12:45:59 -0600209
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500210 # Variable to hold the pending list of resources for which enumeration.
211 # is yet to be obtained.
212 self.__pending_enumeration = set()
George Keishingf2613b72019-02-13 12:45:59 -0600213
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500214 self.__pending_enumeration.add(resource_path)
George Keishingf2613b72019-02-13 12:45:59 -0600215
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500216 # Variable having resources for which enumeration is completed.
217 enumerated_resources = set()
George Keishingf2613b72019-02-13 12:45:59 -0600218
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600219 if include_dead_resources:
220 dead_resources = {}
221
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500222 resources_to_be_enumerated = (resource_path,)
223
224 while resources_to_be_enumerated:
225 for resource in resources_to_be_enumerated:
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600226 # JsonSchemas, SessionService or URLs containing # are not
227 # required in enumeration.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500228 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
Anusha Dathatricdb77db2019-09-10 08:10:29 -0500229 # '/redfish/v1/SessionService'
Anusha Dathatri6d2d42f2019-11-20 06:17:51 -0600230 # '/redfish/v1/Managers/bmc#/Oem'
231 if ('JsonSchemas' in resource) or ('SessionService' in resource)\
232 or ('#' in resource):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500233 continue
234
235 self._rest_response_ = \
236 self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
237 # Enumeration is done for available resources ignoring the
238 # ones for which response is not obtained.
239 if self._rest_response_.status != 200:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600240 if include_dead_resources:
241 try:
242 dead_resources[self._rest_response_.status].append(
243 resource)
244 except KeyError:
245 dead_resources[self._rest_response_.status] = \
246 [resource]
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500247 continue
248
249 self.walk_nested_dict(self._rest_response_.dict, url=resource)
250
251 enumerated_resources.update(set(resources_to_be_enumerated))
252 resources_to_be_enumerated = \
253 tuple(self.__pending_enumeration - enumerated_resources)
254
Michael Walsh37e028f2019-05-22 16:16:32 -0500255 if return_json:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600256 if include_dead_resources:
257 return json.dumps(self.__result, sort_keys=True,
258 indent=4, separators=(',', ': ')), dead_resources
259 else:
260 return json.dumps(self.__result, sort_keys=True,
261 indent=4, separators=(',', ': '))
Michael Walsh37e028f2019-05-22 16:16:32 -0500262 else:
Anusha Dathatri3e7930d2019-11-06 03:55:35 -0600263 if include_dead_resources:
264 return self.__result, dead_resources
265 else:
266 return self.__result
George Keishingf2613b72019-02-13 12:45:59 -0600267
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500268 def walk_nested_dict(self, data, url=''):
George Keishingf2613b72019-02-13 12:45:59 -0600269 r"""
270 Parse through the nested dictionary and get the resource id paths.
George Keishingf2613b72019-02-13 12:45:59 -0600271 Description of argument(s):
272 data Nested dictionary data from response message.
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500273 url Resource for which the response is obtained in data.
George Keishingf2613b72019-02-13 12:45:59 -0600274 """
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500275 url = url.rstrip('/')
George Keishingf2613b72019-02-13 12:45:59 -0600276
277 for key, value in data.items():
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500278
279 # Recursion if nested dictionary found.
George Keishingf2613b72019-02-13 12:45:59 -0600280 if isinstance(value, dict):
281 self.walk_nested_dict(value)
282 else:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500283 # Value contains a list of dictionaries having member data.
George Keishingf2613b72019-02-13 12:45:59 -0600284 if 'Members' == key:
285 if isinstance(value, list):
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500286 for memberDict in value:
287 self.__pending_enumeration.add(memberDict['@odata.id'])
George Keishingf2613b72019-02-13 12:45:59 -0600288 if '@odata.id' == key:
Anusha Dathatri62dfb862019-04-23 06:52:16 -0500289 value = value.rstrip('/')
290 # Data for the given url.
291 if value == url:
292 self.__result[url] = data
293 # Data still needs to be looked up,
294 else:
295 self.__pending_enumeration.add(value)
George Keishing7ec45932019-02-27 14:02:16 -0600296
297 def get_key_value_nested_dict(self, data, key):
298 r"""
299 Parse through the nested dictionary and get the searched key value.
300
301 Description of argument(s):
302 data Nested dictionary data from response message.
303 key Search dictionary key element.
304 """
305
306 for k, v in data.items():
307 if isinstance(v, dict):
308 self.get_key_value_nested_dict(v, key)
309
310 if k == key:
311 target_list.append(v)