blob: 368df471b3f64d01cdf4983781cde2fddbe145df [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
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='/'):
101 for name, value in six.iteritems(kwargs):
102 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
119 def _list_request(self, resource, permanent=False, **kwargs):
120 """Get the list of objects of the specified type.
121
122 :param resource: The name of the REST resource, e.g., 'nodes'.
123 :param **kwargs: Parameters for the request.
124 :returns: A tuple with the server response and deserialized JSON list
125 of objects
126
127 """
128 uri = self._get_uri(resource, permanent=permanent)
129 if kwargs:
130 uri += "?%s" % urllib.urlencode(kwargs)
131
132 resp, body = self.get(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900133 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900134
135 return resp, self.deserialize(body)
136
137 def _show_request(self, resource, uuid, permanent=False, **kwargs):
138 """Gets a specific object of the specified type.
139
140 :param uuid: Unique identifier of the object in UUID format.
141 :returns: Serialized object as a dictionary.
142
143 """
144 if 'uri' in kwargs:
145 uri = kwargs['uri']
146 else:
147 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
148 resp, body = self.get(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900149 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900150
151 return resp, self.deserialize(body)
152
153 def _create_request(self, resource, object_dict):
154 """Create an object of the specified type.
155
156 :param resource: The name of the REST resource, e.g., 'nodes'.
157 :param object_dict: A Python dict that represents an object of the
158 specified type.
159 :returns: A tuple with the server response and the deserialized created
160 object.
161
162 """
163 body = self.serialize(object_dict)
164 uri = self._get_uri(resource)
165
166 resp, body = self.post(uri, body=body)
ghanshyam013f6112016-04-21 17:19:06 +0900167 self.expected_success(201, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900168
169 return resp, self.deserialize(body)
170
171 def _delete_request(self, resource, uuid):
172 """Delete specified object.
173
174 :param resource: The name of the REST resource, e.g., 'nodes'.
175 :param uuid: The unique identifier of an object in UUID format.
176 :returns: A tuple with the server response and the response body.
177
178 """
179 uri = self._get_uri(resource, uuid)
180
181 resp, body = self.delete(uri)
ghanshyam013f6112016-04-21 17:19:06 +0900182 self.expected_success(204, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900183 return resp, body
184
185 def _patch_request(self, resource, uuid, patch_object):
186 """Update specified object with JSON-patch.
187
188 :param resource: The name of the REST resource, e.g., 'nodes'.
189 :param uuid: The unique identifier of an object in UUID format.
190 :returns: A tuple with the server response and the serialized patched
191 object.
192
193 """
194 uri = self._get_uri(resource, uuid)
195 patch_body = json.dumps(patch_object)
196
197 resp, body = self.patch(uri, body=patch_body)
ghanshyam013f6112016-04-21 17:19:06 +0900198 self.expected_success(200, resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900199 return resp, self.deserialize(body)
200
201 @handle_errors
202 def get_api_description(self):
203 """Retrieves all versions of the Ironic API."""
204
205 return self._list_request('', permanent=True)
206
207 @handle_errors
208 def get_version_description(self, version='v1'):
209 """Retrieves the desctription of the API.
210
211 :param version: The version of the API. Default: 'v1'.
212 :returns: Serialized description of API resources.
213
214 """
215 return self._list_request(version, permanent=True)
216
217 def _put_request(self, resource, put_object):
218 """Update specified object with JSON-patch."""
219 uri = self._get_uri(resource)
220 put_body = json.dumps(put_object)
221
222 resp, body = self.put(uri, body=put_body)
ghanshyam013f6112016-04-21 17:19:06 +0900223 self.expected_success([202, 204], resp.status)
Yuiko Takadab6527002015-12-07 11:49:12 +0900224 return resp, body