blob: 835af31addec0801432af9c69c7aaf9f74c88b4e [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__)
Michael Walshce7c4b52019-03-20 17:33:15 -050053
George Keishinge62d8b02018-11-29 12:01:56 -060054 def login(self, *args, **kwargs):
55 r"""
George Keishing97c93942019-03-04 12:45:07 -060056 Assign BMC default values for username, password and auth arguments
57 and call parent class login method.
George Keishinge62d8b02018-11-29 12:01:56 -060058
59 Description of argument(s):
Michael Walsh5cc89192019-03-12 16:43:38 -050060 args See parent class method prolog for details.
61 kwargs See parent class method prolog for details.
George Keishinge62d8b02018-11-29 12:01:56 -060062 """
George Keishing4c394012019-02-01 06:03:02 -060063
Michael Walshce7c4b52019-03-20 17:33:15 -050064 if not self.__inited__:
Michael Walsh707ed0e2019-05-17 15:27:25 -050065 message = "bmc_redfish.__init__() was never successfully run. It "
66 message += "is likely that the target BMC firmware code level "
67 message += "does not support redfish.\n"
Michael Walshce7c4b52019-03-20 17:33:15 -050068 raise ValueError(message)
George Keishing97c93942019-03-04 12:45:07 -060069 # Assign default values for username, password, auth where necessary.
Michael Walsh5cc89192019-03-12 16:43:38 -050070 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
71 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
72 username, args, kwargs = fa.pop_arg(openbmc_username, *args, **kwargs)
73 password, args, kwargs = fa.pop_arg(openbmc_password, *args, **kwargs)
74 auth, args, kwargs = fa.pop_arg('session', *args, **kwargs)
George Keishing4c394012019-02-01 06:03:02 -060075
Michael Walshce7c4b52019-03-20 17:33:15 -050076 super(bmc_redfish, self).login(username, password, auth,
77 *args, **kwargs)
Michael Walsh5cc89192019-03-12 16:43:38 -050078
79 def get_properties(self, *args, **kwargs):
80 r"""
81 Return dictionary of attributes for a given path.
82
83 The difference between calling this function and calling get()
84 directly is that this function returns ONLY the dictionary portion of
85 the response object.
86
87 Example robot code:
88
89 ${properties}= Get Properties /redfish/v1/Systems/system/
Michael Walsh39c00512019-07-17 10:54:06 -050090 Rprint Vars properties
Michael Walsh5cc89192019-03-12 16:43:38 -050091
92 Output:
93
94 properties:
95 [PowerState]: Off
96 [Processors]:
97 [@odata.id]: /redfish/v1/Systems/system/Processors
98 [SerialNumber]: 1234567
99 ...
100
101 Description of argument(s):
102 args See parent class get() prolog for details.
103 kwargs See parent class get() prolog for details.
104 """
105
106 resp = self.get(*args, **kwargs)
107 return resp.dict if hasattr(resp, 'dict') else {}
108
109 def get_attribute(self, path, attribute, default=None, *args, **kwargs):
110 r"""
111 Get and return the named attribute from the properties for a given
112 path.
113
114 This method has the following advantages over calling get_properties
115 directly:
116 - The caller can specify a default value to be returned if the
117 attribute does not exist.
118
119 Example robot code:
120
121 ${attribute}= Get Attribute /redfish/v1/AccountService
122 ... MaxPasswordLength default=600
123 Rprint Vars attribute
124
125 Output:
126
127 attribute: 31
128
129 Description of argument(s):
130 path The path (e.g.
131 "/redfish/v1/AccountService").
132 attribute The name of the attribute to be retrieved
133 (e.g. "MaxPasswordLength").
134 default The default value to be returned if the
135 attribute does not exist (e.g. "").
136 args See parent class get() prolog for details.
137 kwargs See parent class get() prolog for details.
138 """
139
140 return self.get_properties(path, *args, **kwargs).get(attribute,
141 default)
142
143 def get_session_info(self):
144 r"""
145 Get and return session info as a tuple consisting of session_key and
146 session_location.
147 """
148
149 return self.get_session_key(), self.get_session_location()
Michael Walsh1a611fb2020-01-14 17:22:07 -0600150
151 def enumerate(self, resource_path, return_json=1, include_dead_resources=False):
152 r"""
153 Perform a GET enumerate request and return available resource paths.
154
155 Description of argument(s):
156 resource_path URI resource absolute path (e.g. "/redfish/v1/SessionService/Sessions").
157 return_json Indicates whether the result should be returned as a json string or as a
158 dictionary.
159 include_dead_resources Check and return a list of dead/broken URI resources.
160 """
161
162 gp.qprint_executing(style=gp.func_line_style_short)
163 # Set quiet variable to keep subordinate get() calls quiet.
164 quiet = 1
165
166 self.__result = {}
167 # Variable to hold the pending list of resources for which enumeration is yet to be obtained.
168 self.__pending_enumeration = set()
169 self.__pending_enumeration.add(resource_path)
170
171 # Variable having resources for which enumeration is completed.
172 enumerated_resources = set()
173 dead_resources = {}
174 resources_to_be_enumerated = (resource_path,)
175 while resources_to_be_enumerated:
176 for resource in resources_to_be_enumerated:
177 # JsonSchemas, SessionService or URLs containing # are not required in enumeration.
178 # Example: '/redfish/v1/JsonSchemas/' and sub resources.
179 # '/redfish/v1/SessionService'
180 # '/redfish/v1/Managers/bmc#/Oem'
181 if ('JsonSchemas' in resource) or ('SessionService' in resource) or ('#' in resource):
182 continue
183
184 self._rest_response_ = self.get(resource, valid_status_codes=[200, 404, 500])
185 # Enumeration is done for available resources ignoring the ones for which response is not
186 # obtained.
187 if self._rest_response_.status != 200:
188 if include_dead_resources:
189 try:
190 dead_resources[self._rest_response_.status].append(resource)
191 except KeyError:
192 dead_resources[self._rest_response_.status] = [resource]
193 continue
194 self.walk_nested_dict(self._rest_response_.dict, url=resource)
195
196 enumerated_resources.update(set(resources_to_be_enumerated))
197 resources_to_be_enumerated = tuple(self.__pending_enumeration - enumerated_resources)
198
199 if return_json:
200 if include_dead_resources:
201 return json.dumps(self.__result, sort_keys=True,
202 indent=4, separators=(',', ': ')), dead_resources
203 else:
204 return json.dumps(self.__result, sort_keys=True,
205 indent=4, separators=(',', ': '))
206 else:
207 if include_dead_resources:
208 return self.__result, dead_resources
209 else:
210 return self.__result
211
212 def walk_nested_dict(self, data, url=''):
213 r"""
214 Parse through the nested dictionary and get the resource id paths.
215
216 Description of argument(s):
217 data Nested dictionary data from response message.
218 url Resource for which the response is obtained in data.
219 """
220 url = url.rstrip('/')
221
222 for key, value in data.items():
223
224 # Recursion if nested dictionary found.
225 if isinstance(value, dict):
226 self.walk_nested_dict(value)
227 else:
228 # Value contains a list of dictionaries having member data.
229 if 'Members' == key:
230 if isinstance(value, list):
231 for memberDict in value:
232 self.__pending_enumeration.add(memberDict['@odata.id'])
233 if '@odata.id' == key:
234 value = value.rstrip('/')
235 # Data for the given url.
236 if value == url:
237 self.__result[url] = data
238 # Data still needs to be looked up,
239 else:
240 self.__pending_enumeration.add(value)