diff options
155 files changed, 10987 insertions, 108 deletions
diff --git a/.gitignore b/.gitignore index a53b53390..d98b4a070 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ build htmlcov .agignore .coverage +*.retry Session*.vim .tags* .coverage.* diff --git a/ansible/inventory.ini b/ansible/inventory.ini index f27806025..79a6ee0aa 100644 --- a/ansible/inventory.ini +++ b/ansible/inventory.ini @@ -1,7 +1,6 @@ [controller] host1 ansible_host=10.1.0.50 ansible_user=root ansible_ssh_pass=root host2 ansible_host=10.1.0.51 ansible_user=root ansible_ssh_pass=root -host3 ansible_host=10.1.0.52 ansible_user=root ansible_ssh_pass=root [compute] host4 ansible_host=10.1.0.53 ansible_user=root ansible_ssh_pass=root diff --git a/ansible/migrate_pinning_setup.yaml b/ansible/migrate_pinning_setup.yaml new file mode 100644 index 000000000..ee5eef3ff --- /dev/null +++ b/ansible/migrate_pinning_setup.yaml @@ -0,0 +1,49 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- hosts: localhost + roles: + - create_flavor + - role: set_flavor_property + key: "hw:cpu_policy" + value: "dedicated" + - role: set_flavor_property + key: "hw:numa_nodes" + value: "1" + +- hosts: nodes + roles: + - backup_nova_conf + - role: set_nova_conf + section: "DEFAULT" + key: "live_migration_flag" + value: "VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER,VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED" + - role: set_nova_conf + section: "DEFAULT" + key: "vncserver_listen" + value: "0.0.0.0" + +- hosts: controller + roles: + - role: set_nova_conf + section: "DEFAULT" + key: "scheduler_default_filters" + value: "NUMATopologyFilter" + - role: restart_nova_service + service: "nova-scheduler" + +- hosts: compute + roles: + - role: set_nova_conf + section: "DEFAULT" + key: "vcpu_pin_set" + value: "{{ cpu_set }}" + - role: restart_nova_service + service: "nova-compute" diff --git a/ansible/migrate_pinning_teardown.yaml b/ansible/migrate_pinning_teardown.yaml new file mode 100644 index 000000000..13dd6113c --- /dev/null +++ b/ansible/migrate_pinning_teardown.yaml @@ -0,0 +1,31 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- hosts: localhost + roles: + - delete_flavor + +- hosts: nodes + roles: + - recover_nova_conf + +- hosts: controller + roles: + - role: restart_nova_service + service: "nova-scheduler" + - role: restart_nova_service + service: "nova-api" + - role: restart_nova_service + service: "nova-conductor" + +- hosts: compute + roles: + - role: restart_nova_service + service: "nova-compute" diff --git a/ansible/roles/backup_nova_conf/tasks/main.yaml b/ansible/roles/backup_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..ca95bac59 --- /dev/null +++ b/ansible/roles/backup_nova_conf/tasks/main.yaml @@ -0,0 +1,18 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- name: backup nova.conf file + copy: + src: /etc/nova/nova.conf + dest: /tmp/nova.conf + owner: nova + group: nova + remote_src: True + become: true diff --git a/ansible/roles/create_flavor/tasks/main.yaml b/ansible/roles/create_flavor/tasks/main.yaml new file mode 100644 index 000000000..9b776c694 --- /dev/null +++ b/ansible/roles/create_flavor/tasks/main.yaml @@ -0,0 +1,16 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +- name: create flavor {{ flavor }} + os_nova_flavor: + cloud: opnfv + state: present + name: "{{ flavor }}" + ram: "{{ ram }}" + vcpus: "{{ vcpus }}" + disk: "{{ disk }}" diff --git a/ansible/roles/delete_flavor/tasks/main.yaml b/ansible/roles/delete_flavor/tasks/main.yaml new file mode 100644 index 000000000..dc9fc88ce --- /dev/null +++ b/ansible/roles/delete_flavor/tasks/main.yaml @@ -0,0 +1,13 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +- name: delete flavor {{ flavor }} + os_nova_flavor: + cloud: opnfv + state: absent + name: "{{ flavor }}" diff --git a/ansible/roles/recover_nova_conf/tasks/main.yaml b/ansible/roles/recover_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..44919d2ae --- /dev/null +++ b/ansible/roles/recover_nova_conf/tasks/main.yaml @@ -0,0 +1,18 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- name: recover nova.conf file + copy: + src: /tmp/nova.conf + dest: /etc/nova/nova.conf + owner: nova + group: nova + remote_src: True + become: true diff --git a/ansible/roles/restart_nova_service/tasks/main.yaml b/ansible/roles/restart_nova_service/tasks/main.yaml new file mode 100644 index 000000000..2bdce652d --- /dev/null +++ b/ansible/roles/restart_nova_service/tasks/main.yaml @@ -0,0 +1,23 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- name: restart "{{ service }}" service + service: + name: "{{ service }}" + state: restarted + become: true + when: ansible_os_family == "Debian" + +- name: restart "openstack-{{ service }}" service + service: + name: "openstack-{{ service }}" + state: restarted + become: true + when: ansible_os_family == "RedHat" diff --git a/ansible/roles/set_flavor_property/tasks/main.yaml b/ansible/roles/set_flavor_property/tasks/main.yaml new file mode 100644 index 000000000..f98988783 --- /dev/null +++ b/ansible/roles/set_flavor_property/tasks/main.yaml @@ -0,0 +1,14 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +- name: set flavor "{{ flavor }}" property {{ key }} = {{ value }} + shell: + source /etc/yardstick/openstack.creds; + openstack flavor set --property {{ key }}={{ value }} {{ flavor }}; + args: + executable: /bin/bash diff --git a/ansible/roles/set_nova_conf/tasks/main.yaml b/ansible/roles/set_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..ae665c5d0 --- /dev/null +++ b/ansible/roles/set_nova_conf/tasks/main.yaml @@ -0,0 +1,17 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +- name: set "{{ key }}" value + ini_file: + dest: /etc/nova/nova.conf + section: "{{ section }}" + option: "{{ key }}" + value: "{{ value }}" + become: true diff --git a/api/database/v2/models.py b/api/database/v2/models.py index 64d49cc9d..1e85559cb 100644 --- a/api/database/v2/models.py +++ b/api/database/v2/models.py @@ -31,7 +31,7 @@ class V2Environment(Base): class V2Openrc(Base): - __tablename__ = 'V2_openrc' + __tablename__ = 'v2_openrc' id = Column(Integer, primary_key=True) uuid = Column(String(30)) name = Column(String(30)) diff --git a/api/resources/v2/images.py b/api/resources/v2/images.py index 8755e5265..8359e105b 100644 --- a/api/resources/v2/images.py +++ b/api/resources/v2/images.py @@ -37,6 +37,8 @@ class V2Images(ApiResource): 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 return result_handler(consts.API_SUCCESS, {'status': status, 'images': images}) diff --git a/api/resources/v2/tasks.py b/api/resources/v2/tasks.py index b64f5ef24..885a190c6 100644 --- a/api/resources/v2/tasks.py +++ b/api/resources/v2/tasks.py @@ -114,7 +114,8 @@ class V2Task(ApiResource): if project.tasks: LOG.info('update tasks in project') - new_task_list = project.tasks.split(',').remove(task_id) + new_task_list = project.tasks.split(',') + new_task_list.remove(task_id) if new_task_list: new_tasks = ','.join(new_task_list) else: diff --git a/api/resources/v2/testcases.py b/api/resources/v2/testcases.py index ca88e9856..b47a8f6b7 100644 --- a/api/resources/v2/testcases.py +++ b/api/resources/v2/testcases.py @@ -25,7 +25,7 @@ class V2Testcases(ApiResource): def get(self): param = Param({}) testcase_list = Testcase().list_all(param) - return result_handler(consts.API_SUCCESS, testcase_list) + return result_handler(consts.API_SUCCESS, {'testcases': testcase_list}) def post(self): return self._dispatch_post() diff --git a/api/urls.py b/api/urls.py index 2211348f3..3fef91af8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -26,15 +26,18 @@ urlpatterns = [ Url('/api/v2/yardstick/environments/action', 'v2_environments'), Url('/api/v2/yardstick/environments/<environment_id>', 'v2_environment'), + Url('/api/v2/yardstick/openrcs', 'v2_openrcs'), Url('/api/v2/yardstick/openrcs/action', 'v2_openrcs'), Url('/api/v2/yardstick/openrcs/<openrc_id>', 'v2_openrc'), + Url('/api/v2/yardstick/pods', 'v2_pods'), Url('/api/v2/yardstick/pods/action', 'v2_pods'), Url('/api/v2/yardstick/pods/<pod_id>', 'v2_pod'), Url('/api/v2/yardstick/images', 'v2_images'), Url('/api/v2/yardstick/images/action', 'v2_images'), + Url('/api/v2/yardstick/containers', 'v2_containers'), Url('/api/v2/yardstick/containers/action', 'v2_containers'), Url('/api/v2/yardstick/containers/<container_id>', 'v2_container'), diff --git a/docker/Dockerfile b/docker/Dockerfile index 2c4270a09..b48a550bf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,6 +37,7 @@ RUN git clone --depth 1 -b $BRANCH https://gerrit.opnfv.org/gerrit/storperf ${ST WORKDIR ${YARDSTICK_REPO_DIR} RUN ${YARDSTICK_REPO_DIR}/install.sh +RUN ${YARDSTICK_REPO_DIR}/docker/supervisor.sh RUN echo "daemon off;" >> /etc/nginx/nginx.conf diff --git a/docker/nginx.sh b/docker/nginx.sh new file mode 100755 index 000000000..26937d134 --- /dev/null +++ b/docker/nginx.sh @@ -0,0 +1,31 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +# nginx config +nginx_config='/etc/nginx/conf.d/yardstick.conf' + +if [[ ! -e "${nginx_config}" ]];then + + cat << EOF > "${nginx_config}" +server { + listen 5000; + server_name localhost; + index index.htm index.html; + location / { + include uwsgi_params; + uwsgi_pass unix:///var/run/yardstick.sock; + } + + location /gui/ { + alias /etc/nginx/yardstick/gui/; + } +} +EOF +fi diff --git a/docker/supervisor.sh b/docker/supervisor.sh new file mode 100755 index 000000000..b67de2212 --- /dev/null +++ b/docker/supervisor.sh @@ -0,0 +1,26 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +# nginx service start when boot +supervisor_config='/etc/supervisor/conf.d/yardstick.conf' + +if [[ ! -e "${supervisor_config}" ]];then + cat << EOF > "${supervisor_config}" +[supervisord] +nodaemon = true + +[program:nginx] +command = service nginx restart + +[program:yardstick_uwsgi] +directory = /etc/yardstick +command = uwsgi -i yardstick.ini +EOF +fi diff --git a/api/api-prepare.sh b/docker/uwsgi.sh index 7632d9da9..cf4612332 100755 --- a/api/api-prepare.sh +++ b/docker/uwsgi.sh @@ -1,6 +1,6 @@ #!/bin/bash ############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 @@ -12,6 +12,13 @@ # generate uwsgi config file mkdir -p /etc/yardstick + +# create api log directory +mkdir -p /var/log/yardstick + +# create yardstick.sock for communicating +touch /var/run/yardstick.sock + uwsgi_config='/etc/yardstick/yardstick.ini' if [[ ! -e "${uwsgi_config}" ]];then @@ -37,48 +44,3 @@ EOF echo "virtualenv = ${YARDSTICK_VENV}" >> "${uwsgi_config}" fi fi - -# nginx config -nginx_config='/etc/nginx/conf.d/yardstick.conf' - -if [[ ! -e "${nginx_config}" ]];then - - cat << EOF > "${nginx_config}" -server { - listen 5000; - server_name localhost; - index index.htm index.html; - location / { - include uwsgi_params; - uwsgi_pass unix:///var/run/yardstick.sock; - } -} -EOF -fi - -# nginx service start when boot -supervisor_config='/etc/supervisor/conf.d/yardstick.conf' - -if [[ ! -e "${supervisor_config}" ]];then - cat << EOF > "${supervisor_config}" -[supervisord] -nodaemon = true - -[program:yardstick_nginx] -user = root -command = service nginx restart -autorestart = true - -[program:yardstick_uwsgi] -user = root -directory = /etc/yardstick -command = uwsgi -i yardstick.ini -autorestart = true -EOF -fi - -# create api log directory -mkdir -p /var/log/yardstick - -# create yardstick.sock for communicating -touch /var/run/yardstick.sock diff --git a/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml b/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml index 5c5574005..e306d0d94 100644 --- a/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml +++ b/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml @@ -20,30 +20,35 @@ nodes: - name: node1 + host_name: host1 role: Controller ip: 10.1.0.50 user: root password: root - name: node2 + host_name: host2 role: Controller ip: 10.1.0.51 user: root password: root - name: node3 + host_name: host3 role: Controller ip: 10.1.0.52 user: root password: root - name: node4 + host_name: host4 role: Compute ip: 10.1.0.53 user: root password: root - name: node5 + host_name: host5 role: Compute ip: 10.1.0.54 user: root diff --git a/etc/yardstick/yardstick.conf.sample b/etc/yardstick/yardstick.conf.sample index 572051186..1157b9d62 100644 --- a/etc/yardstick/yardstick.conf.sample +++ b/etc/yardstick/yardstick.conf.sample @@ -9,7 +9,7 @@ [DEFAULT] debug = False -dispatcher = http +dispatcher = http # setup multiple dipatcher with comma deperted e.g. file,http [dispatcher_http] timeout = 5 diff --git a/gui/Gruntfile.js b/gui/Gruntfile.js new file mode 100644 index 000000000..171d65add --- /dev/null +++ b/gui/Gruntfile.js @@ -0,0 +1,492 @@ +// Generated on 2017-05-31 using generator-angular 0.15.1 +'use strict'; + +// # Globbing +// for performance reasons we're only matching one level down: +// 'test/spec/{,*/}*.js' +// use this if you want to recursively match all subfolders: +// 'test/spec/**/*.js' + +module.exports = function(grunt) { + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Automatically load required Grunt tasks + require('jit-grunt')(grunt, { + useminPrepare: 'grunt-usemin', + ngtemplates: 'grunt-angular-templates', + cdnify: 'grunt-google-cdn' + }); + + // Configurable paths for the application + var appConfig = { + app: require('./bower.json').appPath || 'app', + dist: 'dist' + }; + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + yeoman: appConfig, + + // Watches files for changes and runs tasks based on the changed files + watch: { + bower: { + files: ['bower.json'], + tasks: ['wiredep'] + }, + js: { + files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], + tasks: ['newer:jshint:all', 'newer:jscs:all'], + options: { + livereload: '<%= connect.options.livereload %>' + } + }, + jsTest: { + files: ['test/spec/{,*/}*.js'], + tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma'] + }, + styles: { + files: ['<%= yeoman.app %>/styles/{,*/}*.css'], + tasks: ['newer:copy:styles', 'postcss'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + livereload: { + options: { + livereload: '<%= connect.options.livereload %>' + }, + files: [ + '<%= yeoman.app %>/{,*/}*.html', + '.tmp/styles/{,*/}*.css', + '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + ] + } + }, + + // The actual grunt server settings + connect: { + options: { + port: 9099, + // Change this to '0.0.0.0' to access the server from outside. + hostname: 'localhost', + livereload: 35745 + }, + livereload: { + options: { + open: true, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect().use( + '/app/styles', + connect.static('./app/styles') + ), + connect.static(appConfig.app) + ]; + } + } + }, + test: { + options: { + port: 9001, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect.static('test'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect.static(appConfig.app) + ]; + } + } + }, + dist: { + options: { + open: true, + base: '<%= yeoman.dist %>' + } + } + }, + + // Make sure there are no obvious mistakes + jshint: { + options: { + jshintrc: '.jshintrc', + reporter: require('jshint-stylish') + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + options: { + jshintrc: 'test/.jshintrc' + }, + src: ['test/spec/{,*/}*.js'] + } + }, + + // Make sure code styles are up to par + jscs: { + options: { + config: '.jscsrc', + verbose: true + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + src: ['test/spec/{,*/}*.js'] + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/{,*/}*', + '!<%= yeoman.dist %>/.git{,*/}*' + ] + }] + }, + server: '.tmp' + }, + + // Add vendor prefixed styles + postcss: { + options: { + processors: [ + require('autoprefixer-core')({ browsers: ['last 1 version'] }) + ] + }, + server: { + options: { + map: true + }, + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + } + }, + + // Automatically inject Bower components into the app + wiredep: { + app: { + src: ['<%= yeoman.app %>/index.html'], + ignorePath: /\.\.\// + }, + test: { + devDependencies: true, + src: '<%= karma.unit.configFile %>', + ignorePath: /\.\.\//, + fileTypes: { + js: { + block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, + detect: { + js: /'(.*\.js)'/gi + }, + replace: { + js: '\'{{filePath}}\',' + } + } + } + } + }, + + // Renames files for browser caching purposes + filerev: { + dist: { + src: [ + '<%= yeoman.dist %>/scripts/{,*/}*.js', + '<%= yeoman.dist %>/styles/{,*/}*.css', + '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', + '<%= yeoman.dist %>/styles/fonts/*' + ] + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + html: '<%= yeoman.app %>/index.html', + options: { + dest: '<%= yeoman.dist %>', + flow: { + html: { + steps: { + js: ['concat', 'uglifyjs'], + css: ['cssmin'] + }, + post: {} + } + } + } + }, + + // Performs rewrites based on filerev and the useminPrepare configuration + usemin: { + html: ['<%= yeoman.dist %>/{,*/}*.html'], + css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], + js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'], + options: { + assetsDirs: [ + '<%= yeoman.dist %>', + '<%= yeoman.dist %>/images', + '<%= yeoman.dist %>/styles' + ], + patterns: { + js: [ + [/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images'] + ] + } + } + }, + + // The following *-min tasks will produce minified files in the dist folder + // By default, your `index.html`'s <!-- Usemin block --> will take care of + // minification. These next options are pre-configured if you do not wish + // to use the Usemin blocks. + // cssmin: { + // dist: { + // files: { + // '<%= yeoman.dist %>/styles/main.css': [ + // '.tmp/styles/{,*/}*.css' + // ] + // } + // } + // }, + // uglify: { + // dist: { + // files: { + // '<%= yeoman.dist %>/scripts/scripts.js': [ + // '<%= yeoman.dist %>/scripts/scripts.js' + // ] + // } + // } + // }, + // concat: { + // dist: {} + // }, + + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.{png,jpg,jpeg,gif}', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.svg', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + htmlmin: { + dist: { + options: { + collapseWhitespace: true, + conservativeCollapse: true, + collapseBooleanAttributes: true, + removeCommentsFromCDATA: true + }, + files: [{ + expand: true, + cwd: '<%= yeoman.dist %>', + src: ['*.html'], + dest: '<%= yeoman.dist %>' + }] + } + }, + + ngtemplates: { + dist: { + options: { + module: 'yardStickGui2App', + htmlmin: '<%= htmlmin.dist.options %>', + usemin: 'scripts/scripts.js' + }, + cwd: '<%= yeoman.app %>', + src: 'views/{,*/}*.html', + dest: '.tmp/templateCache.js' + } + }, + + // ng-annotate tries to make the code safe for minification automatically + // by using the Angular long form for dependency injection. + ngAnnotate: { + dist: { + files: [{ + expand: true, + cwd: '.tmp/concat/scripts', + src: '*.js', + dest: '.tmp/concat/scripts' + }] + } + }, + + // Replace Google CDN references + cdnify: { + dist: { + html: ['<%= yeoman.dist %>/*.html'] + } + }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= yeoman.app %>', + dest: '<%= yeoman.dist %>', + src: [ + '*.{ico,png,txt}', + '*.html', + 'images/{,*/}*.{webp}', + 'styles/fonts/{,*/}*.*' + ] + }, { + expand: true, + cwd: '.tmp/images', + dest: '<%= yeoman.dist %>/images', + src: ['generated/*'] + }, { + expand: true, + cwd: 'bower_components/bootstrap/dist', + src: 'fonts/*', + dest: '<%= yeoman.dist %>' + }, + { + expand: true, + cwd: 'bower_components/components-font-awesome', + src: 'fonts/*', + dest: '<%=yeoman.dist%>' + } + ] + }, + styles: { + expand: true, + cwd: '<%= yeoman.app %>/styles', + dest: '.tmp/styles/', + src: '{,*/}*.css' + } + }, + + // Run some tasks in parallel to speed up the build process + concurrent: { + server: [ + 'copy:styles' + ], + test: [ + 'copy:styles' + ], + dist: [ + 'copy:styles', + 'imagemin', + 'svgmin' + ] + }, + + // Test settings + karma: { + unit: { + configFile: 'test/karma.conf.js', + singleRun: true + } + } + }); + + + grunt.registerTask('serve', 'Compile then start a connect web server', function(target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'wiredep', + 'concurrent:server', + 'postcss:server', + 'connect:livereload', + 'watch' + ]); + }); + + grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function(target) { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run(['serve:' + target]); + }); + + grunt.registerTask('test', [ + 'clean:server', + 'wiredep', + 'concurrent:test', + 'postcss', + 'connect:test', + 'karma' + ]); + + grunt.registerTask('build', [ + 'clean:dist', + 'wiredep', + 'useminPrepare', + 'concurrent:dist', + 'postcss', + 'ngtemplates', + 'concat', + 'ngAnnotate', + 'copy:dist', + // 'cdnify', + 'cssmin', + 'uglify', + 'filerev', + 'usemin', + 'htmlmin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'newer:jscs', + 'test', + 'build' + ]); +}; diff --git a/gui/app/404.html b/gui/app/404.html new file mode 100644 index 000000000..899828a3c --- /dev/null +++ b/gui/app/404.html @@ -0,0 +1,152 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Page Not Found :(</title> + <style> + ::-moz-selection { + background: #b3d4fc; + text-shadow: none; + } + + ::selection { + background: #b3d4fc; + text-shadow: none; + } + + html { + padding: 30px 10px; + font-size: 20px; + line-height: 1.4; + color: #737373; + background: #f0f0f0; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + html, + input { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + body { + max-width: 500px; + padding: 30px 20px 50px; + border: 1px solid #b3b3b3; + border-radius: 4px; + margin: 0 auto; + box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; + background: #fcfcfc; + } + + h1 { + margin: 0 10px; + font-size: 50px; + text-align: center; + } + + h1 span { + color: #bbb; + } + + h3 { + margin: 1.5em 0 0.5em; + } + + p { + margin: 1em 0; + } + + ul { + padding: 0 0 0 40px; + margin: 1em 0; + } + + .container { + max-width: 380px; + margin: 0 auto; + } + + /* google search */ + + #goog-fixurl ul { + list-style: none; + padding: 0; + margin: 0; + } + + #goog-fixurl form { + margin: 0; + } + + #goog-wm-qt, + #goog-wm-sb { + border: 1px solid #bbb; + font-size: 16px; + line-height: normal; + vertical-align: top; + color: #444; + border-radius: 2px; + } + + #goog-wm-qt { + width: 220px; + height: 20px; + padding: 5px; + margin: 5px 10px 0 0; + box-shadow: inset 0 1px 1px #ccc; + } + + #goog-wm-sb { + display: inline-block; + height: 32px; + padding: 0 10px; + margin: 5px 0 0; + white-space: nowrap; + cursor: pointer; + background-color: #f5f5f5; + background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + #goog-wm-sb:hover, + #goog-wm-sb:focus { + border-color: #aaa; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + background-color: #f8f8f8; + } + + #goog-wm-qt:hover, + #goog-wm-qt:focus { + border-color: #105cb6; + outline: 0; + color: #222; + } + + input::-moz-focus-inner { + padding: 0; + border: 0; + } + </style> + </head> + <body> + <div class="container"> + <h1>Not found <span>:(</span></h1> + <p>Sorry, but the page you were trying to view does not exist.</p> + <p>It looks like this was the result of either:</p> + <ul> + <li>a mistyped address</li> + <li>an out-of-date link</li> + </ul> + <script> + var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host; + </script> + <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script> + </div> + </body> +</html> diff --git a/gui/app/favicon.ico b/gui/app/favicon.ico Binary files differnew file mode 100644 index 000000000..652790530 --- /dev/null +++ b/gui/app/favicon.ico diff --git a/gui/app/images/back.png b/gui/app/images/back.png Binary files differnew file mode 100644 index 000000000..917c86edd --- /dev/null +++ b/gui/app/images/back.png diff --git a/gui/app/images/checkno.png b/gui/app/images/checkno.png Binary files differnew file mode 100644 index 000000000..7c6841930 --- /dev/null +++ b/gui/app/images/checkno.png diff --git a/gui/app/images/checkyes.png b/gui/app/images/checkyes.png Binary files differnew file mode 100644 index 000000000..ef6028310 --- /dev/null +++ b/gui/app/images/checkyes.png diff --git a/gui/app/images/close.png b/gui/app/images/close.png Binary files differnew file mode 100644 index 000000000..0d2c14252 --- /dev/null +++ b/gui/app/images/close.png diff --git a/gui/app/images/loading.gif b/gui/app/images/loading.gif Binary files differnew file mode 100644 index 000000000..b04dd11bc --- /dev/null +++ b/gui/app/images/loading.gif diff --git a/gui/app/images/loading2.gif b/gui/app/images/loading2.gif Binary files differnew file mode 100644 index 000000000..9d1534468 --- /dev/null +++ b/gui/app/images/loading2.gif diff --git a/gui/app/images/logo.png b/gui/app/images/logo.png Binary files differnew file mode 100644 index 000000000..c67c0d635 --- /dev/null +++ b/gui/app/images/logo.png diff --git a/gui/app/images/statusno.png b/gui/app/images/statusno.png Binary files differnew file mode 100644 index 000000000..ace4a454d --- /dev/null +++ b/gui/app/images/statusno.png diff --git a/gui/app/images/statusyes.png b/gui/app/images/statusyes.png Binary files differnew file mode 100644 index 000000000..d88a99ee8 --- /dev/null +++ b/gui/app/images/statusyes.png diff --git a/gui/app/images/url.json b/gui/app/images/url.json new file mode 100644 index 000000000..f16c4e0ac --- /dev/null +++ b/gui/app/images/url.json @@ -0,0 +1 @@ +{"url": "192.168.23.2:1948"}
\ No newline at end of file diff --git a/gui/app/images/yeoman.png b/gui/app/images/yeoman.png Binary files differnew file mode 100644 index 000000000..92497addf --- /dev/null +++ b/gui/app/images/yeoman.png diff --git a/gui/app/index.html b/gui/app/index.html new file mode 100644 index 000000000..5592656cc --- /dev/null +++ b/gui/app/index.html @@ -0,0 +1,111 @@ +<!doctype html> +<html> + +<head> + <meta charset="utf-8"> + <title></title> + <meta name="description" content=""> + <meta name="viewport" content="width=device-width"> + <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> + <!-- build:css(.) styles/vendor.css --> + <!-- bower:css --> + <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" /> + <link rel="stylesheet" href="bower_components/angular-wizard/dist/angular-wizard.min.css" /> + <link rel="stylesheet" href="bower_components/AngularJS-Toaster/toaster.css" /> + <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog.css" /> + <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog-theme-default.css" /> + <link rel="stylesheet" href="bower_components/components-font-awesome/css/font-awesome.css" /> + <link rel="stylesheet" href="bower_components/v-accordion/dist/v-accordion.css" /> + <link rel="stylesheet" href="bower_components/angular-loading/angular-loading.css" /> + <!-- endbower --> + <!-- endbuild --> + <!-- build:css(.tmp) styles/main.css --> + <link rel="stylesheet" href="styles/main.css"> + + + <!-- endbuild --> +</head> + +<script> +// read file + + +</script> + +<body ng-app="yardStickGui2App"> + <!--[if lte IE 8]> + <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> + <![endif]--> + + + + + <div ui-view></div> + + + + + <!-- Google Analytics: change UA-XXXXX-X to be your site's ID --> + <!--<script> + ! function(A, n, g, u, l, a, r) { + A.GoogleAnalyticsObject = l, A[l] = A[l] || function() { + (A[l].q = A[l].q || []).push(arguments) + }, A[l].l = +new Date, a = n.createElement(g), + r = n.getElementsByTagName(g)[0], a.src = u, r.parentNode.insertBefore(a, r) + }(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); + + ga('create', 'UA-XXXXX-X'); + ga('send', 'pageview'); + </script>--> + + <!-- build:js(.) scripts/vendor.js --> + <!-- bower:js --> + <script src="bower_components/jquery/dist/jquery.js"></script> + <script src="bower_components/angular/angular.js"></script> + <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> + <script src="bower_components/angular-strap/dist/angular-strap.js"></script> + <script src="bower_components/angular-strap/dist/angular-strap.tpl.js"></script> + <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script> + <script src="bower_components/angular-animate/angular-animate.js"></script> + <script src="bower_components/angular-breadcrumb/release/angular-breadcrumb.js"></script> + <script src="bower_components/angular-wizard/dist/angular-wizard.min.js"></script> + <script src="bower_components/angular-resource/angular-resource.js"></script> + <script src="bower_components/ng-file-upload/ng-file-upload.js"></script> + <script src="bower_components/AngularJS-Toaster/toaster.js"></script> + <script src="bower_components/ng-dialog/js/ngDialog.js"></script> + <script src="bower_components/angularUtils-pagination/dirPagination.js"></script> + <script src="bower_components/ngstorage/ngStorage.js"></script> + <script src="bower_components/v-accordion/dist/v-accordion.js"></script> + <script src="bower_components/spin.js/spin.js"></script> + <script src="bower_components/angular-loading/angular-loading.js"></script> + <script src="bower_components/spin.js/spin.js"></script> + <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> + <script src="bower_components/angular-sanitize/angular-sanitize.js"></script> + <!-- endbower --> + <!-- endbuild --> + + <!-- build:js({.tmp,app}) scripts/scripts.js --> + <script src="scripts/app.js"></script> + <script src="scripts/router.config.js"></script> + <script src="scripts/controllers/main.js"></script> + <script src="scripts/factory/main.factory.js"></script> + <script src="scripts/controllers/content.controller.js"></script> + <script src="scripts/controllers/detail.controller.js"></script> + <script src="scripts/controllers/image.controller.js"></script> + <script src="scripts/controllers/pod.controller.js"></script> + <script src="scripts/controllers/container.controller.js"></script> + <script src="scripts/controllers/testcase.controller.js"></script> + <script src="scripts/controllers/testcasedetail.controller.js"></script> + <script src="scripts/controllers/testsuit.controller.js"></script> + <script src="scripts/controllers/suitedetail.controller.js"></script> + <script src="scripts/controllers/suitecreate.controller.js"></script> + <script src="scripts/controllers/task.controller.js"></script> + <script src="scripts/controllers/report.controller.js"></script> + <script src="scripts/controllers/project.controller.js"></script> + <script src="scripts/controllers/projectDetail.controller.js"></script> + <script src="scripts/controllers/taskModify.controller.js"></script> + + <!-- endbuild --> +</body> + +</html> diff --git a/gui/app/robots.txt b/gui/app/robots.txt new file mode 100644 index 000000000..4d521f952 --- /dev/null +++ b/gui/app/robots.txt @@ -0,0 +1,4 @@ +# robotstxt.org + +User-agent: * +Disallow: diff --git a/gui/app/scripts/app.js b/gui/app/scripts/app.js new file mode 100644 index 000000000..ecb642c95 --- /dev/null +++ b/gui/app/scripts/app.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * @ngdoc overview + * @name yardStickGui2App + * @description + * # yardStickGui2App + * + * Main module of the application. + */ +angular + .module('yardStickGui2App', [ + 'ui.router', + 'ngAnimate', + 'ngSanitize', + 'mgcrea.ngStrap', + 'ncy-angular-breadcrumb', + 'mgo-angular-wizard', + 'ngResource', + 'ngFileUpload', + 'toaster', + 'ngDialog', + 'angularUtils.directives.dirPagination', + 'ngStorage', + 'vAccordion', + 'darthwade.dwLoading', + 'ui.bootstrap' + + + ]); diff --git a/gui/app/scripts/controllers/container.controller.js b/gui/app/scripts/controllers/container.controller.js new file mode 100644 index 000000000..6c2ccd8ff --- /dev/null +++ b/gui/app/scripts/controllers/container.controller.js @@ -0,0 +1,182 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContainerController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + $scope.showloading = false; + + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.createContainer = createContainer; + $scope.openChooseContainnerDialog = openChooseContainnerDialog; + + + getItemIdDetail(); + + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + } + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + response.result.container['id'] = id; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function createContainer() { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': $scope.selectContainer.value, + 'args': { + 'environment_id': $scope.uuid, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getItemIdDetail(); + }, 10000); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openChooseContainnerDialog() { + ngDialog.open({ + template: 'views/modal/chooseContainer.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteContainer = function deleteContainer() { + mainFactory.deleteContainer().delete({ 'containerId': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete container success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + } + ]); diff --git a/gui/app/scripts/controllers/content.controller.js b/gui/app/scripts/controllers/content.controller.js new file mode 100644 index 000000000..d2bc19eea --- /dev/null +++ b/gui/app/scripts/controllers/content.controller.js @@ -0,0 +1,136 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContentController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$localStorage', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $localStorage) { + + + + + init(); + $scope.showEnvironment = false; + $scope.counldGoDetail = false; + $scope.activeStatus = 0; + + $scope.$watch(function() { + return location.hash + }, function(newvalue, oldvalue) { + if (location.hash.indexOf('project') > -1) { + $scope.projectShow = true; + $scope.taskShow = false; + $scope.reportShow = false; + } else if (location.hash.indexOf('task') > -1) { + $scope.taskShow = true; + $scope.projectShow = true; + } else if (location.hash.indexOf('report') > -1) { + $scope.reportShow = true; + $scope.taskShow = true; + $scope.projectShow = true; + } + + }) + + + function init() { + + + $scope.showEnvironments = showEnvironments; + $scope.showSteps = $location.path().indexOf('project'); + $scope.test = test; + $scope.gotoUploadPage = gotoUploadPage; + $scope.gotoOpenrcPage = gotoOpenrcPage; + $scope.gotoPodPage = gotoPodPage; + $scope.gotoContainerPage = gotoContainerPage; + $scope.gotoTestcase = gotoTestcase; + $scope.gotoEnviron = gotoEnviron; + $scope.gotoSuite = gotoSuite; + $scope.gotoProject = gotoProject; + $scope.gotoTask = gotoTask; + $scope.gotoReport = gotoReport; + $scope.stepsStatus = $localStorage.stepsStatus; + $scope.goBack = goBack; + + + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + function test() { + alert('test'); + } + + function gotoOpenrcPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.environmentDetail', { uuid: $scope.uuid }) + } + + function gotoUploadPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + function gotoPodPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + function gotoContainerPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + function gotoTestcase() { + $state.go('app2.testcase'); + } + + function gotoEnviron() { + if ($location.path().indexOf('env') > -1 || $location.path().indexOf('environment') > -1) { + $scope.counldGoDetail = true; + } + $state.go('app2.environment'); + } + + function gotoSuite() { + $state.go('app2.testsuite'); + } + + function gotoProject() { + $state.go('app2.projectList'); + } + + function gotoTask() { + $state.go('app2.tasklist'); + } + + function gotoReport() { + $state.go('app2.report'); + } + + function goBack() { + if ($location.path().indexOf('main/environment')) { + return; + } else if ($location.path().indexOf('main/envDetail/') || $location.path().indexOf('main/imageDetail/') || + $location.path().indexOf('main/podupload/') || $location.path().indexOf('main/container/')) { + $state.go('app2.environment'); + return; + } else { + window.history.back(); + } + + } + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/detail.controller.js b/gui/app/scripts/controllers/detail.controller.js new file mode 100644 index 000000000..3e2eaa100 --- /dev/null +++ b/gui/app/scripts/controllers/detail.controller.js @@ -0,0 +1,384 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('DetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + + + init(); + $scope.showEnvironment = false; + $scope.envInfo = []; + + function init() { + $scope.showEnvironments = showEnvironments; + // $scope.openrcID = $stateParams.uuid; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFiles = uploadFiles; + $scope.addEnvironment = addEnvironment; + + $scope.uuid = $stateParams.uuid; + $scope.openrcID = $stateParams.opercId; + $scope.imageID = $stateParams.imageId; + $scope.podID = $stateParams.podId; + $scope.containerId = $stateParams.containerId; + $scope.ifNew = $stateParams.ifNew; + + + getItemIdDetail(); + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData['args']['openrc'] = $scope.postEnvInfo; + postData['args']['environment_id'] = $scope.uuid; + + + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + + //buildtoEnvInfo + function buildToEnvInfo(object) { + var tempKeyArray = Object.keys(object); + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + function addEnvironment() { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': $scope.baseElementInfo.name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuid = response.result.uuid; + var path = $location.path(); + path = path + $scope.uuid; + $location.url(path); + getItemIdDetail(); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetail($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail() { + mainFactory.ImageDetail().get({ + 'image_id': $scope.baseElementInfo.image_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail() { + mainFactory.podDeatil().get({ + 'podId': $scope.baseElementInfo.pod_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getPodDetail(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteOpenRc = function deleteOpenRc() { + mainFactory.deleteOpenrc().delete({ 'openrc': $scope.baseElementInfo.openrc_id }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete openrc success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + $scope.openrcInfo = null; + $scope.envInfo = []; + $scope.displayOpenrcFile = null; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + } + + + ]); diff --git a/gui/app/scripts/controllers/image.controller.js b/gui/app/scripts/controllers/image.controller.js new file mode 100644 index 000000000..53acff405 --- /dev/null +++ b/gui/app/scripts/controllers/image.controller.js @@ -0,0 +1,166 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ImageController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $interval) { + + + init(); + $scope.showloading = false; + $scope.ifshowStatus = 0; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadImage = uploadImage; + getItemIdDetail(); + getImageListSimple(); + } + + function getItemIdDetail() { + 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 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + 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 + }); + }) + } + + + 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); + } + + } 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 uploadImage() { + $scope.imageStatus = 0; + $interval.cancel($scope.intervalImgae); + $scope.ifshowStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$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); + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/main.js b/gui/app/scripts/controllers/main.js new file mode 100644 index 000000000..e3e880e62 --- /dev/null +++ b/gui/app/scripts/controllers/main.js @@ -0,0 +1,725 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('MainCtrl', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + $scope.project = 0; + $scope.showloading = false; + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + $scope.items = [ + 'The first choice!', + 'And another choice for you.', + 'but wait! A third!' + ]; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalImgae) + }); + $scope.showImageStatus = 0; + + + + + + + function init() { + + + $scope.gotoProject = gotoProject; + $scope.gotoEnvironment = gotoEnvironment; + $scope.gotoTask = gotoTask; + $scope.gotoExcute = gotoExcute; + $scope.gotoReport = gotoReport; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFilesPod = uploadFilesPod; + $scope.uploadFiles = uploadFiles; + $scope.showEnvriomentStatus = showEnvriomentStatus; + $scope.openEnvironmentDialog = openEnvironmentDialog; + $scope.getEnvironmentList = getEnvironmentList; + $scope.gotoDetail = gotoDetail; + $scope.addEnvironment = addEnvironment; + $scope.createContainer = createContainer; + $scope.chooseResult = chooseResult; + + getEnvironmentList(); + // getImageList(); + + } + + function gotoProject() { + $scope.project = 1; + } + + function gotoEnvironment() { + $scope.project = 0; + } + + function gotoTask() { + $scope.project = 2; + } + + function gotoExcute() { + $scope.project = 3; + + } + + function gotoReport() { + $scope.project = 4; + } + $scope.skipPod = function skipPod() { + $scope.showContainer = 1; + + } + $scope.skipContainer = function skipContainer() { + getEnvironmentList(); + ngDialog.close(); + } + + $scope.goToImage = function goToImage() { + getImageListSimple(); + $scope.showImage = 1; + } + $scope.goToPod = function goToPod() { + $scope.showPod = 1; + } + $scope.goToPodPrev = function goToPodPrev() { + $scope.showImage = null; + + } + $scope.skipPodPrev = function skipPodPrev() { + $scope.showImage = 1; + $scope.showPod = null; + + } + $scope.skipContainerPrev = function skipContainerPrev() { + $scope.showPod = 1; + $scope.showContainer = null; + } + + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData.args["openrc"] = $scope.postEnvInfo; + postData.args['environment_id'] = $scope.uuidEnv; + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + // $scope.showImage = response.status; + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + + getItemIdDetailforOpenrc(); + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + function uploadFilesPod($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayPodFile = $file; + timeConstruct($scope.displayPodFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + //display environment + function showEnvriomentStatus() { + $scope.showEnvironment = true; + } + + //open Environment dialog + function openEnvironmentDialog() { + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + + $scope.displayPodFile = null; + $scope.name = null; + $scope.openrcInfo = null; + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + $scope.displayOpenrcFile = null; + $scope.podData = null; + $scope.displayContainerInfo = null; + ngDialog.open({ + preCloseCallback: function(value) { + getEnvironmentList(); + // getImageList(); + }, + template: 'views/modal/environmentDialog.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 950, + showClose: true, + closeByDocument: false + }) + } + + function getEnvironmentList() { + $loading.start('key'); + + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + $loading.finish('key'); + + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + //go to detail page + function gotoDetail(ifNew, uuid) { + + $state.go('app.environmentDetail', { uuid: uuid, ifNew: ifNew }); + } + + + function addEnvironment(name) { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuidEnv = response.result.uuid; + $scope.name = name; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + $scope.displayContainerInfo = []; + + function createContainer(selectContainer) { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': selectContainer.value, + 'args': { + 'environment_id': $scope.uuidEnv, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + + setTimeout(function() { + getItemIdDetail(); + }, 10000); + $scope.ifskipOrClose = 1; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + + } + + + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.uploadImage = function uploadImage() { + $scope.imageStatus = 0; + $scope.showImageStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$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; + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + 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); + } + + } 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 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.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteEnv = function deleteEnv() { + mainFactory.deleteEnv().delete({ 'env_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete environment success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getEnvironmentList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + function getItemIdDetailforOpenrc() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetailForOpenrc($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + //getopenRcid + function getOpenrcDetailForOpenrc(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfoOpenrc($scope.openrcInfo.openrc) + }, function(response) { + toaster.pop({ + type: 'error', + title: 'error', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //buildtoEnvInfo + function buildToEnvInfoOpenrc(object) { + var tempKeyArray = Object.keys(object); + $scope.envInfo = []; + + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/pod.controller.js b/gui/app/scripts/controllers/pod.controller.js new file mode 100644 index 000000000..3ef236854 --- /dev/null +++ b/gui/app/scripts/controllers/pod.controller.js @@ -0,0 +1,179 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('PodController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + init(); + $scope.showloading = false; + $scope.loadingOPENrc = false; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + getItemIdDetail(); + + } + + function getItemIdDetail() { + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.name = response.result.environment.name; + $scope.podId = response.result.environment.pod_id; + if ($scope.podId != null) { + getPodDetail($scope.podId); + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getPodDetail(id) { + mainFactory.getPodDetail().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podData = response.result; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + //upload pod file + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + getItemIdDetail(); + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deletePod = function deletePod() { + mainFactory.deletePod().delete({ 'podId': $scope.podId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete pod success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + $scope.displayOpenrcFile = null; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/project.controller.js b/gui/app/scripts/controllers/project.controller.js new file mode 100644 index 000000000..0a7b8b932 --- /dev/null +++ b/gui/app/scripts/controllers/project.controller.js @@ -0,0 +1,160 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + + + getProjectList(); + $scope.openCreateProject = openCreateProject; + $scope.createName = createName; + $scope.gotoDetail = gotoDetail; + + + } + + function getProjectList() { + $loading.start('key'); + mainFactory.projectList().get({}).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.projectListData = response.result.projects; + + + } else { + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openCreateProject() { + + ngDialog.open({ + template: 'views/modal/projectCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 400, + showClose: true, + closeByDocument: false + }) + } + + function createName(name) { + + mainFactory.createProjectName().post({ + 'action': 'create_project', + 'args': { + 'name': name, + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'create project failed', + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'Something Wrong', + timeout: 3000 + }); + }) + } + + function gotoDetail(id) { + $state.go('app2.projectdetail', { projectId: id }) + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteProject = function deleteProject() { + mainFactory.deleteProject().delete({ 'project_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/projectDetail.controller.js b/gui/app/scripts/controllers/projectDetail.controller.js new file mode 100644 index 000000000..4ab4a055a --- /dev/null +++ b/gui/app/scripts/controllers/projectDetail.controller.js @@ -0,0 +1,690 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + // $scope.taskListDisplay = []; + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalCount) + }); + $scope.finalTaskListDisplay = []; + + + function init() { + + + getProjectDetail(); + + $scope.openCreate = openCreate; + $scope.createTask = createTask; + $scope.constructTestSuit = constructTestSuit; + $scope.addEnvToTask = addEnvToTask; + $scope.triggerContent = triggerContent; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmAddCaseOrSuite = confirmAddCaseOrSuite; + $scope.runAtask = runAtask; + $scope.gotoDetail = gotoDetail; + $scope.gotoReport = gotoReport; + $scope.gotoModify = gotoModify; + $scope.goBack = goBack; + $scope.goToExternal = goToExternal; + + + } + + function getProjectDetail() { + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + $loading.start('key'); + $scope.taskListDisplay = []; + $scope.finalTaskListDisplay = []; + mainFactory.getProjectDetail().get({ + project_id: $stateParams.projectId + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + + $scope.projectData = response.result.project; + if ($scope.projectData.tasks.length != 0) { + + + for (var i = 0; i < $scope.projectData.tasks.length; i++) { + getDetailTaskForList($scope.projectData.tasks[i]); + } + $scope.intervalCount = $interval(function() { + getDetailForEachTask(); + }, 10000); + } else { + + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + } + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + // function getProjectDetailSimple() { + // getDetailForEachTask(); + // } + + function openCreate() { + $scope.newUUID = null; + $scope.displayEnvName = null; + $scope.selectEnv = null; + $scope.selectCase = null; + $scope.selectType = null; + $scope.contentInfo = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + + // getEnvironmentList(); + $scope.selectEnv = null; + ngDialog.open({ + template: 'views/modal/taskCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 800, + showClose: true, + closeByDocument: false, + preCloseCallback: function(value) { + getProjectDetail(); + }, + }) + } + + function createTask(name) { + mainFactory.createTask().post({ + 'action': 'create_task', + 'args': { + 'name': name, + 'project_id': $stateParams.projectId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create task success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.newUUID = response.result.uuid; + getEnvironmentList(); + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function getDetailTaskForList(id) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.taskListDisplay.push(response.result.task); + console.log($scope.taskListDisplay); + + $scope.finalTaskListDisplay = $scope.taskListDisplay; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailTaskForListSimple(id, index) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + + $scope.finalTaskListDisplay[index].stausWidth = '5%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 0) { + + $scope.finalTaskListDisplay[index].stausWidth = '50%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 1) { + + $scope.finalTaskListDisplay[index].stausWidth = '100%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 2) { + + $scope.finalTaskListDisplay[index].stausWidth = 'red'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailForEachTask() { + for (var i = 0; i < $scope.finalTaskListDisplay.length; i++) { + if ($scope.finalTaskListDisplay[i].status != 1 && $scope.finalTaskListDisplay[i].status != -1) { + getDetailTaskForListSimple($scope.finalTaskListDisplay[i].uuid, i); + } + } + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function constructTestSuit(id, name) { + $scope.displayEnvName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + + + + function addEnvToTask() { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_environment', + 'args': { + 'task_id': $scope.newUUID, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + getTestcaseList(); + } else if (name.name == 'Test Suite') { + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_case', + 'args': { + 'task_id': $scope.newUUID, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_suite', + 'args': { + 'task_id': $scope.newUUID, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + function confirmAddCaseOrSuite(content) { + if ($scope.selectType.name == "Test Case") { + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + function runAtask(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + // getProjectDetail(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.runAtaskForTable = function runAtaskForTable(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + // toaster.pop({ + // type: 'success', + // title: 'run a task success', + // body: 'you can go next step', + // timeout: 3000 + // }); + // ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.result, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function gotoDetail(id) { + + + $state.go('app2.tasklist', { taskId: id }); + + } + + function gotoReport(id) { + $state.go('app2.report', { taskId: id }); + } + + function gotoModify(id) { + $state.go('app2.taskModify', { taskId: id }); + } + + function goBack() { + window.history.back(); + } + + function goToExternal() { + window.open(External_URL, '_blank'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTask = function deleteTask() { + mainFactory.deleteTask().delete({ 'task_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/report.controller.js b/gui/app/scripts/controllers/report.controller.js new file mode 100644 index 000000000..6a62cf8ea --- /dev/null +++ b/gui/app/scripts/controllers/report.controller.js @@ -0,0 +1,115 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ReportController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + + + } + $scope.goBack = function goBack() { + window.history.back(); + } + + function getDetailTaskForList(id) { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.result = response.result.task; + $scope.testcaseinfo = response.result.task.result.testcases; + var key = Object.keys($scope.testcaseinfo); + $scope.testcaseResult = $scope.testcaseinfo[key]; + + $scope.envIdForTask = response.result.task.environment_id; + getItemIdDetail(); + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.goToExternal = function goToExternal(id) { + var url = Grafana_URL +':'+$scope.jumpPort+'/dashboard/db'+ '/' + id; + + window.open(url, '_blank'); + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.envIdForTask + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.environment.container_id.grafana != null) { + getConDetail(response.result.environment.container_id.grafana); + + } else { + $scope.jumpPort = 3000; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.jumpPort = response.result.container.port; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/suitecreate.controller.js b/gui/app/scripts/controllers/suitecreate.controller.js new file mode 100644 index 000000000..4a7b6fe85 --- /dev/null +++ b/gui/app/scripts/controllers/suitecreate.controller.js @@ -0,0 +1,104 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suitcreateController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + + getTestcaseList(); + $scope.constructTestSuit = constructTestSuit; + $scope.openDialog = openDialog; + $scope.createSuite = createSuite; + + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.testsuiteList = []; + $scope.suitReconstructList = []; + + function constructTestSuit(name) { + + var index = $scope.testsuiteList.indexOf(name); + if (index > -1) { + $scope.testsuiteList.splice(index, 1); + } else { + $scope.testsuiteList.push(name); + } + + + $scope.suitReconstructList = $scope.testsuiteList; + + } + + function createSuite(name) { + mainFactory.suiteCreate().post({ + 'action': 'create_suite', + 'args': { + 'name': name, + 'testcases': $scope.testsuiteList + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function openDialog() { + ngDialog.open({ + template: 'views/modal/suiteName.html', + className: 'ngdialog-theme-default', + scope: $scope, + width: 314, + showClose: true, + closeByDocument: false + }) + } + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/suitedetail.controller.js b/gui/app/scripts/controllers/suitedetail.controller.js new file mode 100644 index 000000000..0dd39c389 --- /dev/null +++ b/gui/app/scripts/controllers/suitedetail.controller.js @@ -0,0 +1,48 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suiteDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getSuiteDetail(); + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.suiteinfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/task.controller.js b/gui/app/scripts/controllers/task.controller.js new file mode 100644 index 000000000..05546f9bf --- /dev/null +++ b/gui/app/scripts/controllers/task.controller.js @@ -0,0 +1,175 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayEnv = response.result.environment; + + if (response.result.environment.pod_id != null) { + getPodDetail(response.result.environment.pod_id); + } else if (response.result.environment.image_id != null) { + getImageDetail(response.result.environment.image_id); + } else if (response.result.environment.openrc_id != null) { + getOpenrcDetail(response.result.environment.openrc_id != null); + } else if (response.result.environment.container_id.length != 0) { + $scope.displayContainerDetail = []; + var containerArray = response.result.environment.container_id; + for (var i = 0; i < containerArray.length; i++) { + getContainerId(containerArray[i]); + } + + } + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + //openrc数据 + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/taskModify.controller.js b/gui/app/scripts/controllers/taskModify.controller.js new file mode 100644 index 000000000..757d65866 --- /dev/null +++ b/gui/app/scripts/controllers/taskModify.controller.js @@ -0,0 +1,533 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskModifyController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + + $scope.sourceShow = null; + + + + function init() { + getDetailTaskForList(); + getEnvironmentList(); + $scope.triggerContent = triggerContent; + $scope.constructTestSuit = constructTestSuit; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmToServer = confirmToServer; + $scope.addEnvToTask = addEnvToTask; + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + $scope.selectEnv = $scope.taskDetailData.environment_id; + + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + // $scope.selectEnv = $scope.envName; + } else { + alert('Something Wrong!'); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + $scope.taskDetailData.suite = false; + getTestcaseList(); + } else if (name.name == 'Test Suite') { + $scope.taskDetailData.suite = true; + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function constructTestSuit(id, name) { + + $scope.envName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_case', + 'args': { + 'task_id': $stateParams.taskId, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_suite', + 'args': { + 'task_id': $stateParams.taskId, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + $scope.changeStatussourceTrue = function changeStatussourceTrue() { + $scope.selectCase = null; + $scope.sourceShow = true; + } + + $scope.changeStatussourceFalse = function changeStatussourceFalse() { + $scope.sourceShow = false; + } + + function confirmToServer(content1, content2) { + + var content; + if ($scope.sourceShow == false) { + content = content2; + $scope.selectCase = $scope.taskDetailData.case_name; + } else if ($scope.sourceShow == true) { + content = content1; + } + if ($scope.selectCase == 'Test Case' || $scope.taskDetailData.suite == false) { + + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + + function addEnvToTask() { + + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_environment', + 'args': { + 'task_id': $stateParams.taskId, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.runAtask = function runAtask() { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'run', + 'args': { + 'task_id': $stateParams.taskId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'go to task list page...', + timeout: 3000 + }); + setTimeout(function() { + window.history.back(); + }, 2000); + + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/testcase.controller.js b/gui/app/scripts/controllers/testcase.controller.js new file mode 100644 index 000000000..616ceb4a8 --- /dev/null +++ b/gui/app/scripts/controllers/testcase.controller.js @@ -0,0 +1,154 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TestcaseController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + $scope.loadingOPENrc = false; + + + function init() { + $scope.testcaselist = []; + getTestcaseList(); + $scope.gotoDetail = gotoDetail; + $scope.uploadFiles = uploadFiles; + + + } + + function getTestcaseList() { + $loading.start('key'); + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + $state.go('app2.testcasedetail', { name: name }); + } + + + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/testcases', + data: { file: $file, 'action': 'upload_case' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTestCase = function deleteTestCase() { + mainFactory.deleteTestCase().delete({ 'caseName': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Case success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/testcasedetail.controller.js b/gui/app/scripts/controllers/testcasedetail.controller.js new file mode 100644 index 000000000..4e824ca85 --- /dev/null +++ b/gui/app/scripts/controllers/testcasedetail.controller.js @@ -0,0 +1,50 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('testcaseDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getTestcaseDetail(); + + + } + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaseInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/testsuit.controller.js b/gui/app/scripts/controllers/testsuit.controller.js new file mode 100644 index 000000000..abc9095c7 --- /dev/null +++ b/gui/app/scripts/controllers/testsuit.controller.js @@ -0,0 +1,119 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('SuiteListController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + $scope.testsuitlist = []; + getsuiteList(); + $scope.gotoDetail = gotoDetail; + $scope.gotoCreateSuite = gotoCreateSuite; + + + } + + function getsuiteList() { + $loading.start('key'); + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + var temp = name.split('.')[0]; + + $state.go('app2.suitedetail', { name: temp }) + + } + + function gotoCreateSuite() { + $state.go('app2.suitcreate'); + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id.split('.')[0]; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteSuite = function deleteSuite() { + mainFactory.deleteTestSuite().delete({ 'suite_name': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/factory/main.factory.js b/gui/app/scripts/factory/main.factory.js new file mode 100644 index 000000000..f8e9df9a1 --- /dev/null +++ b/gui/app/scripts/factory/main.factory.js @@ -0,0 +1,247 @@ +'use strict'; + +/** + * get data factory + */ + + +var Base_URL; +var Grafana_URL; + +angular.module('yardStickGui2App') + .factory('mainFactory', ['$resource','$rootScope','$http', '$location',function($resource, $rootScope,$http,$location) { + + Base_URL = 'http://' + $location.host() + ':' + $location.port(); + Grafana_URL = 'http://' + $location.host(); + + return { + + postEnvironmentVariable: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + uploadOpenrc: function() { + return $resource(Base_URL + '/ap/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + getEnvironmentList: function() { + return $resource(Base_URL+ '/api/v2/yardstick/environments', {}, { + 'get': { + method: 'GET' + } + }) + }, + getEnvironmentDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc_id', { openrc_id: "@openrc_id" }, { + 'get': { + method: 'GET' + } + }) + }, + addEnvName: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments', {}, { + 'post': { + method: 'POST' + } + }) + }, + ItemDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:envId', { envId: "@envId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/images/:image_id', { image_id: "@image_id" }, { + 'get': { + method: 'GET' + } + }) + }, + podDeatil: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { + 'get': { + method: 'GET' + } + }) + }, + containerDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: "@containerId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageList: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'get': { + method: 'GET' + } + }) + }, + uploadImage: 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': { + method: 'GET' + } + }) + }, + runAcontainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers', { podId: "@podId" }, { + 'post': { + method: 'POST' + } + }) + }, + getTestcaselist: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases', {}, { + 'get': { + method: 'GET' + } + }) + }, + getTestcaseDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:testcasename', { testcasename: "@testcasename" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteList: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'get': { + method: 'GET' + } + }) + }, + suiteDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suiteName', { suiteName: "@suiteName" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteCreate: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'post': { + method: 'POST' + } + }) + }, + projectList: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'get': { + method: 'GET' + } + }) + }, + createProjectName: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'post': { + method: 'POST' + } + }) + }, + getProjectDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: "@project_id" }, { + 'post': { + method: 'POST' + } + }) + }, + createTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks', {}, { + 'post': { + method: 'POST' + } + }) + }, + getTaskDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'get': { + method: 'GET' + } + }) + }, + + taskAddEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'put': { + method: 'PUT' + } + }) + }, + //delete operate + deleteEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:env_id', { env_id: '@env_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteOpenrc: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc', { openrc: '@openrc' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deletePod: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: '@podId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteContainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: '@containerId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestCase: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:caseName', { caseName: '@caseName' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestSuite: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suite_name', { suite_name: '@suite_name' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteProject: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: '@project_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:task_id', { task_id: '@task_id' }, { + 'delete': { + method: 'DELETE' + } + }) + } + + }; + }]); diff --git a/gui/app/scripts/router.config.js b/gui/app/scripts/router.config.js new file mode 100644 index 000000000..b42954272 --- /dev/null +++ b/gui/app/scripts/router.config.js @@ -0,0 +1,184 @@ +'use strict'; + +angular.module('yardStickGui2App') + .run( + ['$rootScope', '$state', '$stateParams', + function($rootScope, $state, $stateParams) { + $rootScope.$state = $state; + $rootScope.$stateParams = $stateParams; + + } + ] + ) + .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', + function($stateProvider, $urlRouterProvider, $locationProvider) { + $urlRouterProvider + .otherwise('main/environment'); + + + + + $stateProvider + + .state('app2', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main2.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + .state('app', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + + .state('app2.environment', { + url: '/environment', + templateUrl: 'views/environmentList.html', + controller: 'MainCtrl', + ncyBreadcrumb: { + label: 'Environment' + } + }) + .state('app2.testcase', { + url: '/testcase', + templateUrl: 'views/testcaselist.html', + controller: 'TestcaseController', + ncyBreadcrumb: { + label: 'Test Case' + } + }) + .state('app2.testsuite', { + url: '/suite', + templateUrl: 'views/suite.html', + controller: 'SuiteListController', + ncyBreadcrumb: { + label: 'Test Suite' + } + }) + .state('app2.suitcreate', { + url: '/suitcreate', + templateUrl: 'views/testcasechoose.html', + controller: 'suitcreateController', + ncyBreadcrumb: { + label: 'Suite Create' + } + }) + .state('app2.testcasedetail', { + url: '/testdetail/:name', + templateUrl: 'views/testcasedetail.html', + controller: 'testcaseDetailController', + ncyBreadcrumb: { + label: 'Test Case Detail' + }, + params: { name: null } + }) + .state('app2.suitedetail', { + url: '/suitedetail/:name', + templateUrl: 'views/suitedetail.html', + controller: 'suiteDetailController', + ncyBreadcrumb: { + label: 'Suite Detail' + }, + params: { name: null } + }) + .state('app.environmentDetail', { + url: '/envDetail/:uuid', + templateUrl: 'views/environmentDetail.html', + controller: 'DetailController', + params: { uuid: null, ifNew: null }, + ncyBreadcrumb: { + label: 'Environment Detail' + } + }) + .state('app.uploadImage', { + url: '/envimageDetail/:uuid', + templateUrl: 'views/uploadImage.html', + controller: 'ImageController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Upload Image' + } + + }) + .state('app.podUpload', { + url: '/envpodupload/:uuid', + templateUrl: 'views/podupload.html', + controller: 'PodController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Pod Upload' + } + }) + .state('app.container', { + url: '/envcontainer/:uuid', + templateUrl: 'views/container.html', + controller: 'ContainerController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Container Manage' + } + }) + .state('app2.projectList', { + url: '/project', + templateUrl: 'views/projectList.html', + controller: 'ProjectController', + ncyBreadcrumb: { + label: 'Project' + } + + }) + .state('app2.tasklist', { + url: '/task/:taskId', + templateUrl: 'views/taskList.html', + controller: 'TaskController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Task' + } + + }) + .state('app2.report', { + url: '/report/:taskId', + templateUrl: 'views/report.html', + controller: 'ReportController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Report' + } + + }) + .state('app2.projectdetail', { + url: '/projectdetail/:projectId', + templateUrl: 'views/projectdetail.html', + controller: 'ProjectDetailController', + params: { projectId: null }, + ncyBreadcrumb: { + label: 'Project Detail' + } + + }) + .state('app2.taskModify', { + url: '/taskModify/:taskId', + templateUrl: 'views/taskmodify.html', + controller: 'TaskModifyController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Modify Task' + } + + + }) + + + + + + } + ]) + .run(); diff --git a/gui/app/styles/main.css b/gui/app/styles/main.css new file mode 100644 index 000000000..e13a66bce --- /dev/null +++ b/gui/app/styles/main.css @@ -0,0 +1,208 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +body { + padding: 0; +} + + +/* Everything but the jumbotron gets side spacing for mobile first views */ + +.header, +.marketing, +.footer { + /*padding-left: 15px; + padding-right: 15px;*/ +} + + +/* Custom page header */ + +.header { + border-bottom: 1px solid #e5e5e5; + margin-bottom: 10px; +} + + +/* Make the masthead heading the same height as the navigation */ + +.header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 40px; + padding-bottom: 19px; +} + + +/* Custom page footer */ + +.footer { + padding-top: 19px; + color: #777; + border-top: 1px solid #e5e5e5; +} + +.container-narrow>hr { + margin: 30px 0; +} + + +/* Main marketing message and sign up button */ + +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + + +/* Supporting marketing content */ + +.marketing { + margin: 40px 0; +} + +.marketing p+h4 { + margin-top: 28px; +} + + +/* Responsive: Portrait tablets and up */ + +@media screen and (min-width: 768px) { + .container { + max-width: 730px; + } + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-left: 0; + padding-right: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} + +.jumbotron.ng-scope { + margin-top: 100px; +} + +.content { + /*margin-left: 300px;*/ + /*margin-left: 100px;*/ + position: relative; + margin-left: 25%; + width: 70%; + border: 1px solid #dfe3e4; + border-radius: 5px; + padding: 20px 20px 5px 20px; + height: 100%; + margin-right: 10px; + /*border-bottom: none;*/ + margin-bottom: 10px; + padding-bottom: 20px; + /* overflow: hidden; */ +} + +.ngdialog.ngdialog-theme-default .ngdialog-content { + background-color: #fff; +} + +.progree-parent { + width: 50%; + background-color: #dfe3e4; + height: 10px; + border-radius: 10px; +} + +.progree-child { + width: 50%; + background-color: #2ecc71; + /* background-color: white; */ + height: 10px; + border-radius: 5px; +} + +textarea { + width: 100%; + height: 400px; + border-radius: 5px; + border: 1px solid #e8e8e8; +} + +.naviSide { + position: fixed; + left: 0px; + top: 0px; + height: 150%; + background-color: #f8f8f8; + width: 210px; + padding: 30px 0 0 0; + border-radius: 10px; + border-right: 1px solid #e7e7e7; + z-index: 2; +} + +.panel-body { + border: none; +} + +.panel-group { + width: 210px; +} + +.panel-group { + margin-bottom: 0px; +} + +* { + border-radius: 0px ! important; +} + +.naviSide.ng-scope { + box-shadow: 1px 1px 2px #888888; +} + +.pagination>li>a, +.pagination>li>span { + color: #333; +} + +.pagination>.active>a, +.pagination>.active>span, +.pagination>.active>a:hover, +.pagination>.active>span:hover, +.pagination>.active>a:focus, +.pagination>.active>span:focus { + background-color: #f9f9f9; + color: #333; + border-color: #ddd; +} + +.ngdialog.ngdialog-theme-default .ngdialog-close{ + border: none; + background-color: #fff; +} + +button:focus {outline:0;} +input:focus{outline: 0} + +.ngdialog-content { + overflow: hidden; +} + diff --git a/gui/app/views/container.html b/gui/app/views/container.html new file mode 100644 index 000000000..b3d78bfb1 --- /dev/null +++ b/gui/app/views/container.html @@ -0,0 +1,134 @@ +<!--container management--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{envName}} -- Container + <!--<button class="btn btn-default" style="float:right">Go Next</button>--> + + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <select ng-model="selectContainer" data-ng-options="container as container.name for container in containerList"> + <option value="">Choose...</option> + </select> + + <button class="btn btn-default" ng-click="createContainer()" ng-disabled="selectContainer==null"> + <div ng-show="!showloading">Create</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + <hr/> + + <div> + <h4 ng-show="displayContainerInfo.length==0">No Container Data</h4> + <div ng-show="displayContainerInfo.length!=0"> + <h4>Current Container</h4> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>status</th> + <th>time</th> + <th>delete</th> + + </tr> + <tr ng-repeat="con in displayContainerInfo"> + <td>{{con.name}}</td> + <td>{{con.status}}</td> + <td>{{con.time}}</td> + <td> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(con.id,'container')">Delete</button> + </td> + + + </tr> + + + + </table> + </div> + </div> + + + + + + + + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style> diff --git a/gui/app/views/content.html b/gui/app/views/content.html new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/gui/app/views/content.html diff --git a/gui/app/views/environmentDetail.html b/gui/app/views/environmentDetail.html new file mode 100644 index 000000000..4d5f21c68 --- /dev/null +++ b/gui/app/views/environmentDetail.html @@ -0,0 +1,143 @@ +<!--environment detail page--> + +<div class="content" style="overflow-x: scroll;"> + <div style="display:flex;flex-direction:row;"> + <div> + + + <h3> {{baseElementInfo.name}} -- Openrc + <button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button> + <button class="btn btn-default" style="float:right;margin-right:10px;" ng-click="openDeleteEnv(1,'openrc')">Delete</button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + + + <div> + + <button style="display:inline;" class="btn btn-default" ng-click="addEnvironment()" ng-show="uuid==null">Add Name</button> + </div> + + + + <hr/> + <div bs-tabs style="width:600px;"> + <div data-title="Detail" bs-pane ng-if="openrcInfo.openrc!=null"> + + <h4> + You have already set up the openrc parameters + </h4> + <hr /> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:14px;">{{key}} : </font> + <font style="font-size:14px;">{{value}}</font> + </nobr> + </div> + + </div> + <div data-title="Update" bs-pane> + + <div style="margin-top:20px;"> + <button class="btn btn-default" ng-click="addInfo()" style="margin-bottom:20px;">Add</button> + <div style="height:300px;width:800px;display:flex;flex-direction:column;flex-wrap:wrap;margin-left:5px;"> + <div ng-repeat="info in envInfo"> + <!--<div> {{info.name}}</div>--> + + <input class="edit-title" ng-model="info.name" ng-class="{'null-edit-title':info.name==null}" ng-attr-type="{{info.name.indexOf('PASSWORD')>-1 ? password : text}}" /> + + <div class="item-info"> + <input class="form-control" type="text" ng-model="info.value" /> + <!--<button class="delete-button" ng-click="deleteEnvItem($index)">delete</button>--> + <img src="images/close.png" ng-click="deleteEnvItem($index)" class="delete-img" /> + </div> + + + + </div> + </div> + <button class="btn btn-default" ng-click="submitOpenRcFile()" style="margin-bottom:20px;"> + <div ng-if="!showloading">submit</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + </div> + + </div> + <div data-title="Upload File" bs-pane> + <div style="margin-top:20px;height:405px;"> + <button class="btn btn-default" style="margin-bottom:20px;" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + </div> + </div> + </div> + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } +</style> diff --git a/gui/app/views/environmentList.html b/gui/app/views/environmentList.html new file mode 100644 index 000000000..1b00b1cc6 --- /dev/null +++ b/gui/app/views/environmentList.html @@ -0,0 +1,152 @@ +<div class="content"> + + <!--environmentList--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + + <h3>Environments + <button class="btn btn-default btn-sm" style="margin-left:30px;display:inline" ng-click="openEnvironmentDialog()">Add</button> + </h3> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9;"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:80px;">Action</div> + + </div> + + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> <a style="color:#4dc5cf" ng-click="gotoDetail('false',env.uuid)">{{env.name}}</a></div> + <div> + + <!-- <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(env.uuid,'environment')">Delete</button> --> + + <div class="btn-group" uib-dropdown is-open="status.isopen" style="margin-right:60px;"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + delete <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem"><a ng-click="openDeleteEnv(env.uuid,'environment')">delete</a></li> + + </ul> + </div> + </div> + + + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .deepColor { + background-color: #f9f9f9; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/layout/footer.html b/gui/app/views/layout/footer.html new file mode 100644 index 000000000..cfdf74af3 --- /dev/null +++ b/gui/app/views/layout/footer.html @@ -0,0 +1,5 @@ +<div class="footer"> + <div class="container"> + <p></p> + </div> +</div>
\ No newline at end of file diff --git a/gui/app/views/layout/header.html b/gui/app/views/layout/header.html new file mode 100644 index 000000000..ad90de952 --- /dev/null +++ b/gui/app/views/layout/header.html @@ -0,0 +1,44 @@ +<div class="header"> + <div class="navbar navbar-default" role="navigation"> + <div> + <div class="navbar-header"> + + + <img src="images/logo.png" style="width:50px;height:50px;float:left;margin-left:20px;" /> + <a class="navbar-brand" href="#/">Yardstick</a> + + </div> + + + </div> + </div> +</div> +</div> + +<style> + .header { + position: fixed; + top: 0px; + width: 100%; + /*box-shadow: 3px 2px 5px #888888;*/ + z-index: 9; + } + + .navbar { + position: relative; + min-height: 50px; + margin-bottom: 0px; + border: none; + /* border: 1px solid transparent; */ + } + + .navbar { + border-radius: 0px; + background-color: #CAEEF1; + color: #fff; + } + + .navbar-default .navbar-brand { + color: #333; + } +</style>
\ No newline at end of file diff --git a/gui/app/views/layout/sideNav.html b/gui/app/views/layout/sideNav.html new file mode 100644 index 000000000..4fc99cd4f --- /dev/null +++ b/gui/app/views/layout/sideNav.html @@ -0,0 +1,141 @@ +<div class="naviSide"> + + + + + <ul class="nav bs-sidenav"> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoProject();"> + Project + </a> + </h4> + + </div> + + </div> + </div> + <div class="panel-group" role="tablist" aria-multiselectable="true" bs-collapse style="margin-bottom:0px;" ng-model="activeStatus"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a bs-collapse-toggle style=" text-decoration: none;"> + <div style="display:inline;" ng-click="gotoEnviron()">Environment </div> + <i class="fa fa-sort-asc" aria-hidden="true" style="margin-left: 71px;display:inline" ng-show="activeStatus==0"></i> + <i class="fa fa-sort-desc" aria-hidden="true" style="margin-left: 71px;display:inline" ng-show="activeStatus==-1"></i> + </a> + </h4> + </div> + <div class="panel-collapse" role="tabpanel" bs-collapse-target> + <div class="panel-body" style="border-top: 2px solid grey;text-align: right;cursor:pointer" ng-click="gotoOpenrcPage()" ng-class="{active:$state.includes('app.environmentDetail')}"> + Openrc + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoUploadPage()" ng-class="{active:$state.includes('app.uploadImage')}"> + Image + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoPodPage()" ng-class="{active:$state.includes('app.podUpload')}"> + Pod File + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoContainerPage()" ng-class="{active:$state.includes('app.container')}"> + Container + </div> + <div class="panel-body " style="border:none;text-align: right;"> + Others + </div> + </div> + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoTestcase()"> + Test Case + </a> + </h4> + + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoSuite()"> + Test Suite + </a> + </h4> + + </div> + + </div> + </div> + + + + </ul> + + + + + +</div> + + +<style> + .bs-sidenav { + margin-top: 21px; + margin-bottom: 20px; + width: 124px; + } + + .naviSide { + height: 150%; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + width: 165px; + } + /* + a:hover { + width: 165px; + }*/ + + .nav>li>a:hover, + .nav>li>a:focus { + text-decoration: underline; + background-color: transparent; + } + + .active.panel-body { + background-color: #dfe3e4; + } +</style> diff --git a/gui/app/views/layout/sideNav2.html b/gui/app/views/layout/sideNav2.html new file mode 100644 index 000000000..93e0de4be --- /dev/null +++ b/gui/app/views/layout/sideNav2.html @@ -0,0 +1,108 @@ +<div class="naviSide"> + + + <ul class="nav bs-sidenav"> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoProject();"> + Project + </a> + </h4> + + </div> + + </div> + </div> + <div class="panel-group" role="tablist" aria-multiselectable="false" bs-collapse style="margin-bottom:0px;"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a bs-collapse-toggle style=" text-decoration: none;"> + <div style="display:inline;" ng-click="gotoEnviron()">Environment </div> + <!--<i class="fa fa-sort-asc" aria-hidden="true" style="margin-left: 71px;display:inline"></i>--> + </a> + </h4> + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoTestcase()"> + Test Case + </a> + </h4> + + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoSuite()"> + Test Suite + </a> + </h4> + + </div> + + </div> + </div> + + + + </ul> + +</div> + +<style> + .bs-sidenav { + margin-top: 21px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + width: 165px; + } + /*a:hover { + width: 165px; + }*/ + + .nav>li>a:hover, + .nav>li>a:focus { + text-decoration: underline; + background-color: transparent; + } +</style> diff --git a/gui/app/views/main.html b/gui/app/views/main.html new file mode 100644 index 000000000..36bcbbd3c --- /dev/null +++ b/gui/app/views/main.html @@ -0,0 +1,174 @@ +<div> + <div ng-include="'views/layout/header.html'"></div> +</div> +<div ng-include="'views/layout/sideNav.html'"></div> + + +<div style="margin-top:80px;margin-left:100px;display:flex;flex-direction:row"> + <!--<div ncy-breadcrumb></div>--> + <div> + <ol class="progressDefine"> + <li data-step="1" ng-click="gotoProject();" style="cursor:pointer" ng-class="{'is-complete':projectShow}"> + Project + </li> + <li data-step="2" ng-class="{'is-complete':taskShow}"> + Task + </li> + + <li data-step="3" ng-class="{'progressDefine__last':reportShow}"> + Reporting + </li> + + </ol> + </div> + + +</div> + + + + + + + + + +<div ui-view></div> + + + +<style> + .stepsContent { + display: flex; + flex-direction: row; + justify-content: space-around; + margin-left: 120px; + margin-top: 100px; + } + + .stepItem { + display: flex; + flex-direction: column; + } + + .nextButton { + margin-left: 500px; + } + + .progressDefine { + list-style: none; + margin: 0; + padding: 0; + display: table; + table-layout: fixed; + width: 100%; + color: #849397; + } + + .progressDefine>li { + position: relative; + display: table-cell; + text-align: center; + font-size: 0.8em; + } + + .progressDefine>li:before { + content: attr(data-step); + display: block; + margin: 0 auto; + background: #DFE3E4; + width: 3em; + height: 3em; + text-align: center; + margin-bottom: 0.25em; + line-height: 3em; + border-radius: 100%; + position: relative; + z-index: 5; + } + + .progressDefine>li:after { + content: ''; + position: absolute; + display: block; + background: #DFE3E4; + width: 100%; + height: 0.5em; + top: 1.25em; + left: 50%; + margin-left: 1.5em\9; + z-index: -1; + } + + .progressDefine>li:last-child:after { + display: none; + } + + .progressDefine>li.is-complete { + color: #4dc5cf; + } + + .progressDefine>li.is-complete:before, + .progressDefine>li.is-complete:after { + color: #FFF; + background: #4dc5cf; + } + + .progressDefine>li.is-active { + color: #3498DB; + } + + .progressDefine>li.is-active:before { + color: #FFF; + background: #3498DB; + } + /** + * Needed for IE8 + */ + + .progressDefine__last:after { + display: none !important; + } + /** + * Size Extensions + */ + + .progressDefine--medium { + font-size: 1.5em; + } + + .progressDefine--large { + font-size: 2em; + } + /** + * Some Generic Stylings + */ + + *, + *:after, + *:before { + box-sizing: border-box; + } + + h1 { + margin-bottom: 1.5em; + } + + .progressDefine { + margin-bottom: 3em; + } + + a { + color: #3498DB; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + /* + body { + text-align: center; + color: #444; + }*/ +</style> diff --git a/gui/app/views/main2.html b/gui/app/views/main2.html new file mode 100644 index 000000000..3f49e82e0 --- /dev/null +++ b/gui/app/views/main2.html @@ -0,0 +1,174 @@ +<div> + <div ng-include="'views/layout/header.html'"></div> +</div> +<div ng-include="'views/layout/sideNav2.html'"></div> + + +<div style="margin-top:80px;margin-left:220px;"> + <!--<div ncy-breadcrumb></div>--> + <div> + <ol class="progressDefine"> + <li data-step="1" ng-click="gotoProject();" style="cursor:pointer" ng-class="{'is-complete':projectShow}"> + Project + </li> + <li data-step="2" ng-class="{'is-complete':taskShow}"> + Task + </li> + + <li data-step="3" ng-class="{'is-complete':reportShow}"> + Reporting + </li> + + </ol> + </div> + + +</div> + + + + + + + + + +<div ui-view></div> + + + +<style> + .stepsContent { + display: flex; + flex-direction: row; + justify-content: space-around; + margin-left: 120px; + margin-top: 100px; + } + + .stepItem { + display: flex; + flex-direction: column; + } + + .nextButton { + margin-left: 500px; + } + + .progressDefine { + list-style: none; + margin: 0; + padding: 0; + display: table; + table-layout: fixed; + width: 100%; + color: #849397; + } + + .progressDefine>li { + position: relative; + display: table-cell; + text-align: center; + font-size: 0.8em; + } + + .progressDefine>li:before { + content: attr(data-step); + display: block; + margin: 0 auto; + background: #DFE3E4; + width: 3em; + height: 3em; + text-align: center; + margin-bottom: 0.25em; + line-height: 3em; + border-radius: 100%; + position: relative; + z-index: 5; + } + + .progressDefine>li:after { + content: ''; + position: absolute; + display: block; + background: #DFE3E4; + width: 100%; + height: 0.5em; + top: 1.25em; + left: 50%; + margin-left: 1.5em\9; + z-index: -1; + } + + .progressDefine>li:last-child:after { + display: none; + } + + .progressDefine>li.is-complete { + color: #4dc5cf; + } + + .progressDefine>li.is-complete:before, + .progressDefine>li.is-complete:after { + color: #FFF; + background: #4dc5cf; + } + + .progressDefine>li.is-active { + color: #3498DB; + } + + .progressDefine>li.is-active:before { + color: #FFF; + background: #3498DB; + } + /** + * Needed for IE8 + */ + + .progressDefine__last:after { + display: none !important; + } + /** + * Size Extensions + */ + + .progressDefine--medium { + font-size: 1.5em; + } + + .progressDefine--large { + font-size: 2em; + } + /** + * Some Generic Stylings + */ + + *, + *:after, + *:before { + box-sizing: border-box; + } + + h1 { + margin-bottom: 1.5em; + } + + .progressDefine { + margin-bottom: 3em; + } + + a { + color: #3498DB; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + /* + body { + text-align: center; + color: #444; + }*/ +</style> diff --git a/gui/app/views/modal/chooseContainer.html b/gui/app/views/modal/chooseContainer.html new file mode 100644 index 000000000..4b857b22f --- /dev/null +++ b/gui/app/views/modal/chooseContainer.html @@ -0,0 +1,15 @@ +<h3>Choose Containers</h3> +<hr/> + + + +<style> + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style>
\ No newline at end of file diff --git a/gui/app/views/modal/deleteConfirm.html b/gui/app/views/modal/deleteConfirm.html new file mode 100644 index 000000000..1659b884b --- /dev/null +++ b/gui/app/views/modal/deleteConfirm.html @@ -0,0 +1,19 @@ +<div>Confirm delete {{deleteName}} ?</div> + +<div style="display:flex;flex-direction:row; margin-left: 150px;margin-top: 30px;"> + <button class="btn btn-default" ng-click="deleteEnv()" ng-show="deleteName=='environment'">Confirm</button> + <button class="btn btn-default" ng-click="deleteProject()" ng-show="deleteName=='project'">Confirm</button> + <button class="btn btn-default" ng-click="deleteTask()" ng-show="deleteName=='task'">Confirm</button> + <button class="btn btn-default" ng-click="deleteTestCase()" ng-show="deleteName=='test case'">Confirm</button> + <button class="btn btn-default" ng-click="deleteSuite()" ng-show="deleteName=='test suite'">Confirm</button> + <button class="btn btn-default" ng-click="deleteContainer()" ng-show="deleteName=='container'">Confirm</button> + <button class="btn btn-default" ng-click="deletePod()" ng-show="deleteName=='pod'">Confirm</button> + <button class="btn btn-default" ng-click="deleteOpenRc()" ng-show="deleteName=='openrc'">Confirm</button> + + + + + + <button class="btn btn-default" style="margin-left:10px;" ng-click="closeThisDialog()">Cancel</button> + +</div>
\ No newline at end of file diff --git a/gui/app/views/modal/environmentDialog.html b/gui/app/views/modal/environmentDialog.html new file mode 100644 index 000000000..389de8340 --- /dev/null +++ b/gui/app/views/modal/environmentDialog.html @@ -0,0 +1,330 @@ +<!--environment input dialog--> + +<div> + <div ng-if="uuidEnv==null"> + <h4>Environment Name</h4> + <input type="text" ng-model="name" style="width:300px;" /> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled=" name==null || name==''" ng-click="addEnvironment(name)">Create</button> + </div> + </div> + + + <div style="display:flex;flex-direction:row;" ng-if="uuidEnv!=null&&showImage==null"> + <div> + <h3> {{name}} -- Openrc + <!--<button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button>--> + <button class="btn btn-default" ng-click="goToImage()" style="margin-bottom:20px;float:right" ng-disabled="showNextOpenRc==null && showNextOpenRc==null "> + Next + </button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + <div> + + <!--<button style="display:inline;" class="btn btn-default" ng-click="addEnvironment()" ng-show="uuid==null">Add Name</button>--> + </div> + + <hr/> + <div bs-tabs style="width:750px;"> + <div data-title="Detail" bs-pane ng-if="openrcInfo.openrc!=null"> + + <h4> + You have already set up the openrc parameters + </h4> + <hr /> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:14px;">{{key}} : </font> + <font style="font-size:14px;">{{value}}</font> + </nobr> + </div> + + </div> + <div data-title="Update" bs-pane> + + <div style="margin-top:20px;"> + <button class="btn btn-default" ng-click="addInfo()" style="margin-bottom:20px;">Add</button> + <div style="height:300px;width:800px;display:flex;flex-direction:column;flex-wrap:wrap;margin-left:5px;overflow-x:scroll"> + <div ng-repeat="info in envInfo"> + <!--<div> {{info.name}}</div>--> + + <input class="edit-title" ng-model="info.name" ng-class="{'null-edit-title':info.name==null}" ng-attr-type="{{info.name.indexOf('PASSWORD')>-1 ? password : text}}" /> + + <div class="item-info"> + <input class="form-control" type="text" ng-model="info.value" /> + <!--<button class="delete-button" ng-click="deleteEnvItem($index)">delete</button>--> + <img src="images/close.png" ng-click="deleteEnvItem($index)" class="delete-img" /> + </div> + + + + </div> + </div> + <button class="btn btn-default" ng-click="submitOpenRcFile();" style="margin-bottom:20px;"> + <div ng-if="!showloading">Submit</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + + </div> + + </div> + <div data-title="Upload File" bs-pane> + <div style="margin-top:20px;height:405px;"> + <button class="btn btn-default" style="margin-bottom:20px;" ngf-select="uploadFiles($file, $invalidFiles);" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + <!--<button class="btn btn-default" style="margin-bottom:20px;" ng-disabled="showNextOpenRc==null" ng-click="goToImage()"> + Next + </button>--> + + <!--<div ng-if="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + </div> + </div> + </div> + + + + </div> + + + </div> + + <div ng-if="showImage==1&&showPod==null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{name}} -- Image + + <button class="btn btn-default" ng-click="goToPod()" ng-disabled="showNextPod==null" style="float:right"> + Next + </button> + <button class="btn btn-default" ng-click="goToPodPrev()" style="margin-right:5px;float:right"> + Back + </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&&showImageStatus==1">done</i> + <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==0&&showImageStatus==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&&showImageStatus==1">error</i> + + + <!--<button class="btn btn-default" ng-click="goToPod()" ng-disabled="showNextPod==null"> + Next + </button>--> + <hr> + <h4>Current Images</h4> + + <div> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>size</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> + + + + </table> + </div> + + + </div> + + + </div> + </div> + + <div ng-if="showPod==1&&showContainer==null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + + <h3>{{name}} -- Pod File + <div style="float:right"> + <button class="btn btn-default" ng-click="skipPodPrev()">Back</button> + <button class="btn btn-default" ng-click="skipPod()" ng-show="podData==null">Skip</button> + <button class="btn btn-default" ng-click="skipPod()" ng-show="podData!=null">Next</button> + + </div> + + </h3> + + <hr/> + + <button class="btn btn-default" ngf-select="uploadFilesPod($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + + <hr/> + + <div> + <h4>Current Pod Configuration</h4> + <table class="table table-striped"> + + <tr> + <th>ip</th> + <th>name</th> + <th>password</th> + <th>role</th> + <th>user</th> + </tr> + <tr ng-repeat="pod in podData.pod.nodes"> + <td>{{pod.ip}}</td> + <td>{{pod.name}}</td> + <td>{{pod.password}}</td> + <td>{{pod.role}}</td> + <td>{{pod.user}}</td> + + </tr> + <tr ng-show="podData.length==0"> + <td>no data</td> + + </tr> + + + + </table> + </div> + + + + + + + + + + + </div> + + + </div> + + </div> + + <div ng-if="showContainer!=null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{name}} -- Container + <div style="float:right"> + <button class="btn btn-default" ng-click="skipContainerPrev()">Back</button> + <button class="btn btn-default" ng-click="skipContainer()" ng-show="ifskipOrClose!=1"> + Skip + </button> + <button class="btn btn-default" ng-click="closeThisDialog(); getEnvironmentList();" ng-show="ifskipOrClose==1"> + Close + </button> + </div> + <!--<button class="btn btn-default" style="float:right">Go Next</button>--> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <select ng-model="selectContainer" data-ng-options="container as container.name for container in containerList"> + <option value="">Choose...</option> + </select> + + <button class="btn btn-default" ng-click="createContainer(selectContainer)" ng-disabled="selectContainer==null"> + <div ng-show="!showloading">Create</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + <!--<button class="btn btn-default" ng-click="skipContainer()" ng-show="ifskipOrClose!=1"> + Skip + </button> + <button class="btn btn-default" ng-click="closeThisDialog(); getEnvironmentList();" ng-show="ifskipOrClose==1"> + Close + </button>--> + + <hr/> + + <div> + <h4>Current Contain</h4> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>status</th> + <th>time</th> + + </tr> + <tr ng-repeat="con in displayContainerInfo"> + <td>{{con.name}}</td> + <td>{{con.status}}</td> + <td>{{con.time}}</td> + + + </tr> + + + + </table> + </div> + + + + + + + + + + + </div> + + + </div> + + </div> + + + + + + +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style> diff --git a/gui/app/views/modal/projectCreate.html b/gui/app/views/modal/projectCreate.html new file mode 100644 index 000000000..74839e798 --- /dev/null +++ b/gui/app/views/modal/projectCreate.html @@ -0,0 +1,21 @@ +<div> + + <h4>Enter Project Name</h4> + <input type="text" ng-model="name" /> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled=" name==null || name==''" ng-click="createName(name)">Create</button> + </div> + + + +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } +</style> diff --git a/gui/app/views/modal/suiteName.html b/gui/app/views/modal/suiteName.html new file mode 100644 index 000000000..981d24210 --- /dev/null +++ b/gui/app/views/modal/suiteName.html @@ -0,0 +1,18 @@ +<h4>Enter Suite Name</h4> +<hr/> You have choose: +<div ng-repeat="selected in suitReconstructList">{{selected}}</div> +<hr/> +<input type="text" ng-model="name" /> + +<div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled="testsuiteList.length==0 || name==null || name==''" ng-click="createSuite(name)">Create</button> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } +</style> diff --git a/gui/app/views/modal/taskCreate.html b/gui/app/views/modal/taskCreate.html new file mode 100644 index 000000000..e7812cf2b --- /dev/null +++ b/gui/app/views/modal/taskCreate.html @@ -0,0 +1,134 @@ + +<h4>Create Task</h4> +<hr/> +<div> + <div style="display:inline">Name <input type="text" ng-model="name" style="width:200px" /></div> + <button style="display:inline" class="btn btn-default" ng-disabled="name==null || name==''" ng-click="createTask(name)" ng-show="newUUID==null">Create</button> +</div> +<hr/> + +<div bs-tabs ng-show="newUUID!=null"> + <div data-title="Environment" bs-pane> + <div style="margin-top:10px" ng-show="displayEnvName!=null"> + <div style="display:inline">Choose Environment : {{displayEnvName}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="addEnvToTask()">confirm</button> + </div> + <hr /> + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{env.name}}</div> + <!--<button class="btn btn-default btn-sm" ng-click="gotoDetail('false',env.uuid)">detail</button>--> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv==env.uuid" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv!=env.uuid" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + <div data-title="Content" bs-pane> + <div style="display:flex;flex-direction:row"> + <div style="margin-top:20px;">Source of Content</div> + + + <select ng-model="selectType" ng-change="triggerContent(selectType)" data-ng-options="blisterPackTemplate as blisterPackTemplate.name for blisterPackTemplate in blisterPackTemplates"> + <option value="">Choose...</option> + </select> + + </div> + <div style="margin-top:10px" ng-show="selectCase!=null"> + <div style="display:inline">Choose Source: {{selectCase}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="confirmAddCaseOrSuite(contentInfo)">Confirm</button> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="getTestDeatil()">Edit</button> + </div> + <hr/> + + <div ng-show="displayTable==true"> + <div ng-show="testcaselist.testcases.length!=0 && selectType.name=='Test Case'"> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10" pagination-id="testcase"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{test.Name}}</div> + <div style="font-size:10px;">{{test.Description}}</div> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase==test.Name" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase!=test.Name" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testcase"></dir-pagination-controls> + </center> + </div> + + <div ng-show="testsuitlist.length!=0 && selectType.name=='Test Suite'"> + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10" pagination-id="testsuite"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{suite}}</div> + + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase==suite" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase!=suite" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testsuite"></dir-pagination-controls> + </center> + </div> + </div> + + <div ng-show="displayTable==false"> + <textarea ng-model="contentInfo" spellcheck="false"> + + + </textarea> + + + </div> + + + + + </div> +</div> + +<div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-click="closeThisDialog()" ng-disabled="newUUID===null || ifHasEnv!=true || (ifHasCase!=true && ifHasSuite!=true)">Close</button> + <button class="btn btn-default" ng-disabled="newUUID===null || ifHasEnv!=true || (ifHasCase!=true && ifHasSuite!=true)" ng-click="runAtask(newUUID)">Run</button> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + .deepColor { + background-color: #f9f9f9; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 400px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .deepColor { + background-color: #f9f9f9; + } +</style> diff --git a/gui/app/views/podupload.html b/gui/app/views/podupload.html new file mode 100644 index 000000000..99e83aca2 --- /dev/null +++ b/gui/app/views/podupload.html @@ -0,0 +1,136 @@ +<!--pod file upload--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + <!--<i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i>--> + + + <h3>{{name}} -- Pod File + <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" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + <button class="btn btn-default" ng-click="openDeleteEnv(1,'pod')">Delete</button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined ||podData.pod.nodes!=null "> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + + <hr/> + + <div> + <h4 ng-show="podData.pod.nodes==null">No Pod Configuration</h4> + <div ng-show="podData.pod.nodes!=null"> + <h4>Current Pod Configuration</h4> + <table class="table table-striped"> + + <tr> + <th>ip</th> + <th>name</th> + <th>password</th> + <th>role</th> + <th>user</th> + </tr> + <tr ng-repeat="pod in podData.pod.nodes"> + <td>{{pod.ip}}</td> + <td>{{pod.name}}</td> + <td>{{pod.password}}</td> + <td>{{pod.role}}</td> + <td>{{pod.user}}</td> + + </tr> + + + + </table> + </div> + </div> + + + + + + + + + + + </div> + <!--<div style="margin-top:60px;margin-left:67px;"> + <h3>Openrc parameters</h3> + <div> + You have already set up the openrc parameters + </div> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:15px;">{{key}} : </font> + <font style="font-size:15px;">{{value}}</font> + </nobr> + </div> + </div>--> + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } +</style> diff --git a/gui/app/views/projectList.html b/gui/app/views/projectList.html new file mode 100644 index 000000000..6edc32fc1 --- /dev/null +++ b/gui/app/views/projectList.html @@ -0,0 +1,57 @@ +<div class="content"> + + <h3>Projects + <button class="btn btn-default btn-sm" style="margin-left:30px;" ng-click="openCreateProject()">Create</button> + </h3> + + <hr/> + + + + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color:#f9f9f9;"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:20px;">Action</div> + + </div> + + <div dir-paginate="project in projectListData | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + <a ng-click="gotoDetail(project.uuid)" style="color:#4dc5cf"> {{project.name}}</a> + </div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="gotoDetail(project.uuid)">Detail</button> --> + <!--<button class="btn btn-default btn-sm" ng-click="openDeleteEnv(project.uuid,'project')">Delete</button>--> + <div class="btn-group" uib-dropdown is-open="status.isopen" > + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + delete <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ng-show="task.status!=0"><a ng-click="openDeleteEnv(project.uuid,'project')">delete</a></li> + + + </ul> + </div> + </div> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + + +</div> + +<toaster-container></toaster-container> + + +<style> + .deepColor { + background-color: #f9f9f9; + } +</style> diff --git a/gui/app/views/projectdetail.html b/gui/app/views/projectdetail.html new file mode 100644 index 000000000..357a26add --- /dev/null +++ b/gui/app/views/projectdetail.html @@ -0,0 +1,97 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h3>Project -- Task + + </h3> + + <hr/> + + <div> + + <h4>{{projectData.name}}</h4> + <h5>{{projectData.time}}</h5> + <hr/> + <h4>Tasks + <button class="btn btn-default btn-sm" style="margin-left:30px;" ng-click="openCreate()">Create</button> + </h4> + <div ng-show="projectData.tasks.length==0">No task in this project</div> + <table class="table " width="100%" dw-loading="key" dw-loading-options="{text:'loading'}"> + <tr style="background-color:#f9f9f9"> + <td style="font-weight:700">Name</td> + <td style="font-weight:700"> Status</td> + <td style="font-weight:700">Action</td> + </tr> + <tr dir-paginate="task in finalTaskListDisplay | orderBy:'-id' | itemsPerPage: 6 " pagination-id="table"> + + <td width="20%"> <a ng-click="gotoDetail(task.uuid)" style="color:#4dc5cf"> {{task.name}} </a></td> + <td width="70%"> + <div class="progree-parent" ng-show="task.status!=2"> + <div class="progree-child" ng-style="{'width':task.stausWidth}"> + </div> + </div> + <div class="progree-parent" ng-show="task.status==2" style="background-color:red"> + <div class="progree-child" style="width:0"> + </div> + </div> + </td> + + + <td width="10%"> + + <div class="btn-group" uib-dropdown is-open="status.isopen"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + modify <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ng-show="task.status!=0"><a ng-click="runAtaskForTable(task.uuid)">run</a></li> + + <li role="menuitem" ng-show="task.status!=0"><a ng-click="gotoModify(task.uuid)">modify</a></li> + <li role="menuitem" ng-show="task.status!=-1 && task.status!=0"><a ng-click="gotoReport(task.uuid)" style="color:#2ecc71">reporting</a></li> + <li role="menuitem"><a ng-click="openDeleteEnv(task.uuid,'task')">delete</a></li> + + + </ul> + </div> + <!-- <button class="btn btn-default btn-sm" ng-click="runAtask(task.uuid)" ng-disabled="task.status!=-1">run</button> + <button class="btn btn-default btn-sm" ng-click="gotoDetail(task.uuid)">detail</button> + <button class="btn btn-default btn-sm" ng-click="gotoModify(task.uuid)" ng-disabled="task.status==0">modify</button> + <button class="btn btn-default btn-sm" ng-click="gotoReport(task.uuid)" style="color:#2ecc71" ng-disabled="task.status==-1 || task.status==0">reporting</button> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(task.uuid,'task')">delete</button> --> + + </td> + + </tr> + </table> + + + + </div> + <center> + <dir-pagination-controls pagination-id="table"></dir-pagination-controls> + </center> + +</div> + + + +</div> + +<toaster-container></toaster-container> + +<style> + .progree-parent { + width: 50%; + background-color: #dfe3e4; + height: 10px; + border-radius: 10px; + } + + .progree-child { + width: 50%; + background-color: #2ecc71; + /* background-color: white; */ + height: 10px; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/report.html b/gui/app/views/report.html new file mode 100644 index 000000000..78ac6a0c9 --- /dev/null +++ b/gui/app/views/report.html @@ -0,0 +1,56 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h3>Yardstick Report </h3> + <hr/> + <div> + + <div>Task ID : {{result.result.task_id}} </div> + <div style="margin-top:5px;">Criteria : + <font style="color:#2ECC71" ng-show="result.result.criteria=='PASS'"> {{result.result.criteria}}</font> + <font style="color:red" ng-show="result.result.criteria=='FAIL'"> {{result.result.criteria}}</font> + </div> + <hr/> + <caption>Information</caption> + <table class="table table-striped"> + <tr> + <th>#</th> + <th>key</th> + <th>value</th> + </tr> + <tbody> + <tr ng-repeat="(key,value) in result.result.info"> + <td>{{$index}}</td> + <td>{{key}}</td> + <td>{{value}}</td> + </tr> + + </tbody> + </table> + <hr/> + + <caption>Test Cases</caption> + <table class="table table-striped"> + <tr> + <th>#</th> + <th>key</th> + + <th>value</th> + <th>grafana</th> + </tr> + <tbody> + <tr ng-repeat="(key,value) in result.result.testcases"> + <td>{{$index}}</td> + <td>{{key}}</td> + + <td>{{value.criteria}}</td> + <td> <button class="btn btn-default btn-sm" ng-click="goToExternal(key)"> grafana</button></td> + </tr> + </tbody> + </table> + + </div> +</div> + + + +</div> diff --git a/gui/app/views/suite.html b/gui/app/views/suite.html new file mode 100644 index 000000000..652cf1e0e --- /dev/null +++ b/gui/app/views/suite.html @@ -0,0 +1,154 @@ +<div class="content"> + <!--suitelist--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + Test Suites + <button class="btn btn-default" style="margin-left:20px;" ng-click="gotoCreateSuite()"> + Create + + </button> + + + <hr/> + + + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9;"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:20px;">Action</div> + + </div> + + + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + <a style="color:#4dc5cf" ng-click="gotoDetail(suite)"> {{suite}} + </a> + </div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(suite,'test suite')">Delete</button> --> + <div class="btn-group" uib-dropdown is-open="status.isopen"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + delete <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem"><a ng-click="openDeleteEnv(suite,'test suite')">delete</a></li> + + </ul> + </div> + + </div> + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .deepColor { + background-color: #f9f9f9; + } + + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style>
\ No newline at end of file diff --git a/gui/app/views/suitedetail.html b/gui/app/views/suitedetail.html new file mode 100644 index 000000000..6122f6560 --- /dev/null +++ b/gui/app/views/suitedetail.html @@ -0,0 +1,110 @@ +<div class="content"> + <!--testcaselist--> + <div> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h3>Detail</h3> + <hr/> + + <textarea ng-model="suiteinfo" spellcheck="false"> + + </textarea> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/taskList.html b/gui/app/views/taskList.html new file mode 100644 index 000000000..159fed5c9 --- /dev/null +++ b/gui/app/views/taskList.html @@ -0,0 +1,62 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h3>Detail</h3> + <hr/> + <div style="display:flex;flex-direction:row"> + <div> + <h4>{{taskDetailData.name}}</h4> + <div style="margin-top:5px;">{{taskDetailData.time}}</div> + </div> + <div class="progree-parent" ng-show="taskDetailData.status!=2" style="margin-top:34px;margin-left:30px;"> + <div class="progree-child" ng-style="{'width':taskDetailData.stausWidth}"> + </div> + + </div> + <div class="progree-parent" ng-show="taskDetailData.status==2" style="background-color:red;margin-top:34px;margin-left:30px;"> + <div class="progree-child" style="width:0"> + </div> + </div> + <i class="fa fa-check" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="taskDetailData.status==1">finish</i> + <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="taskDetailData.status==0">runing</i> + <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: red;" ng-show="taskDetailData.status==2">failed</i> + </div> + + <div style="margin-top:5px;">Environment : {{displayEnv.name}} </div> + <div ng-show="taskDetailData.case_name!=false" style="margin-top:5px;margin-bottom:5px;"> Name : {{taskDetailData.case_name}}</div> + <textarea ng-model="taskDetailData.content" spellcheck="false"> + + </textarea> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-click="createTask(name)" ng-show="">Run</button> + </div> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 350px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .content { + height: 90%; + } +</style> diff --git a/gui/app/views/taskmodify.html b/gui/app/views/taskmodify.html new file mode 100644 index 000000000..a4593f745 --- /dev/null +++ b/gui/app/views/taskmodify.html @@ -0,0 +1,162 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h4>Modify </h4> + + <hr/> + + <div> + <div style="display:inline">Name <input type="text" ng-model="taskDetailData.name" style="width:200px" /></div> + + <button class="btn btn-default" ng-click="runAtask()" style="float:right;margin-right:10px;">Run</button> + </div> + <hr/> + + <div bs-tabs> + <div data-title="Environment" bs-pane> + <div style="margin-top:10px"> + <div style="display:inline">Choose Environment : {{envName}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="addEnvToTask()">Confirm</button> + </div> + <hr /> + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{env.name}}</div> + <!--<button class="btn btn-default btn-sm" ng-click="gotoDetail('false',env.uuid)">detail</button>--> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv==env.uuid" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv!=env.uuid" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + <div data-title="Content" bs-pane> + <div style="margin-top:10px;"> + <button class="btn btn-default" ng-click="changeStatussourceFalse()">Modify Content</button> + <button class="btn btn-default" ng-click="changeStatussourceTrue()">Modify Source</button> + <div class="label-type" ng-show="taskDetailData.suite==false"> Test Case</div> + <div class="label-type" ng-show="taskDetailData.suite==true"> Test Suite</div> + <button class="btn btn-default" style="float:right" ng-disabled="sourceShow==null" ng-click="confirmToServer(contentInfo,taskDetailData.content)">Confirm</button> + </div> + + + <textarea ng-model="taskDetailData.content" ng-show="sourceShow==false" style="margin-top:5px;" spellcheck="false"> + + + </textarea> + + <div ng-show="sourceShow==true"> + <div style="display:flex;flex-direction:row"> + <div style="margin-top:20px;">Source of Content</div> + + + <select ng-model="selectType" ng-change="triggerContent(selectType)" data-ng-options="blisterPackTemplate as blisterPackTemplate.name for blisterPackTemplate in blisterPackTemplates"> + <option value="">Choose...</option> + </select> + + </div> + + <div style="margin-top:10px" ng-show="selectCase!=null "> + <div style="display:inline">Choose Source : {{selectCase}}</div> + <!--<button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="confirmAddCaseOrSuite(contentInfo)">Confirm</button>--> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="getTestDeatil()">Edit</button> + </div> + <hr/> + + <div ng-show="displayTable==true"> + <div ng-show="testcaselist.testcases.length!=0 && selectType.name=='Test Case'"> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10" pagination-id="testcase"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{test.Name}}</div> + <div style="font-size:10px;">{{test.Description}}</div> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase==test.Name" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase!=test.Name" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testcase"></dir-pagination-controls> + </center> + </div> + + <div ng-show="testsuitlist.length!=0 && selectType.name=='Test Suite'"> + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10" pagination-id="testsuite"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{suite}}</div> + + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase==suite" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase!=suite" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testsuite"></dir-pagination-controls> + </center> + </div> + </div> + + <div ng-show="displayTable==false"> + <textarea ng-model="contentInfo" spellcheck="false"> + </textarea> + + + </div> + </div> + + + + + </div> + </div> + + +</div> +<toaster-container></toaster-container> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + padding: 5px; + } + + .deepColor { + background-color: #f9f9f9; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 350px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .label-type { + display: inline; + background-color: #2ecc71; + color: #fff; + border-radius: 5px; + padding: 3px; + font-size: 10px; + } + + .content { + height: auto; + } +</style> diff --git a/gui/app/views/testcasechoose.html b/gui/app/views/testcasechoose.html new file mode 100644 index 000000000..12bdb834f --- /dev/null +++ b/gui/app/views/testcasechoose.html @@ -0,0 +1,48 @@ +<div class="content"> + + <div> + Test case list + <button class="btn btn-default" style="margin-left:20px;" ng-click="openDialog()"> + <div ng-show="!loadingOPENrc">Create </div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + + <hr/> You have choose : + <div ng-repeat="selected in suitReconstructList" style="display:inline;" class="item">{{selected}}</div> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;"> + <img src="images/checkyes.png" style="height:12px;cursor:pointer" ng-click="constructTestSuit(test.Name)" ng-show="testsuiteList.indexOf(test.Name)>-1" /> + <img src="images/checkno.png" style="height:12px;cursor:pointer" ng-click="constructTestSuit(test.Name)" ng-show="testsuiteList.indexOf(test.Name)==-1" /> + <div style="margin-left:50px;"> {{test.Name}}</div> + <div style="font-size:10px;margin-left:100px">{{test.Description}}</div> + + </div> + <hr style="margin-top:5px;margin-bottom:5px;" /> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + + + </div> + <toaster-container></toaster-container> + + <style> + .item { + background-color: #3498db; + color: #fff; + width: 150px; + border-radius: 5px; + padding-left: 10px; + margin-left: 2px; + margin-top: 3px; + padding: 4px; + } + </style> diff --git a/gui/app/views/testcasedetail.html b/gui/app/views/testcasedetail.html new file mode 100644 index 000000000..43a51537f --- /dev/null +++ b/gui/app/views/testcasedetail.html @@ -0,0 +1,110 @@ +<div class="content"> + <!--testcaselist--> + <div> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h4>Detail</h4> + <hr/> + + <textarea ng-model="testcaseInfo" spellcheck="false"> + + </textarea> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/testcaselist.html b/gui/app/views/testcaselist.html new file mode 100644 index 000000000..62237faa8 --- /dev/null +++ b/gui/app/views/testcaselist.html @@ -0,0 +1,158 @@ +<div class="content"> + <!--testcaselist--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + Test Cases + <button class="btn btn-default" style="margin-left:20px;" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:20px;">Action</div> + + </div> + + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + + <a style="color:#4dc5cf" ng-click="gotoDetail(test.Name)"> + {{test.Name}} + </a> + </div> + <div style="font-size:10px;">{{test.Description}}</div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(test.Name,'test case')">Delete</button> --> + <div class="btn-group" uib-dropdown is-open="status.isopen" > + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + delete <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem"><a ng-click="openDeleteEnv(test.Name,'test case')">delete</a></li> + + </ul> + </div> + </div> + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .deepColor { + background-color: #f9f9f9; + } + + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/uploadImage.html b/gui/app/views/uploadImage.html new file mode 100644 index 000000000..17ccfdb8b --- /dev/null +++ b/gui/app/views/uploadImage.html @@ -0,0 +1,145 @@ +<!--upload image page--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{baseElementInfo.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> + + <div> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>size</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> + + + + </table> + </div> + + + + + + + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/bower.json b/gui/bower.json new file mode 100644 index 000000000..d1d934f64 --- /dev/null +++ b/gui/bower.json @@ -0,0 +1,48 @@ +{ + "name": "yard-stick-gui2", + "version": "0.0.0", + "dependencies": { + "angular": "^1.4.0", + "bootstrap": "^3.2.0", + "angular-strap": "^2.3.12", + "angular-ui-router": "^1.0.3", + "angular-animate": "^1.6.4", + "angular-breadcrumb": "^0.5.0", + "angular-wizard": "^0.10.0", + "angular-resource": "^1.6.4", + "ng-file-upload": "^12.2.13", + "AngularJS-Toaster": "angularjs-toaster#^2.1.0", + "ng-dialog": "^1.3.0", + "angularUtils-pagination": "angular-utils-pagination#^0.11.1", + "components-font-awesome": "^4.7.0", + "ngstorage": "^0.3.11", + "v-accordion": "^1.6.0", + "angular-loading": "^0.1.4", + "angular-bootstrap": "^2.5.0", + "angular-sanitize": "^1.6.5" + }, + "resolutions": { + "angular": "~1.6.x" + }, + "devDependencies": { + "angular-mocks": "^1.4.0" + }, + "appPath": "app", + "moduleName": "yardStickGui2App", + "overrides": { + "bootstrap": { + "main": [ + "less/bootstrap.less", + "dist/css/bootstrap.css", + "dist/js/bootstrap.js" + ] + }, + "angular-loading": { + "main": [ + "angular-loading.css", + "angular-loading.js", + "../spin.js/spin.js" + ] + } + } +} diff --git a/gui/gui.sh b/gui/gui.sh new file mode 100755 index 000000000..12a14923e --- /dev/null +++ b/gui/gui.sh @@ -0,0 +1,8 @@ +apt-get install -y nodejs +apt-get install -y npm +ln -s /usr/bin/nodejs /usr/bin/node +npm install +npm install -g grunt +npm install -g bower +bower install --force --allow-root +grunt build diff --git a/gui/package.json b/gui/package.json new file mode 100644 index 000000000..b85c75469 --- /dev/null +++ b/gui/package.json @@ -0,0 +1,43 @@ +{ + "name": "yardstickgui2", + "private": true, + "devDependencies": { + "autoprefixer-core": "^5.2.1", + "grunt": "^0.4.5", + "grunt-angular-templates": "^0.5.7", + "grunt-concurrent": "^1.0.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-connect": "^0.9.0", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-cssmin": "^0.12.0", + "grunt-contrib-htmlmin": "^0.4.0", + "grunt-contrib-imagemin": "^1.0.0", + "grunt-contrib-jshint": "^0.11.0", + "grunt-contrib-uglify": "^0.7.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-filerev": "^2.1.2", + "grunt-google-cdn": "^0.4.3", + "grunt-jscs": "^1.8.0", + "grunt-newer": "^1.1.0", + "grunt-ng-annotate": "^0.9.2", + "grunt-postcss": "^0.5.5", + "grunt-svgmin": "^2.0.0", + "grunt-usemin": "^3.0.0", + "grunt-wiredep": "^2.0.0", + "jasmine-core": "^2.6.2", + "jit-grunt": "^0.9.1", + "jshint-stylish": "^1.0.0", + "karma": "^1.7.0", + "karma-jasmine": "^1.1.0", + "karma-phantomjs-launcher": "^1.0.4", + "phantomjs-prebuilt": "^2.1.14", + "time-grunt": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "karma start test\\karma.conf.js" + } +} diff --git a/gui/test/.jshintrc b/gui/test/.jshintrc new file mode 100644 index 000000000..b2ce4eff4 --- /dev/null +++ b/gui/test/.jshintrc @@ -0,0 +1,18 @@ +{ + "bitwise": true, + "browser": true, + "curly": true, + "eqeqeq": true, + "esnext": true, + "jasmine": true, + "latedef": true, + "noarg": true, + "node": true, + "strict": true, + "undef": true, + "unused": true, + "globals": { + "angular": false, + "inject": false + } +} diff --git a/gui/test/karma.conf.js b/gui/test/karma.conf.js new file mode 100644 index 000000000..a9ab3a824 --- /dev/null +++ b/gui/test/karma.conf.js @@ -0,0 +1,93 @@ +// Karma configuration +// Generated on 2017-05-31 + +module.exports = function(config) { + 'use strict'; + + config.set({ + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // base path, that will be used to resolve files and exclude + basePath: '../', + + // testing framework to use (jasmine/mocha/qunit/...) + // as well as any additional frameworks (requirejs/chai/sinon/...) + frameworks: [ + 'jasmine' + ], + + // list of files / patterns to load in the browser + files: [ + // bower:js + 'bower_components/jquery/dist/jquery.js', + 'bower_components/angular/angular.js', + 'bower_components/bootstrap/dist/js/bootstrap.js', + 'bower_components/angular-strap/dist/angular-strap.js', + 'bower_components/angular-strap/dist/angular-strap.tpl.js', + 'bower_components/angular-ui-router/release/angular-ui-router.js', + 'bower_components/angular-animate/angular-animate.js', + 'bower_components/angular-breadcrumb/release/angular-breadcrumb.js', + 'bower_components/angular-wizard/dist/angular-wizard.min.js', + 'bower_components/angular-resource/angular-resource.js', + 'bower_components/ng-file-upload/ng-file-upload.js', + 'bower_components/AngularJS-Toaster/toaster.js', + 'bower_components/ng-dialog/js/ngDialog.js', + 'bower_components/angularUtils-pagination/dirPagination.js', + 'bower_components/ngstorage/ngStorage.js', + 'bower_components/v-accordion/dist/v-accordion.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-loading/angular-loading.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'bower_components/angular-sanitize/angular-sanitize.js', + 'bower_components/angular-mocks/angular-mocks.js', + // endbower + 'app/scripts/**/*.js', + 'test/mock/**/*.js', + 'test/spec/**/*.js' + ], + + // list of files / patterns to exclude + exclude: [ + ], + + // web server port + port: 8080, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: [ + 'PhantomJS' + ], + + // Which plugins to enable + plugins: [ + 'karma-phantomjs-launcher', + 'karma-jasmine' + ], + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false, + + colors: true, + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, + + // Uncomment the following lines if you are using grunt's server to run the tests + // proxies: { + // '/': 'http://localhost:9000/' + // }, + // URL root prevent conflicts with the site root + // urlRoot: '_karma_' + }); +}; diff --git a/gui/test/spec/controllers/main.js b/gui/test/spec/controllers/main.js new file mode 100644 index 000000000..27e0a5ad3 --- /dev/null +++ b/gui/test/spec/controllers/main.js @@ -0,0 +1,23 @@ +'use strict'; + +describe('Controller: MainCtrl', function () { + + // load the controller's module + beforeEach(module('yardStickGui2App')); + + var MainCtrl, + scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + MainCtrl = $controller('MainCtrl', { + $scope: scope + // place here mocked dependencies + }); + })); + + it('should attach a list of awesomeThings to the scope', function () { + expect(MainCtrl.awesomeThings.length).toBe(3); + }); +}); diff --git a/install.sh b/install.sh index ad14b8e0b..8a5050a61 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,11 @@ easy_install -U pip pip install -r requirements.txt pip install -e . -/bin/bash "$(pwd)/api/api-prepare.sh" +/bin/bash "${PWD}/docker/uwsgi.sh" +/bin/bash "${PWD}/docker/nginx.sh" +cd "${PWD}/gui" && /bin/bash gui.sh +mkdir -p /etc/nginx/yardstick +mv dist /etc/nginx/yardstick/gui service nginx restart uwsgi -i /etc/yardstick/yardstick.ini diff --git a/requirements.txt b/requirements.txt index 3a4cbce0c..2bcc4dfa7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,6 +59,7 @@ oslo.utils==3.22.0 paramiko==2.1.1 pbr==1.10.0 pep8==1.7.0 +ping==0.2; python_version <= '2.7' pika==0.10.0 positional==1.1.1 prettytable==0.7.2 diff --git a/run_tests.sh b/run_tests.sh index 2519d94f6..2cf54c708 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -13,6 +13,9 @@ getopts ":f" FILE_OPTION +# don't write .pyc files this can cause odd unittest results +export PYTHONDONTWRITEBYTECODE=1 + run_flake8() { echo "Running flake8 ... " logfile=test_results.log diff --git a/samples/container_ping_vm.yaml b/samples/container_ping_vm.yaml new file mode 100644 index 000000000..4b7b64f68 --- /dev/null +++ b/samples/container_ping_vm.yaml @@ -0,0 +1,57 @@ +############################################################################## +# Copyright (c) 2017 Huawei AB and others. +# +# 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 +############################################################################## + +--- +# Sample benchmark task config file +# measure network latency using ping in container + +schema: "yardstick:task:0.1" + +scenarios: +- + type: Ping + options: + packetsize: 200 + + host: host-k8s + target: target.openstack + + runner: + type: Duration + duration: 60 + interval: 1 + + sla: + max_rtt: 10 + action: monitor + +contexts: +- + type: Kubernetes + name: k8s + + servers: + host: + image: openretriever/yardstick + command: /bin/bash + args: ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;while true ; do sleep 10000; done'] +- + type: Heat + name: openstack + image: cirros-0.3.5 + flavor: yardstick-flavor + user: cirros + + servers: + target: + floating_ip: true + + networks: + test: + cidr: '10.0.1.0/24' diff --git a/samples/fio_volume.yaml b/samples/fio_volume.yaml new file mode 100644 index 000000000..edb3837e9 --- /dev/null +++ b/samples/fio_volume.yaml @@ -0,0 +1,74 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +--- +# Sample benchmark task config file +# measure storage performance using fio +# +# For this sample just like running the command below on the test vm and +# getting benchmark info back to the yardstick. +# +# sudo fio -filename=/home/ubuntu/data.raw -bs=4k -ipdepth=1 -rw=rw \ +# -ramp_time=10 -runtime=60 -name=yardstick-fio -ioengine=libaio \ +# -direct=1 -group_reporting -numjobs=1 -time_based \ +# --output-format=json + +schema: "yardstick:task:0.1" + +{% set rw = rw or "randrw" %} +{% set bs = bs or "8k" %} +{% set size = size or "100g" %} +{% set rwmixwrite = rwmixwrite or "50" %} +{% set numjobs = numjobs or "1" %} +{% set direct = direct or "1" %} + +scenarios: +- + type: Fio + options: + filename: /dev/vdb + bs: {{bs}} + rw: {{rw}} + size: {{size}} + rwmixwrite: {{rwmixwrite}} + numjobs: {{numjobs}} + direct: {{direct}} + ramp_time: 10 + + host: fio.fio_volume + + runner: + type: Duration + duration: 60 + interval: 1 + + sla: + read_bw: 6000 + read_iops: 1500 + read_lat: 500.1 + write_bw: 6000 + write_iops: 1500 + write_lat: 500.1 + action: monitor + +context: + name: fio_volume + image: yardstick-image + flavor: yardstick-flavor + user: ubuntu + servers: + fio: + volume: + name: fio-volume + size: 200 + volume_mountpoint: "/dev/vdb" + floating_ip: true + networks: + test: + cidr: "10.0.1.0/24" + port_security_enabled: true diff --git a/samples/migrate-node-context.yaml b/samples/migrate-node-context.yaml new file mode 100644 index 000000000..9fe1acf78 --- /dev/null +++ b/samples/migrate-node-context.yaml @@ -0,0 +1,40 @@ +--- + +schema: "yardstick:task:0.1" + +scenarios: +- + type: QemuMigrate + options: + smp: 2 + migrate_to_port: 4444 + incoming_ip: 0 + qmp_src_path: "/tmp/qmp-sock-src" + qmp_dst_path: "/tmp/qmp-sock-dst" + max_down_time: "0.10" + host: kvm.LF + runner: + type: Duration + duration: 1 + interval: 1 + sla: + max_totaltime: 10 + max_downtime: 0.10 + max_setuptime: 0.50 + action: monitor + setup_options: + rpm_dir: "/opt/rpm" + script_dir: "/opt/scripts" + image_dir: "/opt/image" + host_setup_seqs: + - "host-setup0.sh" + - "reboot" + - "host-setup1.sh" + - "setup-ovsdpdk.sh" + - "host-install-qemu.sh" + - "host-run-qemu4lm.sh" + +context: + type: Node + name: LF + file: /root/yardstick/pod.yaml diff --git a/samples/ping.yaml b/samples/ping.yaml index 0c1783c0b..6a19d260b 100644 --- a/samples/ping.yaml +++ b/samples/ping.yaml @@ -12,6 +12,9 @@ schema: "yardstick:task:0.1" +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} scenarios: - type: Ping @@ -49,4 +52,10 @@ context: networks: test: cidr: '10.0.1.0/24' - + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/ci/cover.sh b/tests/ci/cover.sh index 71833757a..822ed2ff2 100644 --- a/tests/ci/cover.sh +++ b/tests/ci/cover.sh @@ -34,7 +34,10 @@ run_coverage_test() { git checkout HEAD^ baseline_report=$(mktemp -t yardstick_coverageXXXXXXX) - find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*" + # workaround 'db type could not be determined' bug + # https://bugs.launchpad.net/testrepository/+bug/1229445 + rm -rf .testrepository + find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --slowest --testr-args="$*" coverage report > $baseline_report baseline_missing=$(awk 'END { print $3 }' $baseline_report) @@ -44,7 +47,10 @@ run_coverage_test() { # Generate and save coverage report current_report=$(mktemp -t yardstick_coverageXXXXXXX) - find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*" + # workaround 'db type could not be determined' bug + # https://bugs.launchpad.net/testrepository/+bug/1229445 + rm -rf .testrepository + find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --slowest --testr-args="$*" coverage report > $current_report current_missing=$(awk 'END { print $3 }' $current_report) diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc001.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc001.yaml index 57f9e958a..4faa0bc5a 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc001.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc001.yaml @@ -15,6 +15,10 @@ description: > Different amounts of flows are tested with, from 2 up to 1001000; All tests are run twice. First twice with the least amount of ports and further on. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: {% for num_ports in [1, 10, 50, 100, 500, 1000] %} - @@ -58,3 +62,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc002.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc002.yaml index 1de573d83..58f5b783a 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc002.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc002.yaml @@ -14,6 +14,11 @@ description: > measure network latency using ping; {% set image = image or "cirros-0.3.5" %} + +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: {% for i in range(2) %} - @@ -53,3 +58,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc005.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc005.yaml index e77fd50a5..101c4210e 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc005.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc005.yaml @@ -13,6 +13,10 @@ description: > Yardstick TC005 config file; Measure Storage IOPS, throughput and latency using fio. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: {% for rw in ['read', 'write', 'randwrite', 'randread', 'rw'] %} {% for bs in ['4k', '64k', '1024k'] %} @@ -56,3 +60,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml index f5ccb255a..22e576015 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml @@ -20,6 +20,10 @@ description: > packet size, and so on. The test sequence continues with the next packet size, with same ports/flows sequence as before. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: {% for pkt_size in [64, 128, 256, 512, 1024, 1280, 1518] %} {% for num_ports in [1, 10, 50, 100, 500, 1000] %} @@ -74,6 +78,13 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} #test-sriov: #cidr: '10.0.1.0/24' #provider: "sriov" diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc009.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc009.yaml index c4e24c499..3c5f72d5a 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc009.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc009.yaml @@ -17,6 +17,10 @@ description: > amount of ports, then 10 times with the next amount of ports, and so on until all packet sizes have been run with; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: {% for num_ports in [1, 10, 50, 100, 500, 1000] %} - @@ -60,3 +64,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc010.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc010.yaml index 2ef3c54fb..cf9706847 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc010.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc010.yaml @@ -13,6 +13,10 @@ description: > Yardstick TC010 config file; measure memory read latency using lmbench. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: Lmbench @@ -45,3 +49,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc011.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc011.yaml index b826a7d5e..eef1a7a62 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc011.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc011.yaml @@ -13,12 +13,18 @@ description: > Yardstick TC011 config file; Measure packet delay variation (jitter) using iperf3. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: Iperf3 options: udp: udp bandwidth: 20m + length: 8K + window: 29200 host: zeus.demo target: hera.demo @@ -51,3 +57,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc012.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc012.yaml index f995b2b52..b8b208f12 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc012.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc012.yaml @@ -13,6 +13,10 @@ description: > Yardstick TC012 config file; Measure memory read and write bandwidth using lmbench. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: Lmbench @@ -46,5 +50,11 @@ context: networks: test: cidr: '10.0.1.0/24' - + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc014.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc014.yaml index dd686a6b4..bd0fe3627 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc014.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc014.yaml @@ -13,6 +13,10 @@ description: > Yardstick TC014 config file; Measure Processing speed using unixbench. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: UnixBench @@ -39,3 +43,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml new file mode 100644 index 000000000..f2cad4cc8 --- /dev/null +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml @@ -0,0 +1,187 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +--- + +schema: "yardstick:task:0.1" + +{% set file = file or "etc/yardstick/nodes/compass_sclab_virtual/pod.yaml" %} +{% set cpu_set = cpu_set or "0,1,2,3" %} +{% set memory_load = memory_load or 0 %} + +{% set flavor = flavor or "yardstick-migrate-flavor" %} +{% set ram = ram or "2048" %} +{% set vcpus = vcpus or "2" %} +{% set disk = disk or "3" %} + +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + +scenarios: +- + type: GetServer + + output: status server + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetNumaInfo + + options: + server: $server + file: {{ file }} + + output: origin_numa_info + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetMigrateTargetHost + + options: + server: $server + output: target_host + + runner: + type: Iteration + iteration: 1 +- + type: GetServerIp + + options: + server: $server + + output: server_ip + + runner: + type: Iteration + iteration: 1 +- + type: AddMemoryLoad + + options: + memory_load: {{ memory_load }} + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: Migrate + + options: + server: $server + host: $target_host + server_ip: $server_ip + + output: status migrate_time1 downtime1 + + runner: + type: Iteration + iteration: 1 +- + type: CheckValue + + options: + value1: $status + value2: 0 + operator: eq + + runner: + type: Iteration + iteration: 1 +- + type: GetServer + + output: status server + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetNumaInfo + + options: + server: $server + file: {{ file }} + + output: new_numa_info + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: CheckNumaInfo + + options: + info1: $origin_numa_info + info2: $new_numa_info + cpu_set: {{ cpu_set }} + + output: status + + runner: + type: Iteration + iteration: 1 +- + type: CheckValue + + options: + value1: $status + value2: true + operator: eq + + runner: + type: Iteration + iteration: 1 + + +contexts: +- + type: Node + name: env-prepare + file: {{ file }} + + env: + type: ansible + setup: migrate_pinning_setup.yaml -e "flavor={{ flavor }} ram={{ ram }} vcpus={{ vcpus }} disk={{ disk }} cpu_set={{ cpu_set }}" + teardown: migrate_pinning_teardown.yaml -e "flavor={{ flavor }}" + +- + name: migrate + image: yardstick-image + flavor: {{ flavor }} + user: ubuntu + + servers: + server: + floating_ip: true + + networks: + test: + cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml index 6a64f0be8..3622b40d7 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml @@ -19,6 +19,10 @@ description: > During the measurements system load and network latency are recorded/measured using ping and mpstat, respectively; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: CPUload @@ -91,3 +95,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc038.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc038.yaml index ba0f2f298..59fb95d07 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc038.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc038.yaml @@ -19,6 +19,10 @@ description: > During the measurements system load and network latency are recorded/measured using ping and mpstat, respectively; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: CPUload @@ -91,3 +95,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc069.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc069.yaml index c55639a04..2a4082310 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc069.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc069.yaml @@ -13,6 +13,10 @@ description: > Yardstick TC069 config file; Measure memory read and write bandwidth using ramspeed. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: Ramspeed @@ -45,3 +49,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc070.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc070.yaml index f9d57c6c8..7ea10d8a4 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc070.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc070.yaml @@ -19,6 +19,10 @@ description: > During the measurements memory usage statistics and network latency are recorded/measured using free and ping, respectively; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: MEMORYload @@ -93,3 +97,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc071.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc071.yaml index 0911d8e68..b6a944bbb 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc071.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc071.yaml @@ -19,6 +19,10 @@ description: > During the measurements cache hit/miss ration, cache usage statistics and network latency are recorded/measured using cachestat and ping, respectively; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: CACHEstat @@ -91,3 +95,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %}
\ No newline at end of file diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc072.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc072.yaml index ca3198448..09930d442 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc072.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc072.yaml @@ -19,6 +19,10 @@ description: > During the measurements network usage statistics and network latency are recorded/measured using sar and ping, respectively; +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: NetUtilization @@ -93,3 +97,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc076.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc076.yaml index c23ee97c2..8c0edac83 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc076.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc076.yaml @@ -8,6 +8,10 @@ description: > IP datagram error rate, ICMP message error rate, TCP segment error rate and UDP datagram error rate. +{% set provider = provider or none %} +{% set physical_network = physical_network or 'physnet1' %} +{% set segmentation_id = segmentation_id or none %} + scenarios: - type: Ping @@ -54,3 +58,10 @@ context: networks: test: cidr: '10.0.1.0/24' + {% if provider == "vlan" %} + provider: {{provider}} + physical_network: {{physical_network}} + {% if segmentation_id %} + segmentation_id: {{segmentation_id}} + {% endif %} + {% endif %} diff --git a/tests/opnfv/test_suites/opnfv_os-odl-sfc-ha_daily.yaml b/tests/opnfv/test_suites/opnfv_os-odl-sfc-ha_daily.yaml new file mode 100644 index 000000000..b464bfeae --- /dev/null +++ b/tests/opnfv/test_suites/opnfv_os-odl-sfc-ha_daily.yaml @@ -0,0 +1,62 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## +--- +# os-odl-sfc-ha daily task suite + +schema: "yardstick:suite:0.1" + +name: "os-odl-sfc-ha" +test_cases_dir: "tests/opnfv/test_cases/" +test_cases: +- + file_name: opnfv_yardstick_tc002.yaml +- + file_name: opnfv_yardstick_tc005.yaml +- + file_name: opnfv_yardstick_tc010.yaml +- + file_name: opnfv_yardstick_tc011.yaml +- + file_name: opnfv_yardstick_tc012.yaml +- + file_name: opnfv_yardstick_tc014.yaml +- + file_name: opnfv_yardstick_tc037.yaml +- + file_name: opnfv_yardstick_tc055.yaml + constraint: + installer: compass + pod: huawei-pod1 + task_args: + huawei-pod1: '{"file": "etc/yardstick/nodes/compass_sclab_physical/pod.yaml", + "host": "node5.yardstick-TC055"}' +- + file_name: opnfv_yardstick_tc063.yaml + constraint: + installer: compass + pod: huawei-pod1 + task_args: + huawei-pod1: '{"file": "etc/yardstick/nodes/compass_sclab_physical/pod.yaml", + "host": "node5.yardstick-TC063"}' +- + file_name: opnfv_yardstick_tc069.yaml +- + file_name: opnfv_yardstick_tc070.yaml +- + file_name: opnfv_yardstick_tc071.yaml +- + file_name: opnfv_yardstick_tc072.yaml +- + file_name: opnfv_yardstick_tc075.yaml + constraint: + installer: compass + pod: huawei-pod1 + task_args: + huawei-pod1: '{"file": "etc/yardstick/nodes/compass_sclab_physical/pod.yaml", + "host": "node1.LF"}' diff --git a/tests/unit/apiserver/resources/test_env_action.py b/tests/unit/apiserver/resources/test_env_action.py index d61092dbc..31afa4862 100644 --- a/tests/unit/apiserver/resources/test_env_action.py +++ b/tests/unit/apiserver/resources/test_env_action.py @@ -21,13 +21,13 @@ class EnvTestCase(APITestCase): data = {'action': 'create_grafana'} resp = self._post(url, data) - time.sleep(1) + time.sleep(0) task_id = resp['result']['task_id'] url = '/yardstick/asynctask?task_id={}'.format(task_id) resp = self._get(url) - time.sleep(2) + time.sleep(0) self.assertTrue(u'status' in resp) diff --git a/tests/unit/benchmark/contexts/test_model.py b/tests/unit/benchmark/contexts/test_model.py index 1ce550306..5444c2bc8 100644 --- a/tests/unit/benchmark/contexts/test_model.py +++ b/tests/unit/benchmark/contexts/test_model.py @@ -237,6 +237,7 @@ class ServerTestCase(unittest.TestCase): mock_network.name = 'some-network' mock_network.stack_name = 'some-network-stack' mock_network.allowed_address_pairs = ["1", "2"] + mock_network.vnic_type = 'normal' mock_network.subnet_stack_name = 'some-network-stack-subnet' mock_network.provider = 'sriov' mock_network.external_network = 'ext_net' @@ -249,6 +250,7 @@ class ServerTestCase(unittest.TestCase): 'some-server-some-network-port', mock_network.stack_name, mock_network.subnet_stack_name, + mock_network.vnic_type, sec_group_id=self.mock_context.secgroup_name, provider=mock_network.provider, allowed_address_pairs=mock_network.allowed_address_pairs) @@ -312,6 +314,7 @@ class ServerTestCase(unittest.TestCase): self.mock_context.flavors = ['flavor2'] mock_network = mock.Mock() mock_network.allowed_address_pairs = ["1", "2"] + mock_network.vnic_type = 'normal' mock_network.configure_mock(name='some-network', stack_name='some-network-stack', subnet_stack_name='some-network-stack-subnet', provider='some-provider') @@ -323,6 +326,7 @@ class ServerTestCase(unittest.TestCase): 'ServerFlavor-2-some-network-port', mock_network.stack_name, mock_network.subnet_stack_name, + mock_network.vnic_type, provider=mock_network.provider, sec_group_id=self.mock_context.secgroup_name, allowed_address_pairs=mock_network.allowed_address_pairs) diff --git a/tests/unit/benchmark/contexts/test_ovsdpdk.py b/tests/unit/benchmark/contexts/test_ovsdpdk.py index 125e475af..ac25ec877 100644 --- a/tests/unit/benchmark/contexts/test_ovsdpdk.py +++ b/tests/unit/benchmark/contexts/test_ovsdpdk.py @@ -18,7 +18,6 @@ import mock import unittest from yardstick.benchmark.contexts import ovsdpdk -from yardstick.benchmark.contexts.ovsdpdk import Ovsdpdk NIC_INPUT = { 'interface': {}, @@ -227,10 +226,8 @@ class OvsdpdkTestCase(unittest.TestCase): mock_ovs = mock.Mock() ssh_mock.put = mock.Mock() ovs_obj.check_output = mock.Mock(return_value=(0, "vm1")) - self.assertIsNone(ovs_obj.setup_ovs_context( - PCIS, - NIC_DETAILS, - DRIVER)) + with mock.patch("yardstick.benchmark.contexts.ovsdpdk.time"): + self.assertIsNone(ovs_obj.setup_ovs_context(PCIS, NIC_DETAILS, DRIVER)) @mock.patch( 'yardstick.benchmark.contexts.ovsdpdk', diff --git a/tests/unit/benchmark/contexts/test_sriov.py b/tests/unit/benchmark/contexts/test_sriov.py index e4d8f5e1a..a8641a2eb 100644 --- a/tests/unit/benchmark/contexts/test_sriov.py +++ b/tests/unit/benchmark/contexts/test_sriov.py @@ -185,8 +185,8 @@ class SriovTestCase(unittest.TestCase): nic_details['vf_pci'][i] = sriov_obj.get_vf_datas.return_value vf_pci = [[], []] vf_pci[i] = sriov_obj.get_vf_datas.return_value - self.assertIsNotNone( - sriov_obj.configure_nics_for_sriov(DRIVER, NIC_DETAILS)) + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNotNone(sriov_obj.configure_nics_for_sriov(DRIVER, NIC_DETAILS)) def test_setup_sriov_context(self): with mock.patch("yardstick.ssh.SSH") as ssh: @@ -224,8 +224,8 @@ class SriovTestCase(unittest.TestCase): mock.Mock(return_value=(0, {}, "")) ssh_mock.put = mock.Mock() sriov_obj.check_output = mock.Mock(return_value=(1, {})) - self.assertIsNone( - sriov_obj.setup_sriov_context(PCIS, nic_details, DRIVER)) + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNone(sriov_obj.setup_sriov_context(PCIS, nic_details, DRIVER)) def test_setup_sriov_context_vm_already_present(self): with mock.patch("yardstick.ssh.SSH") as ssh: @@ -263,10 +263,8 @@ class SriovTestCase(unittest.TestCase): mock.Mock(return_value=(0, {}, "")) ssh_mock.put = mock.Mock() sriov_obj.check_output = mock.Mock(return_value=(0, "vm1")) - self.assertIsNone(sriov_obj.setup_sriov_context( - PCIS, - nic_details, - DRIVER)) + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNone(sriov_obj.setup_sriov_context(PCIS, nic_details, DRIVER)) @mock.patch( 'yardstick.benchmark.contexts.sriov', diff --git a/tests/unit/benchmark/core/test_task.py b/tests/unit/benchmark/core/test_task.py index 8d6d963c3..7f617537e 100644 --- a/tests/unit/benchmark/core/test_task.py +++ b/tests/unit/benchmark/core/test_task.py @@ -47,6 +47,20 @@ class TaskTestCase(unittest.TestCase): self.assertEqual(context_cfg["host"], server_info) self.assertEqual(context_cfg["target"], server_info) + def test_set_dispatchers(self): + t = task.Task() + output_config = {"DEFAULT": {"dispatcher": "file, http"}} + t._set_dispatchers(output_config) + self.assertEqual(output_config, output_config) + + @mock.patch('yardstick.benchmark.core.task.DispatcherBase') + def test__do_output(self, mock_dispatcher): + t = task.Task() + output_config = {"DEFAULT": {"dispatcher": "file, http"}} + mock_dispatcher.get = mock.MagicMock(return_value=[mock.MagicMock(), + mock.MagicMock()]) + self.assertEqual(None, t._do_output(output_config, {})) + @mock.patch('yardstick.benchmark.core.task.Context') def test_parse_networks_from_nodes(self, mock_context): nodes = { diff --git a/tests/unit/benchmark/scenarios/compute/test_qemumigrate.py b/tests/unit/benchmark/scenarios/compute/test_qemumigrate.py new file mode 100644 index 000000000..9514729ba --- /dev/null +++ b/tests/unit/benchmark/scenarios/compute/test_qemumigrate.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and other. +# +# 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 +############################################################################## + +# Unittest for yardstick.benchmark.scenarios.compute.qemu_migrate.QemuMigrate + +from __future__ import absolute_import + +import unittest + +import mock +from oslo_serialization import jsonutils + +from yardstick.benchmark.scenarios.compute import qemu_migrate + + +@mock.patch('yardstick.benchmark.scenarios.compute.qemu_migrate.ssh') +class QemuMigrateTestCase(unittest.TestCase): + + def setUp(self): + self.scenario_cfg = { + "host": "kvm.LF", + "setup_options": { + "rpm_dir": "/opt/rpm", + "script_dir": "/opt/scripts", + "image_dir": "/opt/image", + "host_setup_seqs": [ + "host-setup0.sh", + "host-setup1.sh", + "setup-ovsdpdk.sh", + "host-install-qemu.sh", + "host-run-qemu4lm.sh" + ] + }, + "sla": { + "action": "monitor", + "max_totaltime": 10, + "max_downtime": 0.10, + "max_setuptime": 0.50 + }, + "options": { + "smp": 99, + "migrate_to_port": 4444, + "incoming_ip": 0, + "qmp_src_path": "/tmp/qmp-sock-src", + "qmp_dst_path": "/tmp/qmp-sock-dst", + "max_down_time": "0.10" + } + } + self.context_cfg = { + "host": { + "ip": "10.229.43.154", + "key_filename": "/yardstick/resources/files/yardstick_key", + "role": "BareMetal", + "name": "kvm.LF", + "user": "root" + } + } + + def test_qemu_migrate_successful_setup(self, mock_ssh): + + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + + q.setup() + self.assertIsNotNone(q.host) + self.assertEqual(q.setup_done, True) + + def test_qemu_migrate_successful_no_sla(self, mock_ssh): + result = {} + self.scenario_cfg.pop("sla", None) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + sample_output = '{"totaltime": 15, "downtime": 2, "setuptime": 1}' + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + + q.run(result) + expected_result = jsonutils.loads(sample_output) + self.assertEqual(result, expected_result) + + def test_qemu_migrate_successful_sla(self, mock_ssh): + result = {} + self.scenario_cfg.update({"sla": { + "action": "monitor", + "max_totaltime": 15, + "max_downtime": 2, + "max_setuptime": 1 + } + }) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + sample_output = '{"totaltime": 15, "downtime": 2, "setuptime": 1}' + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + + q.run(result) + expected_result = jsonutils.loads(sample_output) + self.assertEqual(result, expected_result) + + def test_qemu_migrate_unsuccessful_sla_totaltime(self, mock_ssh): + + result = {} + self.scenario_cfg.update({"sla": {"max_totaltime": 10}}) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + sample_output = '{"totaltime": 15, "downtime": 2, "setuptime": 1}' + + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + self.assertRaises(AssertionError, q.run, result) + + def test_qemu_migrate_unsuccessful_sla_downtime(self, mock_ssh): + + result = {} + self.scenario_cfg.update({"sla": {"max_downtime": 0.10}}) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + sample_output = '{"totaltime": 15, "downtime": 2, "setuptime": 1}' + + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + self.assertRaises(AssertionError, q.run, result) + + def test_qemu_migrate_unsuccessful_sla_setuptime(self, mock_ssh): + + result = {} + self.scenario_cfg.update({"sla": {"max_setuptime": 0.50}}) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + sample_output = '{"totaltime": 15, "downtime": 2, "setuptime": 1}' + + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + self.assertRaises(AssertionError, q.run, result) + + def test_qemu_migrate_unsuccessful_script_error(self, mock_ssh): + + result = {} + self.scenario_cfg.update({"sla": {"max_totaltime": 10}}) + q = qemu_migrate.QemuMigrate(self.scenario_cfg, self.context_cfg) + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + q.setup() + + + mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR') + self.assertRaises(RuntimeError, q.run, result) + + +def main(): + unittest.main() + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/__init__.py b/tests/unit/benchmark/scenarios/lib/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/__init__.py diff --git a/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py b/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py new file mode 100644 index 000000000..bda07f723 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py @@ -0,0 +1,65 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest +import mock + +from yardstick.benchmark.scenarios.lib.add_memory_load import AddMemoryLoad + + +class AddMemoryLoadTestCase(unittest.TestCase): + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_with_load(self, mock_from_node): + scenario_cfg = { + 'options': { + 'memory_load': 0.5 + } + } + context_cfg = { + 'host': {} + } + mock_from_node().execute.return_value = (0, '0 2048 512', '') + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_without_load(self, mock_from_node): + scenario_cfg = { + 'options': { + 'memory_load': 0 + } + } + context_cfg = { + 'host': {} + } + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_without_args(self, mock_from_node): + scenario_cfg = { + 'options': { + } + } + context_cfg = { + 'host': {} + } + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py b/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py new file mode 100644 index 000000000..bdf1e66e5 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py @@ -0,0 +1,84 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest +import mock + +from yardstick.benchmark.scenarios.lib.check_numa_info import CheckNumaInfo + + +class CheckNumaInfoTestCase(unittest.TestCase): + + @mock.patch('yardstick.benchmark.scenarios.lib.check_numa_info.CheckNumaInfo._check_vm2_status') + def test_check_numa_info(self, mock_check_vm2): + scenario_cfg = {'info1': {}, 'info2': {}} + obj = CheckNumaInfo(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_check_vm2.called) + + def test_check_vm2_status_length_eq_1(self): + info1 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + info2 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, True) + + def test_check_vm2_status_length_gt_1(self): + info1 = { + 'pinning': [0, 1], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + info2 = { + 'pinning': [0, 1], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, False) + + def test_check_vm2_status_length_not_in_set(self): + info1 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,7' + }] + } + info2 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,7' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, False) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_check_value.py b/tests/unit/benchmark/scenarios/lib/test_check_value.py new file mode 100644 index 000000000..21e83f830 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_check_value.py @@ -0,0 +1,46 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest + +from yardstick.benchmark.scenarios.lib.check_value import CheckValue + + +class CheckValueTestCase(unittest.TestCase): + + def test_check_value_eq(self): + scenario_cfg = {'options': {'operator': 'eq', 'value1': 1, 'value2': 2}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + def test_check_value_eq_pass(self): + scenario_cfg = {'options': {'operator': 'eq', 'value1': 1, 'value2': 1}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + def test_check_value_ne(self): + scenario_cfg = {'options': {'operator': 'ne', 'value1': 1, 'value2': 1}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py b/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py new file mode 100644 index 000000000..f046c92ea --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py @@ -0,0 +1,51 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_migrate_target_host import GetMigrateTargetHost + +BASE = 'yardstick.benchmark.scenarios.lib.get_migrate_target_host' + + +class GetMigrateTargetHostTestCase(unittest.TestCase): + + @mock.patch('{}.openstack_utils.get_nova_client'.format(BASE)) + @mock.patch('{}.GetMigrateTargetHost._get_migrate_host'.format(BASE)) + @mock.patch('{}.GetMigrateTargetHost._get_current_host_name'.format(BASE)) + def test_get_migrate_target_host(self, + mock_get_current_host_name, + mock_get_migrate_host, + mock_get_nova_client): + obj = GetMigrateTargetHost({}, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + self.assertTrue(mock_get_current_host_name.called) + self.assertTrue(mock_get_migrate_host.called) + + @mock.patch('{}.openstack_utils.get_nova_client'.format(BASE)) + def test_get_migrate_host(self, mock_get_nova_client): + class A(object): + def __init__(self, service): + self.service = service + self.host = 'host4' + + mock_get_nova_client().hosts.list_all.return_value = [A('compute')] + obj = GetMigrateTargetHost({}, {}) + host = obj._get_migrate_host('host5') + self.assertTrue(mock_get_nova_client.called) + self.assertEqual(host, 'host4') + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py b/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py new file mode 100644 index 000000000..e7ba3ca73 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py @@ -0,0 +1,106 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_numa_info import GetNumaInfo + +BASE = 'yardstick.benchmark.scenarios.lib.get_numa_info' + + +class GetNumaInfoTestCase(unittest.TestCase): + + @mock.patch('{}.GetNumaInfo._check_numa_node'.format(BASE)) + @mock.patch('{}.GetNumaInfo._get_current_host_name'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_get_numa_info(self, + mock_render, + mock_safe_load, + mock_get_current_host_name, + mock_check_numa_node): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_safe_load.return_value = { + 'nodes': [] + } + obj = GetNumaInfo(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_current_host_name.called) + self.assertTrue(mock_check_numa_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + @mock.patch('{}.GetNumaInfo._get_current_host_name'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_check_numa_node(self, + mock_render, + mock_safe_load, + mock_get_current_host_name, + mock_from_node): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_safe_load.return_value = { + 'nodes': [] + } + data = """ + <data> + </data> + """ + mock_from_node().execute.return_value = (0, data, '') + obj = GetNumaInfo(scenario_cfg, {}) + result = obj._check_numa_node('1', 'host4') + self.assertEqual(result, {'pinning': [], 'vcpupin': []}) + + @mock.patch('{}.change_obj_to_dict'.format(BASE)) + @mock.patch('{}.get_nova_client'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_get_current_host_name(self, + mock_render, + mock_safe_load, + mock_get_nova_client, + mock_change_obj_to_dict): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_get_nova_client().servers.get.return_value = '' + mock_change_obj_to_dict.return_value = {'OS-EXT-SRV-ATTR:host': 'host5'} + + obj = GetNumaInfo(scenario_cfg, {}) + result = obj._get_current_host_name('1') + self.assertEqual(result, 'host5') + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_server.py b/tests/unit/benchmark/scenarios/lib/test_get_server.py new file mode 100644 index 000000000..aebbf5416 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_server.py @@ -0,0 +1,50 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_server import GetServer + + +class GetServerTestCase(unittest.TestCase): + + @mock.patch('yardstick.common.openstack_utils.get_server_by_name') + @mock.patch('yardstick.common.openstack_utils.get_nova_client') + def test_get_server_with_name(self, mock_get_nova_client, mock_get_server_by_name): + scenario_cfg = { + 'options': { + 'server_name': 'yardstick_server' + }, + 'output': 'status server' + } + obj = GetServer(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + self.assertTrue(mock_get_server_by_name.called) + + @mock.patch('yardstick.common.openstack_utils.get_nova_client') + def test_get_server_with_id(self, mock_get_nova_client): + scenario_cfg = { + 'options': { + 'server_id': '1' + }, + 'output': 'status server' + } + mock_get_nova_client().servers.get.return_value = None + obj = GetServer(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py b/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py new file mode 100644 index 000000000..3d20d5439 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py @@ -0,0 +1,41 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 unittest + +from yardstick.benchmark.scenarios.lib.get_server_ip import GetServerIp + + +class GetServerIpTestCase(unittest.TestCase): + def test_get_server_ip(self): + scenario_cfg = { + 'options': { + 'server': { + 'addresses': { + 'net1': [ + { + 'OS-EXT-IPS:type': 'floating', + 'addr': '127.0.0.1' + } + ] + } + } + }, + 'output': 'ip' + } + obj = GetServerIp(scenario_cfg, {}) + result = obj.run({}) + self.assertEqual(result, {'ip': '127.0.0.1'}) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/orchestrator/test_heat.py b/tests/unit/orchestrator/test_heat.py index c127dd0c9..151070423 100644 --- a/tests/unit/orchestrator/test_heat.py +++ b/tests/unit/orchestrator/test_heat.py @@ -135,9 +135,9 @@ class HeatTemplateTestCase(unittest.TestCase): heat_template.add_subnet("subnet2", "network2", "cidr2") heat_template.add_router("router1", "gw1", "subnet1") heat_template.add_router_interface("router_if1", "router1", "subnet1") - heat_template.add_port("port1", "network1", "subnet1") - heat_template.add_port("port2", "network2", "subnet2", sec_group_id="sec_group1",provider="not-sriov") - heat_template.add_port("port3", "network2", "subnet2", sec_group_id="sec_group1",provider="sriov") + heat_template.add_port("port1", "network1", "subnet1", "normal") + heat_template.add_port("port2", "network2", "subnet2", "normal", sec_group_id="sec_group1",provider="not-sriov") + heat_template.add_port("port3", "network2", "subnet2", "normal", sec_group_id="sec_group1",provider="sriov") heat_template.add_floating_ip("floating_ip1", "network1", "port1", "router_if1") heat_template.add_floating_ip("floating_ip2", "network2", "port2", "router_if2", "foo-secgroup") heat_template.add_floating_ip_association("floating_ip1_association", "floating_ip1", "port1") diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py index 6601ecf3b..2db96bade 100644 --- a/yardstick/benchmark/contexts/model.py +++ b/yardstick/benchmark/contexts/model.py @@ -110,7 +110,8 @@ class Network(Object): self.provider = attrs.get('provider') self.segmentation_id = attrs.get('segmentation_id') self.network_type = attrs.get('network_type') - self.port_security_enabled = attrs.get('port_security_enabled', True) + self.port_security_enabled = attrs.get('port_security_enabled') + self.vnic_type = attrs.get('vnic_type', 'normal') self.allowed_address_pairs = attrs.get('allowed_address_pairs', []) try: # we require 'null' or '' to disable setting gateway_ip @@ -184,6 +185,14 @@ class Server(Object): # pragma: no cover self.placement_groups.append(pg) pg.add_member(self.stack_name) + self.volume = None + if "volume" in attrs: + self.volume = attrs.get("volume") + + self.volume_mountpoint = None + if "volume_mountpoint" in attrs: + self.volume_mountpoint = attrs.get("volume_mountpoint") + # support servergroup attr self.server_group = None sg = attrs.get("server_group") @@ -248,14 +257,16 @@ class Server(Object): # pragma: no cover port_name = server_name + "-" + network.name + "-port" self.ports[network.name] = {"stack_name": port_name} # we can't use secgroups if port_security_enabled is False - if network.port_security_enabled: - sec_group_id = self.secgroup_name - else: + if network.port_security_enabled is False: sec_group_id = None + else: + # if port_security_enabled is None we still need to add to secgroup + sec_group_id = self.secgroup_name # don't refactor to pass in network object, that causes JSON # circular ref encode errors template.add_port(port_name, network.stack_name, network.subnet_stack_name, - sec_group_id=sec_group_id, provider=network.provider, + network.vnic_type, sec_group_id=sec_group_id, + provider=network.provider, allowed_address_pairs=network.allowed_address_pairs) port_name_list.append(port_name) @@ -283,6 +294,17 @@ class Server(Object): # pragma: no cover else: self.flavor_name = self.flavor + if self.volume: + if isinstance(self.volume, dict): + self.volume["name"] = \ + self.volume.setdefault("name", server_name + "-volume") + template.add_volume(**self.volume) + template.add_volume_attachment(server_name, self.volume["name"], + mountpoint=self.volume_mountpoint) + else: + template.add_volume_attachment(server_name, self.volume, + mountpoint=self.volume_mountpoint) + template.add_server(server_name, self.image, flavor=self.flavor_name, flavors=self.context.flavors, ports=port_name_list, diff --git a/yardstick/benchmark/contexts/ovsdpdk.py b/yardstick/benchmark/contexts/ovsdpdk.py index 86610305e..cf5529d89 100644 --- a/yardstick/benchmark/contexts/ovsdpdk.py +++ b/yardstick/benchmark/contexts/ovsdpdk.py @@ -329,7 +329,7 @@ class Ovsdpdk(StandaloneContext): for i in range(0, 10): self.connection.execute( "virsh vcpupin vm1 {0} {1}".format( - i, nodes[str(num_nodes - 1)][i])) + i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) def get_numa_nodes(self): nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") diff --git a/yardstick/benchmark/contexts/sriov.py b/yardstick/benchmark/contexts/sriov.py index da143cc4b..fe27d2579 100644 --- a/yardstick/benchmark/contexts/sriov.py +++ b/yardstick/benchmark/contexts/sriov.py @@ -392,7 +392,7 @@ class Sriov(StandaloneContext): for i in range(0, 10): self.connection.execute( "virsh vcpupin vm1 {0} {1}".format( - i, nodes[str(num_nodes - 1)][i])) + i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) def get_numa_nodes(self): nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") diff --git a/yardstick/benchmark/core/plugin.py b/yardstick/benchmark/core/plugin.py index 6c06767d5..c8d0865d1 100644 --- a/yardstick/benchmark/core/plugin.py +++ b/yardstick/benchmark/core/plugin.py @@ -107,8 +107,8 @@ class Plugin(object): if deployment_ip == "local": self.client = ssh.SSH.from_node(deployment, overrides={ - # host can't be None, fail if no INSTALLER_IP - 'ip': os.environ["INSTALLER_IP"], + # host can't be None, fail if no JUMP_HOST_IP + 'ip': os.environ["JUMP_HOST_IP"], }) else: self.client = ssh.SSH.from_node(deployment) diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index ede14b1c0..b2da7a2ee 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -48,6 +48,12 @@ class Task(object): # pragma: no cover self.contexts = [] self.outputs = {} + def _set_dispatchers(self, output_config): + dispatchers = output_config.get('DEFAULT', {}).get('dispatcher', + 'file') + out_types = [s.strip() for s in dispatchers.split(',')] + output_config['DEFAULT']['dispatcher'] = out_types + def start(self, args, **kwargs): """Start a benchmark scenario.""" @@ -68,7 +74,10 @@ class Task(object): # pragma: no cover self._set_output_config(output_config, args.output_file) LOG.debug('Output configuration is: %s', output_config) - if output_config['DEFAULT'].get('dispatcher') == 'file': + self._set_dispatchers(output_config) + + # update dispatcher list + if 'file' in output_config['DEFAULT']['dispatcher']: result = {'status': 0, 'result': {}} utils.write_json_to_file(args.output_file, result) @@ -198,9 +207,10 @@ class Task(object): # pragma: no cover return 'PASS' def _do_output(self, output_config, result): + dispatchers = DispatcherBase.get(output_config) - dispatcher = DispatcherBase.get(output_config) - dispatcher.flush_result_data(result) + for dispatcher in dispatchers: + dispatcher.flush_result_data(result) def _run(self, scenarios, run_in_parallel, output_file): """Deploys context and calls runners""" diff --git a/yardstick/benchmark/scenarios/base.py b/yardstick/benchmark/scenarios/base.py index 5d3c36c38..3cb138dd8 100644 --- a/yardstick/benchmark/scenarios/base.py +++ b/yardstick/benchmark/scenarios/base.py @@ -63,3 +63,15 @@ class Scenario(object): return scenario.__module__ + "." + scenario.__name__ raise RuntimeError("No such scenario type %s" % scenario_type) + + def _push_to_outputs(self, keys, values): + return dict(zip(keys, values)) + + def _change_obj_to_dict(self, obj): + dic = {} + for k, v in vars(obj).items(): + try: + vars(v) + except TypeError: + dic[k] = v + return dic diff --git a/yardstick/benchmark/scenarios/compute/qemu_migrate.py b/yardstick/benchmark/scenarios/compute/qemu_migrate.py new file mode 100644 index 000000000..cee87a545 --- /dev/null +++ b/yardstick/benchmark/scenarios/compute/qemu_migrate.py @@ -0,0 +1,155 @@ +from __future__ import absolute_import +from __future__ import print_function + +import logging +import os +import re +import time + + +import pkg_resources +from oslo_serialization import jsonutils + +import yardstick.ssh as ssh +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class QemuMigrate(base.Scenario): + """ + Execute a live migration for two host using qemu + + """ + + __scenario_type__ = "QemuMigrate" + + TARGET_SCRIPT = "qemu_migrate_benchmark.bash" + WORKSPACE = "/root/workspace" + REBOOT_CMD_PATTERN = r";\s*reboot\b" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.setup_done = False + + def _connect_host(self): + host = self.context_cfg["host"] + self.host = ssh.SSH.from_node(host, defaults={"user": "root"}) + self.host.wait(timeout=600) + + def _put_files(self, client): + setup_options = self.scenario_cfg["setup_options"] + script_dir = setup_options["script_dir"] + LOG.debug("Send scripts from %s to workspace %s", + script_dir, self.WORKSPACE) + client.put(script_dir, self.WORKSPACE, recursive=True) + + def _run_setup_cmd(self, client, cmd): + LOG.debug("Run cmd: %s", cmd) + status, stdout, stderr = client.execute(cmd) + if status: + if re.search(self.REBOOT_CMD_PATTERN, cmd): + LOG.debug("Error on reboot") + else: + raise RuntimeError(stderr) + + def _run_host_setup_scripts(self, scripts): + setup_options = self.scenario_cfg["setup_options"] + script_dir = os.path.basename(setup_options["script_dir"]) + + for script in scripts: + cmd = "cd %s/%s; export PATH=./:$PATH; %s" %\ + (self.WORKSPACE, script_dir, script) + self._run_setup_cmd(self.host, cmd) + + if re.search(self.REBOOT_CMD_PATTERN, cmd): + time.sleep(3) + self._connect_host() + + def setup(self): + """scenario setup""" + setup_options = self.scenario_cfg["setup_options"] + host_setup_seqs = setup_options["host_setup_seqs"] + + self._connect_host() + self._put_files(self.host) + self._run_host_setup_scripts(host_setup_seqs) + + # copy script to host + self.target_script = pkg_resources.resource_filename( + "yardstick.benchmark.scenarios.compute", + QemuMigrate.TARGET_SCRIPT) + self.host.put_file(self.target_script, "~/qemu_migrate_benchmark.sh") + + self.setup_done = True + + def run(self, result): + """execute the benchmark""" + + options = self.scenario_cfg["options"] + smp = options.get("smp", 2) + qmp_sock_src = options.get("qmp_src_path", "/tmp/qmp-sock-src") + qmp_sock_dst = options.get("qmp_dst_path", "/tmp/qmp-sock-dst") + incoming_ip = options.get("incoming_ip", 0) + migrate_to_port = options.get("migrate_to_port", 4444) + max_down_time = options.get("max_down_time", 0.10) + cmd_args = " %s %s %s %s %s %s" %\ + (smp, qmp_sock_src, qmp_sock_dst, incoming_ip, + migrate_to_port, max_down_time) + cmd = "bash migrate_benchmark.sh %s" % (cmd_args) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.host.execute(cmd) + if status: + raise RuntimeError(stderr) + + result.update(jsonutils.loads(stdout)) + + if "sla" in self.scenario_cfg: + sla_error = "" + for t, timevalue in result.items(): + if 'max_%s' % t not in self.scenario_cfg['sla']: + continue + + sla_time = int(self.scenario_cfg['sla'][ + 'max_%s' % t]) + timevalue = int(timevalue) + if timevalue > sla_time: + sla_error += "%s timevalue %d > sla:max_%s(%d); " % \ + (t, timevalue, t, sla_time) + assert sla_error == "", sla_error + + +def _test(): # pragma: no cover + """internal test function""" + key_filename = pkg_resources.resource_filename("yardstick.resources", + "files/yardstick_key") + ctx = { + "host": { + "ip": "10.229.47.137", + "user": "root", + "key_filename": key_filename + } + } + + logger = logging.getLogger("yardstick") + logger.setLevel(logging.DEBUG) + options = { + "smp": 2, + "migrate_to_port": 4444, + "incoming_ip": 0, + "qmp_sock_src": "/tmp/qmp-sock-src", + "qmp_sock_dst": "/tmp/qmp-sock-dst", + "max_down_time": 0.10 + } + args = { + "options": options + } + result = {} + migrate = QemuMigrate(args, ctx) + migrate.run(result) + print(result) + +if __name__ == '__main__': # pragma: no cover + _test() diff --git a/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash b/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash new file mode 100644 index 000000000..552098103 --- /dev/null +++ b/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash @@ -0,0 +1,68 @@ +#!/bin/bash + +############################################################################# +#Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +set -e + +# Commandline arguments + +src=$2 +dst_ip=$4 +migrate_to_port=$5 +max_down_time=$6 + +OUTPUT_FILE=/tmp/output-qemu.log + +do_migrate() +{ +# local src=`echo $OPTIONS | cut -d ':' -f 2 | cut -d ',' -f 1` + echo "info status" | nc -U $src + # with no speed limit + echo "migrate_set_speed 0" |nc -U $src + # set the expected max downtime + echo "migrate_set_downtime ${max_down_time}" |nc -U $src + # start live migration + echo "migrate -d tcp:${dst_ip}:$migrate_to_port" |nc -U $src + # wait until live migration completed + status="" + while [ "${status}" == "" ] + do + status=`echo "info migrate" | nc -U $src |grep completed | cut -d: -f2` + echo ${status} + sleep 1; + done +} >/dev/null + +output_qemu() +{ + # print detail information + echo "info migrate" | nc -U $src + echo "quit" | nc -U $src + sleep 5 + +} > $OUTPUT_FILE + +output_json() +{ +totaltime=$(grep "total time" $OUTPUT_FILE | cut -d' ' -f3) +downtime=$(grep "downtime" $OUTPUT_FILE | cut -d' ' -f2) +setuptime=$(grep "setup" $OUTPUT_FILE | cut -d' ' -f2) +echo -e "{ \ + \"totaltime\":\"$totaltime\", \ + \"downtime\":\"$downtime\", \ + \"setuptime\":\"$setuptime\" \ + }" +} +# main entry +main() +{ + do_migrate +} +main diff --git a/yardstick/benchmark/scenarios/lib/__init__.py b/yardstick/benchmark/scenarios/lib/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/__init__.py diff --git a/yardstick/benchmark/scenarios/lib/add_memory_load.py b/yardstick/benchmark/scenarios/lib/add_memory_load.py new file mode 100644 index 000000000..26cf140d1 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/add_memory_load.py @@ -0,0 +1,57 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +import yardstick.ssh as ssh +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class AddMemoryLoad(base.Scenario): + """Add memory load in server + """ + + __scenario_type__ = "AddMemoryLoad" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = scenario_cfg.get('options', {}) + + self.client = ssh.SSH.from_node(self.context_cfg['host']) + self.client.wait(timeout=600) + + def run(self, result): + self._add_load() + + def _add_load(self): + try: + memory_load = self.options['memory_load'] + except KeyError: + LOG.error('memory_load parameter must be provided') + else: + if float(memory_load) == 0: + return + cmd = 'free | awk "/Mem/ {print $2}"' + code, stdout, stderr = self.client.execute(cmd) + total = int(stdout.split()[1]) + used = int(stdout.split()[2]) + remain_memory = total * float(memory_load) - used + if remain_memory > 0: + count = remain_memory / 1024 / 128 + LOG.info('Add %s vm load', count) + if count != 0: + cmd = 'stress -t 10 -m {} --vm-keep'.format(count) + self.client.execute(cmd) diff --git a/yardstick/benchmark/scenarios/lib/check_numa_info.py b/yardstick/benchmark/scenarios/lib/check_numa_info.py new file mode 100644 index 000000000..59a47547e --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/check_numa_info.py @@ -0,0 +1,61 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class CheckNumaInfo(base.Scenario): + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "CheckNumaInfo" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = self.scenario_cfg.get('options', {}) + + self.cpu_set = self.options.get('cpu_set', '1,2,3,4,5,6') + + def run(self, result): + info1 = self.options.get('info1') + info2 = self.options.get('info2') + LOG.debug('Origin numa info: %s', info1) + LOG.debug('Current numa info: %s', info2) + status = self._check_vm2_status(info1, info2) + + keys = self.scenario_cfg.get('output', '').split() + values = [status] + return self._push_to_outputs(keys, values) + + def _check_vm2_status(self, info1, info2): + if len(info1['pinning']) != 1 or len(info2['pinning']) != 1: + return False + + for i in info1['vcpupin']: + for j in i['cpuset'].split(','): + if j not in self.cpu_set.split(','): + return False + + for i in info2['vcpupin']: + for j in i['cpuset'].split(','): + if j not in self.cpu_set.split(','): + return False + + return True diff --git a/yardstick/benchmark/scenarios/lib/check_value.py b/yardstick/benchmark/scenarios/lib/check_value.py new file mode 100644 index 000000000..759076068 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/check_value.py @@ -0,0 +1,58 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class CheckValue(base.Scenario): + """Check values between value1 and value2 + + options: + operator: equal(eq) and not equal(ne) + value1: + value2: + output: check_result + """ + + __scenario_type__ = "CheckValue" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg['options'] + + def run(self, result): + """execute the test""" + + op = self.options.get("operator") + LOG.debug("options=%s", self.options) + value1 = str(self.options.get("value1")) + value2 = str(self.options.get("value2")) + check_result = "PASS" + if op == "eq" and value1 != value2: + LOG.info("value1=%s, value2=%s, error: should equal!!!", value1, + value2) + check_result = "FAIL" + assert value1 == value2, "Error %s!=%s" % (value1, value2) + elif op == "ne" and value1 == value2: + LOG.info("value1=%s, value2=%s, error: should not equal!!!", + value1, value2) + check_result = "FAIL" + assert value1 != value2, "Error %s==%s" % (value1, value2) + LOG.info("Check result is %s", check_result) + keys = self.scenario_cfg.get('output', '').split() + values = [check_result] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py b/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py new file mode 100644 index 000000000..c19d96d68 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py @@ -0,0 +1,56 @@ + +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.common import openstack_utils +from yardstick.common.utils import change_obj_to_dict +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class GetMigrateTargetHost(base.Scenario): + """Get a migrate target host according server + """ + + __scenario_type__ = "GetMigrateTargetHost" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = self.scenario_cfg.get('options', {}) + default_instance_id = self.options.get('server', {}).get('id', '') + self.instance_id = self.options.get('server_id', default_instance_id) + + self.nova_client = openstack_utils.get_nova_client() + + def run(self, result): + current_host = self._get_current_host_name(self.instance_id) + target_host = self._get_migrate_host(current_host) + + keys = self.scenario_cfg.get('output', '').split() + values = [target_host] + return self._push_to_outputs(keys, values) + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(self.nova_client.servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_migrate_host(self, current_host): + hosts = self.nova_client.hosts.list_all() + compute_hosts = [a.host for a in hosts if a.service == 'compute'] + for host in compute_hosts: + if host.strip() != current_host.strip(): + return host diff --git a/yardstick/benchmark/scenarios/lib/get_numa_info.py b/yardstick/benchmark/scenarios/lib/get_numa_info.py new file mode 100644 index 000000000..4e4a44d95 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_numa_info.py @@ -0,0 +1,79 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging +import os + +import yaml +from xml.etree import ElementTree as ET + +from yardstick import ssh +from yardstick.benchmark.scenarios import base +from yardstick.common import constants as consts +from yardstick.common.utils import change_obj_to_dict +from yardstick.common.openstack_utils import get_nova_client +from yardstick.common.task_template import TaskTemplate + +LOG = logging.getLogger(__name__) + + +class GetNumaInfo(base.Scenario): + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "GetNumaInfo" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + server = self.options['server'] + self.server_id = server['id'] + self.host = self._get_current_host_name(self.server_id) + + node_file = os.path.join(consts.YARDSTICK_ROOT_PATH, + self.options.get('file')) + + with open(node_file) as f: + nodes = yaml.safe_load(TaskTemplate.render(f.read())) + self.nodes = {a['host_name']: a for a in nodes['nodes']} + + def run(self, result): + numa_info = self._check_numa_node(self.server_id, self.host) + + keys = self.scenario_cfg.get('output', '').split() + values = [numa_info] + return self._push_to_outputs(keys, values) + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(get_nova_client().servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_host_client(self, node_name): + self.host_client = ssh.SSH.from_node(self.nodes.get(node_name)) + self.host_client.wait(timeout=600) + + def _check_numa_node(self, server_id, host): + self._get_host_client(host) + + cmd = "sudo virsh dumpxml %s" % server_id + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.host_client.execute(cmd) + if status: + raise RuntimeError(stderr) + root = ET.fromstring(stdout) + vcpupin = [a.attrib for a in root.iter('vcpupin')] + pinning = [a.attrib for a in root.iter('memnode')] + return {"pinning": pinning, 'vcpupin': vcpupin} diff --git a/yardstick/benchmark/scenarios/lib/get_server.py b/yardstick/benchmark/scenarios/lib/get_server.py new file mode 100644 index 000000000..fcf47c80d --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_server.py @@ -0,0 +1,83 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base +import yardstick.common.openstack_utils as op_utils + +LOG = logging.getLogger(__name__) + + +class GetServer(base.Scenario): + """Get a server instance + + Parameters + server_id - ID of the server + type: string + unit: N/A + default: null + server_name - name of the server + type: string + unit: N/A + default: null + + Either server_id or server_name is required. + + Outputs + rc - response code of getting server instance + 0 for success + 1 for failure + type: int + unit: N/A + server - instance of the server + type: dict + unit: N/A + """ + + __scenario_type__ = "GetServer" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + self.server_id = self.options.get("server_id") + if self.server_id: + LOG.debug('Server id is %s', self.server_id) + + default_name = self.scenario_cfg.get('host', + self.scenario_cfg.get('target')) + self.server_name = self.options.get('server_name', default_name) + if self.server_name: + LOG.debug('Server name is %s', self.server_name) + + self.nova_client = op_utils.get_nova_client() + + def run(self, result): + """execute the test""" + + if self.server_id: + server = self.nova_client.servers.get(self.server_id) + else: + server = op_utils.get_server_by_name(self.server_name) + + keys = self.scenario_cfg.get('output', '').split() + + if server: + LOG.info("Get server successful!") + values = [0, self._change_obj_to_dict(server)] + else: + LOG.info("Get server failed!") + values = [1] + + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/get_server_ip.py b/yardstick/benchmark/scenarios/lib/get_server_ip.py new file mode 100644 index 000000000..1eeeb7fca --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_server_ip.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class GetServerIp(base.Scenario): + """Get a server by name""" + + __scenario_type__ = "GetServerIp" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + self.ip_type = self.options.get('ip_type', "floating") + + def run(self, result): + server = self.options.get('server', {}) + ip = next(n['addr'] for k, v in server['addresses'].items() + for n in v if n['OS-EXT-IPS:type'] == self.ip_type) + + keys = self.scenario_cfg.get('output', '').split() + values = [ip] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/migrate.py b/yardstick/benchmark/scenarios/lib/migrate.py new file mode 100644 index 000000000..116bae69e --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/migrate.py @@ -0,0 +1,155 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# 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 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging +import subprocess +import threading +import time + +from datetime import datetime +import ping + +from yardstick.common import openstack_utils +from yardstick.common.utils import change_obj_to_dict +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + +TIMEOUT = 0.05 +PACKAGE_SIZE = 64 + + +class Migrate(base.Scenario): # pragma: no cover + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "Migrate" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + self.nova_client = openstack_utils.get_nova_client() + + def run(self, result): + default_instance_id = self.options.get('server', {}).get('id', '') + instance_id = self.options.get('server_id', default_instance_id) + LOG.info('Instance id is %s', instance_id) + + target_host = self.options.get('host') + LOG.info('Target host is %s', target_host) + + instance_ip = self.options.get('server_ip') + if instance_ip: + LOG.info('Instance ip is %s', instance_ip) + + self._ping_until_connected(instance_ip) + LOG.info('Instance is connected') + + LOG.debug('Start to ping instance') + ping_thread = self._do_ping_task(instance_ip) + + keys = self.scenario_cfg.get('output', '').split() + try: + LOG.info('Start to migrate') + self._do_migrate(instance_id, target_host) + except Exception as e: + return self._push_to_outputs(keys, [1, str(e).split('.')[0]]) + else: + migrate_time = self._get_migrate_time(instance_id) + LOG.info('Migration time is %s s', migrate_time) + + current_host = self._get_current_host_name(instance_id) + LOG.info('Current host is %s', current_host) + if current_host.strip() != target_host.strip(): + LOG.error('current_host not equal to target_host') + values = [1, 'current_host not equal to target_host'] + return self._push_to_outputs(keys, values) + + if instance_ip: + ping_thread.flag = False + ping_thread.join() + + downtime = ping_thread.get_delay() + LOG.info('Downtime is %s s', downtime) + + values = [0, migrate_time, downtime] + return self._push_to_outputs(keys, values) + else: + values = [0, migrate_time] + return self._push_to_outputs(keys, values) + + def _do_migrate(self, server_id, target_host): + + cmd = ['nova', 'live-migration', server_id, target_host] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + p.communicate() + + def _ping_until_connected(self, instance_ip): + for i in range(3000): + res = ping.do_one(instance_ip, TIMEOUT, PACKAGE_SIZE) + if res: + break + + def _do_ping_task(self, instance_ip): + ping_thread = PingThread(instance_ip) + ping_thread.start() + return ping_thread + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(self.nova_client.servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_migrate_time(self, server_id): + while True: + status = self.nova_client.servers.get(server_id).status.lower() + if status == 'migrating': + start_time = datetime.now() + break + LOG.debug('Instance status change to MIGRATING') + + while True: + status = self.nova_client.servers.get(server_id).status.lower() + if status == 'active': + end_time = datetime.now() + break + if status == 'error': + LOG.error('Instance status is ERROR') + raise RuntimeError('The instance status is error') + LOG.debug('Instance status change to ACTIVE') + + duration = end_time - start_time + return duration.seconds + duration.microseconds * 1.0 / 1e6 + + +class PingThread(threading.Thread): # pragma: no cover + + def __init__(self, target): + super(PingThread, self).__init__() + self.target = target + self.flag = True + self.delay = 0.0 + + def run(self): + count = 0 + while self.flag: + res = ping.do_one(self.target, TIMEOUT, PACKAGE_SIZE) + if not res: + count += 1 + time.sleep(0.01) + self.delay = (TIMEOUT + 0.01) * count + + def get_delay(self): + return self.delay diff --git a/yardstick/cmd/NSBperf.py b/yardstick/cmd/NSBperf.py index 011990a3d..2dc0f65e7 100755 --- a/yardstick/cmd/NSBperf.py +++ b/yardstick/cmd/NSBperf.py @@ -30,13 +30,6 @@ from six.moves import input CLI_PATH = os.path.dirname(os.path.realpath(__file__)) REPO_PATH = os.path.abspath(os.path.join(CLI_PATH, os.pardir)) -PYTHONPATH = os.environ.get("PYTHONPATH", False) -VIRTUAL_ENV = os.environ.get("VIRTUAL_ENV", False) - - -if not PYTHONPATH or not VIRTUAL_ENV: - print("Please setup env PYTHONPATH & VIRTUAL_ENV environment varaible.") - raise SystemExit(1) def sigint_handler(*args, **kwargs): @@ -115,10 +108,10 @@ class YardstickNSCli(object): and generates final report in rst format. """ + tc_name = os.path.splitext(test_case)[0] report_caption = '{}\n{} ({})\n{}\n\n'.format( '================================================================', - 'Performance report for', - os.path.splitext(test_case)[0].upper(), + 'Performance report for', tc_name.upper(), '================================================================') print(report_caption) if os.path.isfile("/tmp/yardstick.out"): @@ -127,9 +120,10 @@ class YardstickNSCli(object): lines = jsonutils.load(infile) if lines: - lines = lines['result'] + lines = \ + lines['result']["testcases"][tc_name]["tc_data"] tc_res = lines.pop(len(lines) - 1) - for key, value in tc_res["benchmark"]["data"].items(): + for key, value in tc_res["data"].items(): self.generate_kpi_results(key, value) self.generate_nfvi_results(value) @@ -156,7 +150,7 @@ class YardstickNSCli(object): testcases = os.listdir(test_path + vnf) print(("VNF :(%s)" % vnf)) print("================") - for testcase in [tc for tc in testcases if "tc" in tc]: + for testcase in [tc for tc in testcases if "tc_" in tc]: print('%s' % testcase) print(os.linesep) raise SystemExit(0) diff --git a/yardstick/cmd/commands/task.py b/yardstick/cmd/commands/task.py index 03f6b1b1e..8d8ea2b3c 100644 --- a/yardstick/cmd/commands/task.py +++ b/yardstick/cmd/commands/task.py @@ -51,11 +51,17 @@ class TaskCommands(object): # pragma: no cover self.output_file = param.output_file try: - Task().start(param, **kwargs) + result = Task().start(param, **kwargs) except Exception as e: self._write_error_data(e) LOG.exception("") + if result.get('result', {}).get('criteria') == 'PASS': + LOG.info('Task Success') + else: + LOG.info('Task Failed') + raise RuntimeError('Task Failed') + def _write_error_data(self, error): data = {'status': 2, 'result': str(error)} write_json_to_file(self.output_file, data) diff --git a/yardstick/common/openstack_utils.py b/yardstick/common/openstack_utils.py index 8787e605a..f027b7922 100644 --- a/yardstick/common/openstack_utils.py +++ b/yardstick/common/openstack_utils.py @@ -15,6 +15,7 @@ import logging from keystoneauth1 import loading from keystoneauth1 import session +from cinderclient import client as cinderclient from novaclient import client as novaclient from glanceclient import client as glanceclient from neutronclient.neutron import client as neutronclient @@ -108,6 +109,21 @@ def get_heat_api_version(): # pragma: no cover return api_version +def get_cinder_client_version(): # pragma: no cover + try: + api_version = os.environ['OS_VOLUME_API_VERSION'] + except KeyError: + return DEFAULT_API_VERSION + else: + log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version) + return api_version + + +def get_cinder_client(): # pragma: no cover + sess = get_session() + return cinderclient.Client(get_cinder_client_version(), session=sess) + + def get_nova_client_version(): # pragma: no cover try: api_version = os.environ['OS_COMPUTE_API_VERSION'] @@ -430,3 +446,11 @@ def get_port_id_by_ip(neutron_client, ip_address): # pragma: no cover def get_image_id(glance_client, image_name): # pragma: no cover images = glance_client.images.list() return next((i.id for i in images if i.name == image_name), None) + + +# ********************************************* +# CINDER +# ********************************************* +def get_volume_id(volume_name): # pragma: no cover + volumes = get_cinder_client().volumes.list() + return next((v.id for v in volumes if v.name == volume_name), None) diff --git a/yardstick/dispatcher/base.py b/yardstick/dispatcher/base.py index e77249c54..1fc0a2f31 100644 --- a/yardstick/dispatcher/base.py +++ b/yardstick/dispatcher/base.py @@ -41,9 +41,11 @@ class Base(object): def get(config): """Returns instance of a dispatcher for dispatcher type. """ - out_type = config['DEFAULT']['dispatcher'] + list_dispatcher = \ + [Base.get_cls(out_type.capitalize())(config) + for out_type in config['DEFAULT']['dispatcher']] - return Base.get_cls(out_type.capitalize())(config) + return list_dispatcher @abc.abstractmethod def flush_result_data(self, data): diff --git a/yardstick/orchestrator/heat.py b/yardstick/orchestrator/heat.py index 57b23d393..beb63b421 100644 --- a/yardstick/orchestrator/heat.py +++ b/yardstick/orchestrator/heat.py @@ -230,8 +230,42 @@ name (i.e. %s).\ 'value': {'get_resource': name} } + def add_volume(self, name, size=10): + """add to the template a volume description""" + log.debug("adding Cinder::Volume '%s' size '%d' ", name, size) + + self.resources[name] = { + 'type': 'OS::Cinder::Volume', + 'properties': {'name': name, + 'size': size} + } + + self._template['outputs'][name] = { + 'description': 'Volume %s ID' % name, + 'value': {'get_resource': name} + } + + def add_volume_attachment(self, server_name, volume_name, mountpoint=None): + """add to the template an association of volume to instance""" + log.debug("adding Cinder::VolumeAttachment server '%s' volume '%s' ", server_name, + volume_name) + + name = "%s-%s" % (server_name, volume_name) + + volume_id = op_utils.get_volume_id(volume_name) + if not volume_id: + volume_id = {'get_resource': volume_name} + self.resources[name] = { + 'type': 'OS::Cinder::VolumeAttachment', + 'properties': {'instance_uuid': {'get_resource': server_name}, + 'volume_id': volume_id} + } + + if mountpoint: + self.resources[name]['properties']['mountpoint'] = mountpoint + def add_network(self, name, physical_network='physnet1', provider=None, - segmentation_id=None, port_security_enabled=True): + segmentation_id=None, port_security_enabled=None): """add to the template a Neutron Net""" log.debug("adding Neutron::Net '%s'", name) if provider is None: @@ -239,7 +273,6 @@ name (i.e. %s).\ 'type': 'OS::Neutron::Net', 'properties': { 'name': name, - 'port_security_enabled': port_security_enabled, } } else: @@ -249,11 +282,14 @@ name (i.e. %s).\ 'name': name, 'network_type': 'vlan', 'physical_network': physical_network, - 'port_security_enabled': port_security_enabled, }, } if segmentation_id: self.resources[name]['properties']['segmentation_id'] = segmentation_id + # if port security is not defined then don't add to template: + # some deployments don't have port security plugin installed + if port_security_enabled is not None: + self.resources[name]['properties']['port_security_enabled'] = port_security_enabled def add_server_group(self, name, policies): # pragma: no cover """add to the template a ServerGroup""" @@ -323,17 +359,18 @@ name (i.e. %s).\ } } - def add_port(self, name, network_name, subnet_name, sec_group_id=None, provider=None, - allowed_address_pairs=None): + def add_port(self, name, network_name, subnet_name, vnic_type, sec_group_id=None, + provider=None, allowed_address_pairs=None): """add to the template a named Neutron Port """ - log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', " - "secgroup:%s", name, network_name, subnet_name, sec_group_id) + log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', vnic_type:'%s', " + "secgroup:%s", name, network_name, subnet_name, vnic_type, sec_group_id) self.resources[name] = { 'type': 'OS::Neutron::Port', 'depends_on': [subnet_name], 'properties': { 'name': name, + 'binding:vnic_type': vnic_type, 'fixed_ips': [{'subnet': {'get_resource': subnet_name}}], 'network_id': {'get_resource': network_name}, 'replacement_policy': 'AUTO', |