blob: 036c9909dd4a23f21bea2505f0003785ba1bcf4e [file] [log] [blame]
George Keishinge62d8b02018-11-29 12:01:56 -06001#!/usr/bin/env python
2
3r"""
George Keishing97c93942019-03-04 12:45:07 -06004See class prolog below for details.
George Keishinge62d8b02018-11-29 12:01:56 -06005"""
6
Michael Walshce7c4b52019-03-20 17:33:15 -05007import sys
8import re
Michael Walsh1a611fb2020-01-14 17:22:07 -06009import json
George Keishing97c93942019-03-04 12:45:07 -060010from redfish_plus import redfish_plus
George Keishing2296e8c2019-02-01 05:49:58 -060011from robot.libraries.BuiltIn import BuiltIn
George Keishinge62d8b02018-11-29 12:01:56 -060012
Michael Walsh5cc89192019-03-12 16:43:38 -050013import func_args as fa
Michael Walshce7c4b52019-03-20 17:33:15 -050014import gen_print as gp
Michael Walsh5cc89192019-03-12 16:43:38 -050015
George Keishinge62d8b02018-11-29 12:01:56 -060016
Tony Lee05aa70b2021-01-28 19:18:27 +080017MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
18
19
George Keishing97c93942019-03-04 12:45:07 -060020class bmc_redfish(redfish_plus):
George Keishinge62d8b02018-11-29 12:01:56 -060021 r"""
Michael Walsh5cc89192019-03-12 16:43:38 -050022 bmc_redfish is a child class of redfish_plus that is designed to provide
George Keishing97c93942019-03-04 12:45:07 -060023 benefits specifically for using redfish to communicate with an OpenBMC.
24
25 See the prologs of the methods below for details.
George Keishinge62d8b02018-11-29 12:01:56 -060026 """
George Keishinge62d8b02018-11-29 12:01:56 -060027
Michael Walshce7c4b52019-03-20 17:33:15 -050028 def __init__(self, *args, **kwargs):
29 r"""
30 Do BMC-related redfish initialization.
31
32 Presently, older versions of BMC code may not support redfish
33 requests. This can lead to unsightly error text being printed out for
34 programs that may use lib/bmc_redfish_resource.robot even though they
35 don't necessarily intend to make redfish requests.
36
37 This class method will make an attempt to tolerate this situation. At
38 some future point, when all BMCs can be expected to support redfish,
39 this class method may be considered for deletion. If it is deleted,
40 the self.__inited__ test code in the login() class method below should
41 likewise be deleted.
42 """
43 self.__inited__ = False
44 try:
Tony Lee05aa70b2021-01-28 19:18:27 +080045 if MTLS_ENABLED == 'True':
46 self.__inited__ = True
47 else:
48 super(bmc_redfish, self).__init__(*args, **kwargs)
49 self.__inited__ = True
Michael Walshce7c4b52019-03-20 17:33:15 -050050 except ValueError as get_exception:
51 except_type, except_value, except_traceback = sys.exc_info()
52 regex = r"The HTTP status code was not valid:[\r\n]+status:[ ]+502"
53 result = re.match(regex, str(except_value), flags=re.MULTILINE)
54 if not result:
55 gp.lprint_var(except_type)
56 gp.lprint_varx("except_value", str(except_value))
57 raise(get_exception)
Michael Walshe58df1c2019-08-07 09:57:43 -050058 BuiltIn().set_global_variable("${REDFISH_SUPPORTED}", self.__inited__)
George Keishingb131ff32020-10-13 09:18:24 -050059 BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", True)
Michael Walshce7c4b52019-03-20 17:33:15 -050060
George Keishinge62d8b02018-11-29 12:01:56 -060061 def login(self, *args, **kwargs):
62 r"""
George Keishing97c93942019-03-04 12:45:07 -060063 Assign BMC default values for username, password and auth arguments
64 and call parent class login method.
George Keishinge62d8b02018-11-29 12:01:56 -060065
66 Description of argument(s):
Michael Walsh5cc89192019-03-12 16:43:38 -050067 args See parent class method prolog for details.
68 kwargs See parent class method prolog for details.
George Keishinge62d8b02018-11-29 12:01:56 -060069 """
George Keishing4c394012019-02-01 06:03:02 -060070
Tony Lee05aa70b2021-01-28 19:18:27 +080071 if MTLS_ENABLED == 'True':
72 return None
Michael Walshce7c4b52019-03-20 17:33:15 -050073 if not self.__inited__:
Michael Walsh707ed0e2019-05-17 15:27:25 -050074 message = "bmc_redfish.__init__() was never successfully run. It "
75 message += "is likely that the target BMC firmware code level "
76 message += "does not support redfish.\n"
Michael Walshce7c4b52019-03-20 17:33:15 -050077 raise ValueError(message)
George Keishing97c93942019-03-04 12:45:07 -060078 # Assign default values for username, password, auth where necessary.
Michael Walsh5cc89192019-03-12 16:43:38 -050079 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
80 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
81 username, args, kwargs = fa.pop_arg(openbmc_username, *args, **kwargs)
82 password, args, kwargs = fa.pop_arg(openbmc_password, *args, **kwargs)
83 auth, args, kwargs = fa.pop_arg('session', *args, **kwargs)
George Keishing4c394012019-02-01 06:03:02 -060084
Michael Walshce7c4b52019-03-20 17:33:15 -050085 super(bmc_redfish, self).login(username, password, auth,
86 *args, **kwargs)
Michael Walsh5cc89192019-03-12 16:43:38 -050087
Tony Lee05aa70b2021-01-28 19:18:27 +080088 def logout(self):
89
90 if MTLS_ENABLED == 'True':
91 return None
92 else:
93 super(bmc_redfish, self).logout()
94
Michael Walsh5cc89192019-03-12 16:43:38 -050095 def get_properties(self, *args, **kwargs):
96 r"""
97 Return dictionary of attributes for a given path.
98
99 The difference between calling this function and calling get()
100 directly is that this function returns ONLY the dictionary portion of
101 the response object.
102
103 Example robot code:
104
105 ${properties}= Get Properties /redfish/v1/Systems/system/
Michael Walsh39c00512019-07-17 10:54:06 -0500106 Rprint Vars properties
Michael Walsh5cc89192019-03-12 16:43:38 -0500107
108 Output:
109
110 properties:
111 [PowerState]: Off
112 [Processors]:
113 [@odata.id]: /redfish/v1/Systems/system/Processors
114 [SerialNumber]: 1234567
115 ...
116
117 Description of argument(s):
118 args See parent class get() prolog for details.
119 kwargs See parent class get() prolog for details.
120 """
121
122 resp = self.get(*args, **kwargs)
123 return resp.dict if hasattr(resp, 'dict') else {}
124
125 def get_attribute(self, path, attribute, default=None, *args, **kwargs):
126 r"""
127 Get and return the named attribute from the properties for a given
128 path.
129
130 This method has the following advantages over calling get_properties
131 directly:
132 - The caller can specify a default value to be returned if the
133 attribute does not exist.
134
135 Example robot code:
136
137 ${attribute}= Get Attribute /redfish/v1/AccountService
138 ... MaxPasswordLength default=600
139 Rprint Vars attribute
140
141 Output:
142
143 attribute: 31
144
145 Description of argument(s):
146 path The path (e.g.
147 "/redfish/v1/AccountService").
148 attribute The name of the attribute to be retrieved
149 (e.g. "MaxPasswordLength").
150 default The default value to be returned if the
151 attribute does not exist (e.g. "").
152 args See parent class get() prolog for details.
153 kwargs See parent class get() prolog for details.
154 """
155
156 return self.get_properties(path, *args, **kwargs).get(attribute,
157 default)
158
159 def get_session_info(self):
160 r"""
161 Get and return session info as a tuple consisting of session_key and
162 session_location.
163 """
164
165 return self.get_session_key(), self.get_session_location()
Michael Walsh1a611fb2020-01-14 17:22:07 -0600166
167 def enumerate(self, resource_path, return_json=1, include_dead_resources=False):
168 r"""
169 Perform a GET enumerate request and return available resource paths.
170
171 Description of argument(s):
172 resource_path URI resource absolute path (e.g. "/redfish/v1/SessionService/Sessions").
173 return_json Indicates whether the result should be returned as a json string or as a
174 dictionary.
175 include_dead_resources Check and return a list of dead/broken URI resources.
176 """
177
178 gp.qprint_executing(style=gp.func_line_style_short)
179 # Set quiet variable to keep subordinate get() calls quiet.
180 quiet = 1
181
182 self.__result = {}
183 # Variable to hold the pending list of resources for which enumeration is yet to be obtained.
184 self.__pending_enumeration = set()
185 self.__pending_enumeration.add(resource_path)
186
187 # Variable having resources for which enumeration is completed.
188 enumerated_resources = set()
189 dead_resources = {}
190 resources_to_be_enumerated = (resource_path,)
191 while resources_to_be_enumerated:
192 for resource in resources_to_be_enumerated:
193 # JsonSchemas, SessionService or URLs containing # are not required in enumeration.
194 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
195 # '/redfish/v1/SessionService'
196 # '/redfish/v1/Managers/bmc#/Oem'
197 if ('JsonSchemas' in resource) or ('SessionService' in resource) or ('#' in resource):
198 continue
199
200 self._rest_response_ = self.get(resource, valid_status_codes=[200, 404, 500])
201 # Enumeration is done for available resources ignoring the ones for which response is not
202 # obtained.
203 if self._rest_response_.status != 200:
204 if include_dead_resources:
205 try:
206 dead_resources[self._rest_response_.status].append(resource)
207 except KeyError:
208 dead_resources[self._rest_response_.status] = [resource]
209 continue
210 self.walk_nested_dict(self._rest_response_.dict, url=resource)
211
212 enumerated_resources.update(set(resources_to_be_enumerated))
213 resources_to_be_enumerated = tuple(self.__pending_enumeration - enumerated_resources)
214
215 if return_json:
216 if include_dead_resources:
217 return json.dumps(self.__result, sort_keys=True,
218 indent=4, separators=(',', ': ')), dead_resources
219 else:
220 return json.dumps(self.__result, sort_keys=True,
221 indent=4, separators=(',', ': '))
222 else:
223 if include_dead_resources:
224 return self.__result, dead_resources
225 else:
226 return self.__result
227
228 def walk_nested_dict(self, data, url=''):
229 r"""
230 Parse through the nested dictionary and get the resource id paths.
231
232 Description of argument(s):
233 data Nested dictionary data from response message.
234 url Resource for which the response is obtained in data.
235 """
236 url = url.rstrip('/')
237
238 for key, value in data.items():
239
240 # Recursion if nested dictionary found.
241 if isinstance(value, dict):
242 self.walk_nested_dict(value)
243 else:
244 # Value contains a list of dictionaries having member data.
245 if 'Members' == key:
246 if isinstance(value, list):
247 for memberDict in value:
248 self.__pending_enumeration.add(memberDict['@odata.id'])
249 if '@odata.id' == key:
250 value = value.rstrip('/')
251 # Data for the given url.
252 if value == url:
253 self.__result[url] = data
254 # Data still needs to be looked up,
255 else:
256 self.__pending_enumeration.add(value)