blob: e22cd9c822d34125a0e4dd9d2a146ab85c5bdb66 [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
Matthew Treinish684d8992014-01-30 16:27:40 +000025from tempest import config
Matthew Treinish72ea4422013-02-07 14:42:49 -050026from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040027from tempest.openstack.common import log as logging
Matthew Treinish72ea4422013-02-07 14:42:49 -050028
Matthew Treinish684d8992014-01-30 16:27:40 +000029CONF = config.CONF
30
Attila Fazekase72b7cd2013-03-26 18:34:21 +010031LOG = logging.getLogger(__name__)
32
Matthew Treinish72ea4422013-02-07 14:42:49 -050033
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +090034class ImageClientJSON(rest_client.RestClient):
Matthew Treinish72ea4422013-02-07 14:42:49 -050035
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000036 def __init__(self, auth_provider):
37 super(ImageClientJSON, self).__init__(auth_provider)
Matt Riedemannd3efe902014-02-10 06:46:38 -080038 self.service = CONF.image.catalog_type
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000039 self._http = None
Matthew Treinish72ea4422013-02-07 14:42:49 -050040
41 def _image_meta_from_headers(self, headers):
42 meta = {'properties': {}}
43 for key, value in headers.iteritems():
44 if key.startswith('x-image-meta-property-'):
45 _key = key[22:]
46 meta['properties'][_key] = value
47 elif key.startswith('x-image-meta-'):
48 _key = key[13:]
49 meta[_key] = value
50
51 for key in ['is_public', 'protected', 'deleted']:
52 if key in meta:
53 meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
54 '1')
55 for key in ['size', 'min_ram', 'min_disk']:
56 if key in meta:
57 try:
58 meta[key] = int(meta[key])
59 except ValueError:
60 pass
61 return meta
62
63 def _image_meta_to_headers(self, fields):
64 headers = {}
65 fields_copy = copy.deepcopy(fields)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010066 copy_from = fields_copy.pop('copy_from', None)
67 if copy_from is not None:
68 headers['x-glance-api-copy-from'] = copy_from
Matthew Treinish72ea4422013-02-07 14:42:49 -050069 for key, value in fields_copy.pop('properties', {}).iteritems():
70 headers['x-image-meta-property-%s' % key] = str(value)
Attila Fazekase72b7cd2013-03-26 18:34:21 +010071 for key, value in fields_copy.pop('api', {}).iteritems():
72 headers['x-glance-api-property-%s' % key] = str(value)
Matthew Treinish72ea4422013-02-07 14:42:49 -050073 for key, value in fields_copy.iteritems():
74 headers['x-image-meta-%s' % key] = str(value)
75 return headers
76
77 def _get_file_size(self, obj):
78 """Analyze file-like object and attempt to determine its size.
79
80 :param obj: file-like object, typically redirected from stdin.
81 :retval The file's size or None if it cannot be determined.
82 """
83 # For large images, we need to supply the size of the
84 # image file. See LP Bugs #827660 and #845788.
85 if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
86 try:
87 obj.seek(0, os.SEEK_END)
88 obj_size = obj.tell()
89 obj.seek(0)
90 return obj_size
Dirk Mueller1db5db22013-06-23 20:21:32 +020091 except IOError as e:
Matthew Treinish72ea4422013-02-07 14:42:49 -050092 if e.errno == errno.ESPIPE:
93 # Illegal seek. This means the user is trying
94 # to pipe image data to the client, e.g.
95 # echo testdata | bin/glance add blah..., or
96 # that stdin is empty, or that a file-like
97 # object which doesn't support 'seek/tell' has
98 # been supplied.
99 return None
100 else:
101 raise
102 else:
103 # Cannot determine size of input image
104 return None
105
106 def _get_http(self):
Matthew Treinish684d8992014-01-30 16:27:40 +0000107 dscv = CONF.identity.disable_ssl_certificate_validation
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000108 return glance_http.HTTPClient(auth_provider=self.auth_provider,
109 filters=self.filters,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500110 insecure=dscv)
111
112 def _create_with_data(self, headers, data):
113 resp, body_iter = self.http.raw_request('POST', '/v1/images',
114 headers=headers, body=data)
115 self._error_checker('POST', '/v1/images', headers, data, resp,
116 body_iter)
117 body = json.loads(''.join([c for c in body_iter]))
118 return resp, body['image']
119
120 def _update_with_data(self, image_id, headers, data):
121 url = '/v1/images/%s' % image_id
122 resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
123 body=data)
124 self._error_checker('PUT', url, headers, data,
125 resp, body_iter)
126 body = json.loads(''.join([c for c in body_iter]))
127 return resp, body['image']
128
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000129 @property
130 def http(self):
131 if self._http is None:
132 if CONF.service_available.glance:
133 self._http = self._get_http()
134 return self._http
135
Matthew Treinishce3ef922013-03-11 14:02:46 -0400136 def create_image(self, name, container_format, disk_format, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500137 params = {
138 "name": name,
139 "container_format": container_format,
140 "disk_format": disk_format,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500141 }
Matthew Treinishce3ef922013-03-11 14:02:46 -0400142
Matthew Treinish72ea4422013-02-07 14:42:49 -0500143 headers = {}
144
hi2suresh75a20302013-04-09 09:17:06 +0000145 for option in ['is_public', 'location', 'properties',
146 'copy_from', 'min_ram']:
Matthew Treinishce3ef922013-03-11 14:02:46 -0400147 if option in kwargs:
148 params[option] = kwargs.get(option)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500149
150 headers.update(self._image_meta_to_headers(params))
151
Matthew Treinishce3ef922013-03-11 14:02:46 -0400152 if 'data' in kwargs:
153 return self._create_with_data(headers, kwargs.get('data'))
Matthew Treinish72ea4422013-02-07 14:42:49 -0500154
Matthew Treinishce3ef922013-03-11 14:02:46 -0400155 resp, body = self.post('v1/images', None, headers)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500156 body = json.loads(body)
157 return resp, body['image']
158
159 def update_image(self, image_id, name=None, container_format=None,
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800160 data=None, properties=None):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500161 params = {}
162 headers = {}
163 if name is not None:
164 params['name'] = name
165
166 if container_format is not None:
167 params['container_format'] = container_format
168
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800169 if properties is not None:
170 params['properties'] = properties
171
Matthew Treinish72ea4422013-02-07 14:42:49 -0500172 headers.update(self._image_meta_to_headers(params))
173
174 if data is not None:
175 return self._update_with_data(image_id, headers, data)
176
177 url = 'v1/images/%s' % image_id
178 resp, body = self.put(url, data, headers)
179 body = json.loads(body)
180 return resp, body['image']
181
182 def delete_image(self, image_id):
183 url = 'v1/images/%s' % image_id
ivan-zhu8f992be2013-07-31 14:56:58 +0800184 return self.delete(url)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500185
Attila Fazekas11795b52013-02-24 15:49:08 +0100186 def image_list(self, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500187 url = 'v1/images'
188
Attila Fazekas11795b52013-02-24 15:49:08 +0100189 if len(kwargs) > 0:
190 url += '?%s' % urllib.urlencode(kwargs)
191
192 resp, body = self.get(url)
193 body = json.loads(body)
194 return resp, body['images']
195
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800196 def image_list_detail(self, properties=dict(), changes_since=None,
197 **kwargs):
Attila Fazekas11795b52013-02-24 15:49:08 +0100198 url = 'v1/images/detail'
199
ivan-zhuccc89462013-10-31 16:53:12 +0800200 params = {}
201 for key, value in properties.items():
202 params['property-%s' % key] = value
203
204 kwargs.update(params)
205
ivan-zhud1bbe5d2013-12-29 18:32:46 +0800206 if changes_since is not None:
207 kwargs['changes-since'] = changes_since
208
Attila Fazekas11795b52013-02-24 15:49:08 +0100209 if len(kwargs) > 0:
210 url += '?%s' % urllib.urlencode(kwargs)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500211
212 resp, body = self.get(url)
213 body = json.loads(body)
214 return resp, body['images']
215
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100216 def get_image_meta(self, image_id):
217 url = 'v1/images/%s' % image_id
218 resp, __ = self.head(url)
219 body = self._image_meta_from_headers(resp)
220 return resp, body
221
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100222 def get_image(self, image_id):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500223 url = 'v1/images/%s' % image_id
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100224 resp, body = self.get(url)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500225 return resp, body
226
227 def is_resource_deleted(self, id):
228 try:
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100229 self.get_image(id)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500230 except exceptions.NotFound:
231 return True
232 return False
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500233
234 def get_image_membership(self, image_id):
235 url = 'v1/images/%s/members' % image_id
236 resp, body = self.get(url)
237 body = json.loads(body)
238 return resp, body
239
240 def get_shared_images(self, member_id):
241 url = 'v1/shared-images/%s' % member_id
242 resp, body = self.get(url)
243 body = json.loads(body)
244 return resp, body
245
246 def add_member(self, member_id, image_id, can_share=False):
247 url = 'v1/images/%s/members/%s' % (image_id, member_id)
248 body = None
249 if can_share:
250 body = json.dumps({'member': {'can_share': True}})
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200251 resp, __ = self.put(url, body)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500252 return resp
253
254 def delete_member(self, member_id, image_id):
255 url = 'v1/images/%s/members/%s' % (image_id, member_id)
256 resp, __ = self.delete(url)
257 return resp
258
259 def replace_membership_list(self, image_id, member_list):
260 url = 'v1/images/%s/members' % image_id
261 body = json.dumps({'membership': member_list})
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200262 resp, data = self.put(url, body)
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500263 data = json.loads(data)
264 return resp, data
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100265
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200266 # NOTE(afazekas): just for the wait function
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100267 def _get_image_status(self, image_id):
268 resp, meta = self.get_image_meta(image_id)
269 status = meta['status']
270 return status
271
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200272 # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100273 def wait_for_image_status(self, image_id, status):
274 """Waits for a Image to reach a given status."""
275 start_time = time.time()
276 old_value = value = self._get_image_status(image_id)
277 while True:
278 dtime = time.time() - start_time
279 time.sleep(self.build_interval)
280 if value != old_value:
281 LOG.info('Value transition from "%s" to "%s"'
282 'in %d second(s).', old_value,
283 value, dtime)
Attila Fazekas195f1d42013-10-24 10:33:16 +0200284 if value == status:
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100285 return value
286
Attila Fazekas195f1d42013-10-24 10:33:16 +0200287 if value == 'killed':
288 raise exceptions.ImageKilledException(image_id=image_id,
Attila Fazekas9dfb9072013-11-13 16:45:36 +0100289 status=status)
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100290 if dtime > self.build_timeout:
291 message = ('Time Limit Exceeded! (%ds)'
292 'while waiting for %s, '
293 'but we got %s.' %
294 (self.build_timeout, status, value))
295 raise exceptions.TimeoutException(message)
296 time.sleep(self.build_interval)
297 old_value = value
298 value = self._get_image_status(image_id)