blob: bc5e04ace6b90a286ef25e8a04a4b2fe6c61eab1 [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
23from tempest.common import glance_http
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +090024from tempest.common import rest_client
Matt Riedemann7cec3462014-06-25 12:48:43 -070025from tempest.common.utils import misc as misc_utils
Matthew Treinish684d8992014-01-30 16:27:40 +000026from tempest import config
Matthew Treinish72ea4422013-02-07 14:42:49 -050027from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040028from tempest.openstack.common import log as logging
Matthew Treinish72ea4422013-02-07 14:42:49 -050029
Matthew Treinish684d8992014-01-30 16:27:40 +000030CONF = config.CONF
31
Attila Fazekase72b7cd2013-03-26 18:34:21 +010032LOG = logging.getLogger(__name__)
33
Matthew Treinish72ea4422013-02-07 14:42:49 -050034
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +090035class ImageClientJSON(rest_client.RestClient):
Matthew Treinish72ea4422013-02-07 14:42:49 -050036
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000037 def __init__(self, auth_provider):
38 super(ImageClientJSON, self).__init__(auth_provider)
Matt Riedemannd3efe902014-02-10 06:46:38 -080039 self.service = CONF.image.catalog_type
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000040 self._http = None
Matthew Treinish72ea4422013-02-07 14:42:49 -050041
42 def _image_meta_from_headers(self, headers):
43 meta = {'properties': {}}
44 for key, value in headers.iteritems():
45 if key.startswith('x-image-meta-property-'):
46 _key = key[22:]
47 meta['properties'][_key] = value
48 elif key.startswith('x-image-meta-'):
49 _key = key[13:]
50 meta[_key] = value
51
52 for key in ['is_public', 'protected', 'deleted']:
53 if key in meta:
54 meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
55 '1')
56 for key in ['size', 'min_ram', 'min_disk']:
57 if key in meta:
58 try:
59 meta[key] = int(meta[key])
60 except ValueError:
61 pass
62 return meta
63
64 def _image_meta_to_headers(self, fields):
65 headers = {}
66 fields_copy = copy.deepcopy(fields)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010067 copy_from = fields_copy.pop('copy_from', None)
68 if copy_from is not None:
69 headers['x-glance-api-copy-from'] = copy_from
Matthew Treinish72ea4422013-02-07 14:42:49 -050070 for key, value in fields_copy.pop('properties', {}).iteritems():
71 headers['x-image-meta-property-%s' % key] = str(value)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010072 for key, value in fields_copy.pop('api', {}).iteritems():
73 headers['x-glance-api-property-%s' % key] = str(value)
Matthew Treinish72ea4422013-02-07 14:42:49 -050074 for key, value in fields_copy.iteritems():
75 headers['x-image-meta-%s' % key] = str(value)
76 return headers
77
78 def _get_file_size(self, obj):
79 """Analyze file-like object and attempt to determine its size.
80
81 :param obj: file-like object, typically redirected from stdin.
82 :retval The file's size or None if it cannot be determined.
83 """
84 # For large images, we need to supply the size of the
85 # image file. See LP Bugs #827660 and #845788.
86 if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
87 try:
88 obj.seek(0, os.SEEK_END)
89 obj_size = obj.tell()
90 obj.seek(0)
91 return obj_size
Dirk Mueller1db5db22013-06-23 20:21:32 +020092 except IOError as e:
Matthew Treinish72ea4422013-02-07 14:42:49 -050093 if e.errno == errno.ESPIPE:
94 # Illegal seek. This means the user is trying
95 # to pipe image data to the client, e.g.
96 # echo testdata | bin/glance add blah..., or
97 # that stdin is empty, or that a file-like
98 # object which doesn't support 'seek/tell' has
99 # been supplied.
100 return None
101 else:
102 raise
103 else:
104 # Cannot determine size of input image
105 return None
106
107 def _get_http(self):
Matthew Treinish684d8992014-01-30 16:27:40 +0000108 dscv = CONF.identity.disable_ssl_certificate_validation
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000109 return glance_http.HTTPClient(auth_provider=self.auth_provider,
110 filters=self.filters,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500111 insecure=dscv)
112
113 def _create_with_data(self, headers, data):
114 resp, body_iter = self.http.raw_request('POST', '/v1/images',
115 headers=headers, body=data)
116 self._error_checker('POST', '/v1/images', headers, data, resp,
117 body_iter)
118 body = json.loads(''.join([c for c in body_iter]))
119 return resp, body['image']
120
121 def _update_with_data(self, image_id, headers, data):
122 url = '/v1/images/%s' % image_id
123 resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
124 body=data)
125 self._error_checker('PUT', url, headers, data,
126 resp, body_iter)
127 body = json.loads(''.join([c for c in body_iter]))
128 return resp, body['image']
129
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000130 @property
131 def http(self):
132 if self._http is None:
133 if CONF.service_available.glance:
134 self._http = self._get_http()
135 return self._http
136
Matthew Treinishce3ef922013-03-11 14:02:46 -0400137 def create_image(self, name, container_format, disk_format, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500138 params = {
139 "name": name,
140 "container_format": container_format,
141 "disk_format": disk_format,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500142 }
Matthew Treinishce3ef922013-03-11 14:02:46 -0400143
Matthew Treinish72ea4422013-02-07 14:42:49 -0500144 headers = {}
145
hi2suresh75a20302013-04-09 09:17:06 +0000146 for option in ['is_public', 'location', 'properties',
147 'copy_from', 'min_ram']:
Matthew Treinishce3ef922013-03-11 14:02:46 -0400148 if option in kwargs:
149 params[option] = kwargs.get(option)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500150
151 headers.update(self._image_meta_to_headers(params))
152
Matthew Treinishce3ef922013-03-11 14:02:46 -0400153 if 'data' in kwargs:
154 return self._create_with_data(headers, kwargs.get('data'))
Matthew Treinish72ea4422013-02-07 14:42:49 -0500155
Matthew Treinishce3ef922013-03-11 14:02:46 -0400156 resp, body = self.post('v1/images', None, headers)
David Kranz9c3b3b62014-06-19 16:05:53 -0400157 self.expected_success(201, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500158 body = json.loads(body)
159 return resp, body['image']
160
161 def update_image(self, image_id, name=None, container_format=None,
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800162 data=None, properties=None):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500163 params = {}
164 headers = {}
165 if name is not None:
166 params['name'] = name
167
168 if container_format is not None:
169 params['container_format'] = container_format
170
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800171 if properties is not None:
172 params['properties'] = properties
173
Matthew Treinish72ea4422013-02-07 14:42:49 -0500174 headers.update(self._image_meta_to_headers(params))
175
176 if data is not None:
177 return self._update_with_data(image_id, headers, data)
178
179 url = 'v1/images/%s' % image_id
180 resp, body = self.put(url, data, headers)
David Kranz9c3b3b62014-06-19 16:05:53 -0400181 self.expected_success(200, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500182 body = json.loads(body)
183 return resp, body['image']
184
185 def delete_image(self, image_id):
186 url = 'v1/images/%s' % image_id
David Kranz9c3b3b62014-06-19 16:05:53 -0400187 resp, body = self.delete(url)
188 self.expected_success(200, resp.status)
189 return resp, body
Matthew Treinish72ea4422013-02-07 14:42:49 -0500190
Attila Fazekas11795b52013-02-24 15:49:08 +0100191 def image_list(self, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500192 url = 'v1/images'
193
Attila Fazekas11795b52013-02-24 15:49:08 +0100194 if len(kwargs) > 0:
195 url += '?%s' % urllib.urlencode(kwargs)
196
197 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400198 self.expected_success(200, resp.status)
Attila Fazekas11795b52013-02-24 15:49:08 +0100199 body = json.loads(body)
200 return resp, body['images']
201
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800202 def image_list_detail(self, properties=dict(), changes_since=None,
203 **kwargs):
Attila Fazekas11795b52013-02-24 15:49:08 +0100204 url = 'v1/images/detail'
205
ivan-zhuccc89462013-10-31 16:53:12 +0800206 params = {}
207 for key, value in properties.items():
208 params['property-%s' % key] = value
209
210 kwargs.update(params)
211
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800212 if changes_since is not None:
213 kwargs['changes-since'] = changes_since
214
Attila Fazekas11795b52013-02-24 15:49:08 +0100215 if len(kwargs) > 0:
216 url += '?%s' % urllib.urlencode(kwargs)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500217
218 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400219 self.expected_success(200, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500220 body = json.loads(body)
221 return resp, body['images']
222
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100223 def get_image_meta(self, image_id):
224 url = 'v1/images/%s' % image_id
225 resp, __ = self.head(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400226 self.expected_success(200, resp.status)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100227 body = self._image_meta_from_headers(resp)
228 return resp, body
229
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100230 def get_image(self, image_id):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500231 url = 'v1/images/%s' % image_id
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100232 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400233 self.expected_success(200, resp.status)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500234 return resp, body
235
236 def is_resource_deleted(self, id):
237 try:
Attila Fazekas9fa29472014-08-18 09:48:00 +0200238 self.get_image_meta(id)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500239 except exceptions.NotFound:
240 return True
241 return False
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500242
243 def get_image_membership(self, image_id):
244 url = 'v1/images/%s/members' % image_id
245 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400246 self.expected_success(200, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500247 body = json.loads(body)
248 return resp, body
249
250 def get_shared_images(self, member_id):
251 url = 'v1/shared-images/%s' % member_id
252 resp, body = self.get(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400253 self.expected_success(200, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500254 body = json.loads(body)
255 return resp, body
256
257 def add_member(self, member_id, image_id, can_share=False):
258 url = 'v1/images/%s/members/%s' % (image_id, member_id)
259 body = None
260 if can_share:
261 body = json.dumps({'member': {'can_share': True}})
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200262 resp, __ = self.put(url, body)
David Kranz9c3b3b62014-06-19 16:05:53 -0400263 self.expected_success(204, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500264 return resp
265
266 def delete_member(self, member_id, image_id):
267 url = 'v1/images/%s/members/%s' % (image_id, member_id)
268 resp, __ = self.delete(url)
David Kranz9c3b3b62014-06-19 16:05:53 -0400269 self.expected_success(204, resp.status)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500270 return resp
271
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200272 # NOTE(afazekas): just for the wait function
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100273 def _get_image_status(self, image_id):
274 resp, meta = self.get_image_meta(image_id)
275 status = meta['status']
276 return status
277
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200278 # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100279 def wait_for_image_status(self, image_id, status):
280 """Waits for a Image to reach a given status."""
281 start_time = time.time()
282 old_value = value = self._get_image_status(image_id)
283 while True:
284 dtime = time.time() - start_time
285 time.sleep(self.build_interval)
286 if value != old_value:
287 LOG.info('Value transition from "%s" to "%s"'
288 'in %d second(s).', old_value,
289 value, dtime)
Attila Fazekas195f1d42013-10-24 10:33:16 +0200290 if value == status:
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100291 return value
292
Attila Fazekas195f1d42013-10-24 10:33:16 +0200293 if value == 'killed':
294 raise exceptions.ImageKilledException(image_id=image_id,
Attila Fazekas9dfb9072013-11-13 16:45:36 +0100295 status=status)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100296 if dtime > self.build_timeout:
297 message = ('Time Limit Exceeded! (%ds)'
298 'while waiting for %s, '
299 'but we got %s.' %
300 (self.build_timeout, status, value))
Matt Riedemann7cec3462014-06-25 12:48:43 -0700301 caller = misc_utils.find_test_caller()
302 if caller:
303 message = '(%s) %s' % (caller, message)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100304 raise exceptions.TimeoutException(message)
305 time.sleep(self.build_interval)
306 old_value = value
307 value = self._get_image_status(image_id)