blob: 50f75b3b720311d844d84c9c1a7204009cf4d250 [file] [log] [blame]
Kurt Taylor6a6f5be2013-04-02 18:53:47 -04001# Copyright 2013 IBM Corp.
Matthew Treinish72ea4422013-02-07 14:42:49 -05002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import copy
Attila Fazekas5c068812013-02-14 15:29:44 +010017import errno
Matthew Treinish72ea4422013-02-07 14:42:49 -050018import json
19import os
Attila Fazekase72b7cd2013-03-26 18:34:21 +010020import time
Matthew Treinish72ea4422013-02-07 14:42:49 -050021import urllib
22
Masayuki Igawabfa07602015-01-20 18:47:17 +090023from tempest_lib import exceptions as lib_exc
24
Matthew Treinish72ea4422013-02-07 14:42:49 -050025from tempest.common import glance_http
Ken'ichi Ohmichi0e836652015-01-08 04:38:56 +000026from tempest.common import service_client
Matt Riedemann7cec3462014-06-25 12:48:43 -070027from tempest.common.utils import misc as misc_utils
Matthew Treinish72ea4422013-02-07 14:42:49 -050028from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040029from tempest.openstack.common import log as logging
Matthew Treinish72ea4422013-02-07 14:42:49 -050030
Attila Fazekase72b7cd2013-03-26 18:34:21 +010031LOG = logging.getLogger(__name__)
32
Matthew Treinish72ea4422013-02-07 14:42:49 -050033
Ken'ichi Ohmichi0e836652015-01-08 04:38:56 +000034class ImageClientJSON(service_client.ServiceClient):
Matthew Treinish72ea4422013-02-07 14:42:49 -050035
Masayuki Igawabc7e1892015-03-03 11:46:48 +090036 def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
37 build_interval=None, build_timeout=None,
38 disable_ssl_certificate_validation=None,
39 ca_certs=None, **kwargs):
Ken'ichi Ohmichi0690ea42015-01-02 07:03:51 +000040 super(ImageClientJSON, self).__init__(
41 auth_provider,
Masayuki Igawabc7e1892015-03-03 11:46:48 +090042 catalog_type,
43 region,
44 endpoint_type=endpoint_type,
45 build_interval=build_interval,
46 build_timeout=build_timeout,
47 disable_ssl_certificate_validation=(
48 disable_ssl_certificate_validation),
49 ca_certs=ca_certs,
50 **kwargs)
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000051 self._http = None
Masayuki Igawabc7e1892015-03-03 11:46:48 +090052 self.dscv = disable_ssl_certificate_validation
53 self.ca_certs = ca_certs
Matthew Treinish72ea4422013-02-07 14:42:49 -050054
55 def _image_meta_from_headers(self, headers):
56 meta = {'properties': {}}
57 for key, value in headers.iteritems():
58 if key.startswith('x-image-meta-property-'):
59 _key = key[22:]
60 meta['properties'][_key] = value
61 elif key.startswith('x-image-meta-'):
62 _key = key[13:]
63 meta[_key] = value
64
65 for key in ['is_public', 'protected', 'deleted']:
66 if key in meta:
67 meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
68 '1')
69 for key in ['size', 'min_ram', 'min_disk']:
70 if key in meta:
71 try:
72 meta[key] = int(meta[key])
73 except ValueError:
74 pass
75 return meta
76
77 def _image_meta_to_headers(self, fields):
78 headers = {}
79 fields_copy = copy.deepcopy(fields)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010080 copy_from = fields_copy.pop('copy_from', None)
81 if copy_from is not None:
82 headers['x-glance-api-copy-from'] = copy_from
Matthew Treinish72ea4422013-02-07 14:42:49 -050083 for key, value in fields_copy.pop('properties', {}).iteritems():
84 headers['x-image-meta-property-%s' % key] = str(value)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010085 for key, value in fields_copy.pop('api', {}).iteritems():
86 headers['x-glance-api-property-%s' % key] = str(value)
Matthew Treinish72ea4422013-02-07 14:42:49 -050087 for key, value in fields_copy.iteritems():
88 headers['x-image-meta-%s' % key] = str(value)
89 return headers
90
91 def _get_file_size(self, obj):
92 """Analyze file-like object and attempt to determine its size.
93
94 :param obj: file-like object, typically redirected from stdin.
95 :retval The file's size or None if it cannot be determined.
96 """
97 # For large images, we need to supply the size of the
98 # image file. See LP Bugs #827660 and #845788.
99 if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
100 try:
101 obj.seek(0, os.SEEK_END)
102 obj_size = obj.tell()
103 obj.seek(0)
104 return obj_size
Dirk Mueller1db5db22013-06-23 20:21:32 +0200105 except IOError as e:
Matthew Treinish72ea4422013-02-07 14:42:49 -0500106 if e.errno == errno.ESPIPE:
107 # Illegal seek. This means the user is trying
108 # to pipe image data to the client, e.g.
109 # echo testdata | bin/glance add blah..., or
110 # that stdin is empty, or that a file-like
111 # object which doesn't support 'seek/tell' has
112 # been supplied.
113 return None
114 else:
115 raise
116 else:
117 # Cannot determine size of input image
118 return None
119
120 def _get_http(self):
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000121 return glance_http.HTTPClient(auth_provider=self.auth_provider,
122 filters=self.filters,
Masayuki Igawabc7e1892015-03-03 11:46:48 +0900123 insecure=self.dscv,
124 ca_certs=self.ca_certs)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500125
126 def _create_with_data(self, headers, data):
127 resp, body_iter = self.http.raw_request('POST', '/v1/images',
128 headers=headers, body=data)
129 self._error_checker('POST', '/v1/images', headers, data, resp,
130 body_iter)
131 body = json.loads(''.join([c for c in body_iter]))
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000132 return service_client.ResponseBody(resp, body['image'])
Matthew Treinish72ea4422013-02-07 14:42:49 -0500133
134 def _update_with_data(self, image_id, headers, data):
135 url = '/v1/images/%s' % image_id
136 resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
137 body=data)
138 self._error_checker('PUT', url, headers, data,
139 resp, body_iter)
140 body = json.loads(''.join([c for c in body_iter]))
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000141 return service_client.ResponseBody(resp, body['image'])
Matthew Treinish72ea4422013-02-07 14:42:49 -0500142
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000143 @property
144 def http(self):
145 if self._http is None:
Masayuki Igawabc7e1892015-03-03 11:46:48 +0900146 self._http = self._get_http()
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000147 return self._http
148
Matthew Treinishce3ef922013-03-11 14:02:46 -0400149 def create_image(self, name, container_format, disk_format, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500150 params = {
151 "name": name,
152 "container_format": container_format,
153 "disk_format": disk_format,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500154 }
Matthew Treinishce3ef922013-03-11 14:02:46 -0400155
Matthew Treinish72ea4422013-02-07 14:42:49 -0500156 headers = {}
157
hi2suresh75a20302013-04-09 09:17:06 +0000158 for option in ['is_public', 'location', 'properties',
159 'copy_from', 'min_ram']:
Matthew Treinishce3ef922013-03-11 14:02:46 -0400160 if option in kwargs:
161 params[option] = kwargs.get(option)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500162
163 headers.update(self._image_meta_to_headers(params))
164
Matthew Treinishce3ef922013-03-11 14:02:46 -0400165 if 'data' in kwargs:
166 return self._create_with_data(headers, kwargs.get('data'))
Matthew Treinish72ea4422013-02-07 14:42:49 -0500167
Matthew Treinishce3ef922013-03-11 14:02:46 -0400168 resp, body = self.post('v1/images', None, headers)
David Kranz9c3b3b62014-06-19 16:05:53 -0400169 self.expected_success(201, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500170 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000171 return service_client.ResponseBody(resp, body['image'])
Matthew Treinish72ea4422013-02-07 14:42:49 -0500172
173 def update_image(self, image_id, name=None, container_format=None,
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800174 data=None, properties=None):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500175 params = {}
176 headers = {}
177 if name is not None:
178 params['name'] = name
179
180 if container_format is not None:
181 params['container_format'] = container_format
182
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800183 if properties is not None:
184 params['properties'] = properties
185
Matthew Treinish72ea4422013-02-07 14:42:49 -0500186 headers.update(self._image_meta_to_headers(params))
187
188 if data is not None:
189 return self._update_with_data(image_id, headers, data)
190
191 url = 'v1/images/%s' % image_id
192 resp, body = self.put(url, data, headers)
David Kranz9c3b3b62014-06-19 16:05:53 -0400193 self.expected_success(200, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500194 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000195 return service_client.ResponseBody(resp, body['image'])
Matthew Treinish72ea4422013-02-07 14:42:49 -0500196
197 def delete_image(self, image_id):
198 url = 'v1/images/%s' % image_id
David Kranz9c3b3b62014-06-19 16:05:53 -0400199 resp, body = self.delete(url)
200 self.expected_success(200, resp.status)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000201 return service_client.ResponseBody(resp, body)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500202
Attila Fazekas11795b52013-02-24 15:49:08 +0100203 def image_list(self, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500204 url = 'v1/images'
205
Attila Fazekas11795b52013-02-24 15:49:08 +0100206 if len(kwargs) > 0:
207 url += '?%s' % urllib.urlencode(kwargs)
208
209 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400210 self.expected_success(200, resp.status)
Attila Fazekas11795b52013-02-24 15:49:08 +0100211 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000212 return service_client.ResponseBodyList(resp, body['images'])
Attila Fazekas11795b52013-02-24 15:49:08 +0100213
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800214 def image_list_detail(self, properties=dict(), changes_since=None,
215 **kwargs):
Attila Fazekas11795b52013-02-24 15:49:08 +0100216 url = 'v1/images/detail'
217
ivan-zhuccc89462013-10-31 16:53:12 +0800218 params = {}
219 for key, value in properties.items():
220 params['property-%s' % key] = value
221
222 kwargs.update(params)
223
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800224 if changes_since is not None:
225 kwargs['changes-since'] = changes_since
226
Attila Fazekas11795b52013-02-24 15:49:08 +0100227 if len(kwargs) > 0:
228 url += '?%s' % urllib.urlencode(kwargs)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500229
230 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400231 self.expected_success(200, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500232 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000233 return service_client.ResponseBodyList(resp, body['images'])
Matthew Treinish72ea4422013-02-07 14:42:49 -0500234
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100235 def get_image_meta(self, image_id):
236 url = 'v1/images/%s' % image_id
237 resp, __ = self.head(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400238 self.expected_success(200, resp.status)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100239 body = self._image_meta_from_headers(resp)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000240 return service_client.ResponseBody(resp, body)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100241
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100242 def get_image(self, image_id):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500243 url = 'v1/images/%s' % image_id
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100244 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400245 self.expected_success(200, resp.status)
David Kranzd7e97b42015-02-16 09:37:31 -0500246 return service_client.ResponseBodyData(resp, body)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500247
248 def is_resource_deleted(self, id):
249 try:
Attila Fazekas9fa29472014-08-18 09:48:00 +0200250 self.get_image_meta(id)
Masayuki Igawabfa07602015-01-20 18:47:17 +0900251 except lib_exc.NotFound:
Matthew Treinish72ea4422013-02-07 14:42:49 -0500252 return True
253 return False
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500254
Matt Riedemannd2b96512014-10-13 10:18:16 -0700255 @property
256 def resource_type(self):
257 """Returns the primary type of resource this client works with."""
258 return 'image_meta'
259
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500260 def get_image_membership(self, image_id):
261 url = 'v1/images/%s/members' % image_id
262 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400263 self.expected_success(200, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500264 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000265 return service_client.ResponseBody(resp, body)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500266
267 def get_shared_images(self, member_id):
268 url = 'v1/shared-images/%s' % member_id
269 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400270 self.expected_success(200, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500271 body = json.loads(body)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000272 return service_client.ResponseBody(resp, body)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500273
274 def add_member(self, member_id, image_id, can_share=False):
275 url = 'v1/images/%s/members/%s' % (image_id, member_id)
276 body = None
277 if can_share:
278 body = json.dumps({'member': {'can_share': True}})
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200279 resp, __ = self.put(url, body)
David Kranz9c3b3b62014-06-19 16:05:53 -0400280 self.expected_success(204, resp.status)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000281 return service_client.ResponseBody(resp)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500282
283 def delete_member(self, member_id, image_id):
284 url = 'v1/images/%s/members/%s' % (image_id, member_id)
285 resp, __ = self.delete(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400286 self.expected_success(204, resp.status)
Ken'ichi Ohmichia6ac2422015-01-13 01:09:39 +0000287 return service_client.ResponseBody(resp)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500288
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200289 # NOTE(afazekas): just for the wait function
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100290 def _get_image_status(self, image_id):
David Kranz34f18782015-01-06 13:43:55 -0500291 meta = self.get_image_meta(image_id)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100292 status = meta['status']
293 return status
294
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200295 # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100296 def wait_for_image_status(self, image_id, status):
297 """Waits for a Image to reach a given status."""
298 start_time = time.time()
299 old_value = value = self._get_image_status(image_id)
300 while True:
301 dtime = time.time() - start_time
302 time.sleep(self.build_interval)
303 if value != old_value:
304 LOG.info('Value transition from "%s" to "%s"'
305 'in %d second(s).', old_value,
306 value, dtime)
Attila Fazekas195f1d42013-10-24 10:33:16 +0200307 if value == status:
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100308 return value
309
Attila Fazekas195f1d42013-10-24 10:33:16 +0200310 if value == 'killed':
311 raise exceptions.ImageKilledException(image_id=image_id,
Attila Fazekas9dfb9072013-11-13 16:45:36 +0100312 status=status)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100313 if dtime > self.build_timeout:
314 message = ('Time Limit Exceeded! (%ds)'
315 'while waiting for %s, '
316 'but we got %s.' %
317 (self.build_timeout, status, value))
Matt Riedemann7cec3462014-06-25 12:48:43 -0700318 caller = misc_utils.find_test_caller()
319 if caller:
320 message = '(%s) %s' % (caller, message)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100321 raise exceptions.TimeoutException(message)
322 time.sleep(self.build_interval)
323 old_value = value
324 value = self._get_image_status(image_id)