blob: b7a9c3291d858992e33a139f84888caef37a6e7c [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
16import six
17from 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
56 def request(self, method, url, extra_headers=False, headers=None,
57 body=None):
58 resp, resp_body = super(BaremetalClient, self).request(
59 method, url, extra_headers, headers, body)
60 if (BAREMETAL_MICROVERSION and
61 BAREMETAL_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
62 api_version_utils.assert_version_header_matches_request(
63 self.api_microversion_header_name,
64 BAREMETAL_MICROVERSION,
65 resp)
66 return resp, resp_body
67
Yuiko Takadab6527002015-12-07 11:49:12 +090068 def serialize(self, object_dict):
69 """Serialize an Ironic object."""
70
71 return json.dumps(object_dict)
72
73 def deserialize(self, object_str):
74 """Deserialize an Ironic object."""
75
76 return json.loads(object_str)
77
78 def _get_uri(self, resource_name, uuid=None, permanent=False):
79 """Get URI for a specific resource or object.
80
81 :param resource_name: The name of the REST resource, e.g., 'nodes'.
82 :param uuid: The unique identifier of an object in UUID format.
83 :returns: Relative URI for the resource or object.
84
85 """
86 prefix = self.uri_prefix if not permanent else ''
87
88 return '{pref}/{res}{uuid}'.format(pref=prefix,
89 res=resource_name,
90 uuid='/%s' % uuid if uuid else '')
91
92 def _make_patch(self, allowed_attributes, **kwargs):
93 """Create a JSON patch according to RFC 6902.
94
95 :param allowed_attributes: An iterable object that contains a set of
96 allowed attributes for an object.
97 :param **kwargs: Attributes and new values for them.
98 :returns: A JSON path that sets values of the specified attributes to
99 the new ones.
100
101 """
102 def get_change(kwargs, path='/'):
103 for name, value in six.iteritems(kwargs):
104 if isinstance(value, dict):
105 for ch in get_change(value, path + '%s/' % name):
106 yield ch
107 else:
108 if value is None:
109 yield {'path': path + name,
110 'op': 'remove'}
111 else:
112 yield {'path': path + name,
113 'value': value,
114 'op': 'replace'}
115
116 patch = [ch for ch in get_change(kwargs)
117 if ch['path'].lstrip('/') in allowed_attributes]
118
119 return patch
120
121 def _list_request(self, resource, permanent=False, **kwargs):
122 """Get the list of objects of the specified type.
123
124 :param resource: The name of the REST resource, e.g., 'nodes'.
125 :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
134 resp, body = self.get(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900135 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900136
137 return resp, self.deserialize(body)
138
139 def _show_request(self, resource, uuid, permanent=False, **kwargs):
140 """Gets a specific object of the specified type.
141
142 :param uuid: Unique identifier of the object in UUID format.
143 :returns: Serialized object as a dictionary.
144
145 """
146 if 'uri' in kwargs:
147 uri = kwargs['uri']
148 else:
149 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
150 resp, body = self.get(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900151 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900152
153 return resp, self.deserialize(body)
154
155 def _create_request(self, resource, object_dict):
156 """Create an object of the specified type.
157
158 :param resource: The name of the REST resource, e.g., 'nodes'.
159 :param object_dict: A Python dict that represents an object of the
160 specified type.
161 :returns: A tuple with the server response and the deserialized created
162 object.
163
164 """
165 body = self.serialize(object_dict)
166 uri = self._get_uri(resource)
167
168 resp, body = self.post(uri, body=body)
ghanshyam013f6112016-04-21 17:19:06 +0900169 self.expected_success(201, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900170
171 return resp, self.deserialize(body)
172
173 def _delete_request(self, resource, uuid):
174 """Delete specified object.
175
176 :param resource: The name of the REST resource, e.g., 'nodes'.
177 :param uuid: The unique identifier of an object in UUID format.
178 :returns: A tuple with the server response and the response body.
179
180 """
181 uri = self._get_uri(resource, uuid)
182
183 resp, body = self.delete(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900184 self.expected_success(204, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900185 return resp, body
186
187 def _patch_request(self, resource, uuid, patch_object):
188 """Update specified object with JSON-patch.
189
190 :param resource: The name of the REST resource, e.g., 'nodes'.
191 :param uuid: The unique identifier of an object in UUID format.
192 :returns: A tuple with the server response and the serialized patched
193 object.
194
195 """
196 uri = self._get_uri(resource, uuid)
197 patch_body = json.dumps(patch_object)
198
199 resp, body = self.patch(uri, body=patch_body)
ghanshyam013f6112016-04-21 17:19:06 +0900200 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900201 return resp, self.deserialize(body)
202
203 @handle_errors
204 def get_api_description(self):
205 """Retrieves all versions of the Ironic API."""
206
207 return self._list_request('', permanent=True)
208
209 @handle_errors
210 def get_version_description(self, version='v1'):
211 """Retrieves the desctription of the API.
212
213 :param version: The version of the API. Default: 'v1'.
214 :returns: Serialized description of API resources.
215
216 """
217 return self._list_request(version, permanent=True)
218
219 def _put_request(self, resource, put_object):
220 """Update specified object with JSON-patch."""
221 uri = self._get_uri(resource)
222 put_body = json.dumps(put_object)
223
224 resp, body = self.put(uri, body=put_body)
ghanshyam013f6112016-04-21 17:19:06 +0900225 self.expected_success([202, 204], resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900226 return resp, body