aboutsummaryrefslogtreecommitdiffstats
path: root/api/resources/v2/images.py
blob: 0c36a0a26268ca4ade7e206702e3d57c6f845c3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
###################################################
# All the jobs except verify have been removed!
# They will only be enabled on request by projects!
###################################################
- project:
    name: conductor

    project: '{name}'

    jobs:
        - 'conductor-verify-{stream}'

    stream:
        - master:
            branch: '{stream}'
            gs-pathname: ''
            disabled: false
        - colorado:
            branch: 'stable/{stream}'
            gs-pathname: '/{stream}'
            disabled: false

- job-template:
    name: 'conductor-verify-{stream}'

    disabled: '{obj:disabled}'

    parameters:
        - project-parameter:
            project: '{project}'
        - gerrit-parameter:
            branch: '{branch}'
        - 'opnfv-build-ubuntu-defaults'

    scm:
        - git-scm-gerrit

    triggers:
        - gerrit:
            server-name: 'gerrit.opnfv.org'
            trigger-on:
                - patchset-created-event:
                    exclude-drafts: 'false'
                    exclude-trivial-rebase: 'false'
                    exclude-no-code-change: 'false'
                - draft-published-event
                - comment-added-contains-event:
                    comment-contains-value: 'recheck'
                - comment-added-contains-event:
                    comment-contains-value: 'reverify'
            projects:
              - project-compare-type: 'ANT'
                project-pattern: '{project}'
                
##############################################################################
# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
import logging
import os
import uuid
import threading
import requests
import datetime

from api import ApiResource
from api.database.v2.handlers import V2ImageHandler
from api.database.v2.handlers import V2EnvironmentHandler
from yardstick.common.utils import result_handler
from yardstick.common.utils import source_env
from yardstick.common.utils import change_obj_to_dict
from yardstick.common.openstack_utils import get_nova_client
from yardstick.common.openstack_utils import get_glance_client
from yardstick.common import constants as consts

LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG)

IMAGE_MAP = {
    'yardstick-image': {
        'path': os.path.join(consts.IMAGE_DIR, 'yardstick-image.img'),
        'url': 'http://artifacts.opnfv.org/yardstick/images/yardstick-image.img'
    },
    'Ubuntu-16.04': {
        'path': os.path.join(consts.IMAGE_DIR, 'xenial-server-cloudimg-amd64-disk1.img'),
        'url': 'cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img'
    },
    'cirros-0.3.5': {
        'path': os.path.join(consts.IMAGE_DIR, 'cirros-0.3.5-x86_64-disk.img'),
        'url': 'http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img'
    }
}


