blob: 30589e13c25186f31599cfb6ff01390dff8dc0ab [file] [log] [blame]
Yuiko Takadab6527002015-12-07 11:49:12 +09001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import functools
14
15from oslo_serialization import jsonutils as json
Yuiko Takadab6527002015-12-07 11:49:12 +090016from six.moves.urllib import parse as urllib
Yuiko Takadaff785002015-12-17 15:56:42 +090017from tempest.lib.common import api_version_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020018from tempest.lib.common import rest_client
Yuiko Takadab6527002015-12-07 11:49:12 +090019
Yuiko Takadaff785002015-12-17 15:56:42 +090020BAREMETAL_MICROVERSION = None
21
Yuiko Takadab6527002015-12-07 11:49:12 +090022
23def handle_errors(f):
24 """A decorator that allows to ignore certain types of errors."""
25
26 @functools.wraps(f)
27 def wrapper(*args, **kwargs):
28 param_name = 'ignore_errors'
29 ignored_errors = kwargs.get(param_name, tuple())
30
31 if param_name in kwargs:
32 del kwargs[param_name]
33
34 try:
35 return f(*args, **kwargs)
36 except ignored_errors:
37 # Silently ignore errors
38 pass
39
40 return wrapper
41
42
43class BaremetalClient(rest_client.RestClient):
44 """Base Tempest REST client for Ironic API."""
45
Yuiko Takadaff785002015-12-17 15:56:42 +090046 api_microversion_header_name = 'X-OpenStack-Ironic-API-Version'
Yuiko Takadab6527002015-12-07 11:49:12 +090047 uri_prefix = ''
48
Yuiko Takadaff785002015-12-17 15:56:42 +090049 def get_headers(self):
50 headers = super(BaremetalClient, self).get_headers()
51 if BAREMETAL_MICROVERSION:
52 headers[self.api_microversion_header_name] = BAREMETAL_MICROVERSION
53 return headers
54
Vasyl Saienkof20979c2016-05-27 11:25:01 +030055 def request(self, *args, **kwargs):
56 resp, resp_body = super(BaremetalClient, self).request(*args, **kwargs)
Yuiko Takadaff785002015-12-17 15:56:42 +090057 if (BAREMETAL_MICROVERSION and
58 BAREMETAL_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
59 api_version_utils.assert_version_header_matches_request(
60 self.api_microversion_header_name,
61 BAREMETAL_MICROVERSION,
62 resp)
63 return resp, resp_body
64
Yuiko Takadab6527002015-12-07 11:49:12 +090065 def serialize(self, object_dict):
66 """Serialize an Ironic object."""
67
68 return json.dumps(object_dict)
69
70 def deserialize(self, object_str):
71 """Deserialize an Ironic object."""
72
73 return json.loads(object_str)
74
75 def _get_uri(self, resource_name, uuid=None, permanent=False):
76 """Get URI for a specific resource or object.
77
78 :param resource_name: The name of the REST resource, e.g., 'nodes'.
79 :param uuid: The unique identifier of an object in UUID format.
80 :returns: Relative URI for the resource or object.
81
82 """
83 prefix = self.uri_prefix if not permanent else ''
84
85 return '{pref}/{res}{uuid}'.format(pref=prefix,
86 res=resource_name,
87 uuid='/%s' % uuid if uuid else '')
88
89 def _make_patch(self, allowed_attributes, **kwargs):
90 """Create a JSON patch according to RFC 6902.
91
92 :param allowed_attributes: An iterable object that contains a set of
93 allowed attributes for an object.
94 :param **kwargs: Attributes and new values for them.
95 :returns: A JSON path that sets values of the specified attributes to
96 the new ones.
97
98 """
99 def get_change(kwargs, path='/'):
Luong Anh Tuane22fbe12016-09-12 16:32:14 +0700100 for name, value in kwargs.items():
Yuiko Takadab6527002015-12-07 11:49:12 +0900101 if isinstance(value, dict):
102 for ch in get_change(value, path + '%s/' % name):
103 yield ch
104 else:
105 if value is None:
106 yield {'path': path + name,
107 'op': 'remove'}
108 else:
109 yield {'path': path + name,
110 'value': value,
111 'op': 'replace'}
112
113 patch = [ch for ch in get_change(kwargs)
114 if ch['path'].lstrip('/') in allowed_attributes]
115
116 return patch
117
Sam Betts462e9e62016-11-30 18:43:35 +0000118 def _list_request(self, resource, permanent=False, headers=None,
119 extra_headers=False, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900120 """Get the list of objects of the specified type.
121
122 :param resource: The name of the REST resource, e.g., 'nodes'.
Sam Betts462e9e62016-11-30 18:43:35 +0000123 :param headers: List of headers to use in request.
124 :param extra_headers: Specify whether to use headers.
Yuiko Takadab6527002015-12-07 11:49:12 +0900125 :param **kwargs: Parameters for the request.
126 :returns: A tuple with the server response and deserialized JSON list
127 of objects
128
129 """
130 uri = self._get_uri(resource, permanent=permanent)
131 if kwargs:
132 uri += "?%s" % urllib.urlencode(kwargs)
133
Sam Betts462e9e62016-11-30 18:43:35 +0000134 resp, body = self.get(uri, headers=headers,
135 extra_headers=extra_headers)
ghanshyam013f6112016-04-21 17:19:06 +0900136 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900137
138 return resp, self.deserialize(body)
139
140 def _show_request(self, resource, uuid, permanent=False, **kwargs):
141 """Gets a specific object of the specified type.
142
143 :param uuid: Unique identifier of the object in UUID format.
144 :returns: Serialized object as a dictionary.
145
146 """
147 if 'uri' in kwargs:
148 uri = kwargs['uri']
149 else:
150 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
151 resp, body = self.get(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900152 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900153
154 return resp, self.deserialize(body)
155
156 def _create_request(self, resource, object_dict):
157 """Create an object of the specified type.
158
159 :param resource: The name of the REST resource, e.g., 'nodes'.
160 :param object_dict: A Python dict that represents an object of the
161 specified type.
162 :returns: A tuple with the server response and the deserialized created
163 object.
164
165 """
166 body = self.serialize(object_dict)
167 uri = self._get_uri(resource)
168
169 resp, body = self.post(uri, body=body)
ghanshyam013f6112016-04-21 17:19:06 +0900170 self.expected_success(201, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900171
172 return resp, self.deserialize(body)
173
Sam Betts462e9e62016-11-30 18:43:35 +0000174 def _create_request_no_response_body(self, resource, object_dict):
175 """Create an object of the specified type.
176
177 Do not expect any body in the response.
178
179 :param resource: The name of the REST resource, e.g., 'nodes'.
180 :param object_dict: A Python dict that represents an object of the
181 specified type.
182 :returns: The server response.
183 """
184
185 body = self.serialize(object_dict)
186 uri = self._get_uri(resource)
187
188 resp, body = self.post(uri, body=body)
189 self.expected_success(204, resp.status)
190
191 return resp
192
Yuiko Takadab6527002015-12-07 11:49:12 +0900193 def _delete_request(self, resource, uuid):
194 """Delete specified object.
195
196 :param resource: The name of the REST resource, e.g., 'nodes'.
197 :param uuid: The unique identifier of an object in UUID format.
198 :returns: A tuple with the server response and the response body.
199
200 """
201 uri = self._get_uri(resource, uuid)
202
203 resp, body = self.delete(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900204 self.expected_success(204, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900205 return resp, body
206
207 def _patch_request(self, resource, uuid, patch_object):
208 """Update specified object with JSON-patch.
209
210 :param resource: The name of the REST resource, e.g., 'nodes'.
211 :param uuid: The unique identifier of an object in UUID format.
212 :returns: A tuple with the server response and the serialized patched
213 object.
214
215 """
216 uri = self._get_uri(resource, uuid)
217 patch_body = json.dumps(patch_object)
218
219 resp, body = self.patch(uri, body=patch_body)
ghanshyam013f6112016-04-21 17:19:06 +0900220 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900221 return resp, self.deserialize(body)
222
223 @handle_errors
224 def get_api_description(self):
225 """Retrieves all versions of the Ironic API."""
226
227 return self._list_request('', permanent=True)
228
229 @handle_errors
230 def get_version_description(self, version='v1'):
Kyrylo Romanenko36000542016-09-16 15:07:40 +0300231 """Retrieves the description of the API.
Yuiko Takadab6527002015-12-07 11:49:12 +0900232
233 :param version: The version of the API. Default: 'v1'.
234 :returns: Serialized description of API resources.
235
236 """
237 return self._list_request(version, permanent=True)
238
239 def _put_request(self, resource, put_object):
240 """Update specified object with JSON-patch."""
241 uri = self._get_uri(resource)
242 put_body = json.dumps(put_object)
243
244 resp, body = self.put(uri, body=put_body)
ghanshyam013f6112016-04-21 17:19:06 +0900245 self.expected_success([202, 204], resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900246 return resp, body