blob: dac77a299505ff67661a62e04394b79da4f9047a [file] [log] [blame]
Matthew Treinish72ea4422013-02-07 14:42:49 -05001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2#
Kurt Taylor6a6f5be2013-04-02 18:53:47 -04003# Copyright 2013 IBM Corp.
Matthew Treinish72ea4422013-02-07 14:42:49 -05004# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import copy
Attila Fazekas5c068812013-02-14 15:29:44 +010019import errno
Matthew Treinish72ea4422013-02-07 14:42:49 -050020import json
21import os
Attila Fazekase72b7cd2013-03-26 18:34:21 +010022import time
Matthew Treinish72ea4422013-02-07 14:42:49 -050023import urllib
24
25from tempest.common import glance_http
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090026from tempest.common import log as logging
Matthew Treinish72ea4422013-02-07 14:42:49 -050027from tempest.common.rest_client import RestClient
28from tempest import exceptions
Matthew Treinish72ea4422013-02-07 14:42:49 -050029
Attila Fazekase72b7cd2013-03-26 18:34:21 +010030LOG = logging.getLogger(__name__)
31
Matthew Treinish72ea4422013-02-07 14:42:49 -050032
33class ImageClientJSON(RestClient):
34
35 def __init__(self, config, username, password, auth_url, tenant_name=None):
36 super(ImageClientJSON, self).__init__(config, username, password,
37 auth_url, tenant_name)
38 self.service = self.config.images.catalog_type
39 self.http = self._get_http()
40
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 Treinish801f3aa2013-02-28 15:35:03 -0500107 token, endpoint = self.keystone_auth(self.user,
108 self.password,
109 self.auth_url,
110 self.service,
111 self.tenant_name)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500112 dscv = self.config.identity.disable_ssl_certificate_validation
113 return glance_http.HTTPClient(endpoint=endpoint, token=token,
114 insecure=dscv)
115
116 def _create_with_data(self, headers, data):
117 resp, body_iter = self.http.raw_request('POST', '/v1/images',
118 headers=headers, body=data)
119 self._error_checker('POST', '/v1/images', headers, data, resp,
120 body_iter)
121 body = json.loads(''.join([c for c in body_iter]))
122 return resp, body['image']
123
124 def _update_with_data(self, image_id, headers, data):
125 url = '/v1/images/%s' % image_id
126 resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
127 body=data)
128 self._error_checker('PUT', url, headers, data,
129 resp, body_iter)
130 body = json.loads(''.join([c for c in body_iter]))
131 return resp, body['image']
132
Matthew Treinishce3ef922013-03-11 14:02:46 -0400133 def create_image(self, name, container_format, disk_format, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500134 params = {
135 "name": name,
136 "container_format": container_format,
137 "disk_format": disk_format,
Matthew Treinish72ea4422013-02-07 14:42:49 -0500138 }
Matthew Treinishce3ef922013-03-11 14:02:46 -0400139
Matthew Treinish72ea4422013-02-07 14:42:49 -0500140 headers = {}
141
hi2suresh75a20302013-04-09 09:17:06 +0000142 for option in ['is_public', 'location', 'properties',
143 'copy_from', 'min_ram']:
Matthew Treinishce3ef922013-03-11 14:02:46 -0400144 if option in kwargs:
145 params[option] = kwargs.get(option)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500146
147 headers.update(self._image_meta_to_headers(params))
148
Matthew Treinishce3ef922013-03-11 14:02:46 -0400149 if 'data' in kwargs:
150 return self._create_with_data(headers, kwargs.get('data'))
Matthew Treinish72ea4422013-02-07 14:42:49 -0500151
Matthew Treinishce3ef922013-03-11 14:02:46 -0400152 resp, body = self.post('v1/images', None, headers)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500153 body = json.loads(body)
154 return resp, body['image']
155
156 def update_image(self, image_id, name=None, container_format=None,
157 data=None):
158 params = {}
159 headers = {}
160 if name is not None:
161 params['name'] = name
162
163 if container_format is not None:
164 params['container_format'] = container_format
165
166 headers.update(self._image_meta_to_headers(params))
167
168 if data is not None:
169 return self._update_with_data(image_id, headers, data)
170
171 url = 'v1/images/%s' % image_id
172 resp, body = self.put(url, data, headers)
173 body = json.loads(body)
174 return resp, body['image']
175
176 def delete_image(self, image_id):
177 url = 'v1/images/%s' % image_id
Matthew Treinish801f3aa2013-02-28 15:35:03 -0500178 self.delete(url)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500179
Attila Fazekas11795b52013-02-24 15:49:08 +0100180 def image_list(self, **kwargs):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500181 url = 'v1/images'
182
Attila Fazekas11795b52013-02-24 15:49:08 +0100183 if len(kwargs) > 0:
184 url += '?%s' % urllib.urlencode(kwargs)
185
186 resp, body = self.get(url)
187 body = json.loads(body)
188 return resp, body['images']
189
190 def image_list_detail(self, **kwargs):
191 url = 'v1/images/detail'
192
193 if len(kwargs) > 0:
194 url += '?%s' % urllib.urlencode(kwargs)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500195
196 resp, body = self.get(url)
197 body = json.loads(body)
198 return resp, body['images']
199
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100200 def get_image_meta(self, image_id):
201 url = 'v1/images/%s' % image_id
202 resp, __ = self.head(url)
203 body = self._image_meta_from_headers(resp)
204 return resp, body
205
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100206 def get_image(self, image_id):
Matthew Treinish72ea4422013-02-07 14:42:49 -0500207 url = 'v1/images/%s' % image_id
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100208 resp, body = self.get(url)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500209 return resp, body
210
211 def is_resource_deleted(self, id):
212 try:
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100213 self.get_image(id)
Matthew Treinish72ea4422013-02-07 14:42:49 -0500214 except exceptions.NotFound:
215 return True
216 return False
Matthew Treinishfa23cf82013-03-06 14:23:02 -0500217
218 def get_image_membership(self, image_id):
219 url = 'v1/images/%s/members' % image_id
220 resp, body = self.get(url)
221 body = json.loads(body)
222 return resp, body
223
224 def get_shared_images(self, member_id):
225 url = 'v1/shared-images/%s' % member_id
226 resp, body = self.get(url)
227 body = json.loads(body)
228 return resp, body
229
230 def add_member(self, member_id, image_id, can_share=False):
231 url = 'v1/images/%s/members/%s' % (image_id, member_id)
232 body = None
233 if can_share:
234 body = json.dumps({'member': {'can_share': True}})
235 resp, __ = self.put(url, body, self.headers)
236 return resp
237
238 def delete_member(self, member_id, image_id):
239 url = 'v1/images/%s/members/%s' % (image_id, member_id)
240 resp, __ = self.delete(url)
241 return resp
242
243 def replace_membership_list(self, image_id, member_list):
244 url = 'v1/images/%s/members' % image_id
245 body = json.dumps({'membership': member_list})
246 resp, data = self.put(url, body, self.headers)
247 data = json.loads(data)
248 return resp, data
Attila Fazekase72b7cd2013-03-26 18:34:21 +0100249
250 #NOTE(afazekas): just for the wait function
251 def _get_image_status(self, image_id):
252 resp, meta = self.get_image_meta(image_id)
253 status = meta['status']
254 return status
255
256 #NOTE(afazkas): Wait reinvented again. It is not in the correct layer
257 def wait_for_image_status(self, image_id, status):
258 """Waits for a Image to reach a given status."""
259 start_time = time.time()
260 old_value = value = self._get_image_status(image_id)
261 while True:
262 dtime = time.time() - start_time
263 time.sleep(self.build_interval)
264 if value != old_value:
265 LOG.info('Value transition from "%s" to "%s"'
266 'in %d second(s).', old_value,
267 value, dtime)
268 if (value == status):
269 return value
270
271 if dtime > self.build_timeout:
272 message = ('Time Limit Exceeded! (%ds)'
273 'while waiting for %s, '
274 'but we got %s.' %
275 (self.build_timeout, status, value))
276 raise exceptions.TimeoutException(message)
277 time.sleep(self.build_interval)
278 old_value = value
279 value = self._get_image_status(image_id)