1 ##############################################################################
2 # Copyright (c) 2017 Huawei Technologies Co.,Ltd.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
16 from api import ApiResource
17 from api.database.v2.handlers import V2ImageHandler
18 from api.database.v2.handlers import V2EnvironmentHandler
19 from yardstick.common.utils import result_handler
20 from yardstick.common.utils import source_env
21 from yardstick.common.utils import change_obj_to_dict
22 from yardstick.common.openstack_utils import get_nova_client
23 from yardstick.common.openstack_utils import get_glance_client
24 from yardstick.common import constants as consts
26 LOG = logging.getLogger(__name__)
27 LOG.setLevel(logging.DEBUG)
31 'path': os.path.join(consts.IMAGE_DIR, 'yardstick-image.img'),
32 'url': 'http://artifacts.opnfv.org/yardstick/images/yardstick-image.img'
35 'path': os.path.join(consts.IMAGE_DIR, 'xenial-server-cloudimg-amd64-disk1.img'),
36 'url': 'cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img'
39 'path': os.path.join(consts.IMAGE_DIR, 'cirros-0.3.5-x86_64-disk.img'),
40 'url': 'http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img'
45 class V2Images(ApiResource):
49 source_env(consts.OPENRC)
51 return result_handler(consts.API_ERROR, 'source openrc error')
53 nova_client = get_nova_client()
55 images_list = nova_client.images.list()
57 return result_handler(consts.API_ERROR, 'get images error')
59 images = {i.name: self.get_info(change_obj_to_dict(i)) for i in images_list}
61 return result_handler(consts.API_SUCCESS, {'status': 1, 'images': images})
64 return self._dispatch_post()
66 def get_info(self, data):
68 size = data['OS-EXT-IMG-SIZE:size']
72 size = float(size) / 1024 / 1024
75 'name': data.get('name', ''),
76 'discription': data.get('description', ''),
78 'status': data.get('status'),
79 'time': data.get('updated')
83 def load_image(self, args):
85 image_name = args['name']
87 return result_handler(consts.API_ERROR, 'image name must provided')
89 if image_name not in IMAGE_MAP:
90 return result_handler(consts.API_ERROR, 'wrong image name')
92 thread = threading.Thread(target=self._do_load_image, args=(image_name,))
94 return result_handler(consts.API_SUCCESS, {'image': image_name})
96 def upload_image(self, args):
98 image_file = args['file']
100 return result_handler(consts.API_ERROR, 'file must be provided')
103 environment_id = args['environment_id']
105 return result_handler(consts.API_ERROR, 'environment_id must be provided')
108 uuid.UUID(environment_id)
110 return result_handler(consts.API_ERROR, 'invalid environment id')
112 environment_handler = V2EnvironmentHandler()
114 environment = environment_handler.get_by_uuid(environment_id)
116 return result_handler(consts.API_ERROR, 'no such environment')
118 file_path = os.path.join(consts.IMAGE_DIR, image_file.filename)
119 LOG.info('saving file')
120 image_file.save(file_path)
122 LOG.info('loading image')
123 self._load_image(image_file.filename, file_path)
125 LOG.info('creating image in DB')
126 image_handler = V2ImageHandler()
127 image_id = str(uuid.uuid4())
130 'name': image_file.filename,
131 'environment_id': environment_id
133 image_handler.insert(image_init_data)
135 LOG.info('update image in environment')
136 if environment.image_id:
137 image_list = environment.image_id.split(',')
138 image_list.append(image_id)
139 new_image_id = ','.join(image_list)
141 new_image_id = image_id
143 environment_handler.update_attr(environment_id, {'image_id': new_image_id})
145 return result_handler(consts.API_SUCCESS, {'uuid': image_id})
147 def upload_image_by_url(self, args):
151 return result_handler(consts.API_ERROR, 'url must be provided')
154 environment_id = args['environment_id']
156 return result_handler(consts.API_ERROR, 'environment_id must be provided')
159 uuid.UUID(environment_id)
161 return result_handler(consts.API_ERROR, 'invalid environment id')
163 environment_handler = V2EnvironmentHandler()
165 environment = environment_handler.get_by_uuid(environment_id)
167 return result_handler(consts.API_ERROR, 'no such environment')
169 thread = threading.Thread(target=self._do_upload_image_by_url, args=(url,))
172 file_name = url.split('/')[-1]
174 LOG.info('creating image in DB')
175 image_handler = V2ImageHandler()
176 image_id = str(uuid.uuid4())
180 'environment_id': environment_id
182 image_handler.insert(image_init_data)
184 LOG.info('update image in environment')
185 if environment.image_id:
186 image_list = environment.image_id.split(',')
187 image_list.append(image_id)
188 new_image_id = ','.join(image_list)
190 new_image_id = image_id
192 environment_handler.update_attr(environment_id, {'image_id': new_image_id})
194 return result_handler(consts.API_SUCCESS, {'uuid': image_id})
196 def delete_image(self, args):
198 image_name = args['name']
200 return result_handler(consts.API_ERROR, 'image name must provided')
202 if image_name not in IMAGE_MAP:
203 return result_handler(consts.API_ERROR, 'wrong image name')
205 glance_client = get_glance_client()
207 image = next((i for i in glance_client.images.list() if i.name == image_name))
208 except StopIteration:
209 return result_handler(consts.API_ERROR, 'can not find image')
211 glance_client.images.delete(image.id)
213 return result_handler(consts.API_SUCCESS, {})
215 def _do_upload_image_by_url(self, url):
216 file_name = url.split('/')[-1]
217 path = os.path.join(consts.IMAGE_DIR, file_name)
219 LOG.info('download image')
220 self._download_image(url, path)
222 LOG.info('loading image')
223 self._load_image(file_name, path)
225 def _do_load_image(self, image_name):
226 if not os.path.exists(IMAGE_MAP[image_name]['path']):
227 self._download_image(IMAGE_MAP[image_name]['url'],
228 IMAGE_MAP[image_name]['path'])
230 self._load_image(image_name, IMAGE_MAP[image_name]['path'])
232 def _load_image(self, image_name, image_path):
233 LOG.info('source openrc')
234 source_env(consts.OPENRC)
236 LOG.info('load image')
237 glance_client = get_glance_client()
238 image = glance_client.images.create(name=image_name,
241 container_format='bare')
242 with open(image_path, 'rb') as f:
243 glance_client.images.upload(image.id, f)
247 def _download_image(self, url, path):
248 start = datetime.datetime.now().replace(microsecond=0)
250 LOG.info('download image from: %s', url)
251 self._download_file(url, path)
253 end = datetime.datetime.now().replace(microsecond=0)
254 LOG.info('download image success, total: %s s', end - start)
256 def _download_handler(self, start, end, url, filename):
258 headers = {'Range': 'bytes=%d-%d' % (start, end)}
259 r = requests.get(url, headers=headers, stream=True)
261 with open(filename, "r+b") as fp:
266 def _download_file(self, url, path, num_thread=5):
268 r = requests.head(url)
270 file_size = int(r.headers['content-length'])
274 with open(path, 'wb') as f:
275 f.truncate(file_size)
278 part = file_size // num_thread
279 for i in range(num_thread):
281 end = start + part if i != num_thread - 1 else file_size
283 kwargs = {'start': start, 'end': end, 'url': url, 'filename': path}
284 t = threading.Thread(target=self._download_handler, kwargs=kwargs)
287 thread_list.append(t)
289 for t in thread_list:
293 class V2Image(ApiResource):
294 def get(self, image_id):
298 return result_handler(consts.API_ERROR, 'invalid image id')
300 image_handler = V2ImageHandler()
302 image = image_handler.get_by_uuid(image_id)
304 return result_handler(consts.API_ERROR, 'no such image id')
306 nova_client = get_nova_client()
307 images = nova_client.images.list()
309 image = next((i for i in images if i.name == image.name))
310 except StopIteration:
313 return_image = self.get_info(change_obj_to_dict(image))
314 return_image['id'] = image_id
316 return result_handler(consts.API_SUCCESS, {'image': return_image})
318 def delete(self, image_id):
322 return result_handler(consts.API_ERROR, 'invalid image id')
324 image_handler = V2ImageHandler()
326 image = image_handler.get_by_uuid(image_id)
328 return result_handler(consts.API_ERROR, 'no such image id')
330 LOG.info('delete image in openstack')
331 glance_client = get_glance_client()
333 image_o = next((i for i in glance_client.images.list() if i.name == image.name))
334 except StopIteration:
335 return result_handler(consts.API_ERROR, 'can not find image')
337 glance_client.images.delete(image_o.id)
339 LOG.info('delete image in environment')
340 environment_id = image.environment_id
341 environment_handler = V2EnvironmentHandler()
342 environment = environment_handler.get_by_uuid(environment_id)
343 image_list = environment.image_id.split(',')
344 image_list.remove(image_id)
345 environment_handler.update_attr(environment_id, {'image_id': ','.join(image_list)})
347 LOG.info('delete image in DB')
348 image_handler.delete_by_uuid(image_id)
350 return result_handler(consts.API_SUCCESS, {'image': image_id})
352 def get_info(self, data):
354 size = data['OS-EXT-IMG-SIZE:size']
358 size = float(size) / 1024 / 1024
361 'name': data.get('name', ''),
362 'description': data.get('description', ''),
364 'status': data.get('status'),
365 'time': data.get('updated')