Merge "bugfix: tc063 fails to get the correct result in ubuntu node"
[yardstick.git] / api / resources / v2 / images.py
1 ##############################################################################
2 # Copyright (c) 2017 Huawei Technologies Co.,Ltd.
3 #
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 ##############################################################################
9 import logging
10 import os
11 import uuid
12 import threading
13 import requests
14 import datetime
15
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
25
26 LOG = logging.getLogger(__name__)
27 LOG.setLevel(logging.DEBUG)
28
29 IMAGE_MAP = {
30     'yardstick-image': {
31         'path': os.path.join(consts.IMAGE_DIR, 'yardstick-image.img'),
32         'url': 'http://artifacts.opnfv.org/yardstick/images/yardstick-image.img'
33     },
34     'Ubuntu-16.04': {
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'
37     },
38     'cirros-0.3.5': {
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'
41     }
42 }
43
44
45 class V2Images(ApiResource):
46
47     def get(self):
48         try:
49             source_env(consts.OPENRC)
50         except Exception:
51             return result_handler(consts.API_ERROR, 'source openrc error')
52
53         nova_client = get_nova_client()
54         try:
55             images_list = nova_client.images.list()
56         except Exception:
57             return result_handler(consts.API_ERROR, 'get images error')
58         else:
59             images = {i.name: self.get_info(change_obj_to_dict(i)) for i in images_list}
60
61         return result_handler(consts.API_SUCCESS, {'status': 1, 'images': images})
62
63     def post(self):
64         return self._dispatch_post()
65
66     def get_info(self, data):
67         try:
68             size = data['OS-EXT-IMG-SIZE:size']
69         except KeyError:
70             size = None
71         else:
72             size = float(size) / 1024 / 1024
73
74         result = {
75             'name': data.get('name', ''),
76             'discription': data.get('description', ''),
77             'size': size,
78             'status': data.get('status'),
79             'time': data.get('updated')
80         }
81         return result
82
83     def load_image(self, args):
84         try:
85             image_name = args['name']
86         except KeyError:
87             return result_handler(consts.API_ERROR, 'image name must provided')
88
89         if image_name not in IMAGE_MAP:
90             return result_handler(consts.API_ERROR, 'wrong image name')
91
92         thread = threading.Thread(target=self._do_load_image, args=(image_name,))
93         thread.start()
94         return result_handler(consts.API_SUCCESS, {'image': image_name})
95
96     def upload_image(self, args):
97         try:
98             image_file = args['file']
99         except KeyError:
100             return result_handler(consts.API_ERROR, 'file must be provided')
101
102         try:
103             environment_id = args['environment_id']
104         except KeyError:
105             return result_handler(consts.API_ERROR, 'environment_id must be provided')
106
107         try:
108             uuid.UUID(environment_id)
109         except ValueError:
110             return result_handler(consts.API_ERROR, 'invalid environment id')
111
112         environment_handler = V2EnvironmentHandler()
113         try:
114             environment = environment_handler.get_by_uuid(environment_id)
115         except ValueError:
116             return result_handler(consts.API_ERROR, 'no such environment')
117
118         file_path = os.path.join(consts.IMAGE_DIR, image_file.filename)
119         LOG.info('saving file')
120         image_file.save(file_path)
121
122         LOG.info('loading image')
123         self._load_image(image_file.filename, file_path)
124
125         LOG.info('creating image in DB')
126         image_handler = V2ImageHandler()
127         image_id = str(uuid.uuid4())
128         image_init_data = {
129             'uuid': image_id,
130             'name': image_file.filename,
131             'environment_id': environment_id
132         }
133         image_handler.insert(image_init_data)
134
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)
140         else:
141             new_image_id = image_id
142
143         environment_handler.update_attr(environment_id, {'image_id': new_image_id})
144
145         return result_handler(consts.API_SUCCESS, {'uuid': image_id})
146
147     def upload_image_by_url(self, args):
148         try:
149             url = args['url']
150         except KeyError:
151             return result_handler(consts.API_ERROR, 'url must be provided')
152
153         try:
154             environment_id = args['environment_id']
155         except KeyError:
156             return result_handler(consts.API_ERROR, 'environment_id must be provided')
157
158         try:
159             uuid.UUID(environment_id)
160         except ValueError:
161             return result_handler(consts.API_ERROR, 'invalid environment id')
162
163         environment_handler = V2EnvironmentHandler()
164         try:
165             environment = environment_handler.get_by_uuid(environment_id)
166         except ValueError:
167             return result_handler(consts.API_ERROR, 'no such environment')
168
169         thread = threading.Thread(target=self._do_upload_image_by_url, args=(url,))
170         thread.start()
171
172         file_name = url.split('/')[-1]
173
174         LOG.info('creating image in DB')
175         image_handler = V2ImageHandler()
176         image_id = str(uuid.uuid4())
177         image_init_data = {
178             'uuid': image_id,
179             'name': file_name,
180             'environment_id': environment_id
181         }
182         image_handler.insert(image_init_data)
183
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)
189         else:
190             new_image_id = image_id
191
192         environment_handler.update_attr(environment_id, {'image_id': new_image_id})
193
194         return result_handler(consts.API_SUCCESS, {'uuid': image_id})
195
196     def delete_image(self, args):
197         try:
198             image_name = args['name']
199         except KeyError:
200             return result_handler(consts.API_ERROR, 'image name must provided')
201
202         if image_name not in IMAGE_MAP:
203             return result_handler(consts.API_ERROR, 'wrong image name')
204
205         glance_client = get_glance_client()
206         try:
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')
210
211         glance_client.images.delete(image.id)
212
213         return result_handler(consts.API_SUCCESS, {})
214
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)
218
219         LOG.info('download image')
220         self._download_image(url, path)
221
222         LOG.info('loading image')
223         self._load_image(file_name, path)
224
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'])
229
230         self._load_image(image_name, IMAGE_MAP[image_name]['path'])
231
232     def _load_image(self, image_name, image_path):
233         LOG.info('source openrc')
234         source_env(consts.OPENRC)
235
236         LOG.info('load image')
237         glance_client = get_glance_client()
238         image = glance_client.images.create(name=image_name,
239                                             visibility='public',
240                                             disk_format='qcow2',
241                                             container_format='bare')
242         with open(image_path, 'rb') as f:
243             glance_client.images.upload(image.id, f)
244
245         LOG.info('Done')
246
247     def _download_image(self, url, path):
248         start = datetime.datetime.now().replace(microsecond=0)
249
250         LOG.info('download image from: %s', url)
251         self._download_file(url, path)
252
253         end = datetime.datetime.now().replace(microsecond=0)
254         LOG.info('download image success, total: %s s', end - start)
255
256     def _download_handler(self, start, end, url, filename):
257
258         headers = {'Range': 'bytes=%d-%d' % (start, end)}
259         r = requests.get(url, headers=headers, stream=True)
260
261         with open(filename, "r+b") as fp:
262             fp.seek(start)
263             fp.tell()
264             fp.write(r.content)
265
266     def _download_file(self, url, path, num_thread=5):
267
268         r = requests.head(url)
269         try:
270             file_size = int(r.headers['content-length'])
271         except Exception:
272             return
273
274         with open(path, 'wb') as f:
275             f.truncate(file_size)
276
277         thread_list = []
278         part = file_size // num_thread
279         for i in range(num_thread):
280             start = part * i
281             end = start + part if i != num_thread - 1 else file_size
282
283             kwargs = {'start': start, 'end': end, 'url': url, 'filename': path}
284             t = threading.Thread(target=self._download_handler, kwargs=kwargs)
285             t.setDaemon(True)
286             t.start()
287             thread_list.append(t)
288
289         for t in thread_list:
290             t.join()
291
292
293 class V2Image(ApiResource):
294     def get(self, image_id):
295         try:
296             uuid.UUID(image_id)
297         except ValueError:
298             return result_handler(consts.API_ERROR, 'invalid image id')
299
300         image_handler = V2ImageHandler()
301         try:
302             image = image_handler.get_by_uuid(image_id)
303         except ValueError:
304             return result_handler(consts.API_ERROR, 'no such image id')
305
306         nova_client = get_nova_client()
307         images = nova_client.images.list()
308         try:
309             image = next((i for i in images if i.name == image.name))
310         except StopIteration:
311             pass
312
313         return_image = self.get_info(change_obj_to_dict(image))
314         return_image['id'] = image_id
315
316         return result_handler(consts.API_SUCCESS, {'image': return_image})
317
318     def delete(self, image_id):
319         try:
320             uuid.UUID(image_id)
321         except ValueError:
322             return result_handler(consts.API_ERROR, 'invalid image id')
323
324         image_handler = V2ImageHandler()
325         try:
326             image = image_handler.get_by_uuid(image_id)
327         except ValueError:
328             return result_handler(consts.API_ERROR, 'no such image id')
329
330         LOG.info('delete image in openstack')
331         glance_client = get_glance_client()
332         try:
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')
336
337         glance_client.images.delete(image_o.id)
338
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)})
346
347         LOG.info('delete image in DB')
348         image_handler.delete_by_uuid(image_id)
349
350         return result_handler(consts.API_SUCCESS, {'image': image_id})
351
352     def get_info(self, data):
353         try:
354             size = data['OS-EXT-IMG-SIZE:size']
355         except KeyError:
356             size = None
357         else:
358             size = float(size) / 1024 / 1024
359
360         result = {
361             'name': data.get('name', ''),
362             'description': data.get('description', ''),
363             'size': size,
364             'status': data.get('status'),
365             'time': data.get('updated')
366         }
367         return result