class V2Images(ApiResource):

    def get(self):
        try:
            source_env(consts.OPENRC)
        except Exception:
            return result_handler(consts.API_ERROR, 'source openrc error')

        nova_client = get_nova_client()
        try:
            images_list = nova_client.images.list()
        except Exception:
            return result_handler(consts.API_ERROR, 'get images error')
        else:
            images = {i.name: self.get_info(change_obj_to_dict(i)) for i in images_list}

        return result_handler(consts.API_SUCCESS, {'status': 1, 'images': images})

    def post(self):
        return self._dispatch_post()

    def get_info(self, data):
        try:
            size = data['OS-EXT-IMG-SIZE:size']
        except KeyError:
            size = None
        else:
            size = float(size) / 1024 / 1024

        result = {
            'name': data.get('name', ''),
            'discription': data.get('description', ''),
            'size': size,
            'status': data.get('status'),
            'time': data.get('updated')
        }
        return result

    def load_image(self, args):
        try:
            image_name = args['name']
        except KeyError:
            return result_handler(consts.API_ERROR, 'image name must provided')

        if image_name not in IMAGE_MAP:
            return result_handler(consts.API_ERROR, 'wrong image name')

        thread = threading.Thread(target=self._do_load_image, args=(image_name,))
        thread.start()
        return result_handler(consts.API_SUCCESS, {'image': image_name})

    def upload_image(self, args):
        try:
            image_file = args['file']
        except KeyError:
            return result_handler(consts.API_ERROR, 'file must be provided')

        try:
            environment_id = args['environment_id']
        except KeyError:
            return result_handler(consts.API_ERROR, 'environment_id must be provided')

        try:
            uuid.UUID(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid environment id')

        environment_handler = V2EnvironmentHandler()
        try:
            environment = environment_handler.get_by_uuid(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such environment')

        file_path = os.path.join(consts.IMAGE_DIR, image_file.filename)
        LOG.info('saving file')
        image_file.save(file_path)

        LOG.info('loading image')
        self._load_image(image_file.filename, file_path)

        LOG.info('creating image in DB')
        image_handler = V2ImageHandler()
        image_id = str(uuid.uuid4())
        image_init_data = {
            'uuid': image_id,
            'name': image_file.filename,
            'environment_id': environment_id
        }
        image_handler.insert(image_init_data)

        LOG.info('update image in environment')
        if environment.image_id:
            image_list = environment.image_id.split(',')
            image_list.append(image_id)
            new_image_id = ','.join(image_list)
        else:
            new_image_id = image_id

        environment_handler.update_attr(environment_id, {'image_id': new_image_id})

        return result_handler(consts.API_SUCCESS, {'uuid': image_id})

    def upload_image_by_url(self, args):
        try:
            url = args['url']
        except KeyError:
            return result_handler(consts.API_ERROR, 'url must be provided')

        try:
            environment_id = args['environment_id']
        except KeyError:
            return result_handler(consts.API_ERROR, 'environment_id must be provided')

        try:
            uuid.UUID(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid environment id')

        environment_handler = V2EnvironmentHandler()
        try:
            environment = environment_handler.get_by_uuid(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such environment')

        thread = threading.Thread(target=self._do_upload_image_by_url, args=(url,))
        thread.start()

        file_name = url.split('/')[-1]

        LOG.info('creating image in DB')
        image_handler = V2ImageHandler()
        image_id = str(uuid.uuid4())
        image_init_data = {
            'uuid': image_id,
            'name': file_name,
            'environment_id': environment_id
        }
        image_handler.insert(image_init_data)

        LOG.info('update image in environment')
        if environment.image_id:
            image_list = environment.image_id.split(',')
            image_list.append(image_id)
            new_image_id = ','.join(image_list)
        else:
            new_image_id = image_id

        environment_handler.update_attr(environment_id, {'image_id': new_image_id})

        return result_handler(consts.API_SUCCESS, {'uuid': image_id})

    def delete_image(self, args):
        try:
            image_name = args['name']
        except KeyError:
            return result_handler(consts.API_ERROR, 'image name must provided')

        if image_name not in IMAGE_MAP:
            return result_handler(consts.API_ERROR, 'wrong image name')

        glance_client = get_glance_client()
        try:
            image = next((i for i in glance_client.images.list() if i.name == image_name))
        except StopIteration:
            return result_handler(consts.API_ERROR, 'can not find image')

        glance_client.images.delete(image.id)

        return result_handler(consts.API_SUCCESS, {})

    def _do_upload_image_by_url(self, url):
        file_name = url.split('/')[-1]
        path = os.path.join(consts.IMAGE_DIR, file_name)

        LOG.info('download image')
        self._download_image(url, path)

        LOG.info('loading image')
        self._load_image(file_name, path)

    def _do_load_image(self, image_name):
        if not os.path.exists(IMAGE_MAP[image_name]['path']):
            self._download_image(IMAGE_MAP[image_name]['url'],
                                 IMAGE_MAP[image_name]['path'])

        self._load_image(image_name, IMAGE_MAP[image_name]['path'])

    def _load_image(self, image_name, image_path):
        LOG.info('source openrc')
        source_env(consts.OPENRC)

        LOG.info('load image')
        glance_client = get_glance_client()
        image = glance_client.images.create(name=image_name,
                                            visibility='public',
                                            disk_format='qcow2',
                                            container_format='bare')
        with open(image_path, 'rb') as f:
            glance_client.images.upload(image.id, f)

        LOG.info('Done')

    def _download_image(self, url, path):
        start = datetime.datetime.now().replace(microsecond=0)

        LOG.info('download image from: %s', url)
        self._download_file(url, path)

        end = datetime.datetime.now().replace(microsecond=0)
        LOG.info('download image success, total: %s s', end - start)

    def _download_handler(self, start, end, url, filename):

        headers = {'Range': 'bytes=%d-%d' % (start, end)}
        r = requests.get(url, headers=headers, stream=True)

        with open(filename, "r+b") as fp:
            fp.seek(start)
            fp.tell()
            fp.write(r.content)

    def _download_file(self, url, path, num_thread=5):

        r = requests.head(url)
        try:
            file_size = int(r.headers['content-length'])
        except Exception:
            return

        with open(path, 'wb') as f:
            f.truncate(file_size)

        thread_list = []
        part = file_size // num_thread
        for i in range(num_thread):
            start = part * i
            end = start + part if i != num_thread - 1 else file_size

            kwargs = {'start': start, 'end': end, 'url': url, 'filename': path}
            t = threading.Thread(target=self._download_handler, kwargs=kwargs)
            t.setDaemon(True)
            t.start()
            thread_list.append(t)

        for t in thread_list:
            t.join()


class V2Image(ApiResource):
    def get(self, image_id):
        try:
            uuid.UUID(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid image id')

        image_handler = V2ImageHandler()
        try:
            image = image_handler.get_by_uuid(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such image id')

        nova_client = get_nova_client()
        images = nova_client.images.list()
        try:
            image = next((i for i in images if i.name == image.name))
        except StopIteration:
            pass

        return_image = self.get_info(change_obj_to_dict(image))
        return_image['id'] = image_id

        return result_handler(consts.API_SUCCESS, {'image': return_image})

    def delete(self, image_id):
        try:
            uuid.UUID(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid image id')

        image_handler = V2ImageHandler()
        try:
            image = image_handler.get_by_uuid(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such image id')

        LOG.info('delete image in openstack')
        glance_client = get_glance_client()
        try:
            image_o = next((i for i in glance_client.images.list() if i.name == image.name))
        except StopIteration:
            return result_handler(consts.API_ERROR, 'can not find image')

        glance_client.images.delete(image_o.id)

        LOG.info('delete image in environment')
        environment_id = image.environment_id
        environment_handler = V2EnvironmentHandler()
        environment = environment_handler.get_by_uuid(environment_id)
        image_list = environment.image_id.split(',')
        image_list.remove(image_id)
        environment_handler.update_attr(environment_id, {'image_id': ','.join(image_list)})

        LOG.info('delete image in DB')
        image_handler.delete_by_uuid(image_id)

        return result_handler(consts.API_SUCCESS, {'image': image_id})

    def get_info(self, data):
        try:
            size = data['OS-EXT-IMG-SIZE:size']
        except KeyError:
            size = None
        else:
            size = float(size) / 1024 / 1024

        result = {
            'name': data.get('name', ''),
            'description': data.get('description', ''),
            'size': size,
            'status': data.get('status'),
            'time': data.get('updated')
        }
        return result