aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--ansible/inventory.ini1
-rw-r--r--ansible/migrate_pinning_setup.yaml49
-rw-r--r--ansible/migrate_pinning_teardown.yaml31
-rw-r--r--ansible/roles/backup_nova_conf/tasks/main.yaml18
-rw-r--r--ansible/roles/create_flavor/tasks/main.yaml16
-rw-r--r--ansible/roles/delete_flavor/tasks/main.yaml13
-rw-r--r--ansible/roles/recover_nova_conf/tasks/main.yaml18
-rw-r--r--ansible/roles/restart_nova_service/tasks/main.yaml23
-rw-r--r--ansible/roles/set_flavor_property/tasks/main.yaml14
-rw-r--r--ansible/roles/set_nova_conf/tasks/main.yaml17
-rw-r--r--api/database/v2/models.py2
-rw-r--r--api/resources/v2/images.py2
-rw-r--r--api/resources/v2/tasks.py3
-rw-r--r--api/resources/v2/testcases.py2
-rw-r--r--api/urls.py3
-rw-r--r--docker/Dockerfile1
-rwxr-xr-xdocker/nginx.sh31
-rwxr-xr-xdocker/supervisor.sh26
-rwxr-xr-xdocker/uwsgi.sh (renamed from api/api-prepare.sh)54
-rw-r--r--etc/yardstick/nodes/compass_sclab_virtual/pod.yaml5
-rw-r--r--etc/yardstick/yardstick.conf.sample2
-rw-r--r--gui/Gruntfile.js492
-rw-r--r--gui/app/404.html152
-rw-r--r--gui/app/favicon.icobin0 -> 4286 bytes
-rw-r--r--gui/app/images/back.pngbin0 -> 1976 bytes
-rw-r--r--gui/app/images/checkno.pngbin0 -> 5849 bytes
-rw-r--r--gui/app/images/checkyes.pngbin0 -> 6423 bytes
-rw-r--r--gui/app/images/close.pngbin0 -> 1211 bytes
-rw-r--r--gui/app/images/loading.gifbin0 -> 130310 bytes
-rw-r--r--gui/app/images/loading2.gifbin0 -> 25405 bytes
-rw-r--r--gui/app/images/logo.pngbin0 -> 4323 bytes
-rw-r--r--gui/app/images/statusno.pngbin0 -> 3861 bytes
-rw-r--r--gui/app/images/statusyes.pngbin0 -> 2577 bytes
-rw-r--r--gui/app/images/url.json1
-rw-r--r--gui/app/images/yeoman.pngbin0 -> 13501 bytes
-rw-r--r--gui/app/index.html111
-rw-r--r--gui/app/robots.txt4
-rw-r--r--gui/app/scripts/app.js30
-rw-r--r--gui/app/scripts/controllers/container.controller.js182
-rw-r--r--gui/app/scripts/controllers/content.controller.js136
-rw-r--r--gui/app/scripts/controllers/detail.controller.js384
-rw-r--r--gui/app/scripts/controllers/image.controller.js166
-rw-r--r--gui/app/scripts/controllers/main.js725
-rw-r--r--gui/app/scripts/controllers/pod.controller.js179
-rw-r--r--gui/app/scripts/controllers/project.controller.js160
-rw-r--r--gui/app/scripts/controllers/projectDetail.controller.js690
-rw-r--r--gui/app/scripts/controllers/report.controller.js115
-rw-r--r--gui/app/scripts/controllers/suitecreate.controller.js104
-rw-r--r--gui/app/scripts/controllers/suitedetail.controller.js48
-rw-r--r--gui/app/scripts/controllers/task.controller.js175
-rw-r--r--gui/app/scripts/controllers/taskModify.controller.js533
-rw-r--r--gui/app/scripts/controllers/testcase.controller.js154
-rw-r--r--gui/app/scripts/controllers/testcasedetail.controller.js50
-rw-r--r--gui/app/scripts/controllers/testsuit.controller.js119
-rw-r--r--gui/app/scripts/factory/main.factory.js247
-rw-r--r--gui/app/scripts/router.config.js184
-rw-r--r--gui/app/styles/main.css208
-rw-r--r--gui/app/views/container.html134
-rw-r--r--gui/app/views/content.html0
-rw-r--r--gui/app/views/environmentDetail.html143
-rw-r--r--gui/app/views/environmentList.html152
-rw-r--r--gui/app/views/layout/footer.html5
-rw-r--r--gui/app/views/layout/header.html44
-rw-r--r--gui/app/views/layout/sideNav.html141
-rw-r--r--gui/app/views/layout/sideNav2.html108
-rw-r--r--gui/app/views/main.html174
-rw-r--r--gui/app/views/main2.html174
-rw-r--r--gui/app/views/modal/chooseContainer.html15
-rw-r--r--gui/app/views/modal/deleteConfirm.html19
-rw-r--r--gui/app/views/modal/environmentDialog.html330
-rw-r--r--gui/app/views/modal/projectCreate.html21
-rw-r--r--gui/app/views/modal/suiteName.html18
-rw-r--r--gui/app/views/modal/taskCreate.html134
-rw-r--r--gui/app/views/podupload.html136
-rw-r--r--gui/app/views/projectList.html57
-rw-r--r--gui/app/views/projectdetail.html97
-rw-r--r--gui/app/views/report.html56
-rw-r--r--gui/app/views/suite.html154
-rw-r--r--gui/app/views/suitedetail.html110
-rw-r--r--gui/app/views/taskList.html62
-rw-r--r--gui/app/views/taskmodify.html162
-rw-r--r--gui/app/views/testcasechoose.html48
-rw-r--r--gui/app/views/testcasedetail.html110
-rw-r--r--gui/app/views/testcaselist.html158
-rw-r--r--gui/app/views/uploadImage.html145
-rw-r--r--gui/bower.json48
-rwxr-xr-xgui/gui.sh8
-rw-r--r--gui/package.json43
-rw-r--r--gui/test/.jshintrc18
-rw-r--r--gui/test/karma.conf.js93
-rw-r--r--gui/test/spec/controllers/main.js23
-rwxr-xr-xinstall.sh6
-rw-r--r--requirements.txt1
-rwxr-xr-xrun_tests.sh3
-rw-r--r--samples/container_ping_vm.yaml57
-rw-r--r--samples/fio_volume.yaml74
-rw-r--r--samples/migrate-node-context.yaml40
-rw-r--r--samples/ping.yaml11
-rw-r--r--tests/ci/cover.sh10
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc001.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc002.yaml12
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc005.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc009.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc010.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc011.yaml13
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc012.yaml12
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc014.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml187
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc038.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc069.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc070.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc071.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc072.yaml11
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc076.yaml11
-rw-r--r--tests/opnfv/test_suites/opnfv_os-odl-sfc-ha_daily.yaml62
-rw-r--r--tests/unit/apiserver/resources/test_env_action.py4
-rw-r--r--tests/unit/benchmark/contexts/test_model.py4
-rw-r--r--tests/unit/benchmark/contexts/test_ovsdpdk.py7
-rw-r--r--tests/unit/benchmark/contexts/test_sriov.py14
-rw-r--r--tests/unit/benchmark/core/test_task.py14
-rw-r--r--tests/unit/benchmark/scenarios/compute/test_qemumigrate.py166
-rw-r--r--tests/unit/benchmark/scenarios/lib/__init__.py0
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_add_memory_load.py65
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_check_numa_info.py84
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_check_value.py46
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py51
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_get_numa_info.py106
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_get_server.py50
-rw-r--r--tests/unit/benchmark/scenarios/lib/test_get_server_ip.py41
-rw-r--r--tests/unit/orchestrator/test_heat.py6
-rw-r--r--yardstick/benchmark/contexts/model.py32
-rw-r--r--yardstick/benchmark/contexts/ovsdpdk.py2
-rw-r--r--yardstick/benchmark/contexts/sriov.py2
-rw-r--r--yardstick/benchmark/core/plugin.py4
-rw-r--r--yardstick/benchmark/core/task.py16
-rw-r--r--yardstick/benchmark/scenarios/base.py12
-rw-r--r--yardstick/benchmark/scenarios/compute/qemu_migrate.py155
-rw-r--r--yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash68
-rw-r--r--yardstick/benchmark/scenarios/lib/__init__.py0
-rw-r--r--yardstick/benchmark/scenarios/lib/add_memory_load.py57
-rw-r--r--yardstick/benchmark/scenarios/lib/check_numa_info.py61
-rw-r--r--yardstick/benchmark/scenarios/lib/check_value.py58
-rw-r--r--yardstick/benchmark/scenarios/lib/get_migrate_target_host.py56
-rw-r--r--yardstick/benchmark/scenarios/lib/get_numa_info.py79
-rw-r--r--yardstick/benchmark/scenarios/lib/get_server.py83
-rw-r--r--yardstick/benchmark/scenarios/lib/get_server_ip.py38
-rw-r--r--yardstick/benchmark/scenarios/lib/migrate.py155
-rwxr-xr-xyardstick/cmd/NSBperf.py18
-rw-r--r--yardstick/cmd/commands/task.py8
-rw-r--r--yardstick/common/openstack_utils.py24
-rw-r--r--yardstick/dispatcher/base.py6
-rw-r--r--yardstick/orchestrator/heat.py51
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
new file mode 100644
index 000000000..652790530
--- /dev/null
+++ b/gui/app/favicon.ico
Binary files differ
diff --git a/gui/app/images/back.png b/gui/app/images/back.png
new file mode 100644
index 000000000..917c86edd
--- /dev/null
+++ b/gui/app/images/back.png
Binary files differ
diff --git a/gui/app/images/checkno.png b/gui/app/images/checkno.png
new file mode 100644
index 000000000..7c6841930
--- /dev/null
+++ b/gui/app/images/checkno.png
Binary files differ
diff --git a/gui/app/images/checkyes.png b/gui/app/images/checkyes.png
new file mode 100644
index 000000000..ef6028310
--- /dev/null
+++ b/gui/app/images/checkyes.png
Binary files differ
diff --git a/gui/app/images/close.png b/gui/app/images/close.png
new file mode 100644
index 000000000..0d2c14252
--- /dev/null
+++ b/gui/app/images/close.png
Binary files differ
diff --git a/gui/app/images/loading.gif b/gui/app/images/loading.gif
new file mode 100644
index 000000000..b04dd11bc
--- /dev/null
+++ b/gui/app/images/loading.gif
Binary files differ
diff --git a/gui/app/images/loading2.gif b/gui/app/images/loading2.gif
new file mode 100644
index 000000000..9d1534468
--- /dev/null
+++ b/gui/app/images/loading2.gif
Binary files differ
diff --git a/gui/app/images/logo.png b/gui/app/images/logo.png
new file mode 100644
index 000000000..c67c0d635
--- /dev/null
+++ b/gui/app/images/logo.png
Binary files differ
diff --git a/gui/app/images/statusno.png b/gui/app/images/statusno.png
new file mode 100644
index 000000000..ace4a454d
--- /dev/null
+++ b/gui/app/images/statusno.png
Binary files differ
diff --git a/gui/app/images/statusyes.png b/gui/app/images/statusyes.png
new file mode 100644
index 000000000..d88a99ee8
--- /dev/null
+++ b/gui/app/images/statusyes.png
Binary files differ
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
new file mode 100644
index 000000000..92497addf
--- /dev/null
+++ b/gui/app/images/yeoman.png
Binary files differ
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',