Merge "Add pod.yaml files for Apex"
[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 import openstack_utils
22 from yardstick.common.openstack_utils import get_glance_client
23 from yardstick.common import constants as consts
24
25 LOG = logging.getLogger(__name__)
26 LOG.setLevel(logging.DEBUG)
27
28 IMAGE_MAP = {
29     'yardstick-image': {
30         'path': os.path.join(consts.IMAGE_DIR, 'yardstick-image.img'),
31         'url': 'http://artifacts.opnfv.org/yardstick/images/yardstick-image.img'
32     },
33     'Ubuntu-16.04': {
34         'path': os.path.join(consts.IMAGE_DIR, 'xenial-server-cloudimg-amd64-disk1.img'),
35         'url': 'cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img'
36     },
37     'cirros-0.3.5': {
38         'path': os.path.join(consts.IMAGE_DIR, 'cirros-0.3.5-x86_64-disk.img'),
39         'url': 'http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img'
40     }
41 }
42
43
44 class V2Images(ApiResource):
45
46     def get(self):
47         try:
48             source_env(consts.OPENRC)
49         except OSError:
50             return result_handler(consts.API_ERROR, 'source openrc error')
51
52         image_list = openstack_utils.list_images()
53
54         if image_list is False:
55             return result_handler(consts.API_ERROR, 'get images error')
56
57         images = {i.name: format_image_info(i) for i in image_list}
58
59         return result_handler(consts.API_SUCCESS, {'status': 1, 'images': images})
60
61     def post(self):
62         return self._dispatch_post()
63
64     def load_image(self, args):
65         try:
66             image_name = args['name']
67         except KeyError:
68             return result_handler(consts.API_ERROR, 'image name must provided')
69
70         if image_name not in IMAGE_MAP:
71             return result_handler(consts.API_ERROR, 'wrong image name')
72
73         thread = threading.Thread(target=self._do_load_image, args=(image_name,))
74         thread.start()
75         return result_handler(consts.API_SUCCESS, {'image': image_name})
76
77     def upload_image(self, args):
78         try:
79             image_file = args['file']
80         except KeyError:
81             return result_handler(consts.API_ERROR, 'file must be provided')
82
83         try:
84             environment_id = args['environment_id']
85         except KeyError:
86             return result_handler(consts.API_ERROR, 'environment_id must be provided')
87
88         try:
89             uuid.UUID(environment_id)
90         except ValueError:
91             return result_handler(consts.API_ERROR, 'invalid environment id')
92
93         environment_handler = V2EnvironmentHandler()
94         try:
95             environment = environment_handler.get_by_uuid(environment_id)
96         except ValueError:
97             return result_handler(consts.API_ERROR, 'no such environment')
98
99         file_path = os.path.join(consts.IMAGE_DIR, image_file.filename)
100         LOG.info('saving file')
101         image_file.save(file_path)
102
103         LOG.info('loading image')
104         self._load_image(image_file.filename, file_path)
105
106         LOG.info('creating image in DB')
107         image_handler = V2ImageHandler()
108         image_id = str(uuid.uuid4())
109         image_init_data = {
110             'uuid': image_id,
111             'name': image_file.filename,
112             'environment_id': environment_id
113         }
114         image_handler.insert(image_init_data)
115
116         LOG.info('update image in environment')
117         if environment.image_id:
118             image_list = environment.image_id.split(',')
119             image_list.append(image_id)
120             new_image_id = ','.join(image_list)
121         else:
122             new_image_id = image_id
123
124         environment_handler.update_attr(environment_id, {'image_id': new_image_id})
125
126         return result_handler(consts.API_SUCCESS, {'uuid': image_id})
127
128     def upload_image_by_url(self, args):
129         try:
130             url = args['url']
131         except KeyError:
132             return result_handler(consts.API_ERROR, 'url must be provided')
133
134         try:
135             environment_id = args['environment_id']
136         except KeyError:
137             return result_handler(consts.API_ERROR, 'environment_id must be provided')
138
139         try:
140             uuid.UUID(environment_id)
141         except ValueError:
142             return result_handler(consts.API_ERROR, 'invalid environment id')
143
144         environment_handler = V2EnvironmentHandler()
145         try:
146             environment = environment_handler.get_by_uuid(environment_id)
147         except ValueError:
148             return result_handler(consts.API_ERROR, 'no such environment')
149
150         thread = threading.Thread(target=self._do_upload_image_by_url, args=(url,))
151         thread.start()
152
153         file_name = url.split('/')[-1]
154
155         LOG.info('creating image in DB')
156         image_handler = V2ImageHandler()
157         image_id = str(uuid.uuid4())
158         image_init_data = {
159             'uuid': image_id,
160             'name': file_name,
161             'environment_id': environment_id
162         }
163         image_handler.insert(image_init_data)
164
165         LOG.info('update image in environment')
166         if environment.image_id:
167             image_list = environment.image_id.split(',')
168             image_list.append(image_id)
169             new_image_id = ','.join(image_list)
170         else:
171             new_image_id = image_id
172
173         environment_handler.update_attr(environment_id, {'image_id': new_image_id})
174
175         return result_handler(consts.API_SUCCESS, {'uuid': image_id})
176
177     def delete_image(self, args):
178         try:
179             image_name = args['name']
180         except KeyError:
181             return result_handler(consts.API_ERROR, 'image name must provided')
182
183         if image_name not in IMAGE_MAP:
184             return result_handler(consts.API_ERROR, 'wrong image name')
185
186         glance_client = get_glance_client()
187         try:
188             image = next((i for i in glance_client.images.list() if i.name == image_name))
189         except StopIteration:
190             return result_handler(consts.API_ERROR, 'can not find image')
191
192         glance_client.images.delete(image.id)
193
194         return result_handler(consts.API_SUCCESS, {})
195
196     def _do_upload_image_by_url(self, url):
197         file_name = url.split('/')[-1]
198         path = os.path.join(consts.IMAGE_DIR, file_name)
199
200         LOG.info('download image')
201         self._download_image(url, path)
202
203         LOG.info('loading image')
204         self._load_image(file_name, path)
205
206     def _do_load_image(self, image_name):
207         if not os.path.exists(IMAGE_MAP[image_name]['path']):
208             self._download_image(IMAGE_MAP[image_name]['url'],
209                                  IMAGE_MAP[image_name]['path'])
210
211         self._load_image(image_name, IMAGE_MAP[image_name]['path'])
212
213     def _load_image(self, image_name, image_path):
214         LOG.info('source openrc')
215         source_env(consts.OPENRC)
216
217         LOG.info('load image')
218         glance_client = get_glance_client()
219         image = glance_client.images.create(name=image_name,
220                                             visibility='public',
221                                             disk_format='qcow2',
222                                             container_format='bare')
223         with open(image_path, 'rb') as f:
224             glance_client.images.upload(image.id, f)
225
226         LOG.info('Done')
227
228     def _download_image(self, url, path):
229         start = datetime.datetime.now().replace(microsecond=0)
230
231         LOG.info('download image from: %s', url)
232         self._download_file(url, path)
233
234         end = datetime.datetime.now().replace(microsecond=0)
235         LOG.info('download image success, total: %s s', end - start)
236
237     def _download_handler(self, start, end, url, filename):
238
239         headers = {'Range': 'bytes=%d-%d' % (start, end)}
240         r = requests.get(url, headers=headers, stream=True)
241
242         with open(filename, "r+b") as fp:
243             fp.seek(start)
244             fp.tell()
245             fp.write(r.content)
246
247     def _download_file(self, url, path, num_thread=5):
248
249         r = requests.head(url)
250         try:
251             file_size = int(r.headers['content-length'])
252         except (TypeError, ValueError):
253             return
254
255         with open(path, 'wb') as f:
256             f.truncate(file_size)
257
258         thread_list = []
259         part = file_size // num_thread
260         for i in range(num_thread):
261             start = part * i
262             end = start + part if i != num_thread - 1 else file_size
263
264             kwargs = {'start': start, 'end': end, 'url': url, 'filename': path}
265             t = threading.Thread(target=self._download_handler, kwargs=kwargs)
266             t.setDaemon(True)
267             t.start()
268             thread_list.append(t)
269
270         for t in thread_list:
271             t.join()
272
273
274 class V2Image(ApiResource):
275     def get(self, image_id):
276         try:
277             uuid.UUID(image_id)
278         except ValueError:
279             return result_handler(consts.API_ERROR, 'invalid image id')
280
281         image_handler = V2ImageHandler()
282         try:
283             image = image_handler.get_by_uuid(image_id)
284         except ValueError:
285             return result_handler(consts.API_ERROR, 'no such image id')
286
287         images = openstack_utils.list_images()
288         try:
289             image = next((i for i in images if i.name == image.name))
290         except StopIteration:
291             pass
292
293         return_image = format_image_info(image)
294         return_image['id'] = image_id
295
296         return result_handler(consts.API_SUCCESS, {'image': return_image})
297
298     def delete(self, image_id):
299         try:
300             uuid.UUID(image_id)
301         except ValueError:
302             return result_handler(consts.API_ERROR, 'invalid image id')
303
304         image_handler = V2ImageHandler()
305         try:
306             image = image_handler.get_by_uuid(image_id)
307         except ValueError:
308             return result_handler(consts.API_ERROR, 'no such image id')
309
310         LOG.info('delete image in openstack')
311         glance_client = get_glance_client()
312         try:
313             image_o = next((i for i in glance_client.images.list() if i.name == image.name))
314         except StopIteration:
315             return result_handler(consts.API_ERROR, 'can not find image')
316
317         glance_client.images.delete(image_o.id)
318
319         LOG.info('delete image in environment')
320         environment_id = image.environment_id
321         environment_handler = V2EnvironmentHandler()
322         environment = environment_handler.get_by_uuid(environment_id)
323         image_list = environment.image_id.split(',')
324         image_list.remove(image_id)
325         environment_handler.update_attr(environment_id, {'image_id': ','.join(image_list)})
326
327         LOG.info('delete image in DB')
328         image_handler.delete_by_uuid(image_id)
329
330         return result_handler(consts.API_SUCCESS, {'image': image_id})
331
332
333 def format_image_info(image):
334     image_dict = {}
335
336     if image is None:
337         return image_dict
338
339     image_dict['name'] = image.name
340     image_dict['size'] = float(image.size) / 1024 / 1024
341     image_dict['status'] = image.status.upper()
342     image_dict['time'] = image.updated_at
343
344     return image_dict