diff options
author | Jing Lu <lvjing5@huawei.com> | 2017-08-25 00:50:21 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@opnfv.org> | 2017-08-25 00:50:21 +0000 |
commit | f5a96ffd959b11a122c4beea1fc99c89f755678c (patch) | |
tree | 1c0cad1b94f33a3275c816999653870e6955d6eb | |
parent | ffecd5b84eecdb74d7534b801c1359251c34b34e (diff) | |
parent | efb4f088f14aee394599bea21973f82f1867c4fe (diff) |
Merge "Add function to upload image from local/url in GUI"
-rw-r--r-- | api/database/v2/handlers.py | 5 | ||||
-rw-r--r-- | api/database/v2/models.py | 3 | ||||
-rw-r--r-- | api/resources/v2/environments.py | 8 | ||||
-rw-r--r-- | api/resources/v2/images.py | 341 | ||||
-rw-r--r-- | api/server.py | 1 | ||||
-rw-r--r-- | api/urls.py | 1 | ||||
-rwxr-xr-x | docker/nginx.sh | 1 | ||||
-rw-r--r-- | gui/app/scripts/controllers/image.controller.js | 301 | ||||
-rw-r--r-- | gui/app/scripts/controllers/main.js | 169 | ||||
-rw-r--r-- | gui/app/scripts/controllers/projectDetail.controller.js | 2 | ||||
-rw-r--r-- | gui/app/scripts/factory/main.factory.js | 39 | ||||
-rw-r--r-- | gui/app/views/modal/environmentDialog.html | 15 | ||||
-rw-r--r-- | gui/app/views/modal/imageDialog.html | 19 | ||||
-rw-r--r-- | gui/app/views/podupload.html | 2 | ||||
-rw-r--r-- | gui/app/views/uploadImage.html | 82 | ||||
-rw-r--r-- | yardstick/common/constants.py | 1 |
16 files changed, 723 insertions, 267 deletions
diff --git a/api/database/v2/handlers.py b/api/database/v2/handlers.py index 1bc32bf0e..e4f1dd668 100644 --- a/api/database/v2/handlers.py +++ b/api/database/v2/handlers.py @@ -87,6 +87,11 @@ class V2ImageHandler(object): raise ValueError return image + def delete_by_uuid(self, uuid): + image = self.get_by_uuid(uuid) + db_session.delete(image) + db_session.commit() + class V2PodHandler(object): diff --git a/api/database/v2/models.py b/api/database/v2/models.py index 1e85559cb..59dab3ebc 100644 --- a/api/database/v2/models.py +++ b/api/database/v2/models.py @@ -48,9 +48,6 @@ class V2Image(Base): name = Column(String(30)) description = Column(Text) environment_id = Column(String(30)) - size = Column(String(30)) - status = Column(String(30)) - time = Column(DateTime) class V2Container(Base): diff --git a/api/resources/v2/environments.py b/api/resources/v2/environments.py index f021a3c5a..158e98be7 100644 --- a/api/resources/v2/environments.py +++ b/api/resources/v2/environments.py @@ -35,6 +35,9 @@ class V2Environments(ApiResource): container_info = e['container_id'] e['container_id'] = jsonutils.loads(container_info) if container_info else {} + image_id = e['image_id'] + e['image_id'] = image_id.split(',') if image_id else [] + data = { 'environments': environments } @@ -78,8 +81,13 @@ class V2Environment(ApiResource): return result_handler(consts.API_ERROR, 'no such environment id') environment = change_obj_to_dict(environment) + container_id = environment['container_id'] environment['container_id'] = jsonutils.loads(container_id) if container_id else {} + + image_id = environment['image_id'] + environment['image_id'] = image_id.split(',') if image_id else [] + return result_handler(consts.API_SUCCESS, {'environment': environment}) def delete(self, environment_id): diff --git a/api/resources/v2/images.py b/api/resources/v2/images.py index 8359e105b..0c36a0a26 100644 --- a/api/resources/v2/images.py +++ b/api/resources/v2/images.py @@ -7,76 +7,361 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## import logging -import subprocess +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: + except Exception: return result_handler(consts.API_ERROR, 'source openrc error') nova_client = get_nova_client() try: images_list = nova_client.images.list() - except: + except Exception: return result_handler(consts.API_ERROR, 'get images error') else: - images = [self.get_info(change_obj_to_dict(i)) for i in images_list] - status = 1 if all(i['status'] == 'ACTIVE' for i in images) else 0 - if not images: - status = 0 + images = {i.name: self.get_info(change_obj_to_dict(i)) for i in images_list} - return result_handler(consts.API_SUCCESS, {'status': status, 'images': images}) + 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', ''), - 'size': data.get('OS-EXT-IMG-SIZE:size', ''), - 'status': data.get('status', ''), - 'time': data.get('updated', '') + 'discription': data.get('description', ''), + 'size': size, + 'status': data.get('status'), + 'time': data.get('updated') } return result def load_image(self, args): - thread = threading.Thread(target=self._load_images) + 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 _load_images(self): + 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('clean images') - cmd = [consts.CLEAN_IMAGES_SCRIPT] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - cwd=consts.REPOS_DIR) - _, err = p.communicate() - if p.returncode != 0: - LOG.error('clean image failed: %s', err) - - LOG.info('load images') - cmd = [consts.LOAD_IMAGES_SCRIPT] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - cwd=consts.REPOS_DIR) - _, err = p.communicate() - if p.returncode != 0: - LOG.error('load image failed: %s', err) + 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 diff --git a/api/server.py b/api/server.py index 158b8a508..37a1ab6a6 100644 --- a/api/server.py +++ b/api/server.py @@ -35,6 +35,7 @@ except ImportError: LOG = logging.getLogger(__name__) app = Flask(__name__) +app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 * 1024 Swagger(app) diff --git a/api/urls.py b/api/urls.py index 83cf4daf9..9b0040b6c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -36,6 +36,7 @@ urlpatterns = [ Url('/api/v2/yardstick/images', 'v2_images'), Url('/api/v2/yardstick/images/action', 'v2_images'), + Url('/api/v2/yardstick/images/<image_id>', 'v2_image'), Url('/api/v2/yardstick/containers', 'v2_containers'), Url('/api/v2/yardstick/containers/action', 'v2_containers'), diff --git a/docker/nginx.sh b/docker/nginx.sh index 74009f5bd..1ac1d3f42 100755 --- a/docker/nginx.sh +++ b/docker/nginx.sh @@ -20,6 +20,7 @@ server { index index.htm index.html; location / { include uwsgi_params; + client_max_body_size 2000m; uwsgi_pass unix:///var/run/yardstick.sock; } diff --git a/gui/app/scripts/controllers/image.controller.js b/gui/app/scripts/controllers/image.controller.js index f6c91592f..d7a7edfa9 100644 --- a/gui/app/scripts/controllers/image.controller.js +++ b/gui/app/scripts/controllers/image.controller.js @@ -1,150 +1,235 @@ 'use strict'; angular.module('yardStickGui2App') - .controller('ImageController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$interval', - function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $interval) { + .controller('ImageController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$interval', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $interval, ngDialog) { init(); - $scope.showloading = false; - $scope.ifshowStatus = 0; function init() { + $scope.showloading = false; + $scope.ifshowStatus = 0; + + $scope.yardstickImage = [ + { + 'name': 'yardstick-image', + 'description': '', + 'size': 'N/A', + 'status': 'N/A', + 'time': 'N/A' + }, + { + 'name': 'Ubuntu-16.04', + 'description': '', + 'size': 'N/A', + 'status': 'N/A', + 'time': 'N/A' + }, + { + 'name': 'cirros-0.3.5', + 'description': '', + 'size': 'N/A', + 'status': 'N/A', + 'time': 'N/A' + } + ]; + $scope.customImage = []; $scope.uuid = $stateParams.uuid; - $scope.uploadImage = uploadImage; - getItemIdDetail(); - getImageListSimple(); + $scope.showloading = false; + $scope.url = null; + $scope.environmentInfo = null; + + getYardstickImageList(); + getCustomImageList(function(image, image_id){}); } - function getItemIdDetail() { + function getYardstickImageList(){ + mainFactory.ImageList().get({}).$promise.then(function(response){ + if(response.status == 1){ + angular.forEach($scope.yardstickImage, function(ele, index){ + if(typeof(response.result.images[ele.name]) != 'undefined'){ + $scope.yardstickImage[index] = response.result.images[ele.name]; + } + }); + }else{ + mainFactory.errorHandler1(response); + } + }, function(response){ + mainFactory.errorHandler2(response); + }); + } + + function getCustomImageList(func){ mainFactory.ItemDetail().get({ 'envId': $stateParams.uuid }).$promise.then(function(response) { - if (response.status == 1) { - $scope.baseElementInfo = response.result.environment; - - - } else { - toaster.pop({ - type: 'error', - title: 'fail', - body: response.error_msg, - timeout: 3000 + if(response.status == 1){ + $scope.environmentInfo = response.result.environment; + $scope.customImage = []; + angular.forEach(response.result.environment.image_id, function(ele){ + mainFactory.getImage().get({'imageId': ele}).$promise.then(function(responseData){ + if(responseData.status == 1){ + $scope.customImage.push(responseData.result.image); + func(responseData.result.image, ele); + }else{ + mainFactory.errorHandler1(responseData); + } + }, function(errorData){ + mainFactory.errorHandler2(errorData); + }); }); + }else{ + mainFactory.errorHandler1(response); } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'fail', - body: 'unknow error', - timeout: 3000 - }); - }) + }, function(response){ + mainFactory.errorHandler2(response); + }); } - function getImageListSimple() { - - mainFactory.ImageList().get({}).$promise.then(function(response) { - if (response.status == 1) { - $scope.imageListData = response.result.images; - // $scope.imageStatus = response.result.status; - - } else { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 - }); - } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 + $scope.loadYardstickImage = function(image_name){ + + var updateImageTask = $interval(updateYardstickImage, 10000); + + function updateYardstickImage(){ + mainFactory.ImageList().get({}).$promise.then(function(responseData){ + if(responseData.status == 1){ + if(typeof(responseData.result.images[image_name]) != 'undefined' && responseData.result.images[image_name].status == 'ACTIVE'){ + angular.forEach($scope.yardstickImage, function(ele, index){ + if(ele.name == image_name){ + $scope.yardstickImage[index] = responseData.result.images[ele.name]; + } + }); + $interval.cancel(updateImageTask); + } + }else{ + mainFactory.errorHandler1(responseData); + } + },function(errorData){ + mainFactory.errorHandler2(errorData); }); - }) - } + } + mainFactory.uploadImage().post({'action': 'load_image', 'args': {'name': image_name}}).$promise.then(function(response){ + },function(response){ + mainFactory.errorHandler2(response); + }); + } - function getImageList() { - if ($scope.intervalImgae != undefined) { - $interval.cancel($scope.intervalImgae); - } - mainFactory.ImageList().get({}).$promise.then(function(response) { - if (response.status == 1) { - $scope.imageListData = response.result.images; - $scope.imageStatus = response.result.status; - - if ($scope.imageStatus == 0) { - $scope.intervalImgae = $interval(function() { - getImageList(); - }, 5000); - } else if ($scope.intervalImgae != undefined) { - $interval.cancel($scope.intervalImgae); + $scope.deleteYardstickImage = function(image_name){ + + var updateImageTask = $interval(updateYardstickImage, 10000); + + function updateYardstickImage(){ + mainFactory.ImageList().get({}).$promise.then(function(response){ + if(response.status == 1){ + if(typeof(response.result.images[image_name]) == 'undefined'){ + angular.forEach($scope.yardstickImage, function(ele, index){ + if(ele.name == image_name){ + $scope.yardstickImage[index].size = 'N/A'; + $scope.yardstickImage[index].status = 'N/A'; + $scope.yardstickImage[index].time = 'N/A'; + } + }); + $interval.cancel(updateImageTask); + } + }else{ + mainFactory.errorHandler1(response); } + },function(response){ + mainFactory.errorHandler2(response); + }); + } - } else { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 + mainFactory.uploadImage().post({'action': 'delete_image', 'args': {'name': image_name}}).$promise.then(function(response){ + },function(response){ + mainFactory.errorHandler2(response); + }); + } + + $scope.uploadCustomImageByUrl = function(url){ + mainFactory.uploadImageByUrl().post({ + 'action': 'upload_image_by_url', + 'args': { + 'environment_id': $stateParams.uuid, + 'url': url + } + }).$promise.then(function(response){ + if(response.status == 1){ + var updateImageTask = $interval(getCustomImageList, 30000, 10, true, function(image, image_id){ + if(image_id == response.result.uuid && image.status == 'ACTIVE'){ + $interval.cancel(updateImageTask); + } }); + ngDialog.close(); + }else{ + mainFactory.errorHandler1(response); } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 - }); - }) + }, function(response){ + mainFactory.errorHandler2(response); + }); } - function uploadImage() { - $scope.imageStatus = 0; - $interval.cancel($scope.intervalImgae); - $scope.ifshowStatus = 1; + $scope.uploadCustomImage = function($file, $invalidFiles) { $scope.showloading = true; - mainFactory.uploadImage().post({ - 'action': 'load_image', - 'args': { - 'environment_id': $scope.uuid - } - }).$promise.then(function(response) { + $scope.displayImageFile = $file; + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/images', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_image' } + }).then(function(response) { + $scope.showloading = false; - if (response.status == 1) { + if (response.data.status == 1) { + toaster.pop({ type: 'success', - title: 'create success', + title: 'upload success', body: 'you can go next step', timeout: 3000 }); - setTimeout(function() { - getImageList(); - }, 10000); - } else { - toaster.pop({ - type: 'error', - title: 'failed', - body: 'something wrong', - timeout: 3000 + var updateImageTask = $interval(getCustomImageList, 10000, 10, true, function(image, image_id){ + if(image_id == response.data.result.uuid && image.status == 'ACTIVE'){ + $interval.cancel(updateImageTask); + } }); + }else{ + mainFactory.errorHandler1(response); + } + }, function(response) { + $scope.uploadfile = null; + mainFactory.errorHandler2(response); + }) + } + + $scope.deleteCustomImage = function(image_id){ + mainFactory.deleteImage().delete({'imageId': image_id}).$promise.then(function(response){ + if(response.status == 1){ + $interval(getCustomImageList, 10000, 5, true, function(image, image_id){ + }); + }else{ + mainFactory.errorHandler2(response); } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'failed', - body: 'something wrong', - timeout: 3000 - }); + }, function(response){ + mainFactory.errorHandler2(response); + }); + } + + $scope.openImageDialog = function(){ + $scope.url = null; + ngDialog.open({ + preCloseCallback: function(value) { + }, + template: 'views/modal/imageDialog.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 950, + showClose: true, + closeByDocument: false }) } @@ -158,9 +243,5 @@ angular.module('yardStickGui2App') $state.go('app.podUpload', { uuid: $scope.uuid }); } - - - - } ]); diff --git a/gui/app/scripts/controllers/main.js b/gui/app/scripts/controllers/main.js index ab76bf0f2..ceec83fa9 100644 --- a/gui/app/scripts/controllers/main.js +++ b/gui/app/scripts/controllers/main.js @@ -15,7 +15,7 @@ angular.module('yardStickGui2App') $scope.showImage = null; $scope.showContainer = null; $scope.showNextOpenRc = null; - $scope.showNextPod = null; + $scope.showNextPod = 1; $scope.displayContainerInfo = []; $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] @@ -51,7 +51,6 @@ angular.module('yardStickGui2App') $scope.chooseResult = chooseResult; getEnvironmentList(); - // getImageList(); } @@ -85,7 +84,7 @@ angular.module('yardStickGui2App') } $scope.goToImage = function goToImage() { - getImageListSimple(); + getImageList(); $scope.showImage = 1; } $scope.goToPod = function goToPod() { @@ -290,7 +289,7 @@ angular.module('yardStickGui2App') $scope.showImage = null; $scope.showContainer = null; $scope.showNextOpenRc = null; - $scope.showNextPod = null; + $scope.showNextPod = 1; $scope.displayContainerInfo = []; $scope.displayPodFile = null; @@ -308,7 +307,6 @@ angular.module('yardStickGui2App') ngDialog.open({ preCloseCallback: function(value) { getEnvironmentList(); - // getImageList(); }, template: 'views/modal/environmentDialog.html', scope: $scope, @@ -479,106 +477,97 @@ angular.module('yardStickGui2App') }) } - $scope.uploadImage = function uploadImage() { - $scope.imageStatus = 0; - $scope.showImageStatus = 1; - $scope.showloading = true; - mainFactory.uploadImage().post({ - 'action': 'load_image', - 'args': { - 'environment_id': $scope.uuid + $scope.yardstickImage = { + 'yardstick-image': { + 'name': 'yardstick-image', + 'description': '', + 'status': 'N/A' + }, + 'Ubuntu-16.04': { + 'name': 'Ubuntu-16.04', + 'description': '', + 'status': 'N/A' + }, + 'cirros-0.3.5': { + 'name': 'cirros-0.3.5', + 'description': '', + 'status': 'N/A' + } + }; - } - }).$promise.then(function(response) { - $scope.showloading = false; - if (response.status == 1) { - toaster.pop({ - type: 'success', - title: 'create success', - body: 'you can go next step', - timeout: 3000 - }); - setTimeout(function() { - getImageList(); - }, 10000); - $scope.showNextPod = 1; + $scope.selectImageList = []; - } else { - toaster.pop({ - type: 'error', - title: 'failed', - body: 'something wrong', - timeout: 3000 - }); + $scope.selectImage = function(name){ + $scope.selectImageList.push(name); + } - } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'failed', - body: 'something wrong', - timeout: 3000 - }); - }) + $scope.unselectImage = function(name){ + var index = $scope.selectImageList.indexOf(name); + $scope.selectImageList.splice(index, 1); } - function getImageList() { - if ($scope.intervalImgae != undefined) { - $interval.cancel($scope.intervalImgae); - } - mainFactory.ImageList().get({}).$promise.then(function(response) { - if (response.status == 1) { - $scope.imageListData = response.result.images; - $scope.imageStatus = response.result.status; + $scope.uploadImage = function() { + $scope.imageStatus = 0; + $scope.showImageStatus = 1; + $scope.showloading = true; - if ($scope.imageStatus == 0) { - $scope.intervalImgae = $interval(function() { - getImageList(); - }, 5000); - } else if ($scope.intervalImgae != undefined) { - $interval.cancel($scope.intervalImgae); + var updateImageTask = $interval(function(){ + mainFactory.ImageList().get({}).$promise.then(function(response){ + if(response.status == 1){ + var isOk = true; + angular.forEach($scope.selectImageList, function(ele){ + if(typeof(response.result.images[ele]) != 'undefined' && response.result.images[ele].status == 'ACTIVE'){ + $scope.yardstickImage[ele] = response.result.images[ele]; + }else{ + isOk = false; + } + }); + if(isOk){ + $interval.cancel(updateImageTask); + $scope.imageStatus = 1; + } + }else{ + mainFactory.errorHandler1(response); } - - } else { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 - }); - } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 + }, function(response){ + mainFactory.errorHandler2(response); }); - }) + }, 10000); + + angular.forEach($scope.selectImageList, function(ele){ + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'name': ele + } + }).$promise.then(function(response) { + if(response.status == 1){ + $scope.showloading = false; + $scope.showNextPod = 1; + }else{ + mainFactory.errorHandler1(response); + } + }, function(response) { + mainFactory.errorHandler2(response); + }) + }); } - function getImageListSimple() { + function getImageList() { mainFactory.ImageList().get({}).$promise.then(function(response) { if (response.status == 1) { - $scope.imageListData = response.result.images; - $scope.imageStatus = response.result.status; - - } else { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 + angular.forEach($scope.yardstickImage, function(value, key){ + if(typeof(response.result.images[key]) != 'undefined'){ + $scope.yardstickImage[key] = response.result.images[key]; + } }); + $scope.imageStatus = response.result.status; + }else{ + mainFactory.errorHandler1(response); } - }, function(error) { - toaster.pop({ - type: 'error', - title: 'get data failed', - body: 'please retry', - timeout: 3000 - }); + }, function(response) { + mainFactory.errorHandler2(response); }) } diff --git a/gui/app/scripts/controllers/projectDetail.controller.js b/gui/app/scripts/controllers/projectDetail.controller.js index 843f66c57..e8468045d 100644 --- a/gui/app/scripts/controllers/projectDetail.controller.js +++ b/gui/app/scripts/controllers/projectDetail.controller.js @@ -672,7 +672,7 @@ angular.module('yardStickGui2App') } $scope.gotoLog = function gotoLog(task_id) { - $state.go('app2.taskLog', { taskId: task_id }); + $state.go('app.taskLog', { taskId: task_id }); } } ]); diff --git a/gui/app/scripts/factory/main.factory.js b/gui/app/scripts/factory/main.factory.js index 44fbeb39f..7637a9ff3 100644 --- a/gui/app/scripts/factory/main.factory.js +++ b/gui/app/scripts/factory/main.factory.js @@ -9,7 +9,7 @@ var Base_URL; var Grafana_URL; angular.module('yardStickGui2App') - .factory('mainFactory', ['$resource','$rootScope','$http', '$location',function($resource, $rootScope,$http,$location) { + .factory('mainFactory', ['$resource','$rootScope','$http', '$location', 'toaster',function($resource, $rootScope ,$http ,$location, toaster) { Base_URL = 'http://' + $location.host() + ':' + $location.port(); Grafana_URL = 'http://' + $location.host(); @@ -86,6 +86,20 @@ angular.module('yardStickGui2App') } }) }, + getImage: function(){ + return $resource(Base_URL + '/api/v2/yardstick/images/:imageId', {imageId: "@imageId"}, { + 'get': { + method: 'GET' + } + }) + }, + deleteImage: function() { + return $resource(Base_URL + '/api/v2/yardstick/images/:imageId', { imageId: '@imageId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, uploadImage: function() { return $resource(Base_URL + '/api/v2/yardstick/images', {}, { 'post': { @@ -93,6 +107,13 @@ angular.module('yardStickGui2App') } }) }, + uploadImageByUrl: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'post': { + method: 'POST' + } + }) + }, getPodDetail: function() { return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { 'get': { @@ -249,6 +270,22 @@ angular.module('yardStickGui2App') method: 'DELETE' } }) + }, + errorHandler1: function(response){ + toaster.pop({ + 'type': 'error', + 'title': 'error', + 'body': response.result, + 'showCloseButton': true + }); + }, + errorHandler2: function(response){ + toaster.pop({ + 'type': 'error', + 'title': response.status, + 'body': response.statusText, + 'showCloseButton': true + }); } }; diff --git a/gui/app/views/modal/environmentDialog.html b/gui/app/views/modal/environmentDialog.html index 389de8340..4c539fc33 100644 --- a/gui/app/views/modal/environmentDialog.html +++ b/gui/app/views/modal/environmentDialog.html @@ -133,16 +133,17 @@ <table class="table table-striped"> <tr> + <th>choose</th> <th>name</th> - <th>size</th> + <th>description</th> <th>status</th> - <th>time</th> </tr> - <tr ng-repeat="image in imageListData"> - <td>{{image.name}}</td> - <td>{{image.size/1024}} mb</td> - <td>{{image.status}}</td> - <td>{{image.time}}</td> + <tr ng-repeat="(name, value) in yardstickImage"> + <td ng-if="selectImageList.indexOf(name) > -1"><img src="images/checkyes.png" style="height:12px;cursor:pointer" ng-click="unselectImage(name)" /></td> + <td ng-if="selectImageList.indexOf(name) == -1"><img src="images/checkno.png" style="height:12px;cursor:pointer" ng-click="selectImage(name)" /></td> + <td>{{name}}</td> + <td>{{value.description}}</td> + <td>{{value.status}}</td> </tr> diff --git a/gui/app/views/modal/imageDialog.html b/gui/app/views/modal/imageDialog.html new file mode 100644 index 000000000..c568f2aba --- /dev/null +++ b/gui/app/views/modal/imageDialog.html @@ -0,0 +1,19 @@ +<div> + + <h4>Enter Remote Image Url</h4> + <input type="text" ng-model="url" /> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled=" url==null || url==''" ng-click="uploadCustomImageByUrl(url)">Upload</button> + </div> + +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } +</style> diff --git a/gui/app/views/podupload.html b/gui/app/views/podupload.html index 99e83aca2..d6d7c0c6e 100644 --- a/gui/app/views/podupload.html +++ b/gui/app/views/podupload.html @@ -13,7 +13,7 @@ <hr/> - <button class="btn btn-default" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <button class="btn btn-default" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="1024MB"> <div ng-show="!loadingOPENrc">Upload</div> <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> </button> diff --git a/gui/app/views/uploadImage.html b/gui/app/views/uploadImage.html index 17ccfdb8b..0c337feeb 100644 --- a/gui/app/views/uploadImage.html +++ b/gui/app/views/uploadImage.html @@ -4,56 +4,86 @@ <div style="display:flex;flex-direction:row;"> <div style="width:750px;"> - <h3>{{baseElementInfo.name}} -- Image + <h3>{{environmentInfo.name}} -- Image <button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button> </h3> <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> - <hr/> - <button class="btn btn-default" ng-click="uploadImage()"> - <div ng-if="!showloading">Load Image</div> - <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> - </button> - <i class="fa fa-check" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==1&&ifshowStatus==1">done</i> - <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==0&&ifshowStatus==1">loading</i> - <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: red;" ng-show="imageStatus==2&&ifshowStatus==1">error</i> - <hr> - <h4>Current Images</h4> - + <h4>Alternative Images</h4> <div> <table class="table table-striped"> <tr> <th>name</th> + <th>description</th> <th>size</th> <th>status</th> <th>time</th> + <th>action</th> </tr> - <tr ng-repeat="image in imageListData"> + <tr ng-repeat="image in yardstickImage"> <td>{{image.name}}</td> - <td>{{image.size/1024}} MB</td> + <td>{{image.description}}</td> + <td>{{image.size | number:2}} MB</td> <td>{{image.status}}</td> <td>{{image.time}}</td> - + <td> + <div class="btn-group" uib-dropdown> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + action<span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ng-show="image.status == 'N/A'"><a ng-click="loadYardstickImage(image.name)">load</a></li> + <li role="menuitem" ng-show="image.status != 'N/A'"><a ng-click="deleteYardstickImage(image.name)">delete</a></li> + </ul> + </div> + </td> </tr> - - - </table> </div> + <hr> + <h4 style="display:inline">Custom Images</h4> + <div class="btn-group button-margin" style="float:right;margin-top:-10px;margin-bottom:5px"> + <button class="btn btn-default" style="width:60px" ngf-select="uploadCustomImage($file, $invalidFiles)" ngf-max-size="2048MB"> + <div ng-show="!showloading">Local</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + <button class="btn btn-default" style="width:60px" ng-click="openImageDialog()">Url</button> + </div> + <div> + <table class="table table-striped"> - - - - - - + <tr> + <th>name</th> + <th>description</th> + <th>size</th> + <th>status</th> + <th>time</th> + <th>action</th> + </tr> + <tr ng-repeat="image in customImage"> + <td>{{image.name}}</td> + <td>{{image.description}}</td> + <td>{{image.size | number:2}} MB</td> + <td>{{image.status}}</td> + <td>{{image.time}}</td> + <td> + <div class="btn-group" uib-dropdown> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + action<span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ><a ng-click="deleteCustomImage(image.id)">delete</a></li> + </ul> + </div> + </td> + </tr> + </table> + </div> </div> - - </div> </div> diff --git a/yardstick/common/constants.py b/yardstick/common/constants.py index fe394fd4d..ec683e94b 100644 --- a/yardstick/common/constants.py +++ b/yardstick/common/constants.py @@ -59,6 +59,7 @@ if not SERVER_IP: # dir CONF_DIR = get_param('dir.conf', '/etc/yardstick') +IMAGE_DIR = get_param('dir.images', '/home/opnfv/images/') REPOS_DIR = get_param('dir.repos', '/home/opnfv/repos/yardstick') RELENG_DIR = get_param('dir.releng', '/home/opnfv/repos/releng') LOG_DIR = get_param('dir.log', '/tmp/yardstick/') |