summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJing Lu <lvjing5@huawei.com>2017-08-25 00:50:21 +0000
committerGerrit Code Review <gerrit@opnfv.org>2017-08-25 00:50:21 +0000
commitf5a96ffd959b11a122c4beea1fc99c89f755678c (patch)
tree1c0cad1b94f33a3275c816999653870e6955d6eb
parentffecd5b84eecdb74d7534b801c1359251c34b34e (diff)
parentefb4f088f14aee394599bea21973f82f1867c4fe (diff)
Merge "Add function to upload image from local/url in GUI"
-rw-r--r--api/database/v2/handlers.py5
-rw-r--r--api/database/v2/models.py3
-rw-r--r--api/resources/v2/environments.py8
-rw-r--r--api/resources/v2/images.py341
-rw-r--r--api/server.py1
-rw-r--r--api/urls.py1
-rwxr-xr-xdocker/nginx.sh1
-rw-r--r--gui/app/scripts/controllers/image.controller.js301
-rw-r--r--gui/app/scripts/controllers/main.js169
-rw-r--r--gui/app/scripts/controllers/projectDetail.controller.js2
-rw-r--r--gui/app/scripts/factory/main.factory.js39
-rw-r--r--gui/app/views/modal/environmentDialog.html15
-rw-r--r--gui/app/views/modal/imageDialog.html19
-rw-r--r--gui/app/views/podupload.html2
-rw-r--r--gui/app/views/uploadImage.html82
-rw-r--r--yardstick/common/constants.py1
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/')