blob: ec572b24f8ebd1f6c8d2ff8c7e660032d277c88c [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
Solio Sarabiaf78122e2017-02-23 16:17:34 -060016from six.moves import http_client
Yuiko Takadab6527002015-12-07 11:49:12 +090017from six.moves.urllib import parse as urllib
Yuiko Takadaff785002015-12-17 15:56:42 +090018from tempest.lib.common import api_version_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020019from tempest.lib.common import rest_client
Yuiko Takadab6527002015-12-07 11:49:12 +090020
Yuiko Takadaff785002015-12-17 15:56:42 +090021BAREMETAL_MICROVERSION = None
22
Yuiko Takadab6527002015-12-07 11:49:12 +090023
24def handle_errors(f):
25 """A decorator that allows to ignore certain types of errors."""
26
27 @functools.wraps(f)
28 def wrapper(*args, **kwargs):
29 param_name = 'ignore_errors'
30 ignored_errors = kwargs.get(param_name, tuple())
31
32 if param_name in kwargs:
33 del kwargs[param_name]
34
35 try:
36 return f(*args, **kwargs)
37 except ignored_errors:
38 # Silently ignore errors
39 pass
40
41 return wrapper
42
43
44class BaremetalClient(rest_client.RestClient):
45 """Base Tempest REST client for Ironic API."""
46
Yuiko Takadaff785002015-12-17 15:56:42 +090047 api_microversion_header_name = 'X-OpenStack-Ironic-API-Version'
Yuiko Takadab6527002015-12-07 11:49:12 +090048 uri_prefix = ''
49
Yuiko Takadaff785002015-12-17 15:56:42 +090050 def get_headers(self):
51 headers = super(BaremetalClient, self).get_headers()
52 if BAREMETAL_MICROVERSION:
53 headers[self.api_microversion_header_name] = BAREMETAL_MICROVERSION
54 return headers
55
Vasyl Saienkof20979c2016-05-27 11:25:01 +030056 def request(self, *args, **kwargs):
57 resp, resp_body = super(BaremetalClient, self).request(*args, **kwargs)
Yuiko Takadaff785002015-12-17 15:56:42 +090058 if (BAREMETAL_MICROVERSION and
59 BAREMETAL_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
60 api_version_utils.assert_version_header_matches_request(
61 self.api_microversion_header_name,
62 BAREMETAL_MICROVERSION,
63 resp)
64 return resp, resp_body
65
Yuiko Takadab6527002015-12-07 11:49:12 +090066 def serialize(self, object_dict):
67 """Serialize an Ironic object."""
68
69 return json.dumps(object_dict)
70
71 def deserialize(self, object_str):
72 """Deserialize an Ironic object."""
73
74 return json.loads(object_str)
75
76 def _get_uri(self, resource_name, uuid=None, permanent=False):
77 """Get URI for a specific resource or object.
78
79 :param resource_name: The name of the REST resource, e.g., 'nodes'.
80 :param uuid: The unique identifier of an object in UUID format.
81 :returns: Relative URI for the resource or object.
82
83 """
84 prefix = self.uri_prefix if not permanent else ''
85
86 return '{pref}/{res}{uuid}'.format(pref=prefix,
87 res=resource_name,
88 uuid='/%s' % uuid if uuid else '')
89
90 def _make_patch(self, allowed_attributes, **kwargs):
91 """Create a JSON patch according to RFC 6902.
92
93 :param allowed_attributes: An iterable object that contains a set of
94 allowed attributes for an object.
95 :param **kwargs: Attributes and new values for them.
96 :returns: A JSON path that sets values of the specified attributes to
97 the new ones.
98
99 """
100 def get_change(kwargs, path='/'):
Luong Anh Tuane22fbe12016-09-12 16:32:14 +0700101 for name, value in kwargs.items():
Yuiko Takadab6527002015-12-07 11:49:12 +0900102 if isinstance(value, dict):
103 for ch in get_change(value, path + '%s/' % name):
104 yield ch
105 else:
106 if value is None:
107 yield {'path': path + name,
108 'op': 'remove'}
109 else:
110 yield {'path': path + name,
111 'value': value,
112 'op': 'replace'}
113
114 patch = [ch for ch in get_change(kwargs)
115 if ch['path'].lstrip('/') in allowed_attributes]
116
117 return patch
118
Sam Betts462e9e62016-11-30 18:43:35 +0000119 def _list_request(self, resource, permanent=False, headers=None,
120 extra_headers=False, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900121 """Get the list of objects of the specified type.
122
123 :param resource: The name of the REST resource, e.g., 'nodes'.
Sam Betts462e9e62016-11-30 18:43:35 +0000124 :param headers: List of headers to use in request.
125 :param extra_headers: Specify whether to use headers.
Yuiko Takadab6527002015-12-07 11:49:12 +0900126 :param **kwargs: Parameters for the request.
127 :returns: A tuple with the server response and deserialized JSON list
128 of objects
129
130 """
131 uri = self._get_uri(resource, permanent=permanent)
132 if kwargs:
133 uri += "?%s" % urllib.urlencode(kwargs)
134
Sam Betts462e9e62016-11-30 18:43:35 +0000135 resp, body = self.get(uri, headers=headers,
136 extra_headers=extra_headers)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600137 self.expected_success(http_client.OK, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900138
139 return resp, self.deserialize(body)
140
141 def _show_request(self, resource, uuid, permanent=False, **kwargs):
142 """Gets a specific object of the specified type.
143
144 :param uuid: Unique identifier of the object in UUID format.
145 :returns: Serialized object as a dictionary.
146
147 """
148 if 'uri' in kwargs:
149 uri = kwargs['uri']
150 else:
151 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
152 resp, body = self.get(uri)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600153 self.expected_success(http_client.OK, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900154
155 return resp, self.deserialize(body)
156
157 def _create_request(self, resource, object_dict):
158 """Create an object of the specified type.
159
160 :param resource: The name of the REST resource, e.g., 'nodes'.
161 :param object_dict: A Python dict that represents an object of the
162 specified type.
163 :returns: A tuple with the server response and the deserialized created
164 object.
165
166 """
167 body = self.serialize(object_dict)
168 uri = self._get_uri(resource)
169
170 resp, body = self.post(uri, body=body)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600171 self.expected_success(http_client.CREATED, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900172
173 return resp, self.deserialize(body)
174
Sam Betts462e9e62016-11-30 18:43:35 +0000175 def _create_request_no_response_body(self, resource, object_dict):
176 """Create an object of the specified type.
177
178 Do not expect any body in the response.
179
180 :param resource: The name of the REST resource, e.g., 'nodes'.
181 :param object_dict: A Python dict that represents an object of the
182 specified type.
183 :returns: The server response.
184 """
185
186 body = self.serialize(object_dict)
187 uri = self._get_uri(resource)
188
189 resp, body = self.post(uri, body=body)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600190 self.expected_success(http_client.NO_CONTENT, resp.status)
Sam Betts462e9e62016-11-30 18:43:35 +0000191
192 return resp
193
Yuiko Takadab6527002015-12-07 11:49:12 +0900194 def _delete_request(self, resource, uuid):
195 """Delete specified object.
196
197 :param resource: The name of the REST resource, e.g., 'nodes'.
198 :param uuid: The unique identifier of an object in UUID format.
199 :returns: A tuple with the server response and the response body.
200
201 """
202 uri = self._get_uri(resource, uuid)
203
204 resp, body = self.delete(uri)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600205 self.expected_success(http_client.NO_CONTENT, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900206 return resp, body
207
208 def _patch_request(self, resource, uuid, patch_object):
209 """Update specified object with JSON-patch.
210
211 :param resource: The name of the REST resource, e.g., 'nodes'.
212 :param uuid: The unique identifier of an object in UUID format.
213 :returns: A tuple with the server response and the serialized patched
214 object.
215
216 """
217 uri = self._get_uri(resource, uuid)
218 patch_body = json.dumps(patch_object)
219
220 resp, body = self.patch(uri, body=patch_body)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600221 self.expected_success(http_client.OK, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900222 return resp, self.deserialize(body)
223
224 @handle_errors
225 def get_api_description(self):
226 """Retrieves all versions of the Ironic API."""
227
228 return self._list_request('', permanent=True)
229
230 @handle_errors
231 def get_version_description(self, version='v1'):
Kyrylo Romanenko36000542016-09-16 15:07:40 +0300232 """Retrieves the description of the API.
Yuiko Takadab6527002015-12-07 11:49:12 +0900233
234 :param version: The version of the API. Default: 'v1'.
235 :returns: Serialized description of API resources.
236
237 """
238 return self._list_request(version, permanent=True)
239
240 def _put_request(self, resource, put_object):
241 """Update specified object with JSON-patch."""
242 uri = self._get_uri(resource)
243 put_body = json.dumps(put_object)
244
245 resp, body = self.put(uri, body=put_body)
Solio Sarabiaf78122e2017-02-23 16:17:34 -0600246 self.expected_success([http_client.ACCEPTED, http_client.NO_CONTENT],
247 resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900248 return resp, body