blob: a0ffd28a29eed7296e150923cf393b1cd2c7e4ac [file] [log] [blame]
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +03001# 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
14import json
Sergey Nikitin0d43eb52014-02-03 14:50:02 +040015import urllib
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +030016
17import six
18
Ken'ichi Ohmichi0e836652015-01-08 04:38:56 +000019from tempest.common import service_client
Matthew Treinish684d8992014-01-30 16:27:40 +000020from tempest import config
21
22CONF = config.CONF
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +030023
24
25def handle_errors(f):
26 """A decorator that allows to ignore certain types of errors."""
27
28 @functools.wraps(f)
29 def wrapper(*args, **kwargs):
30 param_name = 'ignore_errors'
31 ignored_errors = kwargs.get(param_name, tuple())
32
33 if param_name in kwargs:
34 del kwargs[param_name]
35
36 try:
37 return f(*args, **kwargs)
38 except ignored_errors:
39 # Silently ignore errors
40 pass
41
42 return wrapper
43
44
Ken'ichi Ohmichi0e836652015-01-08 04:38:56 +000045class BaremetalClient(service_client.ServiceClient):
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +030046 """
47 Base Tempest REST client for Ironic API.
48
49 """
50
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000051 def __init__(self, auth_provider):
Ken'ichi Ohmichi0cd316b2014-12-24 03:51:04 +000052 super(BaremetalClient, self).__init__(
Ken'ichi Ohmichie9f50412015-01-05 04:57:26 +000053 auth_provider,
54 CONF.baremetal.catalog_type,
55 CONF.identity.region,
Ken'ichi Ohmichi0690ea42015-01-02 07:03:51 +000056 endpoint_type=CONF.baremetal.endpoint_type)
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +030057 self.uri_prefix = ''
58
59 def serialize(self, object_type, object_dict):
60 """Serialize an Ironic object."""
61
62 raise NotImplementedError
63
64 def deserialize(self, object_str):
65 """Deserialize an Ironic object."""
66
67 raise NotImplementedError
68
69 def _get_uri(self, resource_name, uuid=None, permanent=False):
70 """
71 Get URI for a specific resource or object.
72
73 :param resource_name: The name of the REST resource, e.g., 'nodes'.
74 :param uuid: The unique identifier of an object in UUID format.
75 :return: Relative URI for the resource or object.
76
77 """
78 prefix = self.uri_prefix if not permanent else ''
79
80 return '{pref}/{res}{uuid}'.format(pref=prefix,
81 res=resource_name,
82 uuid='/%s' % uuid if uuid else '')
83
84 def _make_patch(self, allowed_attributes, **kw):
85 """
86 Create a JSON patch according to RFC 6902.
87
88 :param allowed_attributes: An iterable object that contains a set of
89 allowed attributes for an object.
90 :param **kw: Attributes and new values for them.
91 :return: A JSON path that sets values of the specified attributes to
92 the new ones.
93
94 """
95 def get_change(kw, path='/'):
96 for name, value in six.iteritems(kw):
97 if isinstance(value, dict):
98 for ch in get_change(value, path + '%s/' % name):
99 yield ch
100 else:
Adam Gandelman00682612014-09-02 17:10:36 -0700101 if value is None:
102 yield {'path': path + name,
103 'op': 'remove'}
104 else:
105 yield {'path': path + name,
106 'value': value,
107 'op': 'replace'}
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300108
109 patch = [ch for ch in get_change(kw)
110 if ch['path'].lstrip('/') in allowed_attributes]
111
112 return patch
113
Sergey Nikitin0d43eb52014-02-03 14:50:02 +0400114 def _list_request(self, resource, permanent=False, **kwargs):
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300115 """
116 Get the list of objects of the specified type.
117
118 :param resource: The name of the REST resource, e.g., 'nodes'.
Sergey Nikitin0d43eb52014-02-03 14:50:02 +0400119 "param **kw: Parameters for the request.
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300120 :return: A tuple with the server response and deserialized JSON list
121 of objects
122
123 """
124 uri = self._get_uri(resource, permanent=permanent)
Sergey Nikitin0d43eb52014-02-03 14:50:02 +0400125 if kwargs:
126 uri += "?%s" % urllib.urlencode(kwargs)
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300127
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200128 resp, body = self.get(uri)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000129 self.expected_success(200, resp['status'])
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300130
131 return resp, self.deserialize(body)
132
raiesmh08e5d84572014-06-23 09:49:03 +0530133 def _show_request(self, resource, uuid, permanent=False, **kwargs):
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300134 """
135 Gets a specific object of the specified type.
136
137 :param uuid: Unique identifier of the object in UUID format.
138 :return: Serialized object as a dictionary.
139
140 """
raiesmh08e5d84572014-06-23 09:49:03 +0530141 if 'uri' in kwargs:
142 uri = kwargs['uri']
143 else:
144 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200145 resp, body = self.get(uri)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000146 self.expected_success(200, resp['status'])
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300147
148 return resp, self.deserialize(body)
149
150 def _create_request(self, resource, object_type, object_dict):
151 """
152 Create an object of the specified type.
153
154 :param resource: The name of the REST resource, e.g., 'nodes'.
155 :param object_dict: A Python dict that represents an object of the
156 specified type.
157 :return: A tuple with the server response and the deserialized created
158 object.
159
160 """
161 body = self.serialize(object_type, object_dict)
162 uri = self._get_uri(resource)
163
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200164 resp, body = self.post(uri, body=body)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000165 self.expected_success(201, resp['status'])
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300166
167 return resp, self.deserialize(body)
168
169 def _delete_request(self, resource, uuid):
170 """
171 Delete specified object.
172
173 :param resource: The name of the REST resource, e.g., 'nodes'.
174 :param uuid: The unique identifier of an object in UUID format.
175 :return: A tuple with the server response and the response body.
176
177 """
178 uri = self._get_uri(resource, uuid)
179
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200180 resp, body = self.delete(uri)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000181 self.expected_success(204, resp['status'])
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300182 return resp, body
183
184 def _patch_request(self, resource, uuid, patch_object):
185 """
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 :return: 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
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200197 resp, body = self.patch(uri, body=patch_body)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000198 self.expected_success(200, resp['status'])
Roman Prykhodchenko62b1ed12013-10-16 21:51:47 +0300199 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 """
210 Retrieves the desctription of the API.
211
212 :param version: The version of the API. Default: 'v1'.
213 :return: Serialized description of API resources.
214
215 """
216 return self._list_request(version, permanent=True)
Mh Raiesf8ecf232014-04-17 12:43:55 +0530217
218 def _put_request(self, resource, put_object):
219 """
220 Update specified object with JSON-patch.
221
222 """
223 uri = self._get_uri(resource)
224 put_body = json.dumps(put_object)
225
226 resp, body = self.put(uri, body=put_body)
Swapnil Kulkarniaa57d6e2014-08-19 10:40:35 +0000227 self.expected_success(202, resp['status'])
Mh Raiesf8ecf232014-04-17 12:43:55 +0530228 return resp, body