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