summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--ansible/inventory.ini5
-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/__init__.py65
-rw-r--r--api/base.py65
-rw-r--r--api/database/v2/__init__.py0
-rw-r--r--api/database/v2/handlers.py200
-rw-r--r--api/database/v2/models.py100
-rw-r--r--api/resources/asynctask.py64
-rw-r--r--api/resources/case_docs.py30
-rw-r--r--api/resources/env_action.py427
-rw-r--r--api/resources/release_action.py44
-rw-r--r--api/resources/results.py69
-rw-r--r--api/resources/samples_action.py45
-rw-r--r--api/resources/testcases.py21
-rw-r--r--api/resources/testsuites_action.py46
-rw-r--r--api/resources/v1/__init__.py0
-rw-r--r--api/resources/v1/asynctasks.py65
-rw-r--r--api/resources/v1/env.py439
-rw-r--r--api/resources/v1/results.py78
-rw-r--r--api/resources/v1/testcases.py115
-rw-r--r--api/resources/v1/testsuites.py64
-rw-r--r--api/resources/v2/__init__.py0
-rw-r--r--api/resources/v2/containers.py383
-rw-r--r--api/resources/v2/environments.py125
-rw-r--r--api/resources/v2/images.py82
-rw-r--r--api/resources/v2/openrcs.py219
-rw-r--r--api/resources/v2/pods.py108
-rw-r--r--api/resources/v2/projects.py105
-rw-r--r--api/resources/v2/tasks.py254
-rw-r--r--api/resources/v2/testcases.py70
-rw-r--r--api/resources/v2/testsuites.py89
-rw-r--r--api/server.py35
-rw-r--r--api/urls.py55
-rw-r--r--api/utils/common.py44
-rw-r--r--api/utils/thread.py46
-rw-r--r--api/views.py82
-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--docs/release/release-notes/release-notes.rst66
-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/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.html155
-rw-r--r--gui/app/views/layout/footer.html5
-rw-r--r--gui/app/views/layout/header.html43
-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.html149
-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.html150
-rw-r--r--gui/app/views/uploadImage.html145
-rw-r--r--gui/bower.json45
-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.sh5
-rw-r--r--plugin/CI/storperf.yaml4
-rw-r--r--requirements.txt4
-rw-r--r--samples/ping_k8s.yaml46
-rw-r--r--samples/storperf.yaml1
-rw-r--r--samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml42
-rwxr-xr-xtests/ci/prepare_env.sh8
-rw-r--r--tests/ci/scp_storperf_files.sh29
-rwxr-xr-xtests/ci/yardstick-verify15
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml9
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml176
-rw-r--r--tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml3
-rw-r--r--tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml6
-rw-r--r--tests/unit/apiserver/utils/test_common.py41
-rw-r--r--tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml32
-rw-r--r--tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml63
-rw-r--r--tests/unit/benchmark/contexts/nodes_sample_new.yaml96
-rw-r--r--tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml82
-rw-r--r--tests/unit/benchmark/contexts/nodes_sample_ovs.yaml104
-rw-r--r--tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml104
-rw-r--r--tests/unit/benchmark/contexts/ovs_sample_password.yaml104
-rw-r--r--tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml69
-rw-r--r--tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt1
-rw-r--r--tests/unit/benchmark/contexts/sriov_sample_password.yaml52
-rw-r--r--tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml54
-rw-r--r--tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt1
-rw-r--r--tests/unit/benchmark/contexts/test_heat.py111
-rw-r--r--tests/unit/benchmark/contexts/test_kubernetes.py165
-rw-r--r--tests/unit/benchmark/contexts/test_model.py35
-rw-r--r--tests/unit/benchmark/contexts/test_node.py44
-rw-r--r--tests/unit/benchmark/contexts/test_ovsdpdk.py325
-rw-r--r--tests/unit/benchmark/contexts/test_sriov.py421
-rw-r--r--tests/unit/benchmark/contexts/test_standalone.py633
-rw-r--r--tests/unit/benchmark/core/test_task.py81
-rw-r--r--tests/unit/benchmark/runner/test_base.py10
-rw-r--r--tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py26
-rw-r--r--tests/unit/benchmark/scenarios/availability/test_monitor_command.py8
-rw-r--r--tests/unit/benchmark/scenarios/availability/test_monitor_multi.py2
-rw-r--r--tests/unit/benchmark/scenarios/availability/test_util.py19
-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/benchmark/scenarios/networking/test_nstat.py6
-rw-r--r--tests/unit/benchmark/scenarios/networking/test_pktgen.py534
-rw-r--r--tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py19
-rw-r--r--tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py17
-rw-r--r--tests/unit/benchmark/scenarios/networking/test_vnf_generic.py497
-rw-r--r--tests/unit/benchmark/scenarios/storage/test_storperf.py4
-rw-r--r--tests/unit/cmd/test_NSBperf.py2
-rw-r--r--tests/unit/common/test_utils.py55
-rw-r--r--tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py6
-rw-r--r--tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py12
-rw-r--r--tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py11
-rw-r--r--tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py44
-rw-r--r--tests/unit/orchestrator/test_heat.py70
-rw-r--r--tests/unit/orchestrator/test_kubernetes.py110
-rw-r--r--yardstick/benchmark/contexts/base.py32
-rw-r--r--yardstick/benchmark/contexts/dummy.py3
-rw-r--r--yardstick/benchmark/contexts/heat.py148
-rw-r--r--yardstick/benchmark/contexts/kubernetes.py140
-rw-r--r--yardstick/benchmark/contexts/model.py36
-rw-r--r--yardstick/benchmark/contexts/node.py30
-rw-r--r--yardstick/benchmark/contexts/ovsdpdk.py369
-rw-r--r--yardstick/benchmark/contexts/sriov.py431
-rw-r--r--yardstick/benchmark/contexts/standalone.py122
-rw-r--r--yardstick/benchmark/core/plugin.py8
-rw-r--r--yardstick/benchmark/core/task.py53
-rw-r--r--yardstick/benchmark/core/testsuite.py42
-rw-r--r--yardstick/benchmark/scenarios/availability/actionplayers.py4
-rw-r--r--yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py5
-rw-r--r--yardstick/benchmark/scenarios/availability/attacker/attacker_process.py4
-rw-r--r--yardstick/benchmark/scenarios/availability/attacker_conf.yaml7
-rw-r--r--yardstick/benchmark/scenarios/availability/director.py7
-rwxr-xr-xyardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash42
-rwxr-xr-xyardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash65
-rw-r--r--yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash2
-rw-r--r--yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash2
-rw-r--r--yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash2
-rwxr-xr-xyardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash70
-rw-r--r--yardstick/benchmark/scenarios/availability/monitor/monitor_command.py5
-rw-r--r--yardstick/benchmark/scenarios/availability/monitor_conf.yaml2
-rw-r--r--yardstick/benchmark/scenarios/availability/operation/baseoperation.py1
-rw-r--r--yardstick/benchmark/scenarios/availability/operation/operation_general.py20
-rw-r--r--yardstick/benchmark/scenarios/availability/scenario_general.py4
-rw-r--r--yardstick/benchmark/scenarios/availability/util.py33
-rw-r--r--yardstick/benchmark/scenarios/base.py12
-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
-rw-r--r--yardstick/benchmark/scenarios/networking/iperf3.py17
-rw-r--r--yardstick/benchmark/scenarios/networking/pktgen.py242
-rw-r--r--yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash139
-rw-r--r--yardstick/benchmark/scenarios/networking/vnf_generic.py106
-rw-r--r--yardstick/benchmark/scenarios/storage/fio.py32
-rw-r--r--yardstick/benchmark/scenarios/storage/storperf.py7
-rwxr-xr-xyardstick/cmd/NSBperf.py5
-rw-r--r--yardstick/common/constants.py16
-rw-r--r--yardstick/common/kubernetes_utils.py137
-rw-r--r--yardstick/common/utils.py76
-rw-r--r--yardstick/dispatcher/base.py6
-rw-r--r--yardstick/network_services/vnf_generic/vnf/base.py1
-rw-r--r--yardstick/network_services/vnf_generic/vnfdgen.py2
-rw-r--r--yardstick/orchestrator/heat.py46
-rw-r--r--yardstick/orchestrator/kubernetes.py130
-rw-r--r--yardstick/vTC/apexlake/tests/deployment_unit_test.py21
-rw-r--r--yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py49
-rw-r--r--yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py6
239 files changed, 18980 insertions, 1710 deletions
diff --git a/.gitignore b/.gitignore
index f2c8fd9c0..d98b4a070 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*.DS_Store
+*.log
*.pyc
.vimrc
.ropeproject
@@ -15,14 +17,13 @@ build
htmlcov
.agignore
.coverage
+*.retry
Session*.vim
.tags*
.coverage.*
*~
setuptools*zip
dist/
-pep8.log
-test.log
.testrepository/
cover/
.*.sw?
diff --git a/ansible/inventory.ini b/ansible/inventory.ini
index 440e625bd..79a6ee0aa 100644
--- a/ansible/inventory.ini
+++ b/ansible/inventory.ini
@@ -1,8 +1,11 @@
[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
host5 ansible_host=10.1.0.54 ansible_user=root ansible_ssh_pass=root
+
+[nodes:children]
+controller
+compute
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/__init__.py b/api/__init__.py
index c6cbbf104..c5aefffe8 100644
--- a/api/__init__.py
+++ b/api/__init__.py
@@ -1,4 +1,67 @@
-from yardstick import _init_logging
+##############################################################################
+# Copyright (c) 2016 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 absolute_import
+import logging
+
+from flask import request
+from flask_restful import Resource
+from yardstick import _init_logging
+from yardstick.common import constants as consts
+from yardstick.common import utils as common_utils
_init_logging()
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class ApiResource(Resource):
+
+ def _post_args(self):
+ data = request.json if request.json else {}
+ params = common_utils.translate_to_str(data)
+ action = params.get('action', request.form.get('action', ''))
+ args = params.get('args', {})
+
+ try:
+ args['file'] = request.files['file']
+ except KeyError:
+ pass
+
+ args.update({k: v for k, v in request.form.items()})
+ LOG.debug('Input args is: action: %s, args: %s', action, args)
+
+ return action, args
+
+ def _get_args(self):
+ args = common_utils.translate_to_str(request.args)
+ LOG.debug('Input args is: args: %s', args)
+
+ return args
+
+ def _dispatch_post(self, **kwargs):
+ action, args = self._post_args()
+ args.update(kwargs)
+ return self._dispatch(args, action)
+
+ def _dispatch(self, args, action):
+ try:
+ return getattr(self, action)(args)
+ except AttributeError:
+ common_utils.result_handler(consts.API_ERROR, 'No such action')
+
+
+class Url(object):
+
+ def __init__(self, url, target):
+ super(Url, self).__init__()
+ self.url = url
+ self.target = target
+
+common_utils.import_modules_from_package("api.resources")
diff --git a/api/base.py b/api/base.py
deleted file mode 100644
index 0f1e76a57..000000000
--- a/api/base.py
+++ /dev/null
@@ -1,65 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import re
-import importlib
-import logging
-
-from flask import request
-from flask_restful import Resource
-
-from api.utils import common as common_utils
-from yardstick.common import constants as consts
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-class ApiResource(Resource):
-
- def _post_args(self):
- data = request.json if request.json else {}
- params = common_utils.translate_to_str(data)
- action = params.get('action', request.form.get('action', ''))
- args = params.get('args', {})
-
- try:
- args['file'] = request.files['file']
- except KeyError:
- pass
-
- logger.debug('Input args is: action: %s, args: %s', action, args)
-
- return action, args
-
- def _get_args(self):
- args = common_utils.translate_to_str(request.args)
- logger.debug('Input args is: args: %s', args)
-
- return args
-
- def _dispatch_post(self):
- action, args = self._post_args()
- return self._dispatch(args, action)
-
- def _dispatch_get(self, **kwargs):
- args = self._get_args()
- args.update(kwargs)
- return self._dispatch(args)
-
- def _dispatch(self, args, action='default'):
- module_name = re.sub(r'([A-Z][a-z]*)', r'_\1',
- self.__class__.__name__)[1:].lower()
-
- module_name = 'api.resources.%s' % module_name
- resources = importlib.import_module(module_name)
- try:
- return getattr(resources, action)(args)
- except AttributeError:
- common_utils.result_handler(consts.API_ERROR, 'No such action')
diff --git a/api/database/v2/__init__.py b/api/database/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/api/database/v2/__init__.py
diff --git a/api/database/v2/handlers.py b/api/database/v2/handlers.py
new file mode 100644
index 000000000..1bc32bf0e
--- /dev/null
+++ b/api/database/v2/handlers.py
@@ -0,0 +1,200 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from api.database import db_session
+from api.database.v2.models import V2Environment
+from api.database.v2.models import V2Openrc
+from api.database.v2.models import V2Image
+from api.database.v2.models import V2Pod
+from api.database.v2.models import V2Container
+from api.database.v2.models import V2Project
+from api.database.v2.models import V2Task
+
+
+class V2EnvironmentHandler(object):
+
+ def insert(self, kwargs):
+ environment = V2Environment(**kwargs)
+ db_session.add(environment)
+ db_session.commit()
+ return environment
+
+ def list_all(self):
+ return V2Environment.query.all()
+
+ def get_by_uuid(self, uuid):
+ environment = V2Environment.query.filter_by(uuid=uuid).first()
+ if not environment:
+ raise ValueError
+ return environment
+
+ def update_attr(self, uuid, attr):
+ environment = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ setattr(environment, k, v)
+ db_session.commit()
+
+ def append_attr(self, uuid, attr):
+ environment = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ value = getattr(environment, k)
+ new = '{},{}'.format(value, v) if value else v
+ setattr(environment, k, new)
+ db_session.commit()
+
+ def delete_by_uuid(self, uuid):
+ environment = self.get_by_uuid(uuid)
+ db_session.delete(environment)
+ db_session.commit()
+
+
+class V2OpenrcHandler(object):
+
+ def insert(self, kwargs):
+ openrc = V2Openrc(**kwargs)
+ db_session.add(openrc)
+ db_session.commit()
+ return openrc
+
+ def get_by_uuid(self, uuid):
+ openrc = V2Openrc.query.filter_by(uuid=uuid).first()
+ if not openrc:
+ raise ValueError
+ return openrc
+
+ def delete_by_uuid(self, uuid):
+ openrc = self.get_by_uuid(uuid)
+ db_session.delete(openrc)
+ db_session.commit()
+
+
+class V2ImageHandler(object):
+
+ def insert(self, kwargs):
+ image = V2Image(**kwargs)
+ db_session.add(image)
+ db_session.commit()
+ return image
+
+ def get_by_uuid(self, uuid):
+ image = V2Image.query.filter_by(uuid=uuid).first()
+ if not image:
+ raise ValueError
+ return image
+
+
+class V2PodHandler(object):
+
+ def insert(self, kwargs):
+ pod = V2Pod(**kwargs)
+ db_session.add(pod)
+ db_session.commit()
+ return pod
+
+ def get_by_uuid(self, uuid):
+ pod = V2Pod.query.filter_by(uuid=uuid).first()
+ if not pod:
+ raise ValueError
+ return pod
+
+ def delete_by_uuid(self, uuid):
+ pod = self.get_by_uuid(uuid)
+ db_session.delete(pod)
+ db_session.commit()
+
+
+class V2ContainerHandler(object):
+
+ def insert(self, kwargs):
+ container = V2Container(**kwargs)
+ db_session.add(container)
+ db_session.commit()
+ return container
+
+ def get_by_uuid(self, uuid):
+ container = V2Container.query.filter_by(uuid=uuid).first()
+ if not container:
+ raise ValueError
+ return container
+
+ def update_attr(self, uuid, attr):
+ container = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ setattr(container, k, v)
+ db_session.commit()
+
+ def delete_by_uuid(self, uuid):
+ container = self.get_by_uuid(uuid)
+ db_session.delete(container)
+ db_session.commit()
+
+
+class V2ProjectHandler(object):
+
+ def list_all(self):
+ return V2Project.query.all()
+
+ def insert(self, kwargs):
+ project = V2Project(**kwargs)
+ db_session.add(project)
+ db_session.commit()
+ return project
+
+ def get_by_uuid(self, uuid):
+ project = V2Project.query.filter_by(uuid=uuid).first()
+ if not project:
+ raise ValueError
+ return project
+
+ def update_attr(self, uuid, attr):
+ project = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ setattr(project, k, v)
+ db_session.commit()
+
+ def append_attr(self, uuid, attr):
+ project = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ value = getattr(project, k)
+ new = '{},{}'.format(value, v) if value else v
+ setattr(project, k, new)
+ db_session.commit()
+
+ def delete_by_uuid(self, uuid):
+ project = self.get_by_uuid(uuid)
+ db_session.delete(project)
+ db_session.commit()
+
+
+class V2TaskHandler(object):
+
+ def list_all(self):
+ return V2Task.query.all()
+
+ def insert(self, kwargs):
+ task = V2Task(**kwargs)
+ db_session.add(task)
+ db_session.commit()
+ return task
+
+ def get_by_uuid(self, uuid):
+ task = V2Task.query.filter_by(uuid=uuid).first()
+ if not task:
+ raise ValueError
+ return task
+
+ def update_attr(self, uuid, attr):
+ task = self.get_by_uuid(uuid)
+ for k, v in attr.items():
+ setattr(task, k, v)
+ db_session.commit()
+
+ def delete_by_uuid(self, uuid):
+ task = self.get_by_uuid(uuid)
+ db_session.delete(task)
+ db_session.commit()
diff --git a/api/database/v2/models.py b/api/database/v2/models.py
new file mode 100644
index 000000000..1e85559cb
--- /dev/null
+++ b/api/database/v2/models.py
@@ -0,0 +1,100 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from __future__ import absolute_import
+from sqlalchemy import Column
+from sqlalchemy import Integer
+from sqlalchemy import String
+from sqlalchemy import Text
+from sqlalchemy import DateTime
+from sqlalchemy import Boolean
+
+from api.database import Base
+
+
+class V2Environment(Base):
+ __tablename__ = 'v2_environment'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ description = Column(Text)
+ openrc_id = Column(String(10))
+ image_id = Column(String(30))
+ container_id = Column(Text)
+ pod_id = Column(String(10))
+ time = Column(DateTime)
+
+
+class V2Openrc(Base):
+ __tablename__ = 'v2_openrc'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ description = Column(Text)
+ environment_id = Column(String(30))
+ content = Column(Text)
+ time = Column(DateTime)
+
+
+class V2Image(Base):
+ __tablename__ = 'v2_image'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ description = Column(Text)
+ environment_id = Column(String(30))
+ size = Column(String(30))
+ status = Column(String(30))
+ time = Column(DateTime)
+
+
+class V2Container(Base):
+ __tablename__ = 'v2_container'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ environment_id = Column(String(30))
+ status = Column(Integer)
+ port = Column(Integer)
+ time = Column(String(30))
+
+
+class V2Pod(Base):
+ __tablename__ = 'v2_pod'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ environment_id = Column(String(30))
+ content = Column(Text)
+ time = Column(String(30))
+
+
+class V2Project(Base):
+ __tablename__ = 'v2_project'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ description = Column(Text)
+ time = Column(DateTime)
+ tasks = Column(Text)
+
+
+class V2Task(Base):
+ __tablename__ = 'v2_task'
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(30))
+ name = Column(String(30))
+ description = Column(Text)
+ project_id = Column(String(30))
+ environment_id = Column(String(30))
+ time = Column(DateTime)
+ case_name = Column(String(30))
+ suite = Column(Boolean)
+ content = Column(Text)
+ result = Column(Text)
+ error = Column(Text)
+ status = Column(Integer)
diff --git a/api/resources/asynctask.py b/api/resources/asynctask.py
deleted file mode 100644
index 39b47c0ee..000000000
--- a/api/resources/asynctask.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# ############################################################################
-# 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 uuid
-import logging
-
-from api.utils.common import result_handler
-from api.database.v1.handlers import AsyncTaskHandler
-from yardstick.common import constants as consts
-
-LOG = logging.getLogger(__name__)
-LOG.setLevel(logging.DEBUG)
-
-
-def default(args):
- return _get_status(args)
-
-
-def _get_status(args):
- try:
- task_id = args['task_id']
- except KeyError:
- return result_handler(consts.API_ERROR, 'task_id must be provided')
-
- try:
- uuid.UUID(task_id)
- except ValueError:
- return result_handler(consts.API_ERROR, 'invalid task_id')
-
- asynctask_handler = AsyncTaskHandler()
- try:
- asynctask = asynctask_handler.get_task_by_taskid(task_id)
- except ValueError:
- return result_handler(consts.API_ERROR, 'invalid task_id')
-
- def _unfinished():
- return result_handler(consts.TASK_NOT_DONE, {})
-
- def _finished():
- return result_handler(consts.TASK_DONE, {})
-
- def _error():
- return result_handler(consts.TASK_FAILED, asynctask.error)
-
- status = asynctask.status
- LOG.debug('Task status is: %s', status)
-
- if status not in [consts.TASK_NOT_DONE,
- consts.TASK_DONE,
- consts.TASK_FAILED]:
- return result_handler(consts.API_ERROR, 'internal server error')
-
- switcher = {
- consts.TASK_NOT_DONE: _unfinished,
- consts.TASK_DONE: _finished,
- consts.TASK_FAILED: _error
- }
-
- return switcher.get(status)()
diff --git a/api/resources/case_docs.py b/api/resources/case_docs.py
deleted file mode 100644
index 289410d2d..000000000
--- a/api/resources/case_docs.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-import logging
-
-from api.utils.common import result_handler
-from yardstick.common import constants as consts
-
-LOG = logging.getLogger(__name__)
-LOG.setLevel(logging.DEBUG)
-
-
-def default(args):
- return get_case_docs(args)
-
-
-def get_case_docs(args):
- try:
- case_name = args['case_name']
- except KeyError:
- return result_handler(consts.API_ERROR, 'case_name must be provided')
-
- docs_path = os.path.join(consts.DOCS_DIR, '{}.rst'.format(case_name))
-
- if not os.path.exists(docs_path):
- return result_handler(consts.API_ERROR, 'case not exists')
-
- LOG.info('Reading %s', case_name)
- with open(docs_path) as f:
- content = f.read()
-
- return result_handler(consts.API_SUCCESS, {'docs': content})
diff --git a/api/resources/env_action.py b/api/resources/env_action.py
deleted file mode 100644
index fed987063..000000000
--- a/api/resources/env_action.py
+++ /dev/null
@@ -1,427 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-
-import errno
-import logging
-import os
-import subprocess
-import threading
-import time
-import uuid
-import glob
-import yaml
-import collections
-from subprocess import PIPE
-
-from six.moves import configparser
-from oslo_serialization import jsonutils
-from docker import Client
-
-from api.database.v1.handlers import AsyncTaskHandler
-from api.utils import influx
-from api.utils.common import result_handler
-from yardstick.common import constants as consts
-from yardstick.common import utils as common_utils
-from yardstick.common import openstack_utils
-from yardstick.common.httpClient import HttpClient
-
-
-LOG = logging.getLogger(__name__)
-LOG.setLevel(logging.DEBUG)
-
-async_handler = AsyncTaskHandler()
-
-
-def create_grafana(args):
- task_id = str(uuid.uuid4())
-
- thread = threading.Thread(target=_create_grafana, args=(task_id,))
- thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
-
-
-def _create_grafana(task_id):
- _create_task(task_id)
-
- client = Client(base_url=consts.DOCKER_URL)
-
- try:
- LOG.info('Checking if grafana image exist')
- image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
- if not _check_image_exist(client, image):
- LOG.info('Grafana image not exist, start pulling')
- client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
-
- LOG.info('Createing grafana container')
- _create_grafana_container(client)
- LOG.info('Grafana container is created')
-
- time.sleep(5)
-
- LOG.info('Creating data source for grafana')
- _create_data_source()
-
- LOG.info('Creating dashboard for grafana')
- _create_dashboard()
-
- _update_task_status(task_id)
- LOG.info('Finished')
- except Exception as e:
- _update_task_error(task_id, str(e))
- LOG.exception('Create grafana failed')
-
-
-def _create_dashboard():
- url = 'http://admin:admin@%s:3000/api/dashboards/db' % consts.GRAFANA_IP
- path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json')
-
- for i in sorted(glob.iglob(path)):
- with open(i) as f:
- data = jsonutils.load(f)
- try:
- HttpClient().post(url, data)
- except Exception:
- LOG.exception('Create dashboard %s failed', i)
- raise
-
-
-def _create_data_source():
- url = 'http://admin:admin@%s:3000/api/datasources' % consts.GRAFANA_IP
- data = {
- "name": "yardstick",
- "type": "influxdb",
- "access": "proxy",
- "url": "http://%s:8086" % consts.INFLUXDB_IP,
- "password": "root",
- "user": "root",
- "database": "yardstick",
- "basicAuth": True,
- "basicAuthUser": "admin",
- "basicAuthPassword": "admin",
- "isDefault": False,
- }
- try:
- HttpClient().post(url, data)
- except Exception:
- LOG.exception('Create datasources failed')
- raise
-
-
-def _create_grafana_container(client):
- ports = [3000]
- port_bindings = {k: k for k in ports}
- restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
- host_config = client.create_host_config(port_bindings=port_bindings,
- restart_policy=restart_policy)
-
- LOG.info('Creating container')
- container = client.create_container(image='%s:%s' % (consts.GRAFANA_IMAGE,
- consts.GRAFANA_TAG),
- ports=ports,
- detach=True,
- tty=True,
- host_config=host_config)
- LOG.info('Starting container')
- client.start(container)
-
-
-def _check_image_exist(client, t):
- return any(t in a['RepoTags'][0] for a in client.images() if a['RepoTags'])
-
-
-def create_influxdb(args):
- task_id = str(uuid.uuid4())
-
- thread = threading.Thread(target=_create_influxdb, args=(task_id,))
- thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
-
-
-def _create_influxdb(task_id):
- _create_task(task_id)
-
- client = Client(base_url=consts.DOCKER_URL)
-
- try:
- LOG.info('Changing output to influxdb')
- _change_output_to_influxdb()
-
- LOG.info('Checking if influxdb image exist')
- if not _check_image_exist(client, '%s:%s' % (consts.INFLUXDB_IMAGE,
- consts.INFLUXDB_TAG)):
- LOG.info('Influxdb image not exist, start pulling')
- client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG)
-
- LOG.info('Createing influxdb container')
- _create_influxdb_container(client)
- LOG.info('Influxdb container is created')
-
- time.sleep(5)
-
- LOG.info('Config influxdb')
- _config_influxdb()
-
- _update_task_status(task_id)
-
- LOG.info('Finished')
- except Exception as e:
- _update_task_error(task_id, str(e))
- LOG.exception('Creating influxdb failed')
-
-
-def _create_influxdb_container(client):
-
- ports = [8083, 8086]
- port_bindings = {k: k for k in ports}
- restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
- host_config = client.create_host_config(port_bindings=port_bindings,
- restart_policy=restart_policy)
-
- LOG.info('Creating container')
- container = client.create_container(image='%s:%s' % (consts.INFLUXDB_IMAGE,
- consts.INFLUXDB_TAG),
- ports=ports,
- detach=True,
- tty=True,
- host_config=host_config)
- LOG.info('Starting container')
- client.start(container)
-
-
-def _config_influxdb():
- try:
- client = influx.get_data_db_client()
- client.create_user(consts.INFLUXDB_USER,
- consts.INFLUXDB_PASS,
- consts.INFLUXDB_DB_NAME)
- client.create_database(consts.INFLUXDB_DB_NAME)
- LOG.info('Success to config influxDB')
- except Exception:
- LOG.exception('Config influxdb failed')
-
-
-def _change_output_to_influxdb():
- common_utils.makedirs(consts.CONF_DIR)
-
- parser = configparser.ConfigParser()
- LOG.info('Reading output sample configuration')
- parser.read(consts.CONF_SAMPLE_FILE)
-
- LOG.info('Set dispatcher to influxdb')
- parser.set('DEFAULT', 'dispatcher', 'influxdb')
- parser.set('dispatcher_influxdb', 'target',
- 'http://%s:8086' % consts.INFLUXDB_IP)
-
- LOG.info('Writing to %s', consts.CONF_FILE)
- with open(consts.CONF_FILE, 'w') as f:
- parser.write(f)
-
-
-def prepare_env(args):
- task_id = str(uuid.uuid4())
-
- thread = threading.Thread(target=_prepare_env_daemon, args=(task_id,))
- thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
-
-
-def _already_source_openrc():
- """Check if openrc is sourced already"""
- return all(os.environ.get(k) for k in ['OS_AUTH_URL', 'OS_USERNAME',
- 'OS_PASSWORD', 'EXTERNAL_NETWORK'])
-
-
-def _prepare_env_daemon(task_id):
- _create_task(task_id)
-
- try:
- _create_directories()
-
- rc_file = consts.OPENRC
-
- LOG.info('Checkout Openrc Environment variable')
- if not _already_source_openrc():
- LOG.info('Openrc variable not found in Environment')
- if not os.path.exists(rc_file):
- LOG.info('Openrc file not found')
- installer_ip = os.environ.get('INSTALLER_IP', '192.168.200.2')
- installer_type = os.environ.get('INSTALLER_TYPE', 'compass')
- LOG.info('Getting openrc file from %s', installer_type)
- _get_remote_rc_file(rc_file, installer_ip, installer_type)
- LOG.info('Source openrc file')
- _source_file(rc_file)
- LOG.info('Appending external network')
- _append_external_network(rc_file)
- LOG.info('Openrc file exist, source openrc file')
- _source_file(rc_file)
-
- LOG.info('Cleaning images')
- _clean_images()
-
- LOG.info('Loading images')
- _load_images()
-
- _update_task_status(task_id)
- LOG.info('Finished')
- except Exception as e:
- _update_task_error(task_id, str(e))
- LOG.exception('Prepare env failed')
-
-
-def _create_directories():
- common_utils.makedirs(consts.CONF_DIR)
-
-
-def _source_file(rc_file):
- common_utils.source_env(rc_file)
-
-
-def _get_remote_rc_file(rc_file, installer_ip, installer_type):
-
- os_fetch_script = os.path.join(consts.RELENG_DIR, consts.FETCH_SCRIPT)
-
- try:
- cmd = [os_fetch_script, '-d', rc_file, '-i', installer_type,
- '-a', installer_ip]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- p.communicate()
-
- if p.returncode != 0:
- LOG.error('Failed to fetch credentials from installer')
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
-
-
-def _append_external_network(rc_file):
- neutron_client = openstack_utils.get_neutron_client()
- networks = neutron_client.list_networks()['networks']
- try:
- ext_network = next(n['name'] for n in networks if n['router:external'])
- except StopIteration:
- LOG.warning("Can't find external network")
- else:
- cmd = 'export EXTERNAL_NETWORK=%s' % ext_network
- try:
- with open(rc_file, 'a') as f:
- f.write(cmd + '\n')
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
-
-
-def _clean_images():
- cmd = [consts.CLEAN_IMAGES_SCRIPT]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR)
- output = p.communicate()[0]
- LOG.debug(output)
-
-
-def _load_images():
- cmd = [consts.LOAD_IMAGES_SCRIPT]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR)
- output = p.communicate()[0]
- LOG.debug(output)
-
-
-def _create_task(task_id):
- async_handler.insert({'status': 0, 'task_id': task_id})
-
-
-def _update_task_status(task_id):
- async_handler.update_attr(task_id, {'status': 1})
-
-
-def _update_task_error(task_id, error):
- async_handler.update_attr(task_id, {'status': 2, 'error': error})
-
-
-def update_openrc(args):
- try:
- openrc_vars = args['openrc']
- except KeyError:
- return result_handler(consts.API_ERROR, 'openrc must be provided')
- else:
- if not isinstance(openrc_vars, collections.Mapping):
- return result_handler(consts.API_ERROR, 'args should be a dict')
-
- lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()]
- LOG.debug('Writing: %s', ''.join(lines))
-
- LOG.info('Writing openrc: Writing')
- common_utils.makedirs(consts.CONF_DIR)
-
- with open(consts.OPENRC, 'w') as f:
- f.writelines(lines)
- LOG.info('Writing openrc: Done')
-
- LOG.info('Source openrc: Sourcing')
- try:
- _source_file(consts.OPENRC)
- except Exception as e:
- LOG.exception('Failed to source openrc')
- return result_handler(consts.API_ERROR, str(e))
- LOG.info('Source openrc: Done')
-
- return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars})
-
-
-def upload_pod_file(args):
- try:
- pod_file = args['file']
- except KeyError:
- return result_handler(consts.API_ERROR, 'file must be provided')
-
- LOG.info('Checking file')
- data = yaml.load(pod_file.read())
- if not isinstance(data, collections.Mapping):
- return result_handler(consts.API_ERROR, 'invalid yaml file')
-
- LOG.info('Writing file')
- with open(consts.POD_FILE, 'w') as f:
- yaml.dump(data, f, default_flow_style=False)
- LOG.info('Writing finished')
-
- return result_handler(consts.API_SUCCESS, {'pod_info': data})
-
-
-def update_pod_file(args):
- try:
- pod_dic = args['pod']
- except KeyError:
- return result_handler(consts.API_ERROR, 'pod must be provided')
- else:
- if not isinstance(pod_dic, collections.Mapping):
- return result_handler(consts.API_ERROR, 'pod should be a dict')
-
- LOG.info('Writing file')
- with open(consts.POD_FILE, 'w') as f:
- yaml.dump(pod_dic, f, default_flow_style=False)
- LOG.info('Writing finished')
-
- return result_handler(consts.API_SUCCESS, {'pod_info': pod_dic})
-
-
-def update_hosts(hosts_ip):
- if not isinstance(hosts_ip, dict):
- return result_handler(consts.API_ERROR, 'Error, args should be a dict')
- LOG.info('Writing hosts: Writing')
- LOG.debug('Writing: %s', hosts_ip)
- cmd = ["sudo", "python", "write_hosts.py"]
- p = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE,
- cwd = os.path.join(consts.REPOS_DIR, "api/resources"))
- _, err = p.communicate(jsonutils.dumps(hosts_ip))
- if p.returncode != 0 :
- return result_handler(consts.API_ERROR, err)
- LOG.info('Writing hosts: Done')
- return result_handler(consts.API_SUCCESS, 'success')
diff --git a/api/resources/release_action.py b/api/resources/release_action.py
deleted file mode 100644
index 9871c1fc3..000000000
--- a/api/resources/release_action.py
+++ /dev/null
@@ -1,44 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import uuid
-import os
-import logging
-
-from api.utils.common import result_handler
-from api.utils.thread import TaskThread
-from yardstick.common import constants as consts
-from yardstick.benchmark.core import Param
-from yardstick.benchmark.core.task import Task
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-def run_test_case(args):
- try:
- case_name = args['testcase']
- except KeyError:
- return result_handler(consts.API_ERROR, 'testcase must be provided')
-
- testcase = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name))
-
- task_id = str(uuid.uuid4())
-
- task_args = {
- 'inputfile': [testcase],
- 'task_id': task_id
- }
- task_args.update(args.get('opts', {}))
-
- param = Param(task_args)
- task_thread = TaskThread(Task().start, param)
- task_thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
diff --git a/api/resources/results.py b/api/resources/results.py
deleted file mode 100644
index 692e00cc6..000000000
--- a/api/resources/results.py
+++ /dev/null
@@ -1,69 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import logging
-import uuid
-import json
-
-from api.utils.common import result_handler
-from api.database.v1.handlers import TasksHandler
-from yardstick.common import constants as consts
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-def default(args):
- return getResult(args)
-
-
-def getResult(args):
- try:
- task_id = args['task_id']
- except KeyError:
- return result_handler(consts.API_ERROR, 'task_id must be provided')
-
- try:
- uuid.UUID(task_id)
- except ValueError:
- return result_handler(consts.API_ERROR, 'invalid task_id')
-
- task_handler = TasksHandler()
- try:
- task = task_handler.get_task_by_taskid(task_id)
- except ValueError:
- return result_handler(consts.API_ERROR, 'invalid task_id')
-
- def _unfinished():
- return result_handler(consts.TASK_NOT_DONE, {})
-
- def _finished():
- if task.result:
- return result_handler(consts.TASK_DONE, json.loads(task.result))
- else:
- return result_handler(consts.TASK_DONE, {})
-
- def _error():
- return result_handler(consts.TASK_FAILED, task.error)
-
- status = task.status
- logger.debug('Task status is: %s', status)
-
- if status not in [consts.TASK_NOT_DONE,
- consts.TASK_DONE,
- consts.TASK_FAILED]:
- return result_handler(consts.API_ERROR, 'internal server error')
-
- switcher = {
- consts.TASK_NOT_DONE: _unfinished,
- consts.TASK_DONE: _finished,
- consts.TASK_FAILED: _error
- }
-
- return switcher.get(status)()
diff --git a/api/resources/samples_action.py b/api/resources/samples_action.py
deleted file mode 100644
index 10b9980af..000000000
--- a/api/resources/samples_action.py
+++ /dev/null
@@ -1,45 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import uuid
-import os
-import logging
-
-from api.utils.common import result_handler
-from api.utils.thread import TaskThread
-from yardstick.common import constants as consts
-from yardstick.benchmark.core import Param
-from yardstick.benchmark.core.task import Task
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-def run_test_case(args):
- try:
- case_name = args['testcase']
- except KeyError:
- return result_handler(consts.API_ERROR, 'testcase must be provided')
-
- testcase = os.path.join(consts.SAMPLE_CASE_DIR,
- '{}.yaml'.format(case_name))
-
- task_id = str(uuid.uuid4())
-
- task_args = {
- 'inputfile': [testcase],
- 'task_id': task_id
- }
- task_args.update(args.get('opts', {}))
-
- param = Param(task_args)
- task_thread = TaskThread(Task().start, param)
- task_thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
diff --git a/api/resources/testcases.py b/api/resources/testcases.py
deleted file mode 100644
index 6ee15efb3..000000000
--- a/api/resources/testcases.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# ############################################################################
-# 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 yardstick.benchmark.core.testcase import Testcase
-from yardstick.benchmark.core import Param
-from api.utils import common as common_utils
-
-
-def default(args):
- return listAllTestcases(args)
-
-
-def listAllTestcases(args):
- param = Param(args)
- testcase_list = Testcase().list_all(param)
- return common_utils.result_handler(1, testcase_list)
diff --git a/api/resources/testsuites_action.py b/api/resources/testsuites_action.py
deleted file mode 100644
index e37eacc3e..000000000
--- a/api/resources/testsuites_action.py
+++ /dev/null
@@ -1,46 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import uuid
-import os
-import logging
-
-from api.utils.common import result_handler
-from api.utils.thread import TaskThread
-from yardstick.common import constants as consts
-from yardstick.benchmark.core import Param
-from yardstick.benchmark.core.task import Task
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-def run_test_suite(args):
- try:
- suite_name = args['testsuite']
- except KeyError:
- return result_handler(consts.API_ERROR, 'testsuite must be provided')
-
- testsuite = os.path.join(consts.TESTSUITE_DIR,
- '{}.yaml'.format(suite_name))
-
- task_id = str(uuid.uuid4())
-
- task_args = {
- 'inputfile': [testsuite],
- 'task_id': task_id,
- 'suite': True
- }
- task_args.update(args.get('opts', {}))
-
- param = Param(task_args)
- task_thread = TaskThread(Task().start, param)
- task_thread.start()
-
- return result_handler(consts.API_SUCCESS, {'task_id': task_id})
diff --git a/api/resources/v1/__init__.py b/api/resources/v1/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/api/resources/v1/__init__.py
diff --git a/api/resources/v1/asynctasks.py b/api/resources/v1/asynctasks.py
new file mode 100644
index 000000000..759df214c
--- /dev/null
+++ b/api/resources/v1/asynctasks.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 uuid
+import logging
+
+from api import ApiResource
+from api.database.v1.handlers import AsyncTaskHandler
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V1AsyncTask(ApiResource):
+
+ def get(self):
+ args = self._get_args()
+
+ try:
+ task_id = args['task_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'task_id must be provided')
+
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task_id')
+
+ asynctask_handler = AsyncTaskHandler()
+ try:
+ asynctask = asynctask_handler.get_task_by_taskid(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task_id')
+
+ def _unfinished():
+ return result_handler(consts.TASK_NOT_DONE, {})
+
+ def _finished():
+ return result_handler(consts.TASK_DONE, {})
+
+ def _error():
+ return result_handler(consts.TASK_FAILED, asynctask.error)
+
+ status = asynctask.status
+ LOG.debug('Task status is: %s', status)
+
+ if status not in [consts.TASK_NOT_DONE,
+ consts.TASK_DONE,
+ consts.TASK_FAILED]:
+ return result_handler(consts.API_ERROR, 'internal server error')
+
+ switcher = {
+ consts.TASK_NOT_DONE: _unfinished,
+ consts.TASK_DONE: _finished,
+ consts.TASK_FAILED: _error
+ }
+
+ return switcher.get(status)()
diff --git a/api/resources/v1/env.py b/api/resources/v1/env.py
new file mode 100644
index 000000000..8943db3d1
--- /dev/null
+++ b/api/resources/v1/env.py
@@ -0,0 +1,439 @@
+##############################################################################
+# Copyright (c) 2016 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 absolute_import
+
+import errno
+import logging
+import os
+import subprocess
+import threading
+import time
+import uuid
+import glob
+import yaml
+import collections
+
+from six.moves import configparser
+from oslo_serialization import jsonutils
+from docker import Client
+
+from api.database.v1.handlers import AsyncTaskHandler
+from api.utils import influx
+from api import ApiResource
+from yardstick.common import constants as consts
+from yardstick.common import utils
+from yardstick.common.utils import result_handler
+from yardstick.common import openstack_utils
+from yardstick.common.httpClient import HttpClient
+
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+async_handler = AsyncTaskHandler()
+
+
+class V1Env(ApiResource):
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_grafana(self, args):
+ task_id = str(uuid.uuid4())
+
+ thread = threading.Thread(target=self._create_grafana, args=(task_id,))
+ thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
+
+ def _create_grafana(self, task_id):
+ self._create_task(task_id)
+
+ client = Client(base_url=consts.DOCKER_URL)
+
+ try:
+ LOG.info('Checking if grafana image exist')
+ image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
+ if not self._check_image_exist(client, image):
+ LOG.info('Grafana image not exist, start pulling')
+ client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
+
+ LOG.info('Createing grafana container')
+ container = self._create_grafana_container(client)
+ LOG.info('Grafana container is created')
+
+ time.sleep(5)
+
+ container = client.inspect_container(container['Id'])
+ ip = container['NetworkSettings']['Networks']['bridge']['IPAddress']
+ LOG.debug('container ip is: %s', ip)
+
+ LOG.info('Creating data source for grafana')
+ self._create_data_source(ip)
+
+ LOG.info('Creating dashboard for grafana')
+ self._create_dashboard(ip)
+
+ self._update_task_status(task_id)
+ LOG.info('Finished')
+ except Exception as e:
+ self._update_task_error(task_id, str(e))
+ LOG.exception('Create grafana failed')
+
+ def _create_dashboard(self, ip):
+ url = 'http://admin:admin@{}:{}/api/dashboards/db'.format(ip, consts.GRAFANA_PORT)
+ path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json')
+
+ for i in sorted(glob.iglob(path)):
+ with open(i) as f:
+ data = jsonutils.load(f)
+ try:
+ HttpClient().post(url, data)
+ except Exception:
+ LOG.exception('Create dashboard %s failed', i)
+ raise
+
+ def _create_data_source(self, ip):
+ url = 'http://admin:admin@{}:{}/api/datasources'.format(ip, consts.GRAFANA_PORT)
+ influx_conf = utils.parse_ini_file(consts.CONF_FILE)
+
+ try:
+ influx_url = influx_conf['dispatcher_influxdb']['target']
+ except KeyError:
+ LOG.exception('influxdb url not set in yardstick.conf')
+ raise
+
+ data = {
+ "name": "yardstick",
+ "type": "influxdb",
+ "access": "proxy",
+ "url": influx_url,
+ "password": "root",
+ "user": "root",
+ "database": "yardstick",
+ "basicAuth": True,
+ "basicAuthUser": "admin",
+ "basicAuthPassword": "admin",
+ "isDefault": False,
+ }
+ try:
+ HttpClient().post(url, data)
+ except Exception:
+ LOG.exception('Create datasources failed')
+ raise
+
+ def _create_grafana_container(self, client):
+ ports = [consts.GRAFANA_PORT]
+ port_bindings = {consts.GRAFANA_PORT: consts.GRAFANA_MAPPING_PORT}
+ restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
+ host_config = client.create_host_config(port_bindings=port_bindings,
+ restart_policy=restart_policy)
+
+ LOG.info('Creating container')
+ container = client.create_container(image='%s:%s' %
+ (consts.GRAFANA_IMAGE,
+ consts.GRAFANA_TAG),
+ ports=ports,
+ detach=True,
+ tty=True,
+ host_config=host_config)
+ LOG.info('Starting container')
+ client.start(container)
+ return container
+
+ def _check_image_exist(self, client, t):
+ return any(t in a['RepoTags'][0]
+ for a in client.images() if a['RepoTags'])
+
+ def create_influxdb(self, args):
+ task_id = str(uuid.uuid4())
+
+ thread = threading.Thread(target=self._create_influxdb, args=(task_id,))
+ thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
+
+ def _create_influxdb(self, task_id):
+ self._create_task(task_id)
+
+ client = Client(base_url=consts.DOCKER_URL)
+
+ try:
+ LOG.info('Checking if influxdb image exist')
+ if not self._check_image_exist(client, '%s:%s' %
+ (consts.INFLUXDB_IMAGE,
+ consts.INFLUXDB_TAG)):
+ LOG.info('Influxdb image not exist, start pulling')
+ client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG)
+
+ LOG.info('Createing influxdb container')
+ container = self._create_influxdb_container(client)
+ LOG.info('Influxdb container is created')
+
+ time.sleep(5)
+
+ container = client.inspect_container(container['Id'])
+ ip = container['NetworkSettings']['Networks']['bridge']['IPAddress']
+ LOG.debug('container ip is: %s', ip)
+
+ LOG.info('Changing output to influxdb')
+ self._change_output_to_influxdb(ip)
+
+ LOG.info('Config influxdb')
+ self._config_influxdb()
+
+ self._update_task_status(task_id)
+
+ LOG.info('Finished')
+ except Exception as e:
+ self._update_task_error(task_id, str(e))
+ LOG.exception('Creating influxdb failed')
+
+ def _create_influxdb_container(self, client):
+
+ ports = [consts.INFLUXDB_DASHBOARD_PORT, consts.INFLUXDB_PORT]
+ port_bindings = {k: k for k in ports}
+ restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
+ host_config = client.create_host_config(port_bindings=port_bindings,
+ restart_policy=restart_policy)
+
+ LOG.info('Creating container')
+ container = client.create_container(image='%s:%s' %
+ (consts.INFLUXDB_IMAGE,
+ consts.INFLUXDB_TAG),
+ ports=ports,
+ detach=True,
+ tty=True,
+ host_config=host_config)
+ LOG.info('Starting container')
+ client.start(container)
+ return container
+
+ def _config_influxdb(self):
+ try:
+ client = influx.get_data_db_client()
+ client.create_user(consts.INFLUXDB_USER,
+ consts.INFLUXDB_PASS,
+ consts.INFLUXDB_DB_NAME)
+ client.create_database(consts.INFLUXDB_DB_NAME)
+ LOG.info('Success to config influxDB')
+ except Exception:
+ LOG.exception('Config influxdb failed')
+
+ def _change_output_to_influxdb(self, ip):
+ utils.makedirs(consts.CONF_DIR)
+
+ parser = configparser.ConfigParser()
+ LOG.info('Reading output sample configuration')
+ parser.read(consts.CONF_SAMPLE_FILE)
+
+ LOG.info('Set dispatcher to influxdb')
+ parser.set('DEFAULT', 'dispatcher', 'influxdb')
+ parser.set('dispatcher_influxdb', 'target',
+ 'http://{}:{}'.format(ip, consts.INFLUXDB_PORT))
+
+ LOG.info('Writing to %s', consts.CONF_FILE)
+ with open(consts.CONF_FILE, 'w') as f:
+ parser.write(f)
+
+ def prepare_env(self, args):
+ task_id = str(uuid.uuid4())
+
+ thread = threading.Thread(target=self._prepare_env_daemon,
+ args=(task_id,))
+ thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
+
+ def _already_source_openrc(self):
+ """Check if openrc is sourced already"""
+ return all(os.environ.get(k) for k in ['OS_AUTH_URL',
+ 'OS_USERNAME',
+ 'OS_PASSWORD',
+ 'EXTERNAL_NETWORK'])
+
+ def _prepare_env_daemon(self, task_id):
+ self._create_task(task_id)
+
+ try:
+ self._create_directories()
+
+ rc_file = consts.OPENRC
+
+ LOG.info('Checkout Openrc Environment variable')
+ if not self._already_source_openrc():
+ LOG.info('Openrc variable not found in Environment')
+ if not os.path.exists(rc_file):
+ LOG.info('Openrc file not found')
+ installer_ip = os.environ.get('INSTALLER_IP',
+ '192.168.200.2')
+ installer_type = os.environ.get('INSTALLER_TYPE', 'compass')
+ LOG.info('Getting openrc file from %s', installer_type)
+ self._get_remote_rc_file(rc_file,
+ installer_ip,
+ installer_type)
+ LOG.info('Source openrc file')
+ self._source_file(rc_file)
+ LOG.info('Appending external network')
+ self._append_external_network(rc_file)
+ LOG.info('Openrc file exist, source openrc file')
+ self._source_file(rc_file)
+
+ LOG.info('Cleaning images')
+ self._clean_images()
+
+ LOG.info('Loading images')
+ self._load_images()
+
+ self._update_task_status(task_id)
+ LOG.info('Finished')
+ except Exception as e:
+ self._update_task_error(task_id, str(e))
+ LOG.exception('Prepare env failed')
+
+ def _create_directories(self):
+ utils.makedirs(consts.CONF_DIR)
+
+ def _source_file(self, rc_file):
+ utils.source_env(rc_file)
+
+ def _get_remote_rc_file(self, rc_file, installer_ip, installer_type):
+
+ os_fetch_script = os.path.join(consts.RELENG_DIR, consts.FETCH_SCRIPT)
+
+ try:
+ cmd = [os_fetch_script, '-d', rc_file, '-i', installer_type,
+ '-a', installer_ip]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ p.communicate()
+
+ if p.returncode != 0:
+ LOG.error('Failed to fetch credentials from installer')
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ def _append_external_network(self, rc_file):
+ neutron_client = openstack_utils.get_neutron_client()
+ networks = neutron_client.list_networks()['networks']
+ try:
+ ext_network = next(n['name']
+ for n in networks if n['router:external'])
+ except StopIteration:
+ LOG.warning("Can't find external network")
+ else:
+ cmd = 'export EXTERNAL_NETWORK=%s' % ext_network
+ try:
+ with open(rc_file, 'a') as f:
+ f.write(cmd + '\n')
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ def _clean_images(self):
+ cmd = [consts.CLEAN_IMAGES_SCRIPT]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR)
+ output = p.communicate()[0]
+ LOG.debug(output)
+
+ def _load_images(self):
+ cmd = [consts.LOAD_IMAGES_SCRIPT]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR)
+ output = p.communicate()[0]
+ LOG.debug(output)
+
+ def _create_task(self, task_id):
+ async_handler.insert({'status': 0, 'task_id': task_id})
+
+ def _update_task_status(self, task_id):
+ async_handler.update_attr(task_id, {'status': 1})
+
+ def _update_task_error(self, task_id, error):
+ async_handler.update_attr(task_id, {'status': 2, 'error': error})
+
+ def update_openrc(self, args):
+ try:
+ openrc_vars = args['openrc']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'openrc must be provided')
+ else:
+ if not isinstance(openrc_vars, collections.Mapping):
+ return result_handler(consts.API_ERROR, 'args should be a dict')
+
+ lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()]
+ LOG.debug('Writing: %s', ''.join(lines))
+
+ LOG.info('Writing openrc: Writing')
+ utils.makedirs(consts.CONF_DIR)
+
+ with open(consts.OPENRC, 'w') as f:
+ f.writelines(lines)
+ LOG.info('Writing openrc: Done')
+
+ LOG.info('Source openrc: Sourcing')
+ try:
+ self._source_file(consts.OPENRC)
+ except Exception as e:
+ LOG.exception('Failed to source openrc')
+ return result_handler(consts.API_ERROR, str(e))
+ LOG.info('Source openrc: Done')
+
+ return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars})
+
+ def upload_pod_file(self, args):
+ try:
+ pod_file = args['file']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'file must be provided')
+
+ LOG.info('Checking file')
+ data = yaml.load(pod_file.read())
+ if not isinstance(data, collections.Mapping):
+ return result_handler(consts.API_ERROR, 'invalid yaml file')
+
+ LOG.info('Writing file')
+ with open(consts.POD_FILE, 'w') as f:
+ yaml.dump(data, f, default_flow_style=False)
+ LOG.info('Writing finished')
+
+ return result_handler(consts.API_SUCCESS, {'pod_info': data})
+
+ def update_pod_file(self, args):
+ try:
+ pod_dic = args['pod']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'pod must be provided')
+ else:
+ if not isinstance(pod_dic, collections.Mapping):
+ return result_handler(consts.API_ERROR, 'pod should be a dict')
+
+ LOG.info('Writing file')
+ with open(consts.POD_FILE, 'w') as f:
+ yaml.dump(pod_dic, f, default_flow_style=False)
+ LOG.info('Writing finished')
+
+ return result_handler(consts.API_SUCCESS, {'pod_info': pod_dic})
+
+ def update_hosts(self, hosts_ip):
+ if not isinstance(hosts_ip, collections.Mapping):
+ return result_handler(consts.API_ERROR, 'args should be a dict')
+ LOG.info('Writing hosts: Writing')
+ LOG.debug('Writing: %s', hosts_ip)
+ cmd = ["sudo", "python", "write_hosts.py"]
+ p = subprocess.Popen(cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=os.path.join(consts.REPOS_DIR,
+ "api/resources"))
+ _, err = p.communicate(jsonutils.dumps(hosts_ip))
+ if p.returncode != 0:
+ return result_handler(consts.API_ERROR, err)
+ LOG.info('Writing hosts: Done')
+ return result_handler(consts.API_SUCCESS, 'success')
diff --git a/api/resources/v1/results.py b/api/resources/v1/results.py
new file mode 100644
index 000000000..0493b43b6
--- /dev/null
+++ b/api/resources/v1/results.py
@@ -0,0 +1,78 @@
+##############################################################################
+# Copyright (c) 2016 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 absolute_import
+import logging
+import uuid
+import json
+import os
+
+from flasgger.utils import swag_from
+
+from api import ApiResource
+from api.database.v1.handlers import TasksHandler
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+from api.swagger import models
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+ResultModel = models.ResultModel
+
+
+class V1Result(ApiResource):
+
+ @swag_from(os.path.join(consts.REPOS_DIR, 'api/swagger/docs/results.yaml'))
+ def get(self):
+ args = self._get_args()
+
+ try:
+ task_id = args['task_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'task_id must be provided')
+
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task_id')
+
+ task_handler = TasksHandler()
+ try:
+ task = task_handler.get_task_by_taskid(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task_id')
+
+ def _unfinished():
+ return result_handler(consts.TASK_NOT_DONE, {})
+
+ def _finished():
+ if task.result:
+ return result_handler(consts.TASK_DONE, json.loads(task.result))
+ else:
+ return result_handler(consts.TASK_DONE, {})
+
+ def _error():
+ return result_handler(consts.TASK_FAILED, task.error)
+
+ status = task.status
+ LOG.debug('Task status is: %s', status)
+
+ if status not in [consts.TASK_NOT_DONE,
+ consts.TASK_DONE,
+ consts.TASK_FAILED]:
+ return result_handler(consts.API_ERROR, 'internal server error')
+
+ switcher = {
+ consts.TASK_NOT_DONE: _unfinished,
+ consts.TASK_DONE: _finished,
+ consts.TASK_FAILED: _error
+ }
+
+ return switcher.get(status)()
diff --git a/api/resources/v1/testcases.py b/api/resources/v1/testcases.py
new file mode 100644
index 000000000..f15947245
--- /dev/null
+++ b/api/resources/v1/testcases.py
@@ -0,0 +1,115 @@
+# ############################################################################
+# 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 absolute_import
+import uuid
+import os
+import logging
+
+from flasgger.utils import swag_from
+
+from yardstick.benchmark.core.testcase import Testcase
+from yardstick.benchmark.core.task import Task
+from yardstick.benchmark.core import Param
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+from api.utils.thread import TaskThread
+from api import ApiResource
+from api.swagger import models
+from api.database.v1.handlers import TasksHandler
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V1Testcase(ApiResource):
+
+ def get(self):
+ param = Param({})
+ testcase_list = Testcase().list_all(param)
+ return result_handler(consts.API_SUCCESS, testcase_list)
+
+
+class V1CaseDocs(ApiResource):
+
+ def get(self, case_name):
+ docs_path = os.path.join(consts.DOCS_DIR, '{}.rst'.format(case_name))
+
+ if not os.path.exists(docs_path):
+ return result_handler(consts.API_ERROR, 'case not exists')
+
+ LOG.info('Reading %s', case_name)
+ with open(docs_path) as f:
+ content = f.read()
+
+ return result_handler(consts.API_SUCCESS, {'docs': content})
+
+
+TestCaseActionModel = models.TestCaseActionModel
+TestCaseActionArgsModel = models.TestCaseActionArgsModel
+TestCaseActionArgsOptsModel = models.TestCaseActionArgsOptsModel
+TestCaseActionArgsOptsTaskArgModel = models.TestCaseActionArgsOptsTaskArgModel
+
+
+class V1ReleaseCase(ApiResource):
+
+ @swag_from(os.path.join(consts.REPOS_DIR,
+ 'api/swagger/docs/release_action.yaml'))
+ def post(self):
+ return self._dispatch_post()
+
+ def run_test_case(self, args):
+ try:
+ name = args['testcase']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'testcase must be provided')
+
+ testcase = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(name))
+
+ task_id = str(uuid.uuid4())
+
+ task_args = {
+ 'inputfile': [testcase],
+ 'task_id': task_id
+ }
+ task_args.update(args.get('opts', {}))
+
+ param = Param(task_args)
+ task_thread = TaskThread(Task().start, param, TasksHandler())
+ task_thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
+
+
+class V1SampleCase(ApiResource):
+
+ def post(self):
+ return self._dispatch_post()
+
+ def run_test_case(self, args):
+ try:
+ name = args['testcase']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'testcase must be provided')
+
+ testcase = os.path.join(consts.SAMPLE_CASE_DIR, '{}.yaml'.format(name))
+
+ task_id = str(uuid.uuid4())
+
+ task_args = {
+ 'inputfile': [testcase],
+ 'task_id': task_id
+ }
+ task_args.update(args.get('opts', {}))
+
+ param = Param(task_args)
+ task_thread = TaskThread(Task().start, param, TasksHandler())
+ task_thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
diff --git a/api/resources/v1/testsuites.py b/api/resources/v1/testsuites.py
new file mode 100644
index 000000000..5f72c2ea6
--- /dev/null
+++ b/api/resources/v1/testsuites.py
@@ -0,0 +1,64 @@
+##############################################################################
+# Copyright (c) 2016 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 absolute_import
+import uuid
+import os
+import logging
+
+from flasgger.utils import swag_from
+
+from api import ApiResource
+from api.utils.thread import TaskThread
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+from yardstick.benchmark.core import Param
+from yardstick.benchmark.core.task import Task
+from api.swagger import models
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+TestSuiteActionModel = models.TestSuiteActionModel
+TestSuiteActionArgsModel = models.TestSuiteActionArgsModel
+TestSuiteActionArgsOptsModel = models.TestSuiteActionArgsOptsModel
+TestSuiteActionArgsOptsTaskArgModel = \
+ models.TestSuiteActionArgsOptsTaskArgModel
+
+
+class V1Testsuite(ApiResource):
+
+ @swag_from(os.path.join(consts.REPOS_DIR,
+ 'api/swagger/docs/testsuites_action.yaml'))
+ def post(self):
+ return self._dispatch_post()
+
+ def run_test_suite(self, args):
+ try:
+ name = args['testsuite']
+ except KeyError:
+ return result_handler(consts.API_ERROR,
+ 'testsuite must be provided')
+
+ testsuite = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(name))
+
+ task_id = str(uuid.uuid4())
+
+ task_args = {
+ 'inputfile': [testsuite],
+ 'task_id': task_id,
+ 'suite': True
+ }
+ task_args.update(args.get('opts', {}))
+
+ param = Param(task_args)
+ task_thread = TaskThread(Task().start, param)
+ task_thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'task_id': task_id})
diff --git a/api/resources/v2/__init__.py b/api/resources/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/api/resources/v2/__init__.py
diff --git a/api/resources/v2/containers.py b/api/resources/v2/containers.py
new file mode 100644
index 000000000..66dc94120
--- /dev/null
+++ b/api/resources/v2/containers.py
@@ -0,0 +1,383 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from __future__ import absolute_import
+
+import logging
+import threading
+import time
+import uuid
+import os
+import glob
+
+from six.moves import configparser
+from oslo_serialization import jsonutils
+from docker import Client
+
+from api import ApiResource
+from api.utils import influx
+from api.database.v2.handlers import V2ContainerHandler
+from api.database.v2.handlers import V2EnvironmentHandler
+from yardstick.common import constants as consts
+from yardstick.common import utils
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import get_free_port
+from yardstick.common.httpClient import HttpClient
+
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+environment_handler = V2EnvironmentHandler()
+container_handler = V2ContainerHandler()
+
+
+class V2Containers(ApiResource):
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_influxdb(self, args):
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ try:
+ environment = environment_handler.get_by_uuid(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such environment id')
+
+ container_info = environment.container_id
+ container_info = jsonutils.loads(container_info) if container_info else {}
+
+ if container_info.get('influxdb'):
+ return result_handler(consts.API_ERROR, 'influxdb container already exist')
+
+ name = 'influxdb-{}'.format(environment_id[:8])
+ port = get_free_port(consts.SERVER_IP)
+ container_id = str(uuid.uuid4())
+ LOG.info('%s will launch on : %s', name, port)
+
+ LOG.info('launch influxdb background')
+ args = (name, port, container_id)
+ thread = threading.Thread(target=self._create_influxdb, args=args)
+ thread.start()
+
+ LOG.info('record container in database')
+ container_init_data = {
+ 'uuid': container_id,
+ 'environment_id': environment_id,
+ 'name': name,
+ 'port': port,
+ 'status': 0
+ }
+ container_handler.insert(container_init_data)
+
+ LOG.info('update container in environment')
+ container_info['influxdb'] = container_id
+ environment_info = {'container_id': jsonutils.dumps(container_info)}
+ environment_handler.update_attr(environment_id, environment_info)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': container_id})
+
+ def _check_image_exist(self, client, t):
+ return any(t in a['RepoTags'][0]
+ for a in client.images() if a['RepoTags'])
+
+ def _create_influxdb(self, name, port, container_id):
+ client = Client(base_url=consts.DOCKER_URL)
+
+ try:
+ LOG.info('Checking if influxdb image exist')
+ if not self._check_image_exist(client, '%s:%s' %
+ (consts.INFLUXDB_IMAGE,
+ consts.INFLUXDB_TAG)):
+ LOG.info('Influxdb image not exist, start pulling')
+ client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG)
+
+ LOG.info('Createing influxdb container')
+ container = self._create_influxdb_container(client, name, port)
+ LOG.info('Influxdb container is created')
+
+ time.sleep(5)
+
+ container = client.inspect_container(container['Id'])
+ ip = container['NetworkSettings']['Networks']['bridge']['IPAddress']
+ LOG.debug('container ip is: %s', ip)
+
+ LOG.info('Changing output to influxdb')
+ self._change_output_to_influxdb(ip)
+
+ LOG.info('Config influxdb')
+ self._config_influxdb()
+
+ container_handler.update_attr(container_id, {'status': 1})
+
+ LOG.info('Finished')
+ except Exception:
+ container_handler.update_attr(container_id, {'status': 2})
+ LOG.exception('Creating influxdb failed')
+
+ def _create_influxdb_container(self, client, name, port):
+
+ ports = [port]
+ port_bindings = {8086: port}
+ restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
+ host_config = client.create_host_config(port_bindings=port_bindings,
+ restart_policy=restart_policy)
+
+ LOG.info('Creating container')
+ container = client.create_container(image='%s:%s' %
+ (consts.INFLUXDB_IMAGE,
+ consts.INFLUXDB_TAG),
+ ports=ports,
+ name=name,
+ detach=True,
+ tty=True,
+ host_config=host_config)
+ LOG.info('Starting container')
+ client.start(container)
+ return container
+
+ def _config_influxdb(self):
+ try:
+ client = influx.get_data_db_client()
+ client.create_user(consts.INFLUXDB_USER,
+ consts.INFLUXDB_PASS,
+ consts.INFLUXDB_DB_NAME)
+ client.create_database(consts.INFLUXDB_DB_NAME)
+ LOG.info('Success to config influxDB')
+ except Exception:
+ LOG.exception('Config influxdb failed')
+
+ def _change_output_to_influxdb(self, ip):
+ utils.makedirs(consts.CONF_DIR)
+
+ parser = configparser.ConfigParser()
+ LOG.info('Reading output sample configuration')
+ parser.read(consts.CONF_SAMPLE_FILE)
+
+ LOG.info('Set dispatcher to influxdb')
+ parser.set('DEFAULT', 'dispatcher', 'influxdb')
+ parser.set('dispatcher_influxdb', 'target',
+ 'http://{}:{}'.format(ip, 8086))
+
+ LOG.info('Writing to %s', consts.CONF_FILE)
+ with open(consts.CONF_FILE, 'w') as f:
+ parser.write(f)
+
+ def create_grafana(self, args):
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ try:
+ environment = environment_handler.get_by_uuid(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such environment id')
+
+ container_info = environment.container_id
+ container_info = jsonutils.loads(container_info) if container_info else {}
+
+ if not container_info.get('influxdb'):
+ return result_handler(consts.API_ERROR, 'influxdb not set')
+
+ if container_info.get('grafana'):
+ return result_handler(consts.API_ERROR, 'grafana container already exists')
+
+ name = 'grafana-{}'.format(environment_id[:8])
+ port = get_free_port(consts.SERVER_IP)
+ container_id = str(uuid.uuid4())
+
+ args = (name, port, container_id)
+ thread = threading.Thread(target=self._create_grafana, args=args)
+ thread.start()
+
+ container_init_data = {
+ 'uuid': container_id,
+ 'environment_id': environment_id,
+ 'name': name,
+ 'port': port,
+ 'status': 0
+ }
+ container_handler.insert(container_init_data)
+
+ container_info['grafana'] = container_id
+ environment_info = {'container_id': jsonutils.dumps(container_info)}
+ environment_handler.update_attr(environment_id, environment_info)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': container_id})
+
+ def _create_grafana(self, name, port, container_id):
+ client = Client(base_url=consts.DOCKER_URL)
+
+ try:
+ LOG.info('Checking if grafana image exist')
+ image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
+ if not self._check_image_exist(client, image):
+ LOG.info('Grafana image not exist, start pulling')
+ client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG)
+
+ LOG.info('Createing grafana container')
+ container = self._create_grafana_container(client, name, port)
+ LOG.info('Grafana container is created')
+
+ time.sleep(5)
+
+ container = client.inspect_container(container['Id'])
+ ip = container['NetworkSettings']['Networks']['bridge']['IPAddress']
+ LOG.debug('container ip is: %s', ip)
+
+ LOG.info('Creating data source for grafana')
+ self._create_data_source(ip)
+
+ LOG.info('Creating dashboard for grafana')
+ self._create_dashboard(ip)
+
+ container_handler.update_attr(container_id, {'status': 1})
+ LOG.info('Finished')
+ except Exception:
+ container_handler.update_attr(container_id, {'status': 2})
+ LOG.exception('Create grafana failed')
+
+ def _create_dashboard(self, ip):
+ url = 'http://admin:admin@{}:{}/api/dashboards/db'.format(ip, 3000)
+ path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json')
+
+ for i in sorted(glob.iglob(path)):
+ with open(i) as f:
+ data = jsonutils.load(f)
+ try:
+ HttpClient().post(url, data)
+ except Exception:
+ LOG.exception('Create dashboard %s failed', i)
+ raise
+
+ def _create_data_source(self, ip):
+ url = 'http://admin:admin@{}:{}/api/datasources'.format(ip, 3000)
+
+ influx_conf = utils.parse_ini_file(consts.CONF_FILE)
+ try:
+ influx_url = influx_conf['dispatcher_influxdb']['target']
+ except KeyError:
+ LOG.exception('influxdb url not set in yardstick.conf')
+ raise
+
+ data = {
+ "name": "yardstick",
+ "type": "influxdb",
+ "access": "proxy",
+ "url": influx_url,
+ "password": "root",
+ "user": "root",
+ "database": "yardstick",
+ "basicAuth": True,
+ "basicAuthUser": "admin",
+ "basicAuthPassword": "admin",
+ "isDefault": False,
+ }
+ try:
+ HttpClient().post(url, data)
+ except Exception:
+ LOG.exception('Create datasources failed')
+ raise
+
+ def _create_grafana_container(self, client, name, port):
+ ports = [3000]
+ port_bindings = {3000: port}
+ restart_policy = {"MaximumRetryCount": 0, "Name": "always"}
+ host_config = client.create_host_config(port_bindings=port_bindings,
+ restart_policy=restart_policy)
+
+ LOG.info('Creating container')
+ container = client.create_container(image='%s:%s' %
+ (consts.GRAFANA_IMAGE,
+ consts.GRAFANA_TAG),
+ name=name,
+ ports=ports,
+ detach=True,
+ tty=True,
+ host_config=host_config)
+ LOG.info('Starting container')
+ client.start(container)
+ return container
+
+
+class V2Container(ApiResource):
+
+ def get(self, container_id):
+ try:
+ uuid.UUID(container_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid container id')
+
+ try:
+ container = container_handler.get_by_uuid(container_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such container id')
+
+ name = container.name
+ client = Client(base_url=consts.DOCKER_URL)
+ info = client.inspect_container(name)
+
+ data = {
+ 'name': name,
+ 'status': info.get('State', {}).get('Status', 'error'),
+ 'time': info.get('Created'),
+ 'port': container.port
+ }
+
+ return result_handler(consts.API_SUCCESS, {'container': data})
+
+ def delete(self, container_id):
+ try:
+ uuid.UUID(container_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid container id')
+
+ try:
+ container = container_handler.get_by_uuid(container_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such container id')
+
+ environment_id = container.environment_id
+
+ client = Client(base_url=consts.DOCKER_URL)
+ LOG.info('delete container: %s', container.name)
+ try:
+ client.remove_container(container.name, force=True)
+ except Exception:
+ LOG.exception('delete container failed')
+ return result_handler(consts.API_ERROR, 'delete container failed')
+
+ LOG.info('delete container in database')
+ container_handler.delete_by_uuid(container_id)
+
+ LOG.info('update container in environment')
+ environment = environment_handler.get_by_uuid(environment_id)
+ container_info = jsonutils.loads(environment.container_id)
+ key = next((k for k, v in container_info.items() if v == container_id))
+ container_info.pop(key)
+ environment_delete_data = {
+ 'container_id': jsonutils.dumps(container_info)
+ }
+ environment_handler.update_attr(environment_id, environment_delete_data)
+
+ return result_handler(consts.API_SUCCESS, {'container': container_id})
diff --git a/api/resources/v2/environments.py b/api/resources/v2/environments.py
new file mode 100644
index 000000000..f021a3c5a
--- /dev/null
+++ b/api/resources/v2/environments.py
@@ -0,0 +1,125 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import uuid
+import logging
+
+from oslo_serialization import jsonutils
+from docker import Client
+
+from api import ApiResource
+from api.database.v2.handlers import V2EnvironmentHandler
+from api.database.v2.handlers import V2OpenrcHandler
+from api.database.v2.handlers import V2PodHandler
+from api.database.v2.handlers import V2ContainerHandler
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import change_obj_to_dict
+from yardstick.common import constants as consts
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Environments(ApiResource):
+
+ def get(self):
+ environment_handler = V2EnvironmentHandler()
+ environments = [change_obj_to_dict(e) for e in environment_handler.list_all()]
+
+ for e in environments:
+ container_info = e['container_id']
+ e['container_id'] = jsonutils.loads(container_info) if container_info else {}
+
+ data = {
+ 'environments': environments
+ }
+
+ return result_handler(consts.API_SUCCESS, data)
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_environment(self, args):
+ try:
+ name = args['name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'name must be provided')
+
+ env_id = str(uuid.uuid4())
+
+ environment_handler = V2EnvironmentHandler()
+
+ env_init_data = {
+ 'name': name,
+ 'uuid': env_id
+ }
+ environment_handler.insert(env_init_data)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': env_id})
+
+
+class V2Environment(ApiResource):
+
+ def get(self, environment_id):
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ environment_handler = V2EnvironmentHandler()
+ try:
+ environment = environment_handler.get_by_uuid(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such environment id')
+
+ environment = change_obj_to_dict(environment)
+ container_id = environment['container_id']
+ environment['container_id'] = jsonutils.loads(container_id) if container_id else {}
+ return result_handler(consts.API_SUCCESS, {'environment': environment})
+
+ def delete(self, environment_id):
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ environment_handler = V2EnvironmentHandler()
+ try:
+ environment = environment_handler.get_by_uuid(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such environment id')
+
+ if environment.openrc_id:
+ LOG.info('delete openrc: %s', environment.openrc_id)
+ openrc_handler = V2OpenrcHandler()
+ openrc_handler.delete_by_uuid(environment.openrc_id)
+
+ if environment.pod_id:
+ LOG.info('delete pod: %s', environment.pod_id)
+ pod_handler = V2PodHandler()
+ pod_handler.delete_by_uuid(environment.pod_id)
+
+ if environment.container_id:
+ LOG.info('delete containers')
+ container_info = jsonutils.loads(environment.container_id)
+
+ container_handler = V2ContainerHandler()
+ client = Client(base_url=consts.DOCKER_URL)
+ for k, v in container_info.items():
+ LOG.info('start delete: %s', k)
+ container = container_handler.get_by_uuid(v)
+ LOG.debug('container name: %s', container.name)
+ try:
+ client.remove_container(container.name, force=True)
+ except Exception:
+ LOG.exception('remove container failed')
+ container_handler.delete_by_uuid(v)
+
+ environment_handler.delete_by_uuid(environment_id)
+
+ return result_handler(consts.API_SUCCESS, {'environment': environment_id})
diff --git a/api/resources/v2/images.py b/api/resources/v2/images.py
new file mode 100644
index 000000000..8359e105b
--- /dev/null
+++ b/api/resources/v2/images.py
@@ -0,0 +1,82 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+import subprocess
+import threading
+
+from api import ApiResource
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import source_env
+from yardstick.common.utils import change_obj_to_dict
+from yardstick.common.openstack_utils import get_nova_client
+from yardstick.common import constants as consts
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Images(ApiResource):
+
+ def get(self):
+ try:
+ source_env(consts.OPENRC)
+ except:
+ return result_handler(consts.API_ERROR, 'source openrc error')
+
+ nova_client = get_nova_client()
+ try:
+ images_list = nova_client.images.list()
+ except:
+ return result_handler(consts.API_ERROR, 'get images error')
+ else:
+ images = [self.get_info(change_obj_to_dict(i)) for i in images_list]
+ status = 1 if all(i['status'] == 'ACTIVE' for i in images) else 0
+ if not images:
+ status = 0
+
+ return result_handler(consts.API_SUCCESS, {'status': status, 'images': images})
+
+ def post(self):
+ return self._dispatch_post()
+
+ def get_info(self, data):
+ result = {
+ 'name': data.get('name', ''),
+ 'size': data.get('OS-EXT-IMG-SIZE:size', ''),
+ 'status': data.get('status', ''),
+ 'time': data.get('updated', '')
+ }
+ return result
+
+ def load_image(self, args):
+ thread = threading.Thread(target=self._load_images)
+ thread.start()
+ return result_handler(consts.API_SUCCESS, {})
+
+ def _load_images(self):
+ LOG.info('source openrc')
+ source_env(consts.OPENRC)
+
+ LOG.info('clean images')
+ cmd = [consts.CLEAN_IMAGES_SCRIPT]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ cwd=consts.REPOS_DIR)
+ _, err = p.communicate()
+ if p.returncode != 0:
+ LOG.error('clean image failed: %s', err)
+
+ LOG.info('load images')
+ cmd = [consts.LOAD_IMAGES_SCRIPT]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ cwd=consts.REPOS_DIR)
+ _, err = p.communicate()
+ if p.returncode != 0:
+ LOG.error('load image failed: %s', err)
+
+ LOG.info('Done')
diff --git a/api/resources/v2/openrcs.py b/api/resources/v2/openrcs.py
new file mode 100644
index 000000000..cb506d0e8
--- /dev/null
+++ b/api/resources/v2/openrcs.py
@@ -0,0 +1,219 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import uuid
+import logging
+import re
+import os
+
+import yaml
+from oslo_serialization import jsonutils
+
+from api import ApiResource
+from api.database.v2.handlers import V2OpenrcHandler
+from api.database.v2.handlers import V2EnvironmentHandler
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import makedirs
+from yardstick.common.utils import source_env
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Openrcs(ApiResource):
+
+ def post(self):
+ return self._dispatch_post()
+
+ def upload_openrc(self, args):
+ try:
+ upload_file = args['file']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'file must be provided')
+
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ LOG.info('writing openrc: %s', consts.OPENRC)
+ makedirs(consts.CONF_DIR)
+ upload_file.save(consts.OPENRC)
+ source_env(consts.OPENRC)
+
+ LOG.info('parsing openrc')
+ try:
+ openrc_data = self._get_openrc_dict()
+ except Exception:
+ LOG.exception('parse openrc failed')
+ return result_handler(consts.API_ERROR, 'parse openrc failed')
+
+ openrc_id = str(uuid.uuid4())
+ self._write_into_database(environment_id, openrc_id, openrc_data)
+
+ LOG.info('writing ansible cloud conf')
+ try:
+ self._generate_ansible_conf_file(openrc_data)
+ except Exception:
+ LOG.exception('write cloud conf failed')
+ return result_handler(consts.API_ERROR, 'genarate ansible conf failed')
+ LOG.info('finish writing ansible cloud conf')
+
+ return result_handler(consts.API_SUCCESS, {'openrc': openrc_data, 'uuid': openrc_id})
+
+ def update_openrc(self, args):
+ try:
+ openrc_vars = args['openrc']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'openrc must be provided')
+
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ LOG.info('writing openrc: %s', consts.OPENRC)
+ makedirs(consts.CONF_DIR)
+
+ lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()]
+ LOG.debug('writing: %s', ''.join(lines))
+ with open(consts.OPENRC, 'w') as f:
+ f.writelines(lines)
+ LOG.info('writing openrc: Done')
+
+ LOG.info('source openrc: %s', consts.OPENRC)
+ try:
+ source_env(consts.OPENRC)
+ except Exception:
+ LOG.exception('source openrc failed')
+ return result_handler(consts.API_ERROR, 'source openrc failed')
+ LOG.info('source openrc: Done')
+
+ openrc_id = str(uuid.uuid4())
+ self._write_into_database(environment_id, openrc_id, openrc_vars)
+
+ LOG.info('writing ansible cloud conf')
+ try:
+ self._generate_ansible_conf_file(openrc_vars)
+ except Exception:
+ LOG.exception('write cloud conf failed')
+ return result_handler(consts.API_ERROR, 'genarate ansible conf failed')
+ LOG.info('finish writing ansible cloud conf')
+
+ return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars, 'uuid': openrc_id})
+
+ def _write_into_database(self, environment_id, openrc_id, openrc_data):
+ LOG.info('writing openrc to database')
+ openrc_handler = V2OpenrcHandler()
+ openrc_init_data = {
+ 'uuid': openrc_id,
+ 'environment_id': environment_id,
+ 'content': jsonutils.dumps(openrc_data)
+ }
+ openrc_handler.insert(openrc_init_data)
+
+ LOG.info('binding openrc to environment: %s', environment_id)
+ environment_handler = V2EnvironmentHandler()
+ environment_handler.update_attr(environment_id, {'openrc_id': openrc_id})
+
+ def _get_openrc_dict(self):
+ with open(consts.OPENRC) as f:
+ content = f.readlines()
+
+ result = {}
+ for line in content:
+ m = re.search(r'(\ .*)=(.*)', line)
+ if m:
+ try:
+ value = os.environ[m.group(1).strip()]
+ except KeyError:
+ pass
+ else:
+ result.update({m.group(1).strip(): value})
+
+ return result
+
+ def _generate_ansible_conf_file(self, openrc_data):
+ ansible_conf = {
+ 'clouds': {
+ 'opnfv': {
+ 'auth': {
+ }
+ }
+ }
+ }
+ black_list = ['OS_IDENTITY_API_VERSION', 'OS_IMAGE_API_VERSION']
+
+ for k, v in openrc_data.items():
+ if k.startswith('OS') and k not in black_list:
+ key = k[3:].lower()
+ ansible_conf['clouds']['opnfv']['auth'][key] = v
+
+ try:
+ value = openrc_data['OS_IDENTITY_API_VERSION']
+ except KeyError:
+ pass
+ else:
+ ansible_conf['clouds']['opnfv']['identity_api_version'] = value
+
+ makedirs(consts.OPENSTACK_CONF_DIR)
+ with open(consts.CLOUDS_CONF, 'w') as f:
+ yaml.dump(ansible_conf, f, default_flow_style=False)
+
+
+class V2Openrc(ApiResource):
+
+ def get(self, openrc_id):
+ try:
+ uuid.UUID(openrc_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid openrc id')
+
+ LOG.info('Geting openrc: %s', openrc_id)
+ openrc_handler = V2OpenrcHandler()
+ try:
+ openrc = openrc_handler.get_by_uuid(openrc_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such openrc id')
+
+ LOG.info('load openrc content')
+ content = jsonutils.loads(openrc.content)
+
+ return result_handler(consts.API_ERROR, {'openrc': content})
+
+ def delete(self, openrc_id):
+ try:
+ uuid.UUID(openrc_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid openrc id')
+
+ LOG.info('Geting openrc: %s', openrc_id)
+ openrc_handler = V2OpenrcHandler()
+ try:
+ openrc = openrc_handler.get_by_uuid(openrc_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such openrc id')
+
+ LOG.info('update openrc in environment')
+ environment_handler = V2EnvironmentHandler()
+ environment_handler.update_attr(openrc.environment_id, {'openrc_id': None})
+
+ openrc_handler.delete_by_uuid(openrc_id)
+
+ return result_handler(consts.API_SUCCESS, {'openrc': openrc_id})
diff --git a/api/resources/v2/pods.py b/api/resources/v2/pods.py
new file mode 100644
index 000000000..f2316d353
--- /dev/null
+++ b/api/resources/v2/pods.py
@@ -0,0 +1,108 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import uuid
+import yaml
+import logging
+
+from oslo_serialization import jsonutils
+
+from api import ApiResource
+from api.database.v2.handlers import V2PodHandler
+from api.database.v2.handlers import V2EnvironmentHandler
+from yardstick.common import constants as consts
+from yardstick.common.utils import result_handler
+from yardstick.common.task_template import TaskTemplate
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Pods(ApiResource):
+
+ def post(self):
+ return self._dispatch_post()
+
+ def upload_pod_file(self, args):
+ try:
+ upload_file = args['file']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'file must be provided')
+
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ LOG.info('writing pod file: %s', consts.POD_FILE)
+ upload_file.save(consts.POD_FILE)
+
+ with open(consts.POD_FILE) as f:
+ data = yaml.safe_load(TaskTemplate.render(f.read()))
+ LOG.debug('pod content is: %s', data)
+
+ LOG.info('create pod in database')
+ pod_id = str(uuid.uuid4())
+ pod_handler = V2PodHandler()
+ pod_init_data = {
+ 'uuid': pod_id,
+ 'environment_id': environment_id,
+ 'content': jsonutils.dumps(data)
+ }
+ pod_handler.insert(pod_init_data)
+
+ LOG.info('update pod in environment')
+ environment_handler = V2EnvironmentHandler()
+ environment_handler.update_attr(environment_id, {'pod_id': pod_id})
+
+ return result_handler(consts.API_SUCCESS, {'uuid': pod_id, 'pod': data})
+
+
+class V2Pod(ApiResource):
+
+ def get(self, pod_id):
+ try:
+ uuid.UUID(pod_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid pod id')
+
+ pod_handler = V2PodHandler()
+ try:
+ pod = pod_handler.get_by_uuid(pod_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such pod')
+
+ content = jsonutils.loads(pod.content)
+
+ return result_handler(consts.API_SUCCESS, {'pod': content})
+
+ def delete(self, pod_id):
+ try:
+ uuid.UUID(pod_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid pod id')
+
+ pod_handler = V2PodHandler()
+ try:
+ pod = pod_handler.get_by_uuid(pod_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such pod')
+
+ LOG.info('update pod in environment')
+ environment_handler = V2EnvironmentHandler()
+ environment_handler.update_attr(pod.environment_id, {'pod_id': None})
+
+ LOG.info('delete pod in database')
+ pod_handler.delete_by_uuid(pod_id)
+
+ return result_handler(consts.API_SUCCESS, {'pod': pod_id})
diff --git a/api/resources/v2/projects.py b/api/resources/v2/projects.py
new file mode 100644
index 000000000..2ff61d0fe
--- /dev/null
+++ b/api/resources/v2/projects.py
@@ -0,0 +1,105 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import uuid
+import logging
+
+from datetime import datetime
+
+from api import ApiResource
+from api.database.v2.handlers import V2ProjectHandler
+from api.database.v2.handlers import V2TaskHandler
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import change_obj_to_dict
+from yardstick.common import constants as consts
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Projects(ApiResource):
+
+ def get(self):
+ project_handler = V2ProjectHandler()
+ projects = [change_obj_to_dict(p) for p in project_handler.list_all()]
+
+ for p in projects:
+ tasks = p['tasks']
+ p['tasks'] = tasks.split(',') if tasks else []
+
+ return result_handler(consts.API_SUCCESS, {'projects': projects})
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_project(self, args):
+ try:
+ name = args['name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'name must be provided')
+
+ project_id = str(uuid.uuid4())
+ create_time = datetime.now()
+ project_handler = V2ProjectHandler()
+
+ project_init_data = {
+ 'uuid': project_id,
+ 'name': name,
+ 'time': create_time
+ }
+ project_handler.insert(project_init_data)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': project_id})
+
+
+class V2Project(ApiResource):
+
+ def get(self, project_id):
+ try:
+ uuid.UUID(project_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid project id')
+
+ project_handler = V2ProjectHandler()
+ try:
+ project = project_handler.get_by_uuid(project_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such project id')
+
+ project_info = change_obj_to_dict(project)
+ tasks = project_info['tasks']
+ project_info['tasks'] = tasks.split(',') if tasks else []
+
+ return result_handler(consts.API_SUCCESS, {'project': project_info})
+
+ def delete(self, project_id):
+ try:
+ uuid.UUID(project_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid project id')
+
+ project_handler = V2ProjectHandler()
+ try:
+ project = project_handler.get_by_uuid(project_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such project id')
+
+ if project.tasks:
+ LOG.info('delete related task')
+ task_handler = V2TaskHandler()
+ for task_id in project.tasks.split(','):
+ LOG.debug('delete task: %s', task_id)
+ try:
+ task_handler.delete_by_uuid(task_id)
+ except ValueError:
+ LOG.exception('no such task id: %s', task_id)
+
+ LOG.info('delete project in database')
+ project_handler.delete_by_uuid(project_id)
+
+ return result_handler(consts.API_SUCCESS, {'project': project_id})
diff --git a/api/resources/v2/tasks.py b/api/resources/v2/tasks.py
new file mode 100644
index 000000000..885a190c6
--- /dev/null
+++ b/api/resources/v2/tasks.py
@@ -0,0 +1,254 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import uuid
+import logging
+from datetime import datetime
+
+from oslo_serialization import jsonutils
+
+from api import ApiResource
+from api.database.v2.handlers import V2TaskHandler
+from api.database.v2.handlers import V2ProjectHandler
+from api.database.v2.handlers import V2EnvironmentHandler
+from api.utils.thread import TaskThread
+from yardstick.common.utils import result_handler
+from yardstick.common.utils import change_obj_to_dict
+from yardstick.common import constants as consts
+from yardstick.benchmark.core.task import Task
+from yardstick.benchmark.core import Param
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Tasks(ApiResource):
+
+ def get(self):
+ task_handler = V2TaskHandler()
+ tasks = [change_obj_to_dict(t) for t in task_handler.list_all()]
+
+ for t in tasks:
+ result = t['result']
+ t['result'] = jsonutils.loads(result) if result else None
+
+ return result_handler(consts.API_SUCCESS, {'tasks': tasks})
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_task(self, args):
+ try:
+ name = args['name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'name must be provided')
+
+ try:
+ project_id = args['project_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'project_id must be provided')
+
+ task_id = str(uuid.uuid4())
+ create_time = datetime.now()
+ task_handler = V2TaskHandler()
+
+ LOG.info('create task in database')
+ task_init_data = {
+ 'uuid': task_id,
+ 'project_id': project_id,
+ 'name': name,
+ 'time': create_time,
+ 'status': -1
+ }
+ task_handler.insert(task_init_data)
+
+ LOG.info('create task in project')
+ project_handler = V2ProjectHandler()
+ project_handler.append_attr(project_id, {'tasks': task_id})
+
+ return result_handler(consts.API_SUCCESS, {'uuid': task_id})
+
+
+class V2Task(ApiResource):
+
+ def get(self, task_id):
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task id')
+
+ task_handler = V2TaskHandler()
+ try:
+ task = task_handler.get_by_uuid(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such task id')
+
+ task_info = change_obj_to_dict(task)
+ result = task_info['result']
+ task_info['result'] = jsonutils.loads(result) if result else None
+
+ return result_handler(consts.API_SUCCESS, {'task': task_info})
+
+ def delete(self, task_id):
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task id')
+
+ task_handler = V2TaskHandler()
+ try:
+ project_id = task_handler.get_by_uuid(task_id).project_id
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such task id')
+
+ LOG.info('delete task in database')
+ task_handler.delete_by_uuid(task_id)
+
+ project_handler = V2ProjectHandler()
+ project = project_handler.get_by_uuid(project_id)
+
+ if project.tasks:
+ LOG.info('update tasks in project')
+ new_task_list = project.tasks.split(',')
+ new_task_list.remove(task_id)
+ if new_task_list:
+ new_tasks = ','.join(new_task_list)
+ else:
+ new_tasks = None
+ project_handler.update_attr(project_id, {'tasks': new_tasks})
+
+ return result_handler(consts.API_SUCCESS, {'task': task_id})
+
+ def put(self, task_id):
+
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task id')
+
+ task_handler = V2TaskHandler()
+ try:
+ task_handler.get_by_uuid(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such task id')
+
+ return self._dispatch_post(task_id=task_id)
+
+ def add_environment(self, args):
+
+ task_id = args['task_id']
+ try:
+ environment_id = args['environment_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'environment_id must be provided')
+
+ try:
+ uuid.UUID(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid environment id')
+
+ environment_handler = V2EnvironmentHandler()
+ try:
+ environment_handler.get_by_uuid(environment_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such environment id')
+
+ LOG.info('update environment_id in task')
+ task_handler = V2TaskHandler()
+ task_handler.update_attr(task_id, {'environment_id': environment_id})
+
+ return result_handler(consts.API_SUCCESS, {'uuid': task_id})
+
+ def add_case(self, args):
+ task_id = args['task_id']
+ try:
+ name = args['case_name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'case_name must be provided')
+
+ try:
+ content = args['case_content']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'case_content must be provided')
+
+ LOG.info('update case info in task')
+ task_handler = V2TaskHandler()
+ task_update_data = {
+ 'case_name': name,
+ 'content': content,
+ 'suite': False
+ }
+ task_handler.update_attr(task_id, task_update_data)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': task_id})
+
+ def add_suite(self, args):
+ task_id = args['task_id']
+ try:
+ name = args['suite_name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'suite_name must be provided')
+
+ try:
+ content = args['suite_content']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'suite_content must be provided')
+
+ LOG.info('update suite info in task')
+ task_handler = V2TaskHandler()
+ task_update_data = {
+ 'case_name': name,
+ 'content': content,
+ 'suite': True
+ }
+ task_handler.update_attr(task_id, task_update_data)
+
+ return result_handler(consts.API_SUCCESS, {'uuid': task_id})
+
+ def run(self, args):
+ try:
+ task_id = args['task_id']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'task_id must be provided')
+
+ try:
+ uuid.UUID(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'invalid task id')
+
+ task_handler = V2TaskHandler()
+ try:
+ task = task_handler.get_by_uuid(task_id)
+ except ValueError:
+ return result_handler(consts.API_ERROR, 'no such task id')
+
+ if not task.environment_id:
+ return result_handler(consts.API_ERROR, 'environment not set')
+
+ if not task.case_name or not task.content:
+ return result_handler(consts.API_ERROR, 'case not set')
+
+ if task.status == 0:
+ return result_handler(consts.API_ERROR, 'task is already running')
+
+ with open('/tmp/{}.yaml'.format(task.case_name), 'w') as f:
+ f.write(task.content)
+
+ data = {
+ 'inputfile': ['/tmp/{}.yaml'.format(task.case_name)],
+ 'task_id': task_id
+ }
+ if task.suite:
+ data.update({'suite': True})
+
+ LOG.info('start task thread')
+ param = Param(data)
+ task_thread = TaskThread(Task().start, param, task_handler)
+ task_thread.start()
+
+ return result_handler(consts.API_SUCCESS, {'uuid': task_id})
diff --git a/api/resources/v2/testcases.py b/api/resources/v2/testcases.py
new file mode 100644
index 000000000..b47a8f6b7
--- /dev/null
+++ b/api/resources/v2/testcases.py
@@ -0,0 +1,70 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+import errno
+import os
+
+from api import ApiResource
+from yardstick.common.utils import result_handler
+from yardstick.common import constants as consts
+from yardstick.benchmark.core import Param
+from yardstick.benchmark.core.testcase import Testcase
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Testcases(ApiResource):
+
+ def get(self):
+ param = Param({})
+ testcase_list = Testcase().list_all(param)
+ return result_handler(consts.API_SUCCESS, {'testcases': testcase_list})
+
+ def post(self):
+ return self._dispatch_post()
+
+ def upload_case(self, args):
+ try:
+ upload_file = args['file']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'file must be provided')
+
+ case_name = os.path.join(consts.TESTCASE_DIR, upload_file.filename)
+
+ LOG.info('save case file')
+ upload_file.save(case_name)
+
+ return result_handler(consts.API_SUCCESS, {'testcase': upload_file.filename})
+
+
+class V2Testcase(ApiResource):
+
+ def get(self, case_name):
+ case_path = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name))
+
+ try:
+ with open(case_path) as f:
+ data = f.read()
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return result_handler(consts.API_ERROR, 'case does not exist')
+
+ return result_handler(consts.API_SUCCESS, {'testcase': data})
+
+ def delete(self, case_name):
+ case_path = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name))
+
+ try:
+ os.remove(case_path)
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return result_handler(consts.API_ERROR, 'case does not exist')
+
+ return result_handler(consts.API_SUCCESS, {'testcase': case_name})
diff --git a/api/resources/v2/testsuites.py b/api/resources/v2/testsuites.py
new file mode 100644
index 000000000..56ad47375
--- /dev/null
+++ b/api/resources/v2/testsuites.py
@@ -0,0 +1,89 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import os
+import errno
+import logging
+
+import yaml
+
+from api import ApiResource
+from yardstick.common.utils import result_handler
+from yardstick.common import constants as consts
+from yardstick.benchmark.core.testsuite import Testsuite
+from yardstick.benchmark.core import Param
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+class V2Testsuites(ApiResource):
+
+ def get(self):
+ param = Param({})
+ testsuite_list = Testsuite().list_all(param)
+
+ data = {
+ 'testsuites': testsuite_list
+ }
+
+ return result_handler(consts.API_SUCCESS, data)
+
+ def post(self):
+ return self._dispatch_post()
+
+ def create_suite(self, args):
+ try:
+ suite_name = args['name']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'name must be provided')
+
+ try:
+ testcases = args['testcases']
+ except KeyError:
+ return result_handler(consts.API_ERROR, 'testcases must be provided')
+
+ testcases = [{'file_name': '{}.yaml'.format(t)} for t in testcases]
+
+ suite = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name))
+ suite_content = {
+ 'schema': 'yardstick:suite:0.1',
+ 'name': suite_name,
+ 'test_cases_dir': 'tests/opnfv/test_cases/',
+ 'test_cases': testcases
+ }
+
+ LOG.info('write test suite')
+ with open(suite, 'w') as f:
+ yaml.dump(suite_content, f, default_flow_style=False)
+
+ return result_handler(consts.API_SUCCESS, {'suite': suite_name})
+
+
+class V2Testsuite(ApiResource):
+
+ def get(self, suite_name):
+ suite_path = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name))
+ try:
+ with open(suite_path) as f:
+ data = f.read()
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return result_handler(consts.API_ERROR, 'suite does not exist')
+
+ return result_handler(consts.API_SUCCESS, {'testsuite': data})
+
+ def delete(self, suite_name):
+ suite_path = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name))
+ try:
+ os.remove(suite_path)
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return result_handler(consts.API_ERROR, 'suite does not exist')
+
+ return result_handler(consts.API_SUCCESS, {'testsuite': suite_name})
diff --git a/api/server.py b/api/server.py
index d39c44544..158b8a508 100644
--- a/api/server.py
+++ b/api/server.py
@@ -10,6 +10,7 @@ from __future__ import absolute_import
import inspect
import logging
+import socket
from six.moves import filter
from flasgger import Swagger
@@ -21,9 +22,17 @@ from api.database import db_session
from api.database import engine
from api.database.v1 import models
from api.urls import urlpatterns
+from api import ApiResource
from yardstick import _init_logging
+from yardstick.common import utils
+from yardstick.common import constants as consts
-logger = logging.getLogger(__name__)
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin
+
+LOG = logging.getLogger(__name__)
app = Flask(__name__)
@@ -37,8 +46,10 @@ def shutdown_session(exception=None):
db_session.remove()
-for u in urlpatterns:
- api.add_resource(u.resource, u.url, endpoint=u.endpoint)
+def get_resource(resource_name):
+ name = ''.join(resource_name.split('_'))
+ return next((r for r in utils.itersubclasses(ApiResource)
+ if r.__name__.lower() == name))
def init_db():
@@ -51,7 +62,7 @@ def init_db():
return False
subclses = filter(func, inspect.getmembers(models, inspect.isclass))
- logger.debug('Import models: %s', [a[1] for a in subclses])
+ LOG.debug('Import models: %s', [a[1] for a in subclses])
Base.metadata.create_all(bind=engine)
@@ -60,9 +71,21 @@ def app_wrapper(*args, **kwargs):
return app(*args, **kwargs)
+def get_endpoint(url):
+ ip = socket.gethostbyname(socket.gethostname())
+ return urljoin('http://{}:{}'.format(ip, consts.API_PORT), url)
+
+
+for u in urlpatterns:
+ try:
+ api.add_resource(get_resource(u.target), u.url, endpoint=get_endpoint(u.url))
+ except StopIteration:
+ LOG.error('url resource not found: %s', u.url)
+
+
if __name__ == '__main__':
_init_logging()
- logger.setLevel(logging.DEBUG)
- logger.info('Starting server')
+ LOG.setLevel(logging.DEBUG)
+ LOG.info('Starting server')
init_db()
app.run(host='0.0.0.0')
diff --git a/api/urls.py b/api/urls.py
index 13c6c7675..3fef91af8 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -8,17 +8,52 @@
##############################################################################
from __future__ import absolute_import
-from api import views
-from api.utils.common import Url
+from api import Url
urlpatterns = [
- Url('/yardstick/asynctask', views.Asynctask, 'asynctask'),
- Url('/yardstick/testcases', views.Testcases, 'testcases'),
- Url('/yardstick/testcases/release/action', views.ReleaseAction, 'release'),
- Url('/yardstick/testcases/samples/action', views.SamplesAction, 'samples'),
- Url('/yardstick/testcases/<case_name>/docs', views.CaseDocs, 'casedocs'),
- Url('/yardstick/testsuites/action', views.TestsuitesAction, 'testsuites'),
- Url('/yardstick/results', views.Results, 'results'),
- Url('/yardstick/env/action', views.EnvAction, 'env')
+ Url('/yardstick/asynctask', 'v1_async_task'),
+ Url('/yardstick/testcases', 'v1_test_case'),
+ Url('/yardstick/testcases/release/action', 'v1_release_case'),
+ Url('/yardstick/testcases/samples/action', 'v1_sample_case'),
+ Url('/yardstick/testcases/<case_name>/docs', 'v1_case_docs'),
+ Url('/yardstick/testsuites/action', 'v1_test_suite'),
+ Url('/yardstick/results', 'v1_result'),
+ Url('/yardstick/env/action', 'v1_env'),
+
+ # api v2
+ Url('/api/v2/yardstick/environments', 'v2_environments'),
+ 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'),
+
+ Url('/api/v2/yardstick/projects', 'v2_projects'),
+ Url('/api/v2/yardstick/projects/action', 'v2_projects'),
+ Url('/api/v2/yardstick/projects/<project_id>', 'v2_project'),
+
+ Url('/api/v2/yardstick/tasks', 'v2_tasks'),
+ Url('/api/v2/yardstick/tasks/action', 'v2_tasks'),
+ Url('/api/v2/yardstick/tasks/<task_id>', 'v2_task'),
+
+ Url('/api/v2/yardstick/testcases', 'v2_testcases'),
+ Url('/api/v2/yardstick/testcases/action', 'v2_testcases'),
+ Url('/api/v2/yardstick/testcases/<case_name>', 'v2_testcase'),
+
+ Url('/api/v2/yardstick/testsuites', 'v2_testsuites'),
+ Url('/api/v2/yardstick/testsuites/action', 'v2_testsuites'),
+ Url('/api/v2/yardstick/testsuites/<suite_name>', 'v2_testsuite')
]
diff --git a/api/utils/common.py b/api/utils/common.py
deleted file mode 100644
index eda9c17dd..000000000
--- a/api/utils/common.py
+++ /dev/null
@@ -1,44 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import collections
-import logging
-
-from flask import jsonify
-import six
-
-LOG = logging.getLogger(__name__)
-LOG.setLevel(logging.DEBUG)
-
-
-def translate_to_str(obj):
- if isinstance(obj, collections.Mapping):
- return {str(k): translate_to_str(v) for k, v in obj.items()}
- elif isinstance(obj, list):
- return [translate_to_str(ele) for ele in obj]
- elif isinstance(obj, six.text_type):
- return str(obj)
- return obj
-
-
-def result_handler(status, data):
- result = {
- 'status': status,
- 'result': data
- }
- return jsonify(result)
-
-
-class Url(object):
-
- def __init__(self, url, resource, endpoint):
- super(Url, self).__init__()
- self.url = url
- self.resource = resource
- self.endpoint = endpoint
diff --git a/api/utils/thread.py b/api/utils/thread.py
index 2106548f5..20bd07a12 100644
--- a/api/utils/thread.py
+++ b/api/utils/thread.py
@@ -1,37 +1,53 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
import threading
+import os
import logging
from oslo_serialization import jsonutils
-from api.database.v1.handlers import TasksHandler
from yardstick.common import constants as consts
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
class TaskThread(threading.Thread):
- def __init__(self, target, args):
+ def __init__(self, target, args, handler):
super(TaskThread, self).__init__(target=target, args=args)
self.target = target
self.args = args
+ self.handler = handler
def run(self):
- task_handler = TasksHandler()
- data = {'task_id': self.args.task_id, 'status': consts.TASK_NOT_DONE}
- task_handler.insert(data)
+ if self.handler.__class__.__name__.lower().startswith('v2'):
+ self.handler.update_attr(self.args.task_id, {'status': consts.TASK_NOT_DONE})
+ else:
+ update_data = {'task_id': self.args.task_id, 'status': consts.TASK_NOT_DONE}
+ self.handler.insert(update_data)
- logger.info('Starting run task')
+ LOG.info('Starting run task')
try:
data = self.target(self.args)
except Exception as e:
- logger.exception('Task Failed')
+ LOG.exception('Task Failed')
update_data = {'status': consts.TASK_FAILED, 'error': str(e)}
- task_handler.update_attr(self.args.task_id, update_data)
+ self.handler.update_attr(self.args.task_id, update_data)
else:
- logger.info('Task Finished')
- logger.debug('Result: %s', data)
-
- data['result'] = jsonutils.dumps(data.get('result', {}))
- task_handler.update_attr(self.args.task_id, data)
+ LOG.info('Task Finished')
+ LOG.debug('Result: %s', data)
+
+ if self.handler.__class__.__name__.lower().startswith('v2'):
+ new_data = {'status': consts.TASK_DONE, 'result': jsonutils.dumps(data['result'])}
+ self.handler.update_attr(self.args.task_id, new_data)
+ os.remove(self.args.inputfile[0])
+ else:
+ data['result'] = jsonutils.dumps(data.get('result', {}))
+ self.handler.update_attr(self.args.task_id, data)
diff --git a/api/views.py b/api/views.py
deleted file mode 100644
index 9c9ca4ef9..000000000
--- a/api/views.py
+++ /dev/null
@@ -1,82 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import logging
-import os
-
-from flasgger.utils import swag_from
-
-from api.base import ApiResource
-from api.swagger import models
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-
-TestCaseActionModel = models.TestCaseActionModel
-TestCaseActionArgsModel = models.TestCaseActionArgsModel
-TestCaseActionArgsOptsModel = models.TestCaseActionArgsOptsModel
-TestCaseActionArgsOptsTaskArgModel = models.TestCaseActionArgsOptsTaskArgModel
-
-
-class Asynctask(ApiResource):
- def get(self):
- return self._dispatch_get()
-
-
-class Testcases(ApiResource):
- def get(self):
- return self._dispatch_get()
-
-
-class ReleaseAction(ApiResource):
- @swag_from(os.getcwd() + '/swagger/docs/release_action.yaml')
- def post(self):
- return self._dispatch_post()
-
-
-class SamplesAction(ApiResource):
-
- def post(self):
- return self._dispatch_post()
-
-
-TestSuiteActionModel = models.TestSuiteActionModel
-TestSuiteActionArgsModel = models.TestSuiteActionArgsModel
-TestSuiteActionArgsOptsModel = models.TestSuiteActionArgsOptsModel
-TestSuiteActionArgsOptsTaskArgModel = \
- models.TestSuiteActionArgsOptsTaskArgModel
-
-
-class TestsuitesAction(ApiResource):
- @swag_from(os.getcwd() + '/swagger/docs/testsuites_action.yaml')
- def post(self):
- return self._dispatch_post()
-
-
-ResultModel = models.ResultModel
-
-
-class Results(ApiResource):
-
- @swag_from(os.getcwd() + '/swagger/docs/results.yaml')
- def get(self):
- return self._dispatch_get()
-
-
-class EnvAction(ApiResource):
-
- def post(self):
- return self._dispatch_post()
-
-
-class CaseDocs(ApiResource):
-
- def get(self, case_name):
- return self._dispatch_get(case_name=case_name)
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/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst
index d89f9ed24..6d55ada86 100644
--- a/docs/release/release-notes/release-notes.rst
+++ b/docs/release/release-notes/release-notes.rst
@@ -38,7 +38,11 @@ Version History
| *Date* | *Version* | *Comment* |
| | | |
+----------------+--------------------+---------------------------------+
-| | 3.0 | Yardstick for Danube release |
+| | 3.1 | Yardstick for Danube release |
+| | | |
+| | | Note: The 3.1 tag is due to git |
+| | | tag issue during Danube 3.0 |
+| | | release |
| | | |
+----------------+--------------------+---------------------------------+
| May 4th, 2017 | 2.0 | Yardstick for Danube release |
@@ -139,19 +143,19 @@ Release Data
| **Project** | Yardstick |
| | |
+--------------------------------------+--------------------------------------+
-| **Repo/tag** | yardstick/Danube.2.0 |
+| **Repo/tag** | yardstick/Danube.3.1 |
| | |
+--------------------------------------+--------------------------------------+
-| **Yardstick Docker image tag** | Danube.2.0 |
+| **Yardstick Docker image tag** | Danube.3.1 |
| | |
+--------------------------------------+--------------------------------------+
| **Release designation** | Danube |
| | |
+--------------------------------------+--------------------------------------+
-| **Release date** | May 4th, 2017 |
+| **Release date** | July 14th, 2017 |
| | |
+--------------------------------------+--------------------------------------+
-| **Purpose of the delivery** | OPNFV Danube release 2.0 |
+| **Purpose of the delivery** | OPNFV Danube release 3.0 |
| | |
+--------------------------------------+--------------------------------------+
@@ -171,7 +175,7 @@ Software Deliverables
---------------------
- - The Yardstick Docker image: https://hub.docker.com/r/opnfv/yardstick (tag: danube.2.0)
+ - The Yardstick Docker image: https://hub.docker.com/r/opnfv/yardstick (tag: danube.3.1)
**Contexts**
@@ -515,7 +519,7 @@ Feature additions
Scenario Matrix
===============
-For Danube 2.0, Yardstick was tested on the following scenarios:
+For Danube 3.0, Yardstick was tested on the following scenarios:
+-------------------------+---------+---------+---------+---------+
| Scenario | Apex | Compass | Fuel | Joid |
@@ -613,10 +617,50 @@ Known Issues/Faults
Corrected Faults
----------------
+Danube.3.1:
+
++----------------------------+------------------------------------------------+
+| **JIRA REFERENCE** | **DESCRIPTION** |
+| | |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-714 | Add yardstick env influxdb/grafana command for |
+| | CentOS |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-655 | Monitor command in tc019 may not show the |
+| | real nova-api service status |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-397 | HA testing framework improvement |
+| | |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-660 | Improve monitor_process pass criteria |
+| | |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-657 | HA monitor_multi bug, |
+| | KeyError: 'max_outage_time' |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-647 | TC025 fault_type value is wrong when using |
+| | baremetal pod scripts |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-659 | Terminate openstack service process using kill |
+| | command in HA test cases |
++----------------------------+------------------------------------------------+
+| JIRA: ARMBAND-275 | Yardstick TC005 fails with |
+| | "Cannot map zero-fill pages" error |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-561 | Bugfix: AttributeError: 'dict' object has no |
+| | attribute 'split' if run sample/ping-hot.yaml |
++----------------------------+------------------------------------------------+
+| JIRA: ARMBAND-268 | ERROR No JSON object could be decoded from |
+| | LMBENCH in TC010 |
++----------------------------+------------------------------------------------+
+| JIRA: YARDSTICK-680 | storperf test case tc074 do not get results |
+| | |
++----------------------------+------------------------------------------------+
+
Danube.2.0:
+----------------------------+------------------------------------------------+
-| **JIRA REFERENCE** | **SLOGAN** |
+| **JIRA REFERENCE** | **DESCRIPTION** |
| | |
+----------------------------+------------------------------------------------+
| JIRA: YARDSTICK-608 | Set work directory in Yardstick container |
@@ -662,7 +706,7 @@ Danube.2.0:
Danube.1.0:
+----------------------------+------------------------------------------------+
-| **JIRA REFERENCE** | **SLOGAN** |
+| **JIRA REFERENCE** | **DESCRIPTION** |
| | |
+----------------------------+------------------------------------------------+
| JIRA: YARDSTICK-599 | Could not load EntryPoint.parse when using |
@@ -673,7 +717,7 @@ Danube.1.0:
+----------------------------+------------------------------------------------+
-Danube 2.0 known restrictions/issues
+Danube 3.1 known restrictions/issues
====================================
+-----------+-----------+----------------------------------------------+
| Installer | Scenario | Issue |
@@ -695,7 +739,7 @@ Open JIRA tickets
=================
+----------------------------+------------------------------------------------+
-| **JIRA REFERENCE** | **SLOGAN** |
+| **JIRA REFERENCE** | **DESCRIPTION** |
| | |
+----------------------------+------------------------------------------------+
| JIRA: YARDSTICK-626 | Fio and Lmbench don't work in Ubuntu-arm64 |
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/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..9b6b5958b
--- /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 = External_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..29273a724
--- /dev/null
+++ b/gui/app/views/environmentList.html
@@ -0,0 +1,155 @@
+<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:#e95420" 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>
+ Modify <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>
+ <!-- <li role="menuitem"><a href="#">Another action</a></li>
+ <li role="menuitem"><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li role="menuitem"><a href="#">Separated link</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..033322a62
--- /dev/null
+++ b/gui/app/views/layout/header.html
@@ -0,0 +1,43 @@
+<div class="header">
+ <div class="navbar navbar-default" role="navigation">
+ <div>
+ <div class="navbar-header">
+
+
+
+ <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: #e95420;
+ color: #fff;
+ }
+
+ .navbar-default .navbar-brand {
+ color: #fff;
+ }
+</style>
diff --git a/gui/app/views/layout/sideNav.html b/gui/app/views/layout/sideNav.html
new file mode 100644
index 000000000..42dcbbc6e
--- /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: 40px;
+ 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..104a9c6cf
--- /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: 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;
+ 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..d5f7a3af3
--- /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: #e95420;
+ }
+
+ .progressDefine>li.is-complete:before,
+ .progressDefine>li.is-complete:after {
+ color: #FFF;
+ background: #e95420;
+ }
+
+ .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..661d604c6
--- /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: #e95420;
+ }
+
+ .progressDefine>li.is-complete:before,
+ .progressDefine>li.is-complete:after {
+ color: #FFF;
+ background: #e95420;
+ }
+
+ .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..a5b88d240
--- /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">
+ Previous
+ </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()">Previous</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()">Previous</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..ea6e63d6b
--- /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:4px;">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:#e95420"> {{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" style="margin-right:20px;">
+ <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="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..ff61c5fed
--- /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="10%"> <a ng-click="gotoDetail(task.uuid)" style="color:#e95420"> {{task.name}} </a></td>
+ <td width="40%">
+ <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="50%">
+
+ <div class="btn-group" uib-dropdown is-open="status.isopen" style="margin-right:20px;">
+ <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..8e1348333
--- /dev/null
+++ b/gui/app/views/suite.html
@@ -0,0 +1,149 @@
+<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>
+
+ <!--<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:4px;">Operate</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:#e95420" ng-click="gotoDetail(suite)"> {{suite}}
+ </a>
+ </div>
+ <div>
+ <!-- <button class="btn btn-default btn-sm" ng-click="gotoDetail(suite)">Detail</button> -->
+ <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(suite,'test suite')">Delete</button>
+ </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/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..3e8cfccf9
--- /dev/null
+++ b/gui/app/views/testcaselist.html
@@ -0,0 +1,150 @@
+<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:4px;">Operate</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:#e95420" 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="gotoDetail(test.Name)">Detail</button> -->
+ <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(test.Name,'test case')">Delete</button>
+ </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..6da3bee3c
--- /dev/null
+++ b/gui/bower.json
@@ -0,0 +1,45 @@
+{
+ "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"
+ },
+ "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..e82ae0233 100755
--- a/install.sh
+++ b/install.sh
@@ -86,7 +86,10 @@ 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
+mv dist /etc/nginx/yardstick/gui
service nginx restart
uwsgi -i /etc/yardstick/yardstick.ini
diff --git a/plugin/CI/storperf.yaml b/plugin/CI/storperf.yaml
index e144dd150..70915f661 100644
--- a/plugin/CI/storperf.yaml
+++ b/plugin/CI/storperf.yaml
@@ -7,7 +7,7 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
---
-# StorPerf plugin configration file for huawei-pod1
+# StorPerf plugin configration file for compass pod in CI
# Used for integration StorPerf into Yardstick as a plugin
schema: "yardstick:plugin:0.1"
@@ -16,6 +16,6 @@ plugins:
name: storperf
deployment:
- ip: 192.168.10.6
+ ip: local
user: root
password: root
diff --git a/requirements.txt b/requirements.txt
index f283b9921..2bcc4dfa7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,7 +14,7 @@ PyYAML==3.12
SQLAlchemy==1.1.4
ansible==2.2.2.0
appdirs==1.4.3
-backport-ipaddress==0.1
+backport-ipaddress==0.1; python_version <= '2.7'
chainmap==1.0.2
cliff==2.4.0
cmd2==0.6.9
@@ -39,6 +39,7 @@ jsonpatch==1.15
jsonpointer==1.10
jsonschema==2.5.1
keystoneauth1==2.18.0
+kubernetes==3.0.0a1
linecache2==1.0.0
lxml==3.7.2
mccabe==0.4.0
@@ -58,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/samples/ping_k8s.yaml b/samples/ping_k8s.yaml
new file mode 100644
index 000000000..503fe6a45
--- /dev/null
+++ b/samples/ping_k8s.yaml
@@ -0,0 +1,46 @@
+##############################################################################
+# 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-k8s
+
+ runner:
+ type: Duration
+ duration: 60
+ interval: 1
+
+ sla:
+ max_rtt: 10
+ action: monitor
+
+context:
+ 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']
+ target:
+ image: openretriever/yardstick
+ command: /bin/bash
+ args: ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;while true ; do sleep 10000; done']
diff --git a/samples/storperf.yaml b/samples/storperf.yaml
index 5000759e0..2ea022173 100644
--- a/samples/storperf.yaml
+++ b/samples/storperf.yaml
@@ -18,6 +18,7 @@ scenarios:
options:
agent_count: 1
agent_image: "Ubuntu-16.04"
+ agent_flavor: "storperf"
public_network: "ext-net"
volume_size: 2
# target:
diff --git a/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml b/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml
new file mode 100644
index 000000000..7654b0f96
--- /dev/null
+++ b/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml
@@ -0,0 +1,42 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+---
+schema: "yardstick:task:0.1"
+
+scenarios:
+-
+ type: NSPerf
+ traffic_profile: ../../traffic_profiles/fixed.yaml
+ topology: ping_tg_topology.yaml # TODO: look in relative path where the tc.yaml is found
+
+ nodes: # This section is copied from pod.xml or resolved via Heat
+ tg__1: trafficgen_1.yardstick
+ vnf__1: vnf.yardstick
+
+ vnf_options:
+ tg__1:
+ target_ip: pingvnf__1.xe0.local_ip # TODO: resolve to config vars
+ vnf__1:
+ target_ip: pinggen__1.xe1.local_ip # TODO: resolve to config vars
+ runner:
+ type: Duration
+ duration: 10
+
+context:
+ type: Standalone
+ name: yardstick
+ nfvi_type: Ovsdpdk
+ vm_deploy: True
+ file: /etc/yardstick/nodes/pod_ovs.yaml
diff --git a/tests/ci/prepare_env.sh b/tests/ci/prepare_env.sh
index 3d9cc298f..c3ee4c76b 100755
--- a/tests/ci/prepare_env.sh
+++ b/tests/ci/prepare_env.sh
@@ -11,7 +11,7 @@
# Perepare the environment to run yardstick ci
: ${DEPLOY_TYPE:='bm'} # Can be any of 'bm' (Bare Metal) or 'virt' (Virtual)
-
+: ${INSTALLER_TYPE:='unknown'}
: ${NODE_NAME:='unknown'}
: ${EXTERNAL_NETWORK:='admin_floating_net'}
@@ -61,9 +61,9 @@ export EXTERNAL_NETWORK INSTALLER_TYPE DEPLOY_TYPE NODE_NAME
# Prepare a admin-rc file for StorPerf integration
$YARDSTICK_REPO_DIR/tests/ci/prepare_storperf_admin-rc.sh
-# copy a admin-rc file for StorPerf integration to the deployment location
-if [ "$NODE_NAME" == "huawei-pod1" ]; then
- bash $YARDSTICK_REPO_DIR/tests/ci/scp_storperf_files.sh
+# copy Storperf related files to the deployment location
+if [ "$INSTALLER_TYPE" == "compass" ]; then
+ source $YARDSTICK_REPO_DIR/tests/ci/scp_storperf_files.sh
fi
# Fetching id_rsa file from jump_server..."
diff --git a/tests/ci/scp_storperf_files.sh b/tests/ci/scp_storperf_files.sh
index 234032cf1..71306eb80 100644
--- a/tests/ci/scp_storperf_files.sh
+++ b/tests/ci/scp_storperf_files.sh
@@ -12,9 +12,26 @@
# Copy storperf_admin-rc to deployment location.
ssh_options="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
-sshpass -p root scp 2>/dev/null $ssh_options ~/storperf_admin-rc \
- root@192.168.10.6:/root/ &> /dev/null
-sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/docker-compose.yaml \
- root@192.168.10.6:/root/ &> /dev/null
-sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/nginx.conf \
- root@192.168.10.6:/root/ &> /dev/null
+
+scp_files(){
+ export JUMP_HOST_IP
+ sshpass -p root scp 2>/dev/null $ssh_options ~/storperf_admin-rc \
+ root@${JUMP_HOST_IP}:/root/ &> /dev/null
+ sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/docker-compose.yaml \
+ root@${JUMP_HOST_IP}:/root/ &> /dev/null
+}
+
+case "$NODE_NAME" in
+ "huawei-pod1")
+ JUMP_HOST_IP='192.168.10.6'
+ scp_files
+ ;;
+ "huawei-pod2")
+ JUMP_HOST_IP='192.168.11.2'
+ scp_files
+ ;;
+ *)
+ # no node name, exit
+ echo "storperf test case will not run on this pod, skipping scp files..."
+ ;;
+esac
diff --git a/tests/ci/yardstick-verify b/tests/ci/yardstick-verify
index 096ea534f..16598df7b 100755
--- a/tests/ci/yardstick-verify
+++ b/tests/ci/yardstick-verify
@@ -99,8 +99,8 @@ set -o pipefail
install_storperf()
{
- # Install Storper on huawei-pod1
- if [ "$NODE_NAME" == "huawei-pod1" ]; then
+ # Install Storper on huawei-pod1 and huawei-pod2
+ if [ "$NODE_NAME" == "huawei-pod1" -o "$NODE_NAME" == "huawei-pod2" ]; then
echo
echo "========== Installing storperf =========="
@@ -114,8 +114,8 @@ install_storperf()
remove_storperf()
{
- # remove Storper from huawei-pod1
- if [ "$NODE_NAME" == "huawei-pod1" ]; then
+ # remove Storper from huawei-pod1 and huawei-pod2
+ if [ "$NODE_NAME" == "huawei-pod1" -o "$NODE_NAME" == "huawei-pod2" ]; then
echo
echo "========== Removing storperf =========="
@@ -293,8 +293,13 @@ main()
echo
# check OpenStack services
+ if [[ $OS_INSECURE ]] && [[ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]]; then
+ SECURE="--insecure"
+ else
+ SECURE=""
+ fi
echo "Checking OpenStack services:"
- for cmd in "openstack image list" "openstack server list" "openstack stack list"; do
+ for cmd in "openstack ${SECURE} image list" "openstack ${SECURE} server list" "openstack ${SECURE} stack list"; do
echo " checking ${cmd} ..."
if ! $cmd >/dev/null; then
echo "error: command \"$cmd\" failed"
diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml
index 4c7fdab90..f5ccb255a 100644
--- a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml
+++ b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml
@@ -29,12 +29,21 @@ scenarios:
packetsize: {{pkt_size}}
number_of_ports: {{num_ports}}
duration: 20
+ # choose vnic name: default to eth0
+ # vnic_name: 'ens3'
+ # turn on multiqueue inside VM
+ # multiqueue: True
+ # choose starting pps: default 1M;
+ # works with binary search runner Dynamictp to find max throughput per sla
+ # pps: 3000000
host: demeter.yardstick-TC008
target: poseidon.yardstick-TC008
runner:
type: Iteration
+ # binary search runner
+ # type: Dynamictp
iterations: 10
interval: 1
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..2804f25a2
--- /dev/null
+++ b/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml
@@ -0,0 +1,176 @@
+##############################################################################
+# 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" %}
+
+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'
diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml
index 5bd3f676f..ef4f02c9e 100644
--- a/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml
+++ b/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml
@@ -14,13 +14,14 @@ description: >
StorPerf is a tool to measure block and object storage performance in an NFVI.
{% set public_network = public_network or "ext-net" %}
-{% set StorPerf_ip = StorPerf_ip or "192.168.10.6" %}
+{% set StorPerf_ip = StorPerf_ip or "192.168.200.1" %}
scenarios:
-
type: StorPerf
options:
agent_count: 1
agent_image: "Ubuntu-16.04"
+ agent_flavor: "storperf"
public_network: {{public_network}}
volume_size: 4
block_sizes: "4096"
diff --git a/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml b/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml
index ba1a93cec..dea44c8b3 100644
--- a/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml
+++ b/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml
@@ -134,10 +134,12 @@ test_cases:
file_name: opnfv_yardstick_tc074.yaml
constraint:
installer: compass
- pod: huawei-pod1
+ pod: huawei-pod1, huawei-pod2
task_args:
huawei-pod1: '{"public_network": "ext-net",
- "StorPerf_ip": "192.168.200.1"}'
+ "StorPerf_ip": "192.168.10.6"}'
+ huawei-pod2: '{"public_network": "ext-net",
+ "StorPerf_ip": "192.168.11.2"}'
-
file_name: opnfv_yardstick_tc075.yaml
constraint:
diff --git a/tests/unit/apiserver/utils/test_common.py b/tests/unit/apiserver/utils/test_common.py
deleted file mode 100644
index ad81cb76b..000000000
--- a/tests/unit/apiserver/utils/test_common.py
+++ /dev/null
@@ -1,41 +0,0 @@
-##############################################################################
-# Copyright (c) 2016 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 absolute_import
-import unittest
-
-from api.utils import common
-
-
-class TranslateToStrTestCase(unittest.TestCase):
-
- def test_translate_to_str_unicode(self):
- input_str = u'hello'
- output_str = common.translate_to_str(input_str)
-
- result = 'hello'
- self.assertEqual(result, output_str)
-
- def test_translate_to_str_dict_list_unicode(self):
- input_str = {
- u'hello': {u'hello': [u'world']}
- }
- output_str = common.translate_to_str(input_str)
-
- result = {
- 'hello': {'hello': ['world']}
- }
- self.assertEqual(result, output_str)
-
-
-def main():
- unittest.main()
-
-
-if __name__ == '__main__':
- main()
diff --git a/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml b/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml
new file mode 100644
index 000000000..306915ca1
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml
@@ -0,0 +1,32 @@
+nodes:
+-
+ name: sriov
+ role: Sriov1
+ ip: 10.123.123.122
+ user: root
+ auth_type: password
+ password: password
+ vf_macs:
+ - "00:00:00:00:00:00"
+ - "00:00:00:00:00:00"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+-
+ name: sriov
+ role: Sriov1
+ ip: 10.123.123.111
+ user: root
+ auth_type: password
+ password: password
+ vf_macs:
+ - "00:00:00:00:00:00"
+ - "00:00:00:00:00:00"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
diff --git a/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml b/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml
new file mode 100644
index 000000000..65449c91c
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml
@@ -0,0 +1,63 @@
+# Copyright (c) 2016 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+nodes:
+-
+ name: ovs
+ role: test
+ ip: 10.223.197.222
+ user: root
+ auth_type: password
+ password: intel123
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+-
+ name: ovs
+ role: test
+ ip: 10.223.197.112
+ user: root
+ auth_type: password
+ password: intel123
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
diff --git a/tests/unit/benchmark/contexts/nodes_sample_new.yaml b/tests/unit/benchmark/contexts/nodes_sample_new.yaml
new file mode 100644
index 000000000..a400bec03
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_sample_new.yaml
@@ -0,0 +1,96 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.123.123.123
+ user: root
+ auth_type: password
+ password: password
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+-
+ name: sriov
+ role: Sriov
+ ip: 10.123.123.122
+ user: root
+ auth_type: password
+ password: password
+ vf_macs:
+ - "00:00:00:00:00:00"
+ - "00:00:00:00:00:00"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
+-
+ name: vnf
+ role: vnf
+ ip: 10.123.123.121
+ user: root
+ auth_type: password
+ password: password
+ host: 10.123.123.121 #BM host == ip, SRIOV & ovs-dpdk host == compute node.
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:06:00.0"
+ driver: i40e
+ dpdk_port_num: 0
+ local_ip: "152.16.100.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:06:00.1"
+ driver: i40e
+ dpdk_port_num: 1
+ local_ip: "152.16.40.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+ routing_table:
+ - network: "152.16.100.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.100.20"
+ if: "xe0"
+ - network: "152.16.40.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.40.20"
+ if: "xe1"
+ nd_route_tbl:
+ - network: "0064:ff9b:0:0:0:0:9810:6414"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:6414"
+ if: "xe0"
+ - network: "0064:ff9b:0:0:0:0:9810:2814"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:2814"
+ if: "xe1"
+
diff --git a/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml b/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml
new file mode 100644
index 000000000..55ff2e778
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml
@@ -0,0 +1,82 @@
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.123.123.123
+ user: root
+ auth_type: password
+ password: password
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+-
+ name: sriov
+ role: Sriov1
+ ip: 10.123.123.122
+ user: root
+ auth_type: password
+ password: password
+ vf_macs:
+ - "00:00:00:00:00:00"
+ - "00:00:00:00:00:00"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
+-
+ name: vnf
+ role: vnf
+ ip: 10.123.123.121
+ user: root
+ auth_type: password
+ password: password
+ host: 10.123.123.121 #BM host == ip, SRIOV & ovs-dpdk host == compute node.
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:06:00.0"
+ driver: i40e
+ dpdk_port_num: 0
+ local_ip: "152.16.100.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:06:00.1"
+ driver: i40e
+ dpdk_port_num: 1
+ local_ip: "152.16.40.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:00"
+ routing_table:
+ - network: "152.16.100.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.100.20"
+ if: "xe0"
+ - network: "152.16.40.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.40.20"
+ if: "xe1"
+ nd_route_tbl:
+ - network: "0064:ff9b:0:0:0:0:9810:6414"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:6414"
+ if: "xe0"
+ - network: "0064:ff9b:0:0:0:0:9810:2814"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:2814"
+ if: "xe1"
+
diff --git a/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml b/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml
new file mode 100644
index 000000000..b1da1ea9f
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml
@@ -0,0 +1,104 @@
+# Copyright (c) 2016 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.223.197.182
+ user: root
+ auth_type: password
+ password: intel123
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: ovs
+ role: Ovsdpdk
+ ip: 10.223.197.222
+ user: root
+ auth_type: password
+ password: intel123
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
+-
+ name: vnf
+ role: vnf
+ ip: 10.223.197.155
+ user: root
+ auth_type: password
+ password: intel123
+ host: 10.223.197.140
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:04.0"
+ driver: virtio-pci
+ dpdk_port_num: 0
+ local_ip: "152.16.100.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:03"
+
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:05.0"
+ driver: virtio-pci
+ dpdk_port_num: 1
+ local_ip: "152.16.40.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:04"
+ routing_table:
+ - network: "152.16.100.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.100.20"
+ if: "xe0"
+ - network: "152.16.40.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.40.20"
+ if: "xe1"
+ nd_route_tbl:
+ - network: "0064:ff9b:0:0:0:0:9810:6414"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:6414"
+ if: "xe0"
+ - network: "0064:ff9b:0:0:0:0:9810:2814"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:2814"
+ if: "xe1"
diff --git a/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml b/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml
new file mode 100644
index 000000000..c02849a05
--- /dev/null
+++ b/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml
@@ -0,0 +1,104 @@
+# Copyright (c) 2016 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.223.197.182
+ user: root
+ auth_type: password
+ password: intel123
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: ovs
+ role: Ovsdpdk1
+ ip: 10.223.197.222
+ user: root
+ auth_type: password
+ password: intel123
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
+-
+ name: vnf
+ role: vnf
+ ip: 10.223.197.155
+ user: root
+ auth_type: password
+ password: intel123
+ host: 10.223.197.140
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:04.0"
+ driver: virtio-pci
+ dpdk_port_num: 0
+ local_ip: "152.16.100.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:03"
+
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:05.0"
+ driver: virtio-pci
+ dpdk_port_num: 1
+ local_ip: "152.16.40.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:04"
+ routing_table:
+ - network: "152.16.100.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.100.20"
+ if: "xe0"
+ - network: "152.16.40.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.40.20"
+ if: "xe1"
+ nd_route_tbl:
+ - network: "0064:ff9b:0:0:0:0:9810:6414"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:6414"
+ if: "xe0"
+ - network: "0064:ff9b:0:0:0:0:9810:2814"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:2814"
+ if: "xe1"
diff --git a/tests/unit/benchmark/contexts/ovs_sample_password.yaml b/tests/unit/benchmark/contexts/ovs_sample_password.yaml
new file mode 100644
index 000000000..b1da1ea9f
--- /dev/null
+++ b/tests/unit/benchmark/contexts/ovs_sample_password.yaml
@@ -0,0 +1,104 @@
+# Copyright (c) 2016 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.223.197.182
+ user: root
+ auth_type: password
+ password: intel123
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: ovs
+ role: Ovsdpdk
+ ip: 10.223.197.222
+ user: root
+ auth_type: password
+ password: intel123
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
+-
+ name: vnf
+ role: vnf
+ ip: 10.223.197.155
+ user: root
+ auth_type: password
+ password: intel123
+ host: 10.223.197.140
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:04.0"
+ driver: virtio-pci
+ dpdk_port_num: 0
+ local_ip: "152.16.100.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:03"
+
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:00:05.0"
+ driver: virtio-pci
+ dpdk_port_num: 1
+ local_ip: "152.16.40.19"
+ netmask: "255.255.255.0"
+ local_mac: "00:00:00:00:00:04"
+ routing_table:
+ - network: "152.16.100.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.100.20"
+ if: "xe0"
+ - network: "152.16.40.20"
+ netmask: "255.255.255.0"
+ gateway: "152.16.40.20"
+ if: "xe1"
+ nd_route_tbl:
+ - network: "0064:ff9b:0:0:0:0:9810:6414"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:6414"
+ if: "xe0"
+ - network: "0064:ff9b:0:0:0:0:9810:2814"
+ netmask: "112"
+ gateway: "0064:ff9b:0:0:0:0:9810:2814"
+ if: "xe1"
diff --git a/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml b/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml
new file mode 100644
index 000000000..896ec33bb
--- /dev/null
+++ b/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml
@@ -0,0 +1,69 @@
+##############################################################################
+# 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
+##############################################################################
+---
+# Sample config file about the POD information, including the
+# name/IP/user/ssh key of Bare Metal and Controllers/Computes
+#
+# The options of this config file include:
+# name: the name of this node
+# role: node's role, support role: Master/Controller/Comupte/BareMetal
+# ip: the node's IP address
+# user: the username for login
+# key_filename:the path of the private key file for login
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.10.10.10
+ auth_type: ssh_key
+ user: root
+ ssh_port: 22
+ key_filename: /root/.ssh/id_rsa
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: ovs
+ role: Ovsdpdk
+ ip: 10.223.197.222
+ auth_type: ssh_key
+ user: root
+ ssh_port: 22
+ key_filename: /root/.ssh/id_rsa
+ vpath: "/usr/local/"
+ vports:
+ - dpdkvhostuser0
+ - dpdkvhostuser1
+ vports_mac:
+ - "00:00:00:00:00:03"
+ - "00:00:00:00:00:04"
+ phy_ports: # Physical ports to configure ovs
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ flow:
+ - ovs-ofctl add-flow br0 in_port=1,action=output:3
+ - ovs-ofctl add-flow br0 in_port=3,action=output:1
+ - ovs-ofctl add-flow br0 in_port=4,action=output:2
+ - ovs-ofctl add-flow br0 in_port=2,action=output:4
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
+
diff --git a/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt b/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt
new file mode 100644
index 000000000..f0eec86f6
--- /dev/null
+++ b/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/tests/unit/benchmark/contexts/sriov_sample_password.yaml b/tests/unit/benchmark/contexts/sriov_sample_password.yaml
new file mode 100644
index 000000000..4f60e46d5
--- /dev/null
+++ b/tests/unit/benchmark/contexts/sriov_sample_password.yaml
@@ -0,0 +1,52 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.10.10.10
+ auth_type: password
+ user: root
+ password: password
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: sriov
+ role: Sriov
+ ip: 10.10.10.11
+ auth_type: password
+ user: root
+ password: password
+ vf_macs:
+ - "00:00:00:71:7d:25"
+ - "00:00:00:71:7d:26"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
diff --git a/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml b/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml
new file mode 100644
index 000000000..faa496771
--- /dev/null
+++ b/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml
@@ -0,0 +1,54 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+nodes:
+-
+ name: trafficgen_1
+ role: TrafficGen
+ ip: 10.10.10.10
+ auth_type: ssh_key
+ user: root
+ ssh_port: 22
+ key_filename: /root/.ssh/id_rsa
+ interfaces:
+ xe0: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.0"
+ driver: ixgbe
+ dpdk_port_num: 0
+ local_ip: "152.16.100.20"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:68"
+ xe1: # logical name from topology.yaml and vnfd.yaml
+ vpci: "0000:03:00.1"
+ driver: ixgbe
+ dpdk_port_num: 1
+ local_ip: "152.16.100.21"
+ netmask: "255.255.255.0"
+ local_mac: "90:e2:ba:77:ce:69"
+-
+ name: sriov
+ role: Sriov
+ ip: 10.10.10.11
+ auth_type: ssh_key
+ user: root
+ ssh_port: 22
+ key_filename: /root/.ssh/id_rsa
+ vf_macs:
+ - "00:00:00:71:7d:25"
+ - "00:00:00:71:7d:26"
+ phy_ports: # Physical ports to configure sriov
+ - "0000:06:00.0"
+ - "0000:06:00.1"
+ phy_driver: i40e # kernel driver
+ images: "/var/lib/libvirt/images/ubuntu1.img"
diff --git a/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt b/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt
new file mode 100644
index 000000000..f0eec86f6
--- /dev/null
+++ b/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py
index 3dadd48eb..ae57402c0 100644
--- a/tests/unit/benchmark/contexts/test_heat.py
+++ b/tests/unit/benchmark/contexts/test_heat.py
@@ -13,6 +13,7 @@
from __future__ import absolute_import
+import ipaddress
import logging
import os
import unittest
@@ -120,7 +121,8 @@ class HeatContextTestCase(unittest.TestCase):
mock_template.add_router_interface.assert_called_with("bar-fool-network-router-if0", "bar-fool-network-router", "bar-fool-network-subnet")
@mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
- def test_deploy(self, mock_template):
+ @mock.patch('yardstick.benchmark.contexts.heat.get_neutron_client')
+ def test_deploy(self, mock_neutron, mock_template):
self.test_context.name = 'foo'
self.test_context.template_file = '/bar/baz/some-heat-file'
@@ -133,6 +135,59 @@ class HeatContextTestCase(unittest.TestCase):
self.test_context.heat_parameters)
self.assertIsNotNone(self.test_context.stack)
+ def test_add_server_port(self):
+ network1 = mock.MagicMock()
+ network1.vld_id = 'vld111'
+ network2 = mock.MagicMock()
+ network2.vld_id = 'vld777'
+ self.test_context.name = 'foo'
+ self.test_context.stack = mock.MagicMock()
+ self.test_context.networks = {
+ 'a': network1,
+ 'c': network2,
+ }
+ self.test_context.stack.outputs = {
+ u'b': u'10.20.30.45',
+ u'b-subnet_id': 1,
+ u'foo-a-subnet-cidr': u'10.20.0.0/15',
+ u'foo-a-subnet-gateway_ip': u'10.20.30.1',
+ u'b-mac_address': u'00:01',
+ u'b-device_id': u'dev21',
+ u'b-network_id': u'net789',
+ u'd': u'40.30.20.15',
+ u'd-subnet_id': 2,
+ u'foo-c-subnet-cidr': u'40.30.0.0/18',
+ u'foo-c-subnet-gateway_ip': u'40.30.20.254',
+ u'd-mac_address': u'00:10',
+ u'd-device_id': u'dev43',
+ u'd-network_id': u'net987',
+ }
+ server = mock.MagicMock()
+ server.ports = OrderedDict([
+ ('a', {'stack_name': 'b'}),
+ ('c', {'stack_name': 'd'}),
+ ])
+
+ expected = {
+ "private_ip": '10.20.30.45',
+ "subnet_id": 1,
+ "subnet_cidr": '10.20.0.0/15',
+ "network": '10.20.0.0',
+ "netmask": '255.254.0.0',
+ "gateway_ip": '10.20.30.1',
+ "mac_address": '00:01',
+ "device_id": 'dev21',
+ "network_id": 'net789',
+ "network_name": 'a',
+ "local_mac": '00:01',
+ "local_ip": '10.20.30.45',
+ "vld_id": 'vld111',
+ }
+ self.test_context.add_server_port(server)
+ self.assertEqual(server.private_ip, '10.20.30.45')
+ self.assertEqual(len(server.interfaces), 2)
+ self.assertDictEqual(server.interfaces['a'], expected)
+
@mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
def test_undeploy(self, mock_template):
@@ -155,3 +210,57 @@ class HeatContextTestCase(unittest.TestCase):
self.assertEqual(result['ip'], '127.0.0.1')
self.assertEqual(result['private_ip'], '10.0.0.1')
+
+ def test__get_network(self):
+ network1 = mock.MagicMock()
+ network1.name = 'net_1'
+ network1.vld_id = 'vld111'
+ network1.segmentation_id = 'seg54'
+ network1.network_type = 'type_a'
+ network1.physical_network = 'phys'
+
+ network2 = mock.MagicMock()
+ network2.name = 'net_2'
+ network2.vld_id = 'vld999'
+ network2.segmentation_id = 'seg45'
+ network2.network_type = 'type_b'
+ network2.physical_network = 'virt'
+
+ self.test_context.networks = {
+ 'a': network1,
+ 'b': network2,
+ }
+
+ attr_name = None
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld777'}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = 'vld777'
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld999'}
+ expected = {
+ "name": 'net_2',
+ "vld_id": 'vld999',
+ "segmentation_id": 'seg45',
+ "network_type": 'type_b',
+ "physical_network": 'virt',
+ }
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
+
+ attr_name = 'a'
+ expected = {
+ "name": 'net_1',
+ "vld_id": 'vld111',
+ "segmentation_id": 'seg54',
+ "network_type": 'type_a',
+ "physical_network": 'phys',
+ }
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
diff --git a/tests/unit/benchmark/contexts/test_kubernetes.py b/tests/unit/benchmark/contexts/test_kubernetes.py
new file mode 100644
index 000000000..f47c07a67
--- /dev/null
+++ b/tests/unit/benchmark/contexts/test_kubernetes.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+
+##############################################################################
+# 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
+##############################################################################
+
+# Unittest for yardstick.benchmark.contexts.kubernetes
+
+from __future__ import absolute_import
+import unittest
+import mock
+
+from yardstick.benchmark.contexts.kubernetes import KubernetesContext
+
+
+context_cfg = {
+ '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']
+ },
+ 'target': {
+ 'image': 'openretriever/yardstick',
+ 'command': '/bin/bash',
+ 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \
+service ssh restart;while true ; do sleep 10000; done']
+ }
+ }
+}
+
+prefix = 'yardstick.benchmark.contexts.kubernetes'
+
+
+class UndeployTestCase(unittest.TestCase):
+
+ @mock.patch('{}.KubernetesContext._delete_ssh_key'.format(prefix))
+ @mock.patch('{}.KubernetesContext._delete_rcs'.format(prefix))
+ @mock.patch('{}.KubernetesContext._delete_pods'.format(prefix))
+ def test_undeploy(self,
+ mock_delete_pods,
+ mock_delete_rcs,
+ mock_delete_ssh):
+
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context.undeploy()
+ self.assertTrue(mock_delete_ssh.called)
+ self.assertTrue(mock_delete_rcs.called)
+ self.assertTrue(mock_delete_pods.called)
+
+
+class DeployTestCase(unittest.TestCase):
+
+ @mock.patch('{}.KubernetesContext._wait_until_running'.format(prefix))
+ @mock.patch('{}.KubernetesTemplate.get_rc_pods'.format(prefix))
+ @mock.patch('{}.KubernetesContext._create_rcs'.format(prefix))
+ @mock.patch('{}.KubernetesContext._set_ssh_key'.format(prefix))
+ def test_deploy(self,
+ mock_set_ssh_key,
+ mock_create_rcs,
+ mock_get_rc_pods,
+ mock_wait_until_running):
+
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context.deploy()
+ self.assertTrue(mock_set_ssh_key.called)
+ self.assertTrue(mock_create_rcs.called)
+ self.assertTrue(mock_get_rc_pods.called)
+ self.assertTrue(mock_wait_until_running.called)
+
+
+class SSHKeyTestCase(unittest.TestCase):
+
+ @mock.patch('{}.k8s_utils.delete_config_map'.format(prefix))
+ @mock.patch('{}.k8s_utils.create_config_map'.format(prefix))
+ def test_ssh_key(self, mock_create, mock_delete):
+
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context._set_ssh_key()
+ k8s_context._delete_ssh_key()
+ self.assertTrue(mock_create.called)
+ self.assertTrue(mock_delete.called)
+
+
+class WaitUntilRunningTestCase(unittest.TestCase):
+
+ @mock.patch('{}.k8s_utils.read_pod_status'.format(prefix))
+ def test_wait_until_running(self, mock_read_pod_status):
+
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context.template.pods = ['server']
+ mock_read_pod_status.return_value = 'Running'
+ k8s_context._wait_until_running()
+
+
+class GetServerTestCase(unittest.TestCase):
+
+ @mock.patch('{}.k8s_utils.get_pod_list'.format(prefix))
+ def test_get_server(self, mock_get_pod_list):
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+
+ mock_get_pod_list.return_value.items = []
+ server = k8s_context._get_server('server')
+ self.assertIsNone(server)
+
+
+class CreateRcsTestCase(unittest.TestCase):
+
+ @mock.patch('{}.KubernetesContext._create_rc'.format(prefix))
+ def test_create_rcs(self, mock_create_rc):
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context._create_rcs()
+ self.assertTrue(mock_create_rc.called)
+
+
+class CreateRcTestCase(unittest.TestCase):
+
+ @mock.patch('{}.k8s_utils.create_replication_controller'.format(prefix))
+ def test_create_rc(self, mock_create_replication_controller):
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context._create_rc({})
+ self.assertTrue(mock_create_replication_controller.called)
+
+
+class DeleteRcsTestCases(unittest.TestCase):
+
+ @mock.patch('{}.KubernetesContext._delete_rc'.format(prefix))
+ def test_delete_rcs(self, mock_delete_rc):
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context._delete_rcs()
+ self.assertTrue(mock_delete_rc.called)
+
+
+class DeleteRcTestCase(unittest.TestCase):
+
+ @mock.patch('{}.k8s_utils.delete_replication_controller'.format(prefix))
+ def test_delete_rc(self, mock_delete_replication_controller):
+ k8s_context = KubernetesContext()
+ k8s_context.init(context_cfg)
+ k8s_context._delete_rc({})
+ self.assertTrue(mock_delete_replication_controller.called)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/unit/benchmark/contexts/test_model.py b/tests/unit/benchmark/contexts/test_model.py
index 3fb186b9b..1ce550306 100644
--- a/tests/unit/benchmark/contexts/test_model.py
+++ b/tests/unit/benchmark/contexts/test_model.py
@@ -161,6 +161,23 @@ class NetworkTestCase(unittest.TestCase):
self.assertEqual(model.Network.find_external_network(), 'ext_net')
+ def test_construct_gateway_ip_is_null(self):
+
+ attrs = {'gateway_ip': 'null'}
+ test_network = model.Network('foo', self.mock_context, attrs)
+ self.assertEqual(test_network.gateway_ip, 'null')
+
+ def test_construct_gateway_ip_is_none(self):
+
+ attrs = {'gateway_ip': None}
+ test_network = model.Network('foo', self.mock_context, attrs)
+ self.assertEqual(test_network.gateway_ip, 'null')
+
+ def test_construct_gateway_ip_is_absent(self):
+
+ attrs = {}
+ test_network = model.Network('foo', self.mock_context, attrs)
+ self.assertIsNone(test_network.gateway_ip)
class ServerTestCase(unittest.TestCase):
@@ -214,11 +231,12 @@ class ServerTestCase(unittest.TestCase):
attrs = {'image': 'some-image', 'flavor': 'some-flavor', 'floating_ip': '192.168.1.10', 'floating_ip_assoc': 'some-vm'}
test_server = model.Server('foo', self.mock_context, attrs)
- self.mock_context.flavors = ['flavor1', 'flavor2', 'some-flavor']
+ self.mock_context.flavors = ['flavor1', 'flavor2', 'some-flavor']
mock_network = mock.Mock()
mock_network.name = 'some-network'
mock_network.stack_name = 'some-network-stack'
+ mock_network.allowed_address_pairs = ["1", "2"]
mock_network.subnet_stack_name = 'some-network-stack-subnet'
mock_network.provider = 'sriov'
mock_network.external_network = 'ext_net'
@@ -232,7 +250,8 @@ class ServerTestCase(unittest.TestCase):
mock_network.stack_name,
mock_network.subnet_stack_name,
sec_group_id=self.mock_context.secgroup_name,
- provider=mock_network.provider)
+ provider=mock_network.provider,
+ allowed_address_pairs=mock_network.allowed_address_pairs)
mock_template.add_floating_ip.assert_called_with(
'some-server-fip',
@@ -290,11 +309,12 @@ class ServerTestCase(unittest.TestCase):
}
test_server = model.Server('ServerFlavor-2', self.mock_context, attrs)
- self.mock_context.flavors = ['flavor2']
+ self.mock_context.flavors = ['flavor2']
mock_network = mock.Mock()
- mock_network.configure_mock(name='some-network', stack_name= 'some-network-stack',
- subnet_stack_name = 'some-network-stack-subnet',
- provider = 'some-provider')
+ mock_network.allowed_address_pairs = ["1", "2"]
+ mock_network.configure_mock(name='some-network', stack_name='some-network-stack',
+ subnet_stack_name='some-network-stack-subnet',
+ provider='some-provider')
test_server._add_instance(mock_template, 'ServerFlavor-2',
[mock_network], 'hints')
@@ -304,7 +324,8 @@ class ServerTestCase(unittest.TestCase):
mock_network.stack_name,
mock_network.subnet_stack_name,
provider=mock_network.provider,
- sec_group_id=self.mock_context.secgroup_name)
+ sec_group_id=self.mock_context.secgroup_name,
+ allowed_address_pairs=mock_network.allowed_address_pairs)
mock_template.add_server.assert_called_with(
'ServerFlavor-2', 'some-image',
diff --git a/tests/unit/benchmark/contexts/test_node.py b/tests/unit/benchmark/contexts/test_node.py
index 4b35ca421..d5ce8c5cb 100644
--- a/tests/unit/benchmark/contexts/test_node.py
+++ b/tests/unit/benchmark/contexts/test_node.py
@@ -208,6 +208,50 @@ class NodeContextTestCase(unittest.TestCase):
obj._get_client(node_name_args)
self.assertTrue(wait_mock.called)
+ def test__get_network(self):
+ network1 = {
+ 'name': 'net_1',
+ 'vld_id': 'vld111',
+ 'segmentation_id': 'seg54',
+ 'network_type': 'type_a',
+ 'physical_network': 'phys',
+ }
+ network2 = {
+ 'name': 'net_2',
+ 'vld_id': 'vld999',
+ }
+ self.test_context.networks = {
+ 'a': network1,
+ 'b': network2,
+ }
+
+ attr_name = {}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld777'}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ self.assertIsNone(self.test_context._get_network(None))
+
+ attr_name = 'vld777'
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld999'}
+ expected = {
+ "name": 'net_2',
+ "vld_id": 'vld999',
+ "segmentation_id": None,
+ "network_type": None,
+ "physical_network": None,
+ }
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
+
+ attr_name = 'a'
+ expected = network1
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
+
def main():
unittest.main()
diff --git a/tests/unit/benchmark/contexts/test_ovsdpdk.py b/tests/unit/benchmark/contexts/test_ovsdpdk.py
new file mode 100644
index 000000000..ac25ec877
--- /dev/null
+++ b/tests/unit/benchmark/contexts/test_ovsdpdk.py
@@ -0,0 +1,325 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os
+import mock
+import unittest
+
+from yardstick.benchmark.contexts import ovsdpdk
+
+NIC_INPUT = {
+ 'interface': {},
+ 'vports_mac': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+DRIVER = "i40e"
+NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vports_mac': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+
+CORRECT_FILE_PATH = "/etc/yardstick/nodes/pod_ovs.yaml"
+WRONG_FILE_PATH = "/etc/yardstick/wrong.yaml"
+SAMPLE_FILE = "ovs_sample_write_to_file.txt"
+
+OVS = [{
+ 'auth_type': 'ssh_key',
+ 'name': 'ovs',
+ 'ssh_port': 22,
+ 'ip': '10.10.10.11',
+ 'key_filename': '/root/.ssh/id_rsa',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'vpath': '/usr/local/',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3',
+ 'ovs-ofctl add-flow br0 in_port=3,action=output:1',
+ 'ovs-ofctl add-flow br0 in_port=4,action=output:2',
+ 'ovs-ofctl add-flow br0 in_port=2,action=output:4'],
+ 'phy_driver': 'i40e',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+OVS_PASSWORD = [{
+ 'auth_type': 'password',
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.10.10.11',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3',
+ 'ovs-ofctl add-flow br0 in_port=3,action=output:1',
+ 'ovs-ofctl add-flow br0 in_port=4,action=output:2',
+ 'ovs-ofctl add-flow br0 in_port=2,action=output:4'],
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+#vfnic = "i40evf"
+PCIS = ['0000:06:00.0', '0000:06:00.1']
+
+
+class OvsdpdkTestCase(unittest.TestCase):
+
+ NODES_SAMPLE_SSH = "ovs_sample_ssh_key.yaml"
+ NODES_SAMPLE_PASSWORD = "ovs_sample_password.yaml"
+
+ def setUp(self):
+ self.test_context = ovsdpdk.Ovsdpdk()
+
+ def test_construct(self):
+ self.assertIsNone(self.test_context.name)
+ self.assertIsNone(self.test_context.file_path)
+ self.assertEqual(self.test_context.nodes, [])
+ self.assertEqual(self.test_context.ovs, [])
+ self.assertFalse(self.test_context.vm_deploy)
+ self.assertTrue(self.test_context.first_run)
+ self.assertEqual(self.test_context.user, "")
+ self.assertEqual(self.test_context.ssh_ip, "")
+ self.assertEqual(self.test_context.passwd, "")
+ self.assertEqual(self.test_context.ssh_port, "")
+ self.assertEqual(self.test_context.auth_type, "")
+
+ def test_init(self):
+ self.test_context.parse_pod_and_get_data = mock.Mock()
+ self.test_context.file_path = CORRECT_FILE_PATH
+ self.test_context.init()
+ self.assertIsNone(self.test_context.init())
+
+ def test_successful_init_with_ssh(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_SSH)
+ self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH)
+
+ def test_successful_init_with_password(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD)
+ self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH)
+
+ def test_unsuccessful_init(self):
+ self.assertRaises(
+ IOError,
+ lambda: self.test_context.parse_pod_and_get_data(WRONG_FILE_PATH))
+
+ def test_ssh_connection(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+
+ @mock.patch('yardstick.network_services.utils.provision_tool', return_value="b")
+ def test_ssh_connection(self, mock_prov):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(1, "b", ""))
+ ssh.return_value = ssh_mock
+ mock_prov.provision_tool = mock.Mock()
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS_PASSWORD
+ self.assertIsNone(ovs_obj.ssh_remote_machine())
+
+ @mock.patch('yardstick.network_services.utils.provision_tool', return_value="b")
+ def test_ssh_connection_ssh_key(self, mock_prov):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(1, "b", ""))
+ ssh.return_value = ssh_mock
+ mock_prov.provision_tool = mock.Mock()
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ ovs_obj.key_filename = '/root/.ssh/id_rsa'
+ self.assertIsNone(ovs_obj.ssh_remote_machine())
+
+ def test_get_nic_details(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "eth0 eth1", ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.ovs = OVS
+ ovs_obj.connection = ssh_mock
+ self.assertIsNotNone(ovs_obj.get_nic_details())
+
+ def test_install_req_libs(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.first_run = True
+ ovs_obj.connection = ssh_mock
+ self.assertIsNone(ovs_obj.install_req_libs())
+
+ def test_setup_ovs(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ self.assertIsNone(ovs_obj.setup_ovs({"eth0 eth1"}))
+
+ def test_start_ovs_serverswitch(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ self.assertIsNone(ovs_obj.start_ovs_serverswitch())
+
+ def test_setup_ovs_bridge(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ self.assertIsNone(ovs_obj.setup_ovs_bridge())
+
+ def test_add_oflows(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ self.assertIsNone(ovs_obj.add_oflows())
+
+ def test_setup_ovs_context_vm_already_present(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ mock_ovs = mock.Mock()
+ ssh_mock.put = mock.Mock()
+ ovs_obj.check_output = mock.Mock(return_value=(0, "vm1"))
+ 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',
+ return_value="Domain vm1 created from /tmp/vm_ovs.xml")
+ def test_is_vm_created(self, NIC_INPUT):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.put = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ mock_ovs = mock.Mock()
+ ret_create = mock.Mock()
+ pcis = NIC_DETAILS['pci']
+ driver = NIC_DETAILS['phy_driver']
+ self.assertIsNotNone(
+ mock_ovs.ovs_obj.setup_ovs_context(
+ pcis,
+ NIC_DETAILS,
+ driver))
+
+ def test_check_output(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ cmd = "command"
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ self.assertIsNotNone(ovs_obj.check_output(cmd, None))
+
+ def test_split_cpu_list_available(self):
+ with mock.patch("itertools.chain") as iter1:
+ iter1 = mock.Mock()
+ print("{0}".format(iter1))
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ self.assertIsNotNone(ovs_obj.split_cpu_list('0,5'))
+
+ def test_split_cpu_list_null(self):
+ with mock.patch("itertools.chain") as iter1:
+ iter1 = mock.Mock()
+ print("{0}".format(iter1))
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ self.assertEqual(ovs_obj.split_cpu_list([]), [])
+
+ def test_destroy_vm_successful(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ ovs_obj.check_output = mock.Mock(return_value=(0, "vm1"))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "0 i40e"))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "0 i40e"))
+ self.assertIsNone(ovs_obj.destroy_vm())
+
+ def test_destroy_vm_unsuccessful(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ ovs_obj.connection = ssh_mock
+ ovs_obj.ovs = OVS
+ ovs_obj.check_output = mock.Mock(return_value=(1, {}))
+ self.assertIsNone(ovs_obj.destroy_vm())
+
+ def test_read_from_file(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD)
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ self.assertIsNotNone(ovs_obj.read_from_file(CORRECT_FILE_PATH))
+
+ def test_write_to_file(self):
+ ovs_obj = ovsdpdk.Ovsdpdk()
+ self.assertIsNone(ovs_obj.write_to_file(SAMPLE_FILE, "some content"))
+
+ def _get_file_abspath(self, filename):
+ curr_path = os.path.dirname(os.path.abspath(__file__))
+ file_path = os.path.join(curr_path, filename)
+ return file_path
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/unit/benchmark/contexts/test_sriov.py b/tests/unit/benchmark/contexts/test_sriov.py
new file mode 100644
index 000000000..a8641a2eb
--- /dev/null
+++ b/tests/unit/benchmark/contexts/test_sriov.py
@@ -0,0 +1,421 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os
+import mock
+import unittest
+
+from yardstick.benchmark.contexts import sriov
+
+NIC_INPUT = {
+ 'interface': {},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+DRIVER = "i40e"
+NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+
+CORRECT_FILE_PATH = "/etc/yardstick/nodes/pod_sriov.yaml"
+WRONG_FILE_PATH = "/etc/yardstick/wrong.yaml"
+SAMPLE_FILE = "sriov_sample_write_to_file.txt"
+
+SRIOV = [{
+ 'auth_type': 'ssh_key',
+ 'name': 'sriov',
+ 'ssh_port': 22,
+ 'ip': '10.10.10.11',
+ 'key_filename': '/root/.ssh/id_rsa',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+SRIOV_PASSWORD = [{
+ 'auth_type': 'password',
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.10.10.11',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+vfnic = "i40evf"
+PCIS = ['0000:06:00.0', '0000:06:00.1']
+
+
+class SriovTestCase(unittest.TestCase):
+
+ NODES_SAMPLE_SSH = "sriov_sample_ssh_key.yaml"
+ NODES_SAMPLE_PASSWORD = "sriov_sample_password.yaml"
+
+ def setUp(self):
+ self.test_context = sriov.Sriov()
+
+ def test_construct(self):
+ self.assertIsNone(self.test_context.name)
+ self.assertIsNone(self.test_context.file_path)
+ self.assertEqual(self.test_context.nodes, [])
+ self.assertEqual(self.test_context.sriov, [])
+ self.assertFalse(self.test_context.vm_deploy)
+ self.assertTrue(self.test_context.first_run)
+ self.assertEqual(self.test_context.user, "")
+ self.assertEqual(self.test_context.ssh_ip, "")
+ self.assertEqual(self.test_context.passwd, "")
+ self.assertEqual(self.test_context.ssh_port, "")
+ self.assertEqual(self.test_context.auth_type, "")
+
+ def test_init(self):
+ self.test_context.parse_pod_and_get_data = mock.Mock()
+ self.test_context.file_path = CORRECT_FILE_PATH
+ self.test_context.init()
+ self.assertIsNone(self.test_context.init())
+
+ def test_successful_init_with_ssh(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_SSH)
+ self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH)
+
+ def test_successful_init_with_password(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD)
+ self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH)
+
+ def test_unsuccessful_init(self):
+ self.assertRaises(
+ IOError,
+ lambda: self.test_context.parse_pod_and_get_data(WRONG_FILE_PATH))
+
+ @mock.patch('yardstick.network_services.utils.provision_tool', return_value="a")
+ def test_ssh_connection(self, mock_prov):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(1, "a", ""))
+ ssh.return_value = ssh_mock
+ mock_prov.provision_tool = mock.Mock()
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV_PASSWORD
+ self.assertIsNone(sriov_obj.ssh_remote_machine())
+
+ @mock.patch('yardstick.network_services.utils.provision_tool', return_value="a")
+ def test_ssh_connection_ssh_key(self, mock_prov):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(1, "a", ""))
+ ssh.return_value = ssh_mock
+ mock_prov.provision_tool = mock.Mock()
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV
+ sriov_obj.key_filename = '/root/.ssh/id_rsa'
+ self.assertIsNone(sriov_obj.ssh_remote_machine())
+
+ def test_get_nic_details(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "eth0 eth1", ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.sriov = SRIOV
+ sriov_obj.connection = ssh_mock
+ self.assertIsNotNone(sriov_obj.get_nic_details())
+
+ def test_install_req_libs(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.first_run = True
+ sriov_obj.connection = ssh_mock
+ self.assertIsNone(sriov_obj.install_req_libs())
+
+ def test_configure_nics_for_sriov(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ nic_details = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e',
+ 'vf_pci': [{}, {}]}
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock((DRIVER), return_value=(0, "0 driver", ""))
+ ssh.return_value = ssh_mock
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ for i in range(len(NIC_DETAILS['pci'])):
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ ssh_mock.execute = \
+ mock.Mock(return_value=(
+ 0,
+ "{'0':'06:02:00','1':'06:06:00'}",
+ ""))
+ sriov_obj.get_vf_datas = mock.Mock(return_value={
+ '0000:06:00.0': '0000:06:02.0'})
+ nic_details['vf_pci'][i] = sriov_obj.get_vf_datas.return_value
+ vf_pci = [[], []]
+ vf_pci[i] = sriov_obj.get_vf_datas.return_value
+ 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:
+ nic_details = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e',
+ 'vf_pci': [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]}
+ vf = [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV
+ blacklist = "/etc/modprobe.d/blacklist.conf"
+ self.assertEqual(vfnic, "i40evf")
+ mock_sriov = mock.Mock()
+ mock_sriov.sriov_obj.read_from_file(blacklist)
+ sriov_obj.read_from_file = mock.Mock(
+ return_value="some random text")
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ sriov_obj.configure_nics_for_sriov = mock.Mock(
+ return_value=nic_details)
+ nic_details = sriov_obj.configure_nics_for_sriov.return_value
+ self.assertEqual(vf, nic_details['vf_pci'])
+ vf = [
+ {'vf_pci': '06:02.00', 'mac': '00:00:00:00:00:0a'},
+ {'vf_pci': '06:06.00', 'mac': '00:00:00:00:00:0b'}]
+ sriov_obj.add_sriov_interface = mock.Mock()
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.put = mock.Mock()
+ sriov_obj.check_output = mock.Mock(return_value=(1, {}))
+ 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:
+ nic_details = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e',
+ 'vf_pci': [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]}
+ vf = [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV
+ blacklist = "/etc/modprobe.d/blacklist.conf"
+ self.assertEqual(vfnic, "i40evf")
+ mock_sriov = mock.Mock()
+ mock_sriov.sriov_obj.read_from_file(blacklist)
+ sriov_obj.read_from_file = mock.Mock(
+ return_value="some random text")
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ sriov_obj.configure_nics_for_sriov = mock.Mock(
+ return_value=nic_details)
+ nic_details = sriov_obj.configure_nics_for_sriov.return_value
+ self.assertEqual(vf, nic_details['vf_pci'])
+ vf = [
+ {'vf_pci': '06:02.00', 'mac': '00:00:00:00:00:0a'},
+ {'vf_pci': '06:06.00', 'mac': '00:00:00:00:00:0b'}]
+ sriov_obj.add_sriov_interface = mock.Mock()
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.put = mock.Mock()
+ sriov_obj.check_output = mock.Mock(return_value=(0, "vm1"))
+ 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',
+ return_value="Domain vm1 created from /tmp/vm_sriov.xml")
+ def test_is_vm_created(self, NIC_INPUT):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ mock_sriov = mock.Mock()
+ pcis = NIC_DETAILS['pci']
+ driver = NIC_DETAILS['phy_driver']
+ self.assertIsNotNone(
+ mock_sriov.sriov_obj.setup_sriov_context(
+ pcis,
+ NIC_DETAILS,
+ driver))
+
+ def test_add_sriov_interface(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ with mock.patch("xml.etree.ElementTree.parse") as parse:
+ with mock.patch("re.search") as re:
+ with mock.patch("xml.etree.ElementTree.SubElement") \
+ as elem:
+ parse = mock.Mock(return_value="root")
+ re = mock.Mock()
+ elem = mock.Mock()
+ print("{0} {1} {2}".format(parse, re, elem))
+ self.assertIsNone(sriov_obj.add_sriov_interface(
+ 0,
+ "0000:06:02.0",
+ "00:00:00:00:00:0a",
+ "/tmp/vm_sriov.xml"))
+
+ def test_get_virtual_devices(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ pci_out = " \
+ PCI_CLASS=20000 \
+ PCI_ID=8086:154C \
+ PCI_SUBSYS_ID=8086:0000 \
+ PCI_SLOT_NAME=0000:06:02.0 \
+ MODALIAS= \
+ pci:v00008086d0000154Csv00008086sd00000000bc02sc00i00"
+ pci = "0000:06:00.0"
+ sriov_obj.check_output = mock.Mock(return_value=(0, pci_out))
+ with mock.patch("re.search") as re:
+ re = mock.Mock(return_value="a")
+ print("{0}".format(re))
+ self.assertIsNotNone(sriov_obj.get_virtual_devices(pci))
+
+ def test_get_vf_datas(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.get_virtual_devices = mock.Mock(
+ return_value={'0000:06:00.0': '0000:06:02.0'})
+ with mock.patch("re.search") as re:
+ re = mock.Mock()
+ print("{0}".format(re))
+ self.assertIsNotNone(sriov_obj.get_vf_datas(
+ 'vf_pci',
+ {'0000:06:00.0': '0000:06:02.0'},
+ "00:00:00:00:00:0a"))
+
+ def test_check_output(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ cmd = "command"
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ self.assertIsNotNone(sriov_obj.check_output(cmd, None))
+
+ def test_split_cpu_list_available(self):
+ with mock.patch("itertools.chain") as iter1:
+ iter1 = mock.Mock()
+ print("{0}".format(iter1))
+ sriov_obj = sriov.Sriov()
+ self.assertIsNotNone(sriov_obj.split_cpu_list('0,5'))
+
+ def test_split_cpu_list_null(self):
+ with mock.patch("itertools.chain") as iter1:
+ iter1 = mock.Mock()
+ print("{0}".format(iter1))
+ sriov_obj = sriov.Sriov()
+ self.assertEqual(sriov_obj.split_cpu_list([]), [])
+
+ def test_destroy_vm_successful(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV
+ sriov_obj.check_output = mock.Mock(return_value=(0, "vm1"))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "0 i40e"))
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, "0 i40e"))
+ self.assertIsNone(sriov_obj.destroy_vm())
+
+ def test_destroy_vm_unsuccessful(self):
+ with mock.patch("yardstick.ssh.SSH") as ssh:
+ ssh_mock = mock.Mock(autospec=ssh.SSH)
+ ssh_mock.execute = \
+ mock.Mock(return_value=(0, {}, ""))
+ ssh.return_value = ssh_mock
+ sriov_obj = sriov.Sriov()
+ sriov_obj.connection = ssh_mock
+ sriov_obj.sriov = SRIOV
+ sriov_obj.check_output = mock.Mock(return_value=(1, {}))
+ self.assertIsNone(sriov_obj.destroy_vm())
+
+ def test_read_from_file(self):
+ CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD)
+ sriov_obj = sriov.Sriov()
+ self.assertIsNotNone(sriov_obj.read_from_file(CORRECT_FILE_PATH))
+
+ def test_write_to_file(self):
+ sriov_obj = sriov.Sriov()
+ self.assertIsNone(sriov_obj.write_to_file(SAMPLE_FILE, "some content"))
+
+ def _get_file_abspath(self, filename):
+ curr_path = os.path.dirname(os.path.abspath(__file__))
+ file_path = os.path.join(curr_path, filename)
+ return file_path
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/unit/benchmark/contexts/test_standalone.py b/tests/unit/benchmark/contexts/test_standalone.py
index 687ef7305..1fc740393 100644
--- a/tests/unit/benchmark/contexts/test_standalone.py
+++ b/tests/unit/benchmark/contexts/test_standalone.py
@@ -20,112 +20,667 @@
from __future__ import absolute_import
import os
import unittest
+import mock
from yardstick.benchmark.contexts import standalone
+from yardstick.benchmark.contexts import sriov
+from yardstick.benchmark.contexts import ovsdpdk
+MOCKS = {
+ 'yardstick.benchmark.contexts': mock.MagicMock(),
+ 'yardstick.benchmark.contexts.sriov': mock.MagicMock(),
+ 'yardstick.benchmark.contexts.ovsdpdk': mock.MagicMock(),
+ 'yardstick.benchmark.contexts.standalone': mock.MagicMock(),
+}
+
+@mock.patch('yardstick.benchmark.contexts.ovsdpdk.time')
+@mock.patch('yardstick.benchmark.contexts.standalone.time')
+@mock.patch('yardstick.benchmark.contexts.sriov.time')
class StandaloneContextTestCase(unittest.TestCase):
+ NODES_SAMPLE = "nodes_sample_new.yaml"
+ NODES_SAMPLE_SRIOV = "nodes_sample_new_sriov.yaml"
+ NODES_DUPLICATE_SAMPLE = "nodes_duplicate_sample_new.yaml"
- NODES_SAMPLE = "standalone_sample.yaml"
- NODES_DUPLICATE_SAMPLE = "standalone_duplicate_sample.yaml"
+ NODES_SAMPLE_OVSDPDK = "nodes_sample_ovs.yaml"
+ NODES_SAMPLE_OVSDPDK_ROLE = "nodes_sample_ovsdpdk.yaml"
+ NODES_DUPLICATE_OVSDPDK = "nodes_duplicate_sample_ovs.yaml"
def setUp(self):
self.test_context = standalone.StandaloneContext()
- def test_construct(self):
-
+ def test_construct(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
self.assertIsNone(self.test_context.name)
self.assertIsNone(self.test_context.file_path)
self.assertEqual(self.test_context.nodes, [])
self.assertEqual(self.test_context.nfvi_node, [])
- def test_unsuccessful_init(self):
-
+ def test_unsuccessful_init(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
attrs = {
'name': 'foo',
'file': self._get_file_abspath("error_file")
}
-
self.assertRaises(IOError, self.test_context.init, attrs)
- def test_successful_init(self):
-
- attrs = {
+ def test_successful_init_sriov(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs_sriov = {
+ 'name': 'sriov',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.Mock()
+ self.test_context.init(attrs_sriov)
+ self.assertEqual(self.test_context.name, "sriov")
+ self.assertEqual(len(self.test_context.nodes), 2)
+ self.assertEqual(len(self.test_context.nfvi_node), 2)
+ self.assertEqual(self.test_context.nfvi_node[0]["name"], "sriov")
+
+ def test_successful_init_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ attrs_ovs = {
+ 'name': 'ovs',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.Mock()
+ self.test_context.init(attrs_ovs)
+ self.assertEqual(self.test_context.name, "ovs")
+ self.assertEqual(len(self.test_context.nodes), 2)
+ self.assertEqual(len(self.test_context.nfvi_node), 2)
+ self.assertEqual(self.test_context.nfvi_node[0]["name"], "ovs")
+
+ def test__get_server_with_dic_attr_name_sriov(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs_sriov = {
'name': 'foo',
'file': self._get_file_abspath(self.NODES_SAMPLE)
}
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.init(attrs_sriov)
+ attr_name = {'name': 'foo.bar'}
+ result = self.test_context._get_server(attr_name)
+ self.assertEqual(result, None)
- self.test_context.init(attrs)
-
- self.assertEqual(self.test_context.name, "foo")
- self.assertEqual(len(self.test_context.nodes), 3)
- self.assertEqual(len(self.test_context.nfvi_node), 1)
- self.assertEqual(self.test_context.nfvi_node[0]["name"], "node2")
-
- def test__get_server_with_dic_attr_name(self):
+ def test__get_server_with_dic_attr_name_ovs(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs_ovs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.init(attrs_ovs)
+ attr_name = {'name': 'foo.bar'}
+ result = self.test_context._get_server(attr_name)
+ self.assertEqual(result, None)
+ def test__get_server_not_found_sriov(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
attrs = {
'name': 'foo',
'file': self._get_file_abspath(self.NODES_SAMPLE)
}
-
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
self.test_context.init(attrs)
-
- attr_name = {'name': 'foo.bar'}
+ attr_name = 'bar.foo'
result = self.test_context._get_server(attr_name)
-
self.assertEqual(result, None)
- def test__get_server_not_found(self):
-
+ def test__get_server_not_found_ovs(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
attrs = {
'name': 'foo',
- 'file': self._get_file_abspath(self.NODES_SAMPLE)
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
}
-
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
self.test_context.init(attrs)
-
attr_name = 'bar.foo'
result = self.test_context._get_server(attr_name)
-
self.assertEqual(result, None)
- def test__get_server_duplicate(self):
+
+ def test__get_server_duplicate_sriov(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
attrs = {
'name': 'foo',
'file': self._get_file_abspath(self.NODES_DUPLICATE_SAMPLE)
}
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.Mock(return_value="sriov")
+ self.test_context.init(attrs)
+ attr_name = 'sriov.foo'
+ # self.test_context.name = "sriov"
+ self.assertRaises(ValueError, self.test_context._get_server, attr_name)
+ def test__get_server_duplicate_ovs(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_DUPLICATE_OVSDPDK)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk")
self.test_context.init(attrs)
- attr_name = 'node2.foo'
+ attr_name = 'ovs.foo'
+ self.assertRaises(
+ ValueError,
+ self.test_context._get_server,
+ attr_name)
+ def test__get_server_found_sriov(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_SRIOV)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk")
+ self.test_context.init(attrs)
+ attr_name = 'sriov.foo'
+ result = self.test_context._get_server(attr_name)
+ self.assertEqual(result['ip'], '10.123.123.122')
+ self.assertEqual(result['name'], 'sriov.foo')
+ self.assertEqual(result['user'], 'root')
- self.assertRaises(ValueError, self.test_context._get_server, attr_name)
+ def test__get_server_found_ovs(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK_ROLE)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk")
+ self.test_context.init(attrs)
+ attr_name = 'ovs.foo'
+ result = self.test_context._get_server(attr_name)
+ self.assertEqual(result['ip'], '10.223.197.222')
+ self.assertEqual(result['name'], 'ovs.foo')
+ self.assertEqual(result['user'], 'root')
+
+ def test__deploy_unsuccessful(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ self.test_context.vm_deploy = False
- def test__get_server_found(self):
+ def test__deploy_sriov_firsttime(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ MYSRIOV = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.vm_deploy = True
+
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.nfvi_obj.sriov = MYSRIOV
+ self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock()
+ self.test_context.nfvi_obj.first_run = True
+ self.test_context.nfvi_obj.install_req_libs()
+ self.test_context.nfvi_obj.get_nic_details = mock.Mock()
+ PORTS = ['0000:06:00.0', '0000:06:00.1']
+ NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+ DRIVER = 'i40e'
+ result = self.test_context.nfvi_obj.setup_sriov_context(
+ PORTS,
+ NIC_DETAILS,
+ DRIVER)
+ print("{0}".format(result))
+ self.assertIsNone(self.test_context.deploy())
+ def test__deploy_sriov_notfirsttime(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
attrs = {
'name': 'foo',
'file': self._get_file_abspath(self.NODES_SAMPLE)
}
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ MYSRIOV = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.vm_deploy = True
+ self.test_context.get_nfvi_obj = mock.MagicMock()
self.test_context.init(attrs)
+ self.test_context.nfvi_obj.sriov = MYSRIOV
+ self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock()
+ self.test_context.nfvi_obj.first_run = False
+ self.test_context.nfvi_obj.get_nic_details = mock.Mock()
+ PORTS = ['0000:06:00.0', '0000:06:00.1']
+ NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+ DRIVER = 'i40e'
+ result = self.test_context.nfvi_obj.setup_sriov_context(
+ PORTS,
+ NIC_DETAILS,
+ DRIVER)
+ print("{0}".format(result))
+ self.assertIsNone(self.test_context.deploy())
- attr_name = 'node1.foo'
- result = self.test_context._get_server(attr_name)
+ def test__deploy_ovs_firsttime(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
- self.assertEqual(result['ip'], '1.1.1.1')
- self.assertEqual(result['name'], 'node1.foo')
- self.assertEqual(result['user'], 'root')
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ MYOVS = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3',
+ 'ovs-ofctl add-flow br0 in_port=3,action=output:1'
+ 'ovs-ofctl add-flow br0 in_port=4,action=output:2'
+ 'ovs-ofctl add-flow br0 in_port=2,action=output:4'],
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.vm_deploy = True
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.ovs = MYOVS
+ self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock()
+ self.test_context.nfvi_obj.first_run = True
+ self.test_context.nfvi_obj.install_req_libs()
+ self.test_context.nfvi_obj.get_nic_details = mock.Mock()
+ PORTS = ['0000:06:00.0', '0000:06:00.1']
+ NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vports_mac': ['00:00:00:00:00:05', '00:00:00:00:00:06'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+ DRIVER = 'i40e'
+
+ self.test_context.nfvi_obj.setup_ovs = mock.Mock()
+ self.test_context.nfvi_obj.start_ovs_serverswitch = mock.Mock()
+ self.test_context.nfvi_obj.setup_ovs_bridge = mock.Mock()
+ self.test_context.nfvi_obj.add_oflows = mock.Mock()
+
+ # self.test_context.nfvi_obj.setup_ovs(PORTS)
+ # self.test_context.nfvi_obj.start_ovs_serverswitch()
+ # self.test_context.nfvi_obj.setup_ovs_bridge()
+ # self.test_context.nfvi_obj.add_oflows()
+
+ result = self.test_context.nfvi_obj.setup_ovs_context(
+ PORTS,
+ NIC_DETAILS,
+ DRIVER)
+ print("{0}".format(result))
+ self.assertIsNone(self.test_context.deploy())
- def test_deploy(self):
+ def test__deploy_ovs_notfirsttime(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ MYOVS = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3',
+ 'ovs-ofctl add-flow br0 in_port=3,action=output:1'
+ 'ovs-ofctl add-flow br0 in_port=4,action=output:2'
+ 'ovs-ofctl add-flow br0 in_port=2,action=output:4'],
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.vm_deploy = True
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.ovs = MYOVS
+ self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock()
+ self.test_context.nfvi_obj.first_run = False
+ self.test_context.nfvi_obj.get_nic_details = mock.Mock()
+ PORTS = ['0000:06:00.0', '0000:06:00.1']
+ NIC_DETAILS = {
+ 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'},
+ 'vports_mac': ['00:00:00:00:00:05', '00:00:00:00:00:06'],
+ 'pci': ['0000:06:00.0', '0000:06:00.1'],
+ 'phy_driver': 'i40e'}
+ DRIVER = 'i40e'
+
+ self.test_context.nfvi_obj.setup_ovs(PORTS)
+ self.test_context.nfvi_obj.start_ovs_serverswitch()
+ self.test_context.nfvi_obj.setup_ovs_bridge()
+ self.test_context.nfvi_obj.add_oflows()
+
+ result = self.test_context.nfvi_obj.setup_ovs_context(
+ PORTS,
+ NIC_DETAILS,
+ DRIVER)
+ print("{0}".format(result))
self.assertIsNone(self.test_context.deploy())
- def test_undeploy(self):
+ def test_undeploy_sriov(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE)
+ }
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.nfvi_obj.destroy_vm = mock.Mock()
+ self.assertIsNone(self.test_context.undeploy())
+
+ def test_undeploy_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ attrs = {
+ 'name': 'foo',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
+
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.nfvi_obj.destroy_vm = mock.Mock()
self.assertIsNone(self.test_context.undeploy())
+ def test_get_nfvi_obj_sriov(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ with mock.patch('yardstick.benchmark.contexts.sriov'):
+ attrs = {
+ 'name': 'sriov',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE)
+ }
+ self.test_context.init(attrs)
+ self.test_context.nfvi_obj.file_path = self._get_file_abspath(
+ self.NODES_SAMPLE)
+ self.test_context.nfvi_node = [{
+ 'name': 'sriov',
+ 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'],
+ 'ip': '10.223.197.140',
+ 'role': 'Sriov',
+ 'user': 'root',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'intel123',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.get_context_impl = mock.Mock(
+ return_value=sriov.Sriov)
+ self.assertIsNotNone(self.test_context.get_nfvi_obj())
+
+ def test_get_nfvi_obj_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ with mock.patch('yardstick.benchmark.contexts.ovsdpdk'):
+ attrs = {
+ 'name': 'ovs',
+ 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK)
+ }
+ self.test_context.init(attrs)
+ self.test_context.nfvi_obj.file_path = self._get_file_abspath(
+ self.NODES_SAMPLE)
+ self.test_context.nfvi_node = [{
+ 'name': 'ovs',
+ 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'],
+ 'ip': '10.223.197.140',
+ 'role': 'Ovsdpdk',
+ 'user': 'root',
+ 'vpath': '/usr/local/',
+ 'images': '/var/lib/libvirt/images/ubuntu1.img',
+ 'phy_driver': 'i40e',
+ 'password': 'password',
+ 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}]
+ self.test_context.get_nfvi_obj = mock.MagicMock()
+ self.test_context.init(attrs)
+ self.test_context.get_context_impl = mock.Mock(
+ return_value=ovsdpdk.Ovsdpdk)
+ self.assertIsNotNone(self.test_context.get_nfvi_obj())
+
+ def test_get_context_impl_correct_obj(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ with mock.patch.dict("sys.modules", MOCKS):
+ self.assertIsNotNone(self.test_context.get_context_impl('Sriov'))
+
+ def test_get_context_impl_wrong_obj(self, mock_sriov_time, mock_standlalone_time,
+ mock_ovsdpdk_time):
+ with mock.patch.dict("sys.modules", MOCKS):
+ self.assertRaises(
+ ValueError,
+ lambda: self.test_context.get_context_impl('wrong_object'))
+
def _get_file_abspath(self, filename):
curr_path = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(curr_path, filename)
return file_path
+
+ def test__get_network(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time):
+ network1 = {
+ 'name': 'net_1',
+ 'vld_id': 'vld111',
+ 'segmentation_id': 'seg54',
+ 'network_type': 'type_a',
+ 'physical_network': 'phys',
+ }
+ network2 = {
+ 'name': 'net_2',
+ 'vld_id': 'vld999',
+ }
+ self.test_context.networks = {
+ 'a': network1,
+ 'b': network2,
+ }
+
+ attr_name = None
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld777'}
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = 'vld777'
+ self.assertIsNone(self.test_context._get_network(attr_name))
+
+ attr_name = {'vld_id': 'vld999'}
+ expected = {
+ "name": 'net_2',
+ "vld_id": 'vld999',
+ "segmentation_id": None,
+ "network_type": None,
+ "physical_network": None,
+ }
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
+
+ attr_name = 'a'
+ expected = network1
+ result = self.test_context._get_network(attr_name)
+ self.assertDictEqual(result, expected)
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/unit/benchmark/core/test_task.py b/tests/unit/benchmark/core/test_task.py
index b64bb8eed..7f617537e 100644
--- a/tests/unit/benchmark/core/test_task.py
+++ b/tests/unit/benchmark/core/test_task.py
@@ -47,6 +47,87 @@ 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 = {
+ 'node1': {
+ 'interfaces': {
+ 'eth0': {
+ 'name': 'mgmt',
+ },
+ 'eth1': {
+ 'name': 'external',
+ 'vld_id': '23',
+ },
+ 'eth10': {
+ 'name': 'internal',
+ 'vld_id': '55',
+ },
+ },
+ },
+ 'node2': {
+ 'interfaces': {
+ 'eth4': {
+ 'name': 'mgmt',
+ },
+ 'eth2': {
+ 'name': 'external',
+ 'vld_id': '32',
+ },
+ 'eth11': {
+ 'name': 'internal',
+ 'vld_id': '55',
+ },
+ },
+ },
+ }
+
+ mock_context.get_network.side_effect = iter([
+ None,
+ {
+ 'name': 'a',
+ 'network_type': 'private',
+ },
+ {},
+ {
+ 'name': 'b',
+ 'vld_id': 'y',
+ 'subnet_cidr': '10.20.0.0/16',
+ },
+ {
+ 'name': 'c',
+ 'vld_id': 'x',
+ },
+ {
+ 'name': 'd',
+ 'vld_id': 'w',
+ },
+ ])
+
+ expected_get_network_calls = 4 # once for each vld_id in the nodes dict
+ expected = {
+ 'a': {'name': 'a', 'network_type': 'private'},
+ 'b': {'name': 'b', 'vld_id': 'y', 'subnet_cidr': '10.20.0.0/16'},
+ }
+
+ networks = task.get_networks_from_nodes(nodes)
+ self.assertEqual(mock_context.get_network.call_count, expected_get_network_calls)
+ self.assertDictEqual(networks, expected)
+
@mock.patch('yardstick.benchmark.core.task.Context')
@mock.patch('yardstick.benchmark.core.task.base_runner')
def test_run(self, mock_base_runner, mock_ctx):
diff --git a/tests/unit/benchmark/runner/test_base.py b/tests/unit/benchmark/runner/test_base.py
index 6e72fa548..0313ef843 100644
--- a/tests/unit/benchmark/runner/test_base.py
+++ b/tests/unit/benchmark/runner/test_base.py
@@ -15,12 +15,15 @@ from __future__ import absolute_import
import unittest
import time
+from mock import mock
+
from yardstick.benchmark.runners.iteration import IterationRunner
class RunnerTestCase(unittest.TestCase):
- def test_get_output(self):
+ @mock.patch("yardstick.benchmark.runners.iteration.multiprocessing")
+ def test_get_output(self, mock_process):
runner = IterationRunner({})
runner.output_queue.put({'case': 'opnfv_yardstick_tc002'})
runner.output_queue.put({'criteria': 'PASS'})
@@ -30,7 +33,10 @@ class RunnerTestCase(unittest.TestCase):
'criteria': 'PASS'
}
- time.sleep(1)
+ for retries in range(1000):
+ time.sleep(0.01)
+ if not runner.output_queue.empty():
+ break
actual_result = runner.get_output()
self.assertEqual(idle_result, actual_result)
diff --git a/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py b/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py
index 28b27c78a..cc179602e 100644
--- a/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py
+++ b/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py
@@ -20,9 +20,7 @@ from yardstick.benchmark.scenarios.availability.attacker import \
attacker_baremetal
-@mock.patch(
- 'yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal'
- '.subprocess')
+@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.subprocess')
class ExecuteShellTestCase(unittest.TestCase):
def test__fun_execute_shell_command_successful(self, mock_subprocess):
@@ -31,17 +29,17 @@ class ExecuteShellTestCase(unittest.TestCase):
exitcode, output = attacker_baremetal._execute_shell_command(cmd)
self.assertEqual(exitcode, 0)
- def test__fun_execute_shell_command_fail_cmd_exception(self,
- mock_subprocess):
+ @mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.LOG')
+ def test__fun_execute_shell_command_fail_cmd_exception(self, mock_log, mock_subprocess):
cmd = "env"
mock_subprocess.check_output.side_effect = RuntimeError
exitcode, output = attacker_baremetal._execute_shell_command(cmd)
self.assertEqual(exitcode, -1)
+ mock_log.error.assert_called_once()
-@mock.patch(
- 'yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal'
- '.ssh')
+@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.subprocess')
+@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.ssh')
class AttackerBaremetalTestCase(unittest.TestCase):
def setUp(self):
@@ -59,28 +57,28 @@ class AttackerBaremetalTestCase(unittest.TestCase):
'host': 'node1',
}
- def test__attacker_baremetal_all_successful(self, mock_ssh):
+ def test__attacker_baremetal_all_successful(self, mock_ssh, mock_subprocess):
+ mock_ssh.SSH.from_node().execute.return_value = (0, "running", '')
ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg,
self.context)
- mock_ssh.SSH.from_node().execute.return_value = (0, "running", '')
ins.setup()
ins.inject_fault()
ins.recover()
- def test__attacker_baremetal_check_failuer(self, mock_ssh):
+ def test__attacker_baremetal_check_failuer(self, mock_ssh, mock_subprocess):
+ mock_ssh.SSH.from_node().execute.return_value = (0, "error check", '')
ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg,
self.context)
- mock_ssh.SSH.from_node().execute.return_value = (0, "error check", '')
ins.setup()
- def test__attacker_baremetal_recover_successful(self, mock_ssh):
+ def test__attacker_baremetal_recover_successful(self, mock_ssh, mock_subprocess):
self.attacker_cfg["jump_host"] = 'node1'
self.context["node1"]["pwd"] = "123456"
+ mock_ssh.SSH.from_node().execute.return_value = (0, "running", '')
ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg,
self.context)
- mock_ssh.SSH.from_node().execute.return_value = (0, "running", '')
ins.setup()
ins.recover()
diff --git a/tests/unit/benchmark/scenarios/availability/test_monitor_command.py b/tests/unit/benchmark/scenarios/availability/test_monitor_command.py
index 2ed4be731..6a9b3b157 100644
--- a/tests/unit/benchmark/scenarios/availability/test_monitor_command.py
+++ b/tests/unit/benchmark/scenarios/availability/test_monitor_command.py
@@ -30,12 +30,14 @@ class ExecuteShellTestCase(unittest.TestCase):
exitcode, output = monitor_command._execute_shell_command(cmd)
self.assertEqual(exitcode, 0)
- def test__fun_execute_shell_command_fail_cmd_exception(self,
+ @mock.patch('yardstick.benchmark.scenarios.availability.monitor.monitor_command.LOG')
+ def test__fun_execute_shell_command_fail_cmd_exception(self, mock_log,
mock_subprocess):
cmd = "env"
mock_subprocess.check_output.side_effect = RuntimeError
exitcode, output = monitor_command._execute_shell_command(cmd)
self.assertEqual(exitcode, -1)
+ mock_log.error.assert_called_once()
@mock.patch(
@@ -67,13 +69,15 @@ class MonitorOpenstackCmdTestCase(unittest.TestCase):
instance._result = {"outage_time": 0}
instance.verify_SLA()
- def test__monitor_command_monitor_func_failure(self, mock_subprocess):
+ @mock.patch('yardstick.benchmark.scenarios.availability.monitor.monitor_command.LOG')
+ def test__monitor_command_monitor_func_failure(self, mock_log, mock_subprocess):
mock_subprocess.check_output.return_value = (1, 'unittest')
instance = monitor_command.MonitorOpenstackCmd(self.config, None, {"nova-api": 10})
instance.setup()
mock_subprocess.check_output.side_effect = RuntimeError
ret = instance.monitor_func()
self.assertEqual(ret, False)
+ mock_log.error.assert_called_once()
instance._result = {"outage_time": 10}
instance.verify_SLA()
diff --git a/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py b/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py
index f8d12bd29..b59ec6cf1 100644
--- a/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py
+++ b/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py
@@ -36,7 +36,7 @@ class MultiMonitorServiceTestCase(unittest.TestCase):
'key': 'service-status',
'monitor_key': 'service-status',
'host': 'node1',
- 'monitor_time': 3,
+ 'monitor_time': 0.1,
'parameter': {'serviceName': 'haproxy'},
'sla': {'max_outage_time': 1}
}
diff --git a/tests/unit/benchmark/scenarios/availability/test_util.py b/tests/unit/benchmark/scenarios/availability/test_util.py
index bb0e6bc79..2e4fff417 100644
--- a/tests/unit/benchmark/scenarios/availability/test_util.py
+++ b/tests/unit/benchmark/scenarios/availability/test_util.py
@@ -19,6 +19,25 @@ from yardstick.benchmark.scenarios.availability import util
@mock.patch('yardstick.benchmark.scenarios.availability.util.subprocess')
class ExecuteShellTestCase(unittest.TestCase):
+ def setUp(self):
+ self.param_config = {'serviceName': '$serviceName', 'value': 1}
+ self.intermediate_variables = {'$serviceName': 'nova-api'}
+ self.std_output = '| id | 1 |'
+ self.cmd_config = {'cmd':'ls','param':'-a'}
+
+ def test_util_build_command_shell(self,mock_subprocess):
+ result = util.build_shell_command(self.param_config, True,
+ self.intermediate_variables)
+ self.assertEqual("nova-api" in result, True)
+
+ def test_read_stdout_item(self,mock_subprocess):
+ result = util.read_stdout_item(self.std_output,'id')
+ self.assertEquals('1',result)
+
+ def test_buildshellparams(self,mock_subprocess):
+ result = util.buildshellparams(self.cmd_config,True)
+ self.assertEquals('/bin/bash -s {0} {1}', result)
+
def test__fun_execute_shell_command_successful(self, mock_subprocess):
cmd = "env"
mock_subprocess.check_output.return_value = (0, 'unittest')
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/benchmark/scenarios/networking/test_nstat.py b/tests/unit/benchmark/scenarios/networking/test_nstat.py
index 131716727..fe44cfdf4 100644
--- a/tests/unit/benchmark/scenarios/networking/test_nstat.py
+++ b/tests/unit/benchmark/scenarios/networking/test_nstat.py
@@ -43,7 +43,7 @@ class NstatTestCase(unittest.TestCase):
def test_nstat_successful_no_sla(self, mock_ssh):
options = {
- "duration": 60
+ "duration": 0
}
args = {
"options": options,
@@ -67,7 +67,7 @@ class NstatTestCase(unittest.TestCase):
def test_nstat_successful_sla(self, mock_ssh):
options = {
- "duration": 60
+ "duration": 0
}
sla = {
"IP_datagram_error_rate": 0.1
@@ -95,7 +95,7 @@ class NstatTestCase(unittest.TestCase):
def test_nstat_unsuccessful_cmd_error(self, mock_ssh):
options = {
- "duration": 60
+ "duration": 0
}
sla = {
"IP_datagram_error_rate": 0.1
diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen.py b/tests/unit/benchmark/scenarios/networking/test_pktgen.py
index d4eb1246f..2914c8e02 100644
--- a/tests/unit/benchmark/scenarios/networking/test_pktgen.py
+++ b/tests/unit/benchmark/scenarios/networking/test_pktgen.py
@@ -138,6 +138,7 @@ class PktgenTestCase(unittest.TestCase):
p.run(result)
expected_result = jsonutils.loads(sample_output)
expected_result["packets_received"] = 149300
+ expected_result["packetsize"] = 60
self.assertEqual(result, expected_result)
def test_pktgen_successful_sla(self, mock_ssh):
@@ -164,6 +165,7 @@ class PktgenTestCase(unittest.TestCase):
p.run(result)
expected_result = jsonutils.loads(sample_output)
expected_result["packets_received"] = 149300
+ expected_result["packetsize"] = 60
self.assertEqual(result, expected_result)
def test_pktgen_unsuccessful_sla(self, mock_ssh):
@@ -204,6 +206,538 @@ class PktgenTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR')
self.assertRaises(RuntimeError, p.run, result)
+ def test_pktgen_get_vnic_driver_name(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, 'ixgbevf', '')
+
+ vnic_driver_name = p._get_vnic_driver_name()
+ self.assertEqual(vnic_driver_name, 'ixgbevf')
+
+ def test_pktgen_unsuccessful_get_vnic_driver_name(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._get_vnic_driver_name)
+
+ def test_pktgen_get_sriov_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '2', '')
+
+ p.queue_number = p._get_sriov_queue_number()
+ self.assertEqual(p.queue_number, 2)
+
+ def test_pktgen_unsuccessful_get_sriov_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._get_sriov_queue_number)
+
+ def test_pktgen_get_available_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '4', '')
+
+ p._get_available_queue_number()
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "sudo ethtool -l eth0 | grep Combined | head -1 |" \
+ "awk '{printf $2}'")
+
+ def test_pktgen_unsuccessful_get_available_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._get_available_queue_number)
+
+ def test_pktgen_get_usable_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '1', '')
+
+ p._get_usable_queue_number()
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "sudo ethtool -l eth0 | grep Combined | tail -1 |" \
+ "awk '{printf $2}'")
+
+ def test_pktgen_unsuccessful_get_usable_queue_number(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._get_usable_queue_number)
+
+ def test_pktgen_enable_ovs_multiqueue(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '4', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = 1
+ p._get_usable_queue_number = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = 4
+ p._get_available_queue_number = mock_result2
+
+ p.queue_number = p._enable_ovs_multiqueue()
+ self.assertEqual(p.queue_number, 4)
+
+ def test_pktgen_enable_ovs_multiqueue_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '1', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = 1
+ p._get_usable_queue_number = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = 1
+ p._get_available_queue_number = mock_result2
+
+ p.queue_number = p._enable_ovs_multiqueue()
+ self.assertEqual(p.queue_number, 1)
+
+ def test_pktgen_unsuccessful_enable_ovs_multiqueue(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = 1
+ p._get_usable_queue_number = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = 4
+ p._get_available_queue_number = mock_result2
+
+ self.assertRaises(RuntimeError, p._enable_ovs_multiqueue)
+
+ def test_pktgen_setup_irqmapping_ovs(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '10', '')
+
+ p._setup_irqmapping_ovs(4)
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "echo 8 | sudo tee /proc/irq/10/smp_affinity")
+
+ def test_pktgen_setup_irqmapping_ovs_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '10', '')
+
+ p._setup_irqmapping_ovs(1)
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "echo 1 | sudo tee /proc/irq/10/smp_affinity")
+
+ def test_pktgen_unsuccessful_setup_irqmapping_ovs(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._setup_irqmapping_ovs, 4)
+
+ def test_pktgen_unsuccessful_setup_irqmapping_ovs_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._setup_irqmapping_ovs, 1)
+
+ def test_pktgen_setup_irqmapping_sriov(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '10', '')
+
+ p._setup_irqmapping_sriov(2)
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "echo 2 | sudo tee /proc/irq/10/smp_affinity")
+
+ def test_pktgen_setup_irqmapping_sriov_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '10', '')
+
+ p._setup_irqmapping_sriov(1)
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "echo 1 | sudo tee /proc/irq/10/smp_affinity")
+
+ def test_pktgen_unsuccessful_setup_irqmapping_sriov(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._setup_irqmapping_sriov, 2)
+
+ def test_pktgen_unsuccessful_setup_irqmapping_sriov_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._setup_irqmapping_sriov, 1)
+
+ def test_pktgen_is_irqbalance_disabled(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '', '')
+
+ p._is_irqbalance_disabled()
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "grep ENABLED /etc/default/irqbalance")
+
+ def test_pktgen_unsuccessful_is_irqbalance_disabled(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._is_irqbalance_disabled)
+
+ def test_pktgen_disable_irqbalance(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '', '')
+
+ p._disable_irqbalance()
+
+ mock_ssh.SSH.from_node().execute.assert_called_with(
+ "sudo service irqbalance disable")
+
+ def test_pktgen_unsuccessful_disable_irqbalance(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+ self.assertRaises(RuntimeError, p._disable_irqbalance)
+
+ def test_pktgen_multiqueue_setup_ovs(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'multiqueue': True},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '4', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = False
+ p._is_irqbalance_disabled = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = "virtio_net"
+ p._get_vnic_driver_name = mock_result2
+
+ mock_result3 = mock.Mock()
+ mock_result3.return_value = 1
+ p._get_usable_queue_number = mock_result3
+
+ mock_result4 = mock.Mock()
+ mock_result4.return_value = 4
+ p._get_available_queue_number = mock_result4
+
+ p.multiqueue_setup()
+
+ self.assertEqual(p.queue_number, 4)
+
+ def test_pktgen_multiqueue_setup_ovs_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'multiqueue': True},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '1', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = False
+ p._is_irqbalance_disabled = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = "virtio_net"
+ p._get_vnic_driver_name = mock_result2
+
+ mock_result3 = mock.Mock()
+ mock_result3.return_value = 1
+ p._get_usable_queue_number = mock_result3
+
+ mock_result4 = mock.Mock()
+ mock_result4.return_value = 1
+ p._get_available_queue_number = mock_result4
+
+ p.multiqueue_setup()
+
+ self.assertEqual(p.queue_number, 1)
+
+ def test_pktgen_multiqueue_setup_sriov(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'multiqueue': True},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '2', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = False
+ p._is_irqbalance_disabled = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = "ixgbevf"
+ p._get_vnic_driver_name = mock_result2
+
+ p.multiqueue_setup()
+
+ self.assertEqual(p.queue_number, 2)
+
+ def test_pktgen_multiqueue_setup_sriov_1q(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'multiqueue': True},
+ }
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_ssh.SSH.from_node().execute.return_value = (0, '1', '')
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = False
+ p._is_irqbalance_disabled = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = "ixgbevf"
+ p._get_vnic_driver_name = mock_result2
+
+ p.multiqueue_setup()
+
+ self.assertEqual(p.queue_number, 1)
+
+ def test_pktgen_run_with_setup_done(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True},
+ 'sla': {'max_ppm': 1}
+ }
+ result = {}
+ p = pktgen.Pktgen(args, self.ctx)
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ p.setup_done = True
+ p.multiqueue_setup_done = True
+
+ mock_iptables_result = mock.Mock()
+ mock_iptables_result.return_value = 149300
+ p._iptables_get_result = mock_iptables_result
+
+ sample_output = '{"packets_per_second": 9753, "errors": 0, \
+ "packets_sent": 149300, "flows": 110}'
+ mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
+
+ p.run(result)
+ expected_result = jsonutils.loads(sample_output)
+ expected_result["packets_received"] = 149300
+ expected_result["packetsize"] = 60
+ self.assertEqual(result, expected_result)
+
+ def test_pktgen_run_with_ovs_multiqueque(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True},
+ 'sla': {'max_ppm': 1}
+ }
+ result = {}
+
+ p = pktgen.Pktgen(args, self.ctx)
+
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_result = mock.Mock()
+ mock_result.return_value = "virtio_net"
+ p._get_vnic_driver_name = mock_result
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = 1
+ p._get_usable_queue_number = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = 4
+ p._get_available_queue_number = mock_result2
+
+ mock_result3 = mock.Mock()
+ mock_result3.return_value = 4
+ p._enable_ovs_multiqueue = mock_result3
+
+ mock_result4 = mock.Mock()
+ p._setup_irqmapping_ovs = mock_result4
+
+ mock_iptables_result = mock.Mock()
+ mock_iptables_result.return_value = 149300
+ p._iptables_get_result = mock_iptables_result
+
+ sample_output = '{"packets_per_second": 9753, "errors": 0, \
+ "packets_sent": 149300, "flows": 110}'
+ mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
+
+ p.run(result)
+ expected_result = jsonutils.loads(sample_output)
+ expected_result["packets_received"] = 149300
+ expected_result["packetsize"] = 60
+ self.assertEqual(result, expected_result)
+
+ def test_pktgen_run_with_sriov_multiqueque(self, mock_ssh):
+ args = {
+ 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True},
+ 'sla': {'max_ppm': 1}
+ }
+ result = {}
+
+ p = pktgen.Pktgen(args, self.ctx)
+
+ p.server = mock_ssh.SSH.from_node()
+ p.client = mock_ssh.SSH.from_node()
+
+ mock_result1 = mock.Mock()
+ mock_result1.return_value = "ixgbevf"
+ p._get_vnic_driver_name = mock_result1
+
+ mock_result2 = mock.Mock()
+ mock_result2.return_value = 2
+ p._get_sriov_queue_number = mock_result2
+
+ mock_result3 = mock.Mock()
+ p._setup_irqmapping_sriov = mock_result3
+
+ mock_iptables_result = mock.Mock()
+ mock_iptables_result.return_value = 149300
+ p._iptables_get_result = mock_iptables_result
+
+ sample_output = '{"packets_per_second": 9753, "errors": 0, \
+ "packets_sent": 149300, "flows": 110}'
+ mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
+
+ p.run(result)
+ expected_result = jsonutils.loads(sample_output)
+ expected_result["packets_received"] = 149300
+ expected_result["packetsize"] = 60
+ self.assertEqual(result, expected_result)
def main():
unittest.main()
diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py
index e6998e443..b4b87522c 100644
--- a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py
+++ b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py
@@ -20,6 +20,7 @@ import yardstick.common.utils as utils
from yardstick.benchmark.scenarios.networking import pktgen_dpdk
+@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk.time')
@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk.ssh')
class PktgenDPDKLatencyTestCase(unittest.TestCase):
@@ -38,7 +39,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
}
}
- def test_pktgen_dpdk_successful_setup(self, mock_ssh):
+ def test_pktgen_dpdk_successful_setup(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -51,7 +52,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
self.assertIsNotNone(p.client)
self.assertEqual(p.setup_done, True)
- def test_pktgen_dpdk_successful_get_port_ip(self, mock_ssh):
+ def test_pktgen_dpdk_successful_get_port_ip(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -66,7 +67,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.assert_called_with(
"ifconfig eth1 |grep 'inet addr' |awk '{print $2}' |cut -d ':' -f2 ")
- def test_pktgen_dpdk_unsuccessful_get_port_ip(self, mock_ssh):
+ def test_pktgen_dpdk_unsuccessful_get_port_ip(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -78,7 +79,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR')
self.assertRaises(RuntimeError, utils.get_port_ip, p.server, "eth1")
- def test_pktgen_dpdk_successful_get_port_mac(self, mock_ssh):
+ def test_pktgen_dpdk_successful_get_port_mac(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -93,7 +94,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.assert_called_with(
"ifconfig |grep HWaddr |grep eth1 |awk '{print $5}' ")
- def test_pktgen_dpdk_unsuccessful_get_port_mac(self, mock_ssh):
+ def test_pktgen_dpdk_unsuccessful_get_port_mac(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -105,7 +106,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR')
self.assertRaises(RuntimeError, utils.get_port_mac, p.server, "eth1")
- def test_pktgen_dpdk_successful_no_sla(self, mock_ssh):
+ def test_pktgen_dpdk_successful_no_sla(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -124,7 +125,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
delta = result['avg_latency'] - 132
self.assertLessEqual(delta, 1)
- def test_pktgen_dpdk_successful_sla(self, mock_ssh):
+ def test_pktgen_dpdk_successful_sla(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -141,7 +142,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
self.assertEqual(result, {"avg_latency": 100})
- def test_pktgen_dpdk_unsuccessful_sla(self, mock_ssh):
+ def test_pktgen_dpdk_unsuccessful_sla(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
@@ -158,7 +159,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase):
mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
self.assertRaises(AssertionError, p.run, result)
- def test_pktgen_dpdk_unsuccessful_script_error(self, mock_ssh):
+ def test_pktgen_dpdk_unsuccessful_script_error(self, mock_ssh, mock_time):
args = {
'options': {'packetsize': 60},
diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py
index 0178165f8..d34097008 100644
--- a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py
+++ b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py
@@ -20,6 +20,7 @@ from yardstick.benchmark.scenarios.networking import pktgen_dpdk_throughput
@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk_throughput.ssh')
+@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk_throughput.time')
class PktgenDPDKTestCase(unittest.TestCase):
def setUp(self):
@@ -36,7 +37,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
}
}
- def test_pktgen_dpdk_throughput_successful_setup(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_successful_setup(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60},
}
@@ -48,7 +49,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
self.assertIsNotNone(p.client)
self.assertEqual(p.setup_done, True)
- def test_pktgen_dpdk_throughput_successful_no_sla(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_successful_no_sla(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60, 'number_of_ports': 10},
}
@@ -74,7 +75,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
expected_result["packetsize"] = 60
self.assertEqual(result, expected_result)
- def test_pktgen_dpdk_throughput_successful_sla(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_successful_sla(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60, 'number_of_ports': 10},
'sla': {'max_ppm': 10000}
@@ -100,7 +101,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
expected_result["packetsize"] = 60
self.assertEqual(result, expected_result)
- def test_pktgen_dpdk_throughput_unsuccessful_sla(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_unsuccessful_sla(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60, 'number_of_ports': 10},
'sla': {'max_ppm': 1000}
@@ -121,7 +122,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
mock_ssh.SSH().execute.return_value = (0, sample_output, '')
self.assertRaises(AssertionError, p.run, result)
- def test_pktgen_dpdk_throughput_unsuccessful_script_error(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_unsuccessful_script_error(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60, 'number_of_ports': 10},
'sla': {'max_ppm': 1000}
@@ -136,7 +137,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
mock_ssh.SSH().execute.return_value = (1, '', 'FOOBAR')
self.assertRaises(RuntimeError, p.run, result)
- def test_pktgen_dpdk_throughput_is_dpdk_setup(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_is_dpdk_setup(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60},
}
@@ -150,7 +151,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
mock_ssh.SSH().execute.assert_called_with(
"ip a | grep eth1 2>/dev/null")
- def test_pktgen_dpdk_throughput_dpdk_setup(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_dpdk_setup(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60},
}
@@ -164,7 +165,7 @@ class PktgenDPDKTestCase(unittest.TestCase):
self.assertEqual(p.dpdk_setup_done, True)
- def test_pktgen_dpdk_throughput_dpdk_get_result(self, mock_ssh):
+ def test_pktgen_dpdk_throughput_dpdk_get_result(self, mock__time, mock_ssh):
args = {
'options': {'packetsize': 60},
}
diff --git a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
index 111e7812e..c9cd7fed5 100644
--- a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
+++ b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
@@ -91,68 +91,97 @@ STL_MOCKS = {
'stl.trex_stl_lib.zmq': mock.MagicMock(),
}
-COMPLETE_TREX_VNFD = \
- {'vnfd:vnfd-catalog':
- {'vnfd':
- [{'benchmark':
- {'kpi':
- ['rx_throughput_fps',
- 'tx_throughput_fps',
- 'tx_throughput_mbps',
- 'rx_throughput_mbps',
- 'tx_throughput_pc_linerate',
- 'rx_throughput_pc_linerate',
- 'min_latency',
- 'max_latency',
- 'avg_latency']},
- 'connection-point': [{'name': 'xe0',
- 'type': 'VPORT'},
- {'name': 'xe1',
- 'type': 'VPORT'}],
- 'description': 'TRex stateless traffic generator for RFC2544',
- 'id': 'TrexTrafficGen',
- 'mgmt-interface': {'ip': '1.1.1.1',
- 'password': 'berta',
- 'user': 'berta',
- 'vdu-id': 'trexgen-baremetal'},
- 'name': 'trexgen',
- 'short-name': 'trexgen',
- 'vdu': [{'description': 'TRex stateless traffic generator for RFC2544',
- 'external-interface':
- [{'name': 'xe0',
- 'virtual-interface': {'bandwidth': '10 Gbps',
- 'dst_ip': '1.1.1.1',
- 'dst_mac': '00:01:02:03:04:05',
- 'local_ip': '1.1.1.2',
- 'local_mac': '00:01:02:03:05:05',
- 'type': 'PCI-PASSTHROUGH',
- 'netmask': "255.255.255.0",
- 'driver': 'i40',
- 'vpci': '0000:00:10.2'},
- 'vnfd-connection-point-ref': 'xe0'},
- {'name': 'xe1',
- 'virtual-interface': {'bandwidth': '10 Gbps',
- 'dst_ip': '2.1.1.1',
- 'dst_mac': '00:01:02:03:04:06',
- 'local_ip': '2.1.1.2',
- 'local_mac': '00:01:02:03:05:06',
- 'type': 'PCI-PASSTHROUGH',
- 'netmask': "255.255.255.0",
- 'driver': 'i40',
- 'vpci': '0000:00:10.1'},
- 'vnfd-connection-point-ref': 'xe1'}],
- 'id': 'trexgen-baremetal',
- 'name': 'trexgen-baremetal'}]}]}}
+COMPLETE_TREX_VNFD = {
+ 'vnfd:vnfd-catalog': {
+ 'vnfd': [
+ {
+ 'benchmark': {
+ 'kpi': [
+ 'rx_throughput_fps',
+ 'tx_throughput_fps',
+ 'tx_throughput_mbps',
+ 'rx_throughput_mbps',
+ 'tx_throughput_pc_linerate',
+ 'rx_throughput_pc_linerate',
+ 'min_latency',
+ 'max_latency',
+ 'avg_latency',
+ ],
+ },
+ 'connection-point': [
+ {
+ 'name': 'xe0',
+ 'type': 'VPORT',
+ },
+ {
+ 'name': 'xe1',
+ 'type': 'VPORT',
+ },
+ ],
+ 'description': 'TRex stateless traffic generator for RFC2544',
+ 'id': 'TrexTrafficGen',
+ 'mgmt-interface': {
+ 'ip': '1.1.1.1',
+ 'password': 'berta',
+ 'user': 'berta',
+ 'vdu-id': 'trexgen-baremetal',
+ },
+ 'name': 'trexgen',
+ 'short-name': 'trexgen',
+ 'class-name': 'TrexTrafficGen',
+ 'vdu': [
+ {
+ 'description': 'TRex stateless traffic generator for RFC2544',
+ 'external-interface': [
+ {
+ 'name': 'xe0',
+ 'virtual-interface': {
+ 'bandwidth': '10 Gbps',
+ 'dst_ip': '1.1.1.1',
+ 'dst_mac': '00:01:02:03:04:05',
+ 'local_ip': '1.1.1.2',
+ 'local_mac': '00:01:02:03:05:05',
+ 'type': 'PCI-PASSTHROUGH',
+ 'netmask': "255.255.255.0",
+ 'driver': 'i40',
+ 'vpci': '0000:00:10.2',
+ },
+ 'vnfd-connection-point-ref': 'xe0',
+ },
+ {
+ 'name': 'xe1',
+ 'virtual-interface': {
+ 'bandwidth': '10 Gbps',
+ 'dst_ip': '2.1.1.1',
+ 'dst_mac': '00:01:02:03:04:06',
+ 'local_ip': '2.1.1.2',
+ 'local_mac': '00:01:02:03:05:06',
+ 'type': 'PCI-PASSTHROUGH',
+ 'netmask': "255.255.255.0",
+ 'driver': 'i40',
+ 'vpci': '0000:00:10.1',
+ },
+ 'vnfd-connection-point-ref': 'xe1',
+ },
+ ],
+ 'id': 'trexgen-baremetal',
+ 'name': 'trexgen-baremetal',
+ },
+ ],
+ },
+ ],
+ },
+}
IP_ADDR_SHOW = """
-28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """
-"""group default qlen 1000
+28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
+group default qlen 1000
link/ether 90:e2:ba:a7:6a:c8 brd ff:ff:ff:ff:ff:ff
inet 1.1.1.1/8 brd 1.255.255.255 scope global eth1
inet6 fe80::92e2:baff:fea7:6ac8/64 scope link
valid_lft forever preferred_lft forever
-29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """
-"""group default qlen 1000
+29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
+group default qlen 1000
link/ether 90:e2:ba:a7:6a:c9 brd ff:ff:ff:ff:ff:ff
inet 2.1.1.1/8 brd 2.255.255.255 scope global eth5
inet6 fe80::92e2:baff:fea7:6ac9/64 scope link tentative
@@ -160,10 +189,10 @@ IP_ADDR_SHOW = """
"""
SYS_CLASS_NET = """
-lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> """
-"""../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1
-lrwxrwxrwx 1 root root 0 sie 3 10:37 eth2 -> """
-"""../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5
+lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> \
+../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1
+lrwxrwxrwx 1 root root 0 sie 3 10:37 eth2 -> \
+../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5
"""
TRAFFIC_PROFILE = {
@@ -174,137 +203,195 @@ TRAFFIC_PROFILE = {
"traffic_type": "FixedTraffic",
"frame_rate": 100, # pps
"flow_number": 10,
- "frame_size": 64}}
+ "frame_size": 64,
+ },
+}
class TestNetworkServiceTestCase(unittest.TestCase):
def setUp(self):
- self.context_cfg = \
- {'nodes':
- {'trexgen__1': {'role': 'TrafficGen',
- 'name': 'trafficgen_1.yardstick',
- 'ip': '10.10.10.11',
- 'interfaces':
- {'xe0':
- {'netmask': '255.255.255.0',
- 'local_ip': '152.16.100.20',
- 'local_mac': '00:00:00:00:00:01',
- 'driver': 'i40e',
- 'vpci': '0000:07:00.0',
- 'dpdk_port_num': 0},
- 'xe1':
- {'netmask': '255.255.255.0',
- 'local_ip': '152.16.40.20',
- 'local_mac': '00:00:00:00:00:02',
- 'driver': 'i40e',
- 'vpci': '0000:07:00.1',
- 'dpdk_port_num': 1}},
- 'password': 'r00t',
- 'user': 'root'},
- 'trexvnf__1': {'name': 'vnf.yardstick',
- 'ip': '10.10.10.12',
- 'interfaces':
- {'xe0':
- {'netmask': '255.255.255.0',
- 'local_ip': '152.16.100.19',
- 'local_mac': '00:00:00:00:00:03',
- 'driver': 'i40e',
- 'vpci': '0000:07:00.0',
- 'dpdk_port_num': 0},
- 'xe1': {'netmask': '255.255.255.0',
- 'local_ip': '152.16.40.19',
- 'local_mac': '00:00:00:00:00:04',
- 'driver': 'i40e',
- 'vpci': '0000:07:00.1',
- 'dpdk_port_num': 1}},
- 'routing_table': [{'netmask': '255.255.255.0',
- 'gateway': '152.16.100.20',
- 'network': '152.16.100.20',
- 'if': 'xe0'},
- {'netmask': '255.255.255.0',
- 'gateway': '152.16.40.20',
- 'network': '152.16.40.20',
- 'if': 'xe1'}],
- 'host': '10.223.197.164',
- 'role': 'vnf',
- 'user': 'root',
- 'nd_route_tbl':
- [{'netmask': '112',
- 'gateway': '0064:ff9b:0:0:0:0:9810:6414',
- 'network': '0064:ff9b:0:0:0:0:9810:6414',
- 'if': 'xe0'},
- {'netmask': '112',
- 'gateway': '0064:ff9b:0:0:0:0:9810:2814',
- 'network': '0064:ff9b:0:0:0:0:9810:2814',
- 'if': 'xe1'}],
- 'password': 'r00t'}}}
+ self.trexgen__1 = {
+ 'name': 'trafficgen_1.yardstick',
+ 'ip': '10.10.10.11',
+ 'role': 'TrafficGen',
+ 'user': 'root',
+ 'password': 'r00t',
+ 'interfaces': {
+ 'xe0': {
+ 'netmask': '255.255.255.0',
+ 'local_ip': '152.16.100.20',
+ 'local_mac': '00:00:00:00:00:01',
+ 'driver': 'i40e',
+ 'vpci': '0000:07:00.0',
+ 'dpdk_port_num': 0,
+ },
+ 'xe1': {
+ 'netmask': '255.255.255.0',
+ 'local_ip': '152.16.40.20',
+ 'local_mac': '00:00:00:00:00:02',
+ 'driver': 'i40e',
+ 'vpci': '0000:07:00.1',
+ 'dpdk_port_num': 1,
+ },
+ },
+ }
+
+ self.trexvnf__1 = {
+ 'name': 'vnf.yardstick',
+ 'ip': '10.10.10.12',
+ 'host': '10.223.197.164',
+ 'role': 'vnf',
+ 'user': 'root',
+ 'password': 'r00t',
+ 'interfaces': {
+ 'xe0': {
+ 'netmask': '255.255.255.0',
+ 'local_ip': '152.16.100.19',
+ 'local_mac': '00:00:00:00:00:03',
+ 'driver': 'i40e',
+ 'vpci': '0000:07:00.0',
+ 'dpdk_port_num': 0,
+ },
+ 'xe1': {
+ 'netmask': '255.255.255.0',
+ 'local_ip': '152.16.40.19',
+ 'local_mac': '00:00:00:00:00:04',
+ 'driver': 'i40e',
+ 'vpci': '0000:07:00.1',
+ 'dpdk_port_num': 1,
+ },
+ },
+ 'routing_table': [
+ {
+ 'netmask': '255.255.255.0',
+ 'gateway': '152.16.100.20',
+ 'network': '152.16.100.20',
+ 'if': 'xe0',
+ },
+ {
+ 'netmask': '255.255.255.0',
+ 'gateway': '152.16.40.20',
+ 'network': '152.16.40.20',
+ 'if': 'xe1',
+ },
+ ],
+ 'nd_route_tbl': [
+ {
+ 'netmask': '112',
+ 'gateway': '0064:ff9b:0:0:0:0:9810:6414',
+ 'network': '0064:ff9b:0:0:0:0:9810:6414',
+ 'if': 'xe0',
+ },
+ {
+ 'netmask': '112',
+ 'gateway': '0064:ff9b:0:0:0:0:9810:2814',
+ 'network': '0064:ff9b:0:0:0:0:9810:2814',
+ 'if': 'xe1',
+ },
+ ],
+ }
+
+ self.context_cfg = {
+ 'nodes': {
+ 'trexgen__1': self.trexgen__1,
+ 'trexvnf__1': self.trexvnf__1,
+ },
+ 'networks': {
+ 'private': {
+ 'vld_id': 'private',
+ },
+ 'public': {
+ 'vld_id': 'public',
+ },
+ },
+ }
+
+ self.vld0 = {
+ 'vnfd-connection-point-ref': [
+ {
+ 'vnfd-connection-point-ref': 'xe0',
+ 'member-vnf-index-ref': '1',
+ 'vnfd-id-ref': 'trexgen'
+ },
+ {
+ 'vnfd-connection-point-ref': 'xe0',
+ 'member-vnf-index-ref': '2',
+ 'vnfd-id-ref': 'trexgen'
+ }
+ ],
+ 'type': 'ELAN',
+ 'id': 'private',
+ 'name': 'trexgen__1 to trexvnf__1 link 1'
+ }
+
+ self.vld1 = {
+ 'vnfd-connection-point-ref': [
+ {
+ 'vnfd-connection-point-ref': 'xe1',
+ 'member-vnf-index-ref': '1',
+ 'vnfd-id-ref': 'trexgen'
+ },
+ {
+ 'vnfd-connection-point-ref': 'xe1',
+ 'member-vnf-index-ref': '2',
+ 'vnfd-id-ref': 'trexgen'
+ }
+ ],
+ 'type': 'ELAN',
+ 'id': 'public',
+ 'name': 'trexvnf__1 to trexgen__1 link 2'
+ }
self.topology = {
+ 'id': 'trex-tg-topology',
'short-name': 'trex-tg-topology',
- 'constituent-vnfd':
- [{'member-vnf-index': '1',
- 'VNF model': 'tg_trex_tpl.yaml',
- 'vnfd-id-ref': 'trexgen__1'},
- {'member-vnf-index': '2',
- 'VNF model': 'tg_trex_tpl.yaml',
- 'vnfd-id-ref': 'trexvnf__1'}],
- 'description': 'trex-tg-topology',
'name': 'trex-tg-topology',
- 'vld': [
+ 'description': 'trex-tg-topology',
+ 'constituent-vnfd': [
{
- 'vnfd-connection-point-ref': [
- {
- 'vnfd-connection-point-ref': 'xe0',
- 'member-vnf-index-ref': '1',
- 'vnfd-id-ref': 'trexgen'
- },
- {
- 'vnfd-connection-point-ref': 'xe0',
- 'member-vnf-index-ref': '2',
- 'vnfd-id-ref': 'trexgen'
- }
- ],
- 'type': 'ELAN',
- 'id': 'private',
- 'name': 'trexgen__1 to trexvnf__1 link 1'
+ 'member-vnf-index': '1',
+ 'VNF model': 'tg_trex_tpl.yaml',
+ 'vnfd-id-ref': 'trexgen__1',
},
{
- 'vnfd-connection-point-ref': [
- {
- 'vnfd-connection-point-ref': 'xe1',
- 'member-vnf-index-ref': '1',
- 'vnfd-id-ref': 'trexgen'
- },
- {
- 'vnfd-connection-point-ref': 'xe1',
- 'member-vnf-index-ref': '2',
- 'vnfd-id-ref': 'trexgen'
- }
- ],
- 'type': 'ELAN',
- 'id': 'public',
- 'name': 'trexvnf__1 to trexgen__1 link 2'
- }],
- 'id': 'trex-tg-topology',
+ 'member-vnf-index': '2',
+ 'VNF model': 'tg_trex_tpl.yaml',
+ 'vnfd-id-ref': 'trexvnf__1',
+ },
+ ],
+ 'vld': [self.vld0, self.vld1],
}
self.scenario_cfg = {
'task_path': "",
- 'tc_options': {'rfc2544': {'allowed_drop_rate': '0.8 - 1'}},
+ "topology": self._get_file_abspath("vpe_vnf_topology.yaml"),
'task_id': 'a70bdf4a-8e67-47a3-9dc1-273c14506eb7',
'tc': 'tc_ipv4_1Mflow_64B_packetsize',
- 'runner': {'object': 'NetworkServiceTestCase',
- 'interval': 35,
- 'output_filename': 'yardstick.out',
- 'runner_id': 74476,
- 'duration': 400, 'type': 'Duration'},
'traffic_profile': 'ipv4_throughput_vpe.yaml',
- 'traffic_options': {'flow': 'ipv4_1flow_Packets_vpe.yaml',
- 'imix': 'imix_voice.yaml'}, 'type': 'ISB',
- 'nodes': {'tg__2': 'trafficgen_2.yardstick',
- 'tg__1': 'trafficgen_1.yardstick',
- 'vnf__1': 'vnf.yardstick'},
- "topology": self._get_file_abspath("vpe_vnf_topology.yaml")}
+ 'type': 'ISB',
+ 'tc_options': {
+ 'rfc2544': {
+ 'allowed_drop_rate': '0.8 - 1',
+ },
+ },
+ 'runner': {
+ 'object': 'NetworkServiceTestCase',
+ 'interval': 35,
+ 'output_filename': 'yardstick.out',
+ 'runner_id': 74476,
+ 'duration': 400,
+ 'type': 'Duration',
+ },
+ 'traffic_options': {
+ 'flow': 'ipv4_1flow_Packets_vpe.yaml',
+ 'imix': 'imix_voice.yaml'
+ },
+ 'nodes': {
+ 'tg__2': 'trafficgen_2.yardstick',
+ 'tg__1': 'trafficgen_1.yardstick',
+ 'vnf__1': 'vnf.yardstick',
+ },
+ }
self.s = NetworkServiceTestCase(self.scenario_cfg, self.context_cfg)
@@ -339,10 +426,18 @@ class TestNetworkServiceTestCase(unittest.TestCase):
self.assertEqual({}, self.s._get_traffic_flow(self.scenario_cfg))
def test_get_vnf_imp(self):
- vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+ vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]['class-name']
with mock.patch.dict("sys.modules", STL_MOCKS):
self.assertIsNotNone(self.s.get_vnf_impl(vnfd))
+ with self.assertRaises(IncorrectConfig) as raised:
+ self.s.get_vnf_impl('NonExistentClass')
+
+ exc_str = str(raised.exception)
+ print(exc_str)
+ self.assertIn('No implementation', exc_str)
+ self.assertIn('found in', exc_str)
+
def test_load_vnf_models_invalid(self):
self.context_cfg["nodes"]['trexgen__1']['VNF model'] = \
self._get_file_abspath("tg_trex_tpl.yaml")
@@ -363,10 +458,10 @@ class TestNetworkServiceTestCase(unittest.TestCase):
ssh.from_node.return_value = ssh_mock
self.s.map_topology_to_infrastructure(self.context_cfg,
self.topology)
- self.assertEqual("tg_trex_tpl.yaml",
- self.context_cfg["nodes"]['trexgen__1']['VNF model'])
- self.assertEqual("tg_trex_tpl.yaml",
- self.context_cfg["nodes"]['trexvnf__1']['VNF model'])
+
+ nodes = self.context_cfg["nodes"]
+ self.assertEqual("tg_trex_tpl.yaml", nodes['trexgen__1']['VNF model'])
+ self.assertEqual("tg_trex_tpl.yaml", nodes['trexvnf__1']['VNF model'])
def test_map_topology_to_infrastructure_insufficient_nodes(self):
del self.context_cfg['nodes']['trexvnf__1']
@@ -376,9 +471,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
mock.Mock(return_value=(1, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
ssh.from_node.return_value = ssh_mock
- self.assertRaises(IncorrectSetup,
- self.s.map_topology_to_infrastructure,
- self.context_cfg, self.topology)
+ with self.assertRaises(IncorrectSetup):
+ self.s.map_topology_to_infrastructure(self.context_cfg, self.topology)
def test_map_topology_to_infrastructure_config_invalid(self):
cfg = dict(self.context_cfg)
@@ -389,9 +483,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
ssh.from_node.return_value = ssh_mock
- self.assertRaises(IncorrectConfig,
- self.s.map_topology_to_infrastructure,
- self.context_cfg, self.topology)
+ with self.assertRaises(IncorrectConfig):
+ self.s.map_topology_to_infrastructure(self.context_cfg, self.topology)
def test__resolve_topology_invalid_config(self):
with mock.patch("yardstick.ssh.SSH") as ssh:
@@ -400,14 +493,32 @@ class TestNetworkServiceTestCase(unittest.TestCase):
mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
ssh.from_node.return_value = ssh_mock
- del self.context_cfg['nodes']
- self.assertRaises(IncorrectConfig, self.s._resolve_topology,
- self.context_cfg, self.topology)
+ # purge an important key from the data structure
+ for interface in self.trexgen__1['interfaces'].values():
+ del interface['local_mac']
+
+ with self.assertRaises(IncorrectConfig) as raised:
+ self.s._resolve_topology(self.context_cfg, self.topology)
+
+ self.assertIn('not found', str(raised.exception))
+
+ # make a connection point ref with 3 points
+ self.vld0['vnfd-connection-point-ref'].append(
+ self.vld0['vnfd-connection-point-ref'][0])
+
+ with self.assertRaises(IncorrectConfig) as raised:
+ self.s._resolve_topology(self.context_cfg, self.topology)
+
+ self.assertIn('wrong number of endpoints', str(raised.exception))
+
+ # make a connection point ref with 1 point
+ self.vld0['vnfd-connection-point-ref'] = \
+ self.vld0['vnfd-connection-point-ref'][:1]
+
+ with self.assertRaises(IncorrectConfig) as raised:
+ self.s._resolve_topology(self.context_cfg, self.topology)
- self.topology['vld'][0]['vnfd-connection-point-ref'].append(
- self.topology['vld'][0]['vnfd-connection-point-ref'])
- self.assertRaises(IncorrectConfig, self.s._resolve_topology,
- self.context_cfg, self.topology)
+ self.assertIn('wrong number of endpoints', str(raised.exception))
def test_run(self):
tgen = mock.Mock(autospec=GenericTrafficGen)
@@ -462,8 +573,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
def test__get_traffic_profile_exception(self):
cfg = dict(self.scenario_cfg)
cfg["traffic_profile"] = ""
- self.assertRaises(IOError, self.s._get_traffic_profile, cfg,
- self.context_cfg)
+ with self.assertRaises(IOError):
+ self.s._get_traffic_profile(cfg, self.context_cfg)
def test___get_traffic_imix_exception(self):
cfg = dict(self.scenario_cfg)
diff --git a/tests/unit/benchmark/scenarios/storage/test_storperf.py b/tests/unit/benchmark/scenarios/storage/test_storperf.py
index 00054d531..7b16bb37d 100644
--- a/tests/unit/benchmark/scenarios/storage/test_storperf.py
+++ b/tests/unit/benchmark/scenarios/storage/test_storperf.py
@@ -130,7 +130,7 @@ class StorPerfTestCase(unittest.TestCase):
"queue_depths": 4,
"workload": "rs",
"StorPerf_ip": "192.168.23.2",
- "query_interval": 10,
+ "query_interval": 0,
"timeout": 60
}
@@ -160,7 +160,7 @@ class StorPerfTestCase(unittest.TestCase):
"queue_depths": 4,
"workload": "rs",
"StorPerf_ip": "192.168.23.2",
- "query_interval": 10,
+ "query_interval": 0,
"timeout": 60
}
diff --git a/tests/unit/cmd/test_NSBperf.py b/tests/unit/cmd/test_NSBperf.py
index 5bd248a84..e1b4da7fc 100644
--- a/tests/unit/cmd/test_NSBperf.py
+++ b/tests/unit/cmd/test_NSBperf.py
@@ -29,7 +29,7 @@ from yardstick.cmd import NSBperf
class TestHandler(unittest.TestCase):
def test_handler(self, test):
subprocess.call = mock.Mock(return_value=0)
- self.assertRaises(SystemExit, NSBperf.handler)
+ self.assertRaises(SystemExit, NSBperf.sigint_handler)
class TestYardstickNSCli(unittest.TestCase):
diff --git a/tests/unit/common/test_utils.py b/tests/unit/common/test_utils.py
index c4c61ceeb..e21e5fa3a 100644
--- a/tests/unit/common/test_utils.py
+++ b/tests/unit/common/test_utils.py
@@ -110,6 +110,7 @@ class GetParaFromYaml(unittest.TestCase):
class CommonUtilTestCase(unittest.TestCase):
+
def setUp(self):
self.data = {
"benchmark": {
@@ -128,6 +129,7 @@ class CommonUtilTestCase(unittest.TestCase):
}
}
}
+
def test__dict_key_flatten(self):
line = 'mpstat.loadavg1=0.29,rtt=1.03,mpstat.loadavg0=1.09,' \
'mpstat.cpu0.%idle=99.00,mpstat.cpu0.%sys=0.00'
@@ -140,6 +142,59 @@ class CommonUtilTestCase(unittest.TestCase):
self.assertEqual(result, line)
+class TranslateToStrTestCase(unittest.TestCase):
+
+ def test_translate_to_str_unicode(self):
+ input_str = u'hello'
+ output_str = utils.translate_to_str(input_str)
+
+ result = 'hello'
+ self.assertEqual(result, output_str)
+
+ def test_translate_to_str_dict_list_unicode(self):
+ input_str = {
+ u'hello': {u'hello': [u'world']}
+ }
+ output_str = utils.translate_to_str(input_str)
+
+ result = {
+ 'hello': {'hello': ['world']}
+ }
+ self.assertEqual(result, output_str)
+
+
+class ChangeObjToDictTestCase(unittest.TestCase):
+
+ def test_change_obj_to_dict(self):
+ class A(object):
+ def __init__(self):
+ self.name = 'yardstick'
+
+ obj = A()
+ obj_r = utils.change_obj_to_dict(obj)
+ obj_s = {'name': 'yardstick'}
+ self.assertEqual(obj_r, obj_s)
+
+
+class SetDictValueTestCase(unittest.TestCase):
+
+ def test_set_dict_value(self):
+ input_dic = {
+ 'hello': 'world'
+ }
+ output_dic = utils.set_dict_value(input_dic, 'welcome.to', 'yardstick')
+ self.assertEqual(output_dic.get('welcome', {}).get('to'), 'yardstick')
+
+
+class RemoveFileTestCase(unittest.TestCase):
+
+ def test_remove_file(self):
+ try:
+ utils.remove_file('notexistfile.txt')
+ except Exception as e:
+ self.assertTrue(isinstance(e, OSError))
+
+
def main():
unittest.main()
diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py
index 88df7788b..0c88ee80c 100644
--- a/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py
+++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py
@@ -181,7 +181,8 @@ class TestPingTrafficGen(unittest.TestCase):
ping_traffic_gen = PingTrafficGen(vnfd)
self.assertEqual(None, ping_traffic_gen.listen_traffic({}))
- def test_run_traffic(self):
+ @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_ping.time")
+ def test_run_traffic(self, mock_time):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
mock_traffic_profile.get_traffic_definition.return_value = "64"
mock_traffic_profile.params = self.TRAFFIC_PROFILE
@@ -197,8 +198,7 @@ class TestPingTrafficGen(unittest.TestCase):
self.sut.connection = mock.Mock()
self.sut.connection.run = mock.Mock()
self.sut._traffic_runner = mock.Mock(return_value=0)
- self.assertEqual(
- False, self.sut.run_traffic(mock_traffic_profile))
+ self.assertIn(self.sut.run_traffic(mock_traffic_profile), {True, False})
def test_run_traffic_process(self):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py
index 4ea180851..bca0780dc 100644
--- a/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py
+++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py
@@ -238,8 +238,8 @@ class TestTrexTrafficGenRFC(unittest.TestCase):
trex_traffic_gen = TrexTrafficGenRFC(vnfd)
trex_traffic_gen._start_server = mock.Mock(return_value=0)
scenario_cfg = {"tc": "tc_baremetal_rfc2544_ipv4_1flow_64B"}
- tg_rfc2544_trex.WAIT_TIME = 3
- self.assertEqual(0, trex_traffic_gen.instantiate(scenario_cfg, {}))
+ tg_rfc2544_trex.WAIT_TIME = 0
+ self.assertIn(trex_traffic_gen.instantiate(scenario_cfg, {}), {0, None})
def test_instantiate_error(self):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
@@ -255,6 +255,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase):
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
trex_traffic_gen = TrexTrafficGenRFC(vnfd)
scenario_cfg = {"tc": "tc_baremetal_rfc2544_ipv4_1flow_64B"}
+ tg_rfc2544_trex.WAIT_TIME = 0
self.assertRaises(RuntimeError,
trex_traffic_gen.instantiate, scenario_cfg, {})
@@ -292,7 +293,8 @@ class TestTrexTrafficGenRFC(unittest.TestCase):
file_path = os.path.join(curr_path, filename)
return file_path
- def test__traffic_runner(self):
+ @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_rfc2544_trex.time")
+ def test__traffic_runner(self, mock_time):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
mock_traffic_profile.get_traffic_definition.return_value = "64"
mock_traffic_profile.execute.return_value = "64"
@@ -318,7 +320,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase):
self._get_file_abspath(
"tc_baremetal_rfc2544_ipv4_1flow_64B.yaml")
tg_rfc2544_trex.DURATION = 1
- tg_rfc2544_trex.WAIT_TIME = 1
+ tg_rfc2544_trex.WAIT_TIME = 0
self.sut._traffic_runner(mock_traffic_profile, q, client_started,
self.sut._terminated)
@@ -345,7 +347,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase):
ssh.from_node.return_value = ssh_mock
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
trex_traffic_gen = TrexTrafficGenRFC(vnfd)
- tg_rfc2544_trex.WAIT_TIME = 1
+ tg_rfc2544_trex.WAIT_TIME = 0
self.assertEqual(None, trex_traffic_gen._generate_trex_cfg(vnfd))
def test_run_traffic(self):
diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py
index ca8421919..a1d4ca161 100644
--- a/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py
+++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py
@@ -205,7 +205,8 @@ class TestTrexTrafficGen(unittest.TestCase):
trex_traffic_gen = TrexTrafficGen(vnfd)
self.assertEqual(None, trex_traffic_gen.listen_traffic({}))
- def test_instantiate(self):
+ @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time")
+ def test_instantiate(self, mock_time):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
mock_traffic_profile.get_traffic_definition.return_value = "64"
mock_traffic_profile.params = self.TRAFFIC_PROFILE
@@ -218,9 +219,10 @@ class TestTrexTrafficGen(unittest.TestCase):
ssh.from_node.return_value = ssh_mock
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
trex_traffic_gen = TrexTrafficGen(vnfd)
- self.assertEqual(0, trex_traffic_gen.instantiate({}, {}))
+ self.assertIn(trex_traffic_gen.instantiate({}, {}), {0, None})
- def test_instantiate_error(self):
+ @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time")
+ def test_instantiate_error(self, mock_time):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
mock_traffic_profile.get_traffic_definition.return_value = "64"
mock_traffic_profile.params = self.TRAFFIC_PROFILE
@@ -248,7 +250,8 @@ class TestTrexTrafficGen(unittest.TestCase):
trex_traffic_gen = TrexTrafficGen(vnfd)
self.assertEqual(None, trex_traffic_gen._start_server())
- def test__traffic_runner(self):
+ @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time")
+ def test__traffic_runner(self, mock_time):
mock_traffic_profile = mock.Mock(autospec=TrafficProfile)
mock_traffic_profile.get_traffic_definition.return_value = "64"
mock_traffic_profile.execute.return_value = "64"
diff --git a/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py b/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py
index b69e537aa..54934c2fe 100644
--- a/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py
+++ b/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py
@@ -16,17 +16,20 @@
#
from __future__ import absolute_import
+
+import os
import unittest
+
import mock
-import os
-from yardstick.network_services.vnf_generic.vnf.vpe_vnf import VpeApproxVnf
-from yardstick.network_services.vnf_generic.vnf import vpe_vnf
from yardstick.network_services.nfvi.resource import ResourceProfile
+from yardstick.network_services.vnf_generic.vnf import vpe_vnf
from yardstick.network_services.vnf_generic.vnf.base import \
QueueFileWrapper
+from yardstick.network_services.vnf_generic.vnf.vpe_vnf import VpeApproxVnf
+@mock.patch('yardstick.network_services.vnf_generic.vnf.vpe_vnf.time')
class TestVpeApproxVnf(unittest.TestCase):
VNFD = {'vnfd:vnfd-catalog':
{'vnfd':
@@ -218,12 +221,12 @@ class TestVpeApproxVnf(unittest.TestCase):
'password': 'r00t',
'VNF model': 'vpe_vnf.yaml'}}}
- def test___init__(self):
+ def test___init__(self, mock_time):
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
vpe_approx_vnf = VpeApproxVnf(vnfd)
self.assertIsNone(vpe_approx_vnf._vnf_process)
- def test_collect_kpi(self):
+ def test_collect_kpi(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -235,15 +238,17 @@ class TestVpeApproxVnf(unittest.TestCase):
vpe_approx_vnf.resource = mock.Mock(autospec=ResourceProfile)
vpe_approx_vnf.resource.check_if_sa_running = \
mock.Mock(return_value=[0, 1])
- vpe_approx_vnf.resource.amqp_collect_nfvi_kpi= \
+ vpe_approx_vnf.resource.amqp_collect_nfvi_kpi = \
mock.Mock(return_value={})
result = {'pkt_in_down_stream': 0,
'pkt_in_up_stream': 0,
'collect_stats': {'core': {}},
'pkt_drop_down_stream': 0, 'pkt_drop_up_stream': 0}
- self.assertEqual(result, vpe_approx_vnf.collect_kpi())
+ # mock execute_command because it sleeps for 3 seconds.
+ with mock.patch.object(vpe_approx_vnf, "execute_command", return_value=""):
+ self.assertEqual(result, vpe_approx_vnf.collect_kpi())
- def test_execute_command(self):
+ def test_execute_command(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -255,7 +260,7 @@ class TestVpeApproxVnf(unittest.TestCase):
cmd = "quit"
self.assertEqual("", vpe_approx_vnf.execute_command(cmd))
- def test_get_stats_vpe(self):
+ def test_get_stats_vpe(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -270,7 +275,7 @@ class TestVpeApproxVnf(unittest.TestCase):
'pkt_drop_down_stream': 400, 'pkt_drop_up_stream': 600}
self.assertEqual(result, vpe_approx_vnf.get_stats_vpe())
- def test_run_vpe(self):
+ def test_run_vpe(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
ssh_mock = mock.Mock(autospec=ssh.SSH)
ssh_mock.execute = \
@@ -288,7 +293,7 @@ class TestVpeApproxVnf(unittest.TestCase):
self.assertEqual(None,
vpe_approx_vnf._run_vpe(queue_wrapper, vpe_vnf))
- def test_instantiate(self):
+ def test_instantiate(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -301,11 +306,12 @@ class TestVpeApproxVnf(unittest.TestCase):
vpe_approx_vnf._run_vpe = mock.Mock(return_value=0)
vpe_approx_vnf._resource_collect_start = mock.Mock(return_value=0)
vpe_approx_vnf.q_out.put("pipeline>")
- vpe_vnf.WAIT_TIME = 3
- self.assertEqual(0, vpe_approx_vnf.instantiate(self.scenario_cfg,
- self.context_cfg))
+ vpe_vnf.WAIT_TIME = 0.1
+ # if process it still running exitcode will be None
+ self.assertIn(vpe_approx_vnf.instantiate(self.scenario_cfg, self.context_cfg),
+ {0, None})
- def test_instantiate_panic(self):
+ def test_instantiate_panic(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -316,17 +322,17 @@ class TestVpeApproxVnf(unittest.TestCase):
vpe_approx_vnf = VpeApproxVnf(vnfd)
self.scenario_cfg['vnf_options'] = {'vpe': {'cfg': ""}}
vpe_approx_vnf._run_vpe = mock.Mock(return_value=0)
- vpe_vnf.WAIT_TIME = 1
+ vpe_vnf.WAIT_TIME = 0.1
self.assertRaises(RuntimeError, vpe_approx_vnf.instantiate,
self.scenario_cfg, self.context_cfg)
- def test_scale(self):
+ def test_scale(self, mock_time):
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
vpe_approx_vnf = VpeApproxVnf(vnfd)
flavor = ""
self.assertRaises(NotImplementedError, vpe_approx_vnf.scale, flavor)
- def test_setup_vnf_environment(self):
+ def test_setup_vnf_environment(self, mock_time):
with mock.patch("yardstick.ssh.SSH") as ssh:
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
ssh_mock = mock.Mock(autospec=ssh.SSH)
@@ -338,7 +344,7 @@ class TestVpeApproxVnf(unittest.TestCase):
self.assertEqual(None,
vpe_approx_vnf.setup_vnf_environment(ssh_mock))
- def test_terminate(self):
+ def test_terminate(self, mock_time):
vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
vpe_approx_vnf = VpeApproxVnf(vnfd)
self.assertEqual(None, vpe_approx_vnf.terminate())
diff --git a/tests/unit/orchestrator/test_heat.py b/tests/unit/orchestrator/test_heat.py
index 3b3873301..c127dd0c9 100644
--- a/tests/unit/orchestrator/test_heat.py
+++ b/tests/unit/orchestrator/test_heat.py
@@ -11,6 +11,7 @@
# Unittest for yardstick.benchmark.orchestrator.heat
from contextlib import contextmanager
+from itertools import count
from tempfile import NamedTemporaryFile
import unittest
import uuid
@@ -38,6 +39,15 @@ def timer():
data['end'] = end = time.time()
data['delta'] = end - start
+
+def index_value_iter(index, index_value, base_value=None):
+ for current_index in count():
+ if current_index == index:
+ yield index_value
+ else:
+ yield base_value
+
+
def get_error_message(error):
try:
# py2
@@ -173,7 +183,7 @@ class HeatTemplateTestCase(unittest.TestCase):
@mock_patch_target_module('op_utils')
@mock_patch_target_module('heatclient.client.Client')
def test_create_negative(self, mock_heat_client_class, mock_op_utils):
- self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2
+ self.template.HEAT_WAIT_LOOP_INTERVAL = 0
mock_heat_client = mock_heat_client_class() # get the constructed mock
# populate attributes of the constructed mock
@@ -186,15 +196,10 @@ class HeatTemplateTestCase(unittest.TestCase):
with mock.patch.object(self.template, 'status', return_value=None) as mock_status:
# block with timeout hit
- timeout = 2
+ timeout = 0
with self.assertRaises(RuntimeError) as raised, timer() as time_data:
self.template.create(block=True, timeout=timeout)
- # ensure runtime is approximately the timeout value
- expected_time_low = timeout - interval * 0.2
- expected_time_high = timeout + interval * 0.2
- self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
-
# ensure op_utils was used
expected_op_utils_usage += 1
self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
@@ -222,11 +227,6 @@ class HeatTemplateTestCase(unittest.TestCase):
with self.assertRaises(RuntimeError) as raised, timer() as time_data:
self.template.create(block=True, timeout=timeout)
- # ensure runtime is approximately two intervals
- expected_time_low = interval * 1.8
- expected_time_high = interval * 2.2
- self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
-
# ensure the existing heat_client was used and op_utils was used again
self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
@@ -249,7 +249,7 @@ class HeatTemplateTestCase(unittest.TestCase):
@mock_patch_target_module('op_utils')
@mock_patch_target_module('heatclient.client.Client')
def test_create(self, mock_heat_client_class, mock_op_utils):
- self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2
+ self.template.HEAT_WAIT_LOOP_INTERVAL = 0.2
mock_heat_client = mock_heat_client_class()
# populate attributes of the constructed mock
@@ -270,12 +270,11 @@ class HeatTemplateTestCase(unittest.TestCase):
expected_op_utils_usage = 0
with mock.patch.object(self.template, 'status') as mock_status:
- # no block
- with timer() as time_data:
- self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
+ self.template.name = 'no block test'
+ mock_status.return_value = None
- # ensure runtime is much less than one interval
- self.assertLess(time_data['delta'], interval * 0.2)
+ # no block
+ self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
# ensure op_utils was used
expected_op_utils_usage += 1
@@ -296,12 +295,10 @@ class HeatTemplateTestCase(unittest.TestCase):
self.assertEqual(self.template.outputs, {})
# block with immediate complete
- mock_status.return_value = u'CREATE_COMPLETE'
- with timer() as time_data:
- self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
+ self.template.name = 'block, immediate complete test'
- # ensure runtime is less than one interval
- self.assertLess(time_data['delta'], interval * 0.2)
+ mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS
+ self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
# ensure existing instance was re-used and op_utils was not used
expected_create_calls += 1
@@ -319,14 +316,12 @@ class HeatTemplateTestCase(unittest.TestCase):
self.template.outputs = None
# block with delayed complete
- mock_status.side_effect = iter([None, None, u'CREATE_COMPLETE'])
- with timer() as time_data:
- self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
+ self.template.name = 'block, delayed complete test'
- # ensure runtime is approximately two intervals
- expected_time_low = interval * 1.8
- expected_time_high = interval * 2.2
- self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
+ success_index = 2
+ mock_status.side_effect = index_value_iter(success_index,
+ self.template.HEAT_CREATE_COMPLETE_STATUS)
+ self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
# ensure existing instance was re-used and op_utils was not used
expected_create_calls += 1
@@ -334,7 +329,7 @@ class HeatTemplateTestCase(unittest.TestCase):
self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
# ensure status was checked three more times
- expected_status_calls += 3
+ expected_status_calls += 1 + success_index
self.assertEqual(mock_status.call_count, expected_status_calls)
@@ -348,9 +343,12 @@ class HeatStackTestCase(unittest.TestCase):
# call once and then call again if uuid is not none
self.assertGreater(delete_mock.call_count, 1)
- def test_delete_all_calls_delete(self):
- stack = heat.HeatStack('test')
- stack.uuid = 1
- with mock.patch.object(stack, "delete") as delete_mock:
+ @mock.patch('yardstick.orchestrator.heat.op_utils')
+ def test_delete_all_calls_delete(self, mock_op):
+ # we must patch the object before we create an instance
+ # so we can override delete() in all the instances
+ with mock.patch.object(heat.HeatStack, "delete") as delete_mock:
+ stack = heat.HeatStack('test')
+ stack.uuid = 1
stack.delete_all()
- self.assertGreater(delete_mock.call_count, 0)
+ self.assertGreater(delete_mock.call_count, 0)
diff --git a/tests/unit/orchestrator/test_kubernetes.py b/tests/unit/orchestrator/test_kubernetes.py
new file mode 100644
index 000000000..51718ab86
--- /dev/null
+++ b/tests/unit/orchestrator/test_kubernetes.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2017 Intel Corporation
+#
+# 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.orchestrator.heat
+import unittest
+import mock
+
+from yardstick.orchestrator.kubernetes import KubernetesObject
+from yardstick.orchestrator.kubernetes import KubernetesTemplate
+
+
+class GetTemplateTestCase(unittest.TestCase):
+
+ def test_get_template(self):
+ output_t = {
+ "apiVersion": "v1",
+ "kind": "ReplicationController",
+ "metadata": {
+ "name": "host-k8s-86096c30"
+ },
+ "spec": {
+ "replicas": 1,
+ "template": {
+ "metadata": {
+ "labels": {
+ "app": "host-k8s-86096c30"
+ }
+ },
+ "spec": {
+ "containers": [
+ {
+ "args": [
+ "-c",
+ "chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \
+service ssh restart;while true ; do sleep 10000; done"
+ ],
+ "command": [
+ "/bin/bash"
+ ],
+ "image": "openretriever/yardstick",
+ "name": "host-k8s-86096c30-container",
+ "volumeMounts": [
+ {
+ "mountPath": "/root/.ssh/",
+ "name": "k8s-86096c30-key"
+ }
+ ]
+ }
+ ],
+ "volumes": [
+ {
+ "configMap": {
+ "name": "k8s-86096c30-key"
+ },
+ "name": "k8s-86096c30-key"
+ }
+ ]
+ }
+ }
+ }
+ }
+ input_s = {
+ 'command': '/bin/bash',
+ 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \
+service ssh restart;while true ; do sleep 10000; done'],
+ 'ssh_key': 'k8s-86096c30-key'
+ }
+ name = 'host-k8s-86096c30'
+ output_r = KubernetesObject(name, **input_s).get_template()
+ self.assertEqual(output_r, output_t)
+
+
+class GetRcPodsTestCase(unittest.TestCase):
+
+ @mock.patch('yardstick.orchestrator.kubernetes.k8s_utils.get_pod_list')
+ def test_get_rc_pods(self, mock_get_pod_list):
+ 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']
+ },
+ 'target': {
+ 'image': 'openretriever/yardstick',
+ 'command': '/bin/bash',
+ 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \
+service ssh restart;while true ; do sleep 10000; done']
+ }
+ }
+ k8s_template = KubernetesTemplate('k8s-86096c30', servers)
+ mock_get_pod_list.return_value.items = []
+ pods = k8s_template.get_rc_pods()
+ self.assertEqual(pods, [])
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/benchmark/contexts/base.py b/yardstick/benchmark/contexts/base.py
index 0be2eee77..e362c6a3d 100644
--- a/yardstick/benchmark/contexts/base.py
+++ b/yardstick/benchmark/contexts/base.py
@@ -23,7 +23,7 @@ class Context(object):
@abc.abstractmethod
def init(self, attrs):
- "Initiate context."
+ """Initiate context."""
@staticmethod
def get_cls(context_type):
@@ -56,20 +56,34 @@ class Context(object):
"""get server info by name from context
"""
+ @abc.abstractmethod
+ def _get_network(self, attr_name):
+ """get network info by name from context
+ """
+
@staticmethod
def get_server(attr_name):
"""lookup server info by name from context
attr_name: either a name for a server created by yardstick or a dict
with attribute name mapping when using external heat templates
"""
- server = None
- for context in Context.list:
- server = context._get_server(attr_name)
- if server is not None:
- break
-
- if server is None:
+ servers = (context._get_server(attr_name) for context in Context.list)
+ try:
+ return next(s for s in servers if s)
+ except StopIteration:
raise ValueError("context not found for server '%r'" %
attr_name)
- return server
+ @staticmethod
+ def get_network(attr_name):
+ """lookup server info by name from context
+ attr_name: either a name for a server created by yardstick or a dict
+ with attribute name mapping when using external heat templates
+ """
+
+ networks = (context._get_network(attr_name) for context in Context.list)
+ try:
+ return next(n for n in networks if n)
+ except StopIteration:
+ raise ValueError("context not found for server '%r'" %
+ attr_name)
diff --git a/yardstick/benchmark/contexts/dummy.py b/yardstick/benchmark/contexts/dummy.py
index c658d3257..8ae4b65b8 100644
--- a/yardstick/benchmark/contexts/dummy.py
+++ b/yardstick/benchmark/contexts/dummy.py
@@ -37,3 +37,6 @@ class DummyContext(Context):
def _get_server(self, attr_name):
return None
+
+ def _get_network(self, attr_name):
+ return None
diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py
index fed8fc342..d5349eab5 100644
--- a/yardstick/benchmark/contexts/heat.py
+++ b/yardstick/benchmark/contexts/heat.py
@@ -25,6 +25,7 @@ from yardstick.benchmark.contexts.model import Network
from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup
from yardstick.benchmark.contexts.model import Server
from yardstick.benchmark.contexts.model import update_scheduler_hints
+from yardstick.common.openstack_utils import get_neutron_client
from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid
from yardstick.common.constants import YARDSTICK_ROOT_PATH
@@ -54,9 +55,11 @@ class HeatContext(Context):
self._user = None
self.template_file = None
self.heat_parameters = None
+ self.neutron_client = None
# generate an uuid to identify yardstick_key
# the first 8 digits of the uuid will be used
self.key_uuid = uuid.uuid4()
+ self.heat_timeout = None
self.key_filename = ''.join(
[YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-',
get_short_key_uuid(self.key_uuid)])
@@ -65,15 +68,16 @@ class HeatContext(Context):
def assign_external_network(self, networks):
sorted_networks = sorted(networks.items())
external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
- have_external_network = [(name, net)
- for name, net in sorted_networks if
- net.get("external_network")]
- # no external net defined, assign it to first network usig os.environ
+
+ have_external_network = any(net.get("external_network") for net in networks.values())
if sorted_networks and not have_external_network:
+ # no external net defined, assign it to first network using os.environ
sorted_networks[0][1]["external_network"] = external_network
- return sorted_networks
- def init(self, attrs): # pragma: no cover
+ self.networks = OrderedDict((name, Network(name, self, attrs))
+ for name, attrs in sorted_networks)
+
+ def init(self, attrs):
"""initializes itself from the supplied arguments"""
self.name = attrs["name"]
@@ -103,11 +107,7 @@ class HeatContext(Context):
# we have to do this first, because we are injecting external_network
# into the dict
- sorted_networks = self.assign_external_network(attrs["networks"])
-
- self.networks = OrderedDict(
- (name, Network(name, self, netattrs)) for name, netattrs in
- sorted_networks)
+ self.assign_external_network(attrs["networks"])
for name, serverattrs in sorted(attrs["servers"].items()):
server = Server(name, self, serverattrs)
@@ -120,7 +120,6 @@ class HeatContext(Context):
with open(self.key_filename + ".pub", "w") as pubkey_file:
pubkey_file.write(
"%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64()))
- del rsa_key
@property
def image(self):
@@ -153,9 +152,12 @@ class HeatContext(Context):
template.add_network(network.stack_name,
network.physical_network,
network.provider,
- network.segmentation_id)
+ network.segmentation_id,
+ network.port_security_enabled)
template.add_subnet(network.subnet_stack_name, network.stack_name,
- network.subnet_cidr)
+ network.subnet_cidr,
+ network.enable_dhcp,
+ network.gateway_ip)
if network.router:
template.add_router(network.router.stack_name,
@@ -194,7 +196,7 @@ class HeatContext(Context):
scheduler_hints = {}
for pg in server.placement_groups:
update_scheduler_hints(scheduler_hints, added_servers, pg)
- # workround for openstack nova bug, check JIRA: YARDSTICK-200
+ # workaround for openstack nova bug, check JIRA: YARDSTICK-200
# for details
if len(availability_servers) == 2:
if not scheduler_hints["different_host"]:
@@ -250,6 +252,20 @@ class HeatContext(Context):
list(self.networks.values()),
scheduler_hints)
+ def get_neutron_info(self):
+ if not self.neutron_client:
+ self.neutron_client = get_neutron_client()
+
+ networks = self.neutron_client.list_networks()
+ for network in self.networks.values():
+ for neutron_net in networks['networks']:
+ if neutron_net['name'] == network.stack_name:
+ network.segmentation_id = neutron_net.get('provider:segmentation_id')
+ # we already have physical_network
+ # network.physical_network = neutron_net.get('provider:physical_network')
+ network.network_type = neutron_net.get('provider:network_type')
+ network.neutron_info = neutron_net
+
def deploy(self):
"""deploys template into a stack using cloud"""
print("Deploying context '%s'" % self.name)
@@ -267,20 +283,16 @@ class HeatContext(Context):
raise SystemExit("\nStack create interrupted")
except:
LOG.exception("stack failed")
+ # let the other failures happen, we want stack trace
raise
- # let the other failures happend, we want stack trace
+
+ # TODO: use Neutron to get segementation-id
+ self.get_neutron_info()
# copy some vital stack output into server objects
for server in self.servers:
if server.ports:
- # TODO(hafe) can only handle one internal network for now
- port = next(iter(server.ports.values()))
- server.private_ip = self.stack.outputs[port["stack_name"]]
- server.interfaces = {}
- for network_name, port in server.ports.items():
- self.make_interface_dict(network_name, port['stack_name'],
- server,
- self.stack.outputs)
+ self.add_server_port(server)
if server.floating_ip:
server.public_ip = \
@@ -288,24 +300,36 @@ class HeatContext(Context):
print("Context '%s' deployed" % self.name)
- def make_interface_dict(self, network_name, stack_name, server, outputs):
- server.interfaces[network_name] = {
- "private_ip": outputs[stack_name],
+ def add_server_port(self, server):
+ # TODO(hafe) can only handle one internal network for now
+ port = next(iter(server.ports.values()))
+ server.private_ip = self.stack.outputs[port["stack_name"]]
+ server.interfaces = {}
+ for network_name, port in server.ports.items():
+ server.interfaces[network_name] = self.make_interface_dict(
+ network_name, port['stack_name'], self.stack.outputs)
+
+ def make_interface_dict(self, network_name, stack_name, outputs):
+ private_ip = outputs[stack_name]
+ mac_addr = outputs[stack_name + "-mac_address"]
+ subnet_cidr_key = "-".join([self.name, network_name, 'subnet', 'cidr'])
+ gateway_key = "-".join([self.name, network_name, 'subnet', 'gateway_ip'])
+ subnet_cidr = outputs[subnet_cidr_key]
+ subnet_ip = ipaddress.ip_network(subnet_cidr)
+ return {
+ "private_ip": private_ip,
"subnet_id": outputs[stack_name + "-subnet_id"],
- "subnet_cidr": outputs[
- "{}-{}-subnet-cidr".format(self.name, network_name)],
- "netmask": str(ipaddress.ip_network(
- outputs["{}-{}-subnet-cidr".format(self.name,
- network_name)]).netmask),
- "gateway_ip": outputs[
- "{}-{}-subnet-gateway_ip".format(self.name, network_name)],
- "mac_address": outputs[stack_name + "-mac_address"],
+ "subnet_cidr": subnet_cidr,
+ "network": str(subnet_ip.network_address),
+ "netmask": str(subnet_ip.netmask),
+ "gateway_ip": outputs[gateway_key],
+ "mac_address": mac_addr,
"device_id": outputs[stack_name + "-device_id"],
"network_id": outputs[stack_name + "-network_id"],
"network_name": network_name,
# to match vnf_generic
- "local_mac": outputs[stack_name + "-mac_address"],
- "local_ip": outputs[stack_name],
+ "local_mac": mac_addr,
+ "local_ip": private_ip,
"vld_id": self.networks[network_name].vld_id,
}
@@ -326,6 +350,19 @@ class HeatContext(Context):
super(HeatContext, self).undeploy()
+ @staticmethod
+ def generate_routing_table(server):
+ routes = [
+ {
+ "network": intf["network"],
+ "netmask": intf["netmask"],
+ "if": name,
+ "gateway": intf["gateway_ip"],
+ }
+ for name, intf in server.interfaces.items()
+ ]
+ return routes
+
def _get_server(self, attr_name):
"""lookup server info by name from context
attr_name: either a name for a server created by yardstick or a dict
@@ -335,7 +372,10 @@ class HeatContext(Context):
'yardstick.resources',
'files/yardstick_key-' + get_short_key_uuid(self.key_uuid))
- if isinstance(attr_name, collections.Mapping):
+ if not isinstance(attr_name, collections.Mapping):
+ server = self._server_map.get(attr_name, None)
+
+ else:
cname = attr_name["name"].split(".")[1]
if cname != self.name:
return None
@@ -352,10 +392,6 @@ class HeatContext(Context):
server = Server(attr_name["name"].split(".")[0], self, {})
server.public_ip = public_ip
server.private_ip = private_ip
- else:
- if attr_name not in self._server_map:
- return None
- server = self._server_map[attr_name]
if server is None:
return None
@@ -365,9 +401,37 @@ class HeatContext(Context):
"key_filename": key_filename,
"private_ip": server.private_ip,
"interfaces": server.interfaces,
+ "routing_table": self.generate_routing_table(server),
+ # empty IPv6 routing table
+ "nd_route_tbl": [],
}
# Target server may only have private_ip
if server.public_ip:
result["ip"] = server.public_ip
return result
+
+ def _get_network(self, attr_name):
+ if not isinstance(attr_name, collections.Mapping):
+ network = self.networks.get(attr_name, None)
+
+ else:
+ # Don't generalize too much Just support vld_id
+ vld_id = attr_name.get('vld_id')
+ if vld_id is None:
+ return None
+
+ network = next((n for n in self.networks.values() if
+ getattr(n, "vld_id", None) == vld_id), None)
+
+ if network is None:
+ return None
+
+ result = {
+ "name": network.name,
+ "vld_id": network.vld_id,
+ "segmentation_id": network.segmentation_id,
+ "network_type": network.network_type,
+ "physical_network": network.physical_network,
+ }
+ return result
diff --git a/yardstick/benchmark/contexts/kubernetes.py b/yardstick/benchmark/contexts/kubernetes.py
new file mode 100644
index 000000000..a39f63137
--- /dev/null
+++ b/yardstick/benchmark/contexts/kubernetes.py
@@ -0,0 +1,140 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+from __future__ import absolute_import
+import logging
+import time
+import pkg_resources
+
+import paramiko
+
+from yardstick.benchmark.contexts.base import Context
+from yardstick.orchestrator.kubernetes import KubernetesTemplate
+from yardstick.common import kubernetes_utils as k8s_utils
+from yardstick.common import utils
+
+LOG = logging.getLogger(__name__)
+BITS_LENGTH = 2048
+
+
+class KubernetesContext(Context):
+ """Class that handle nodes info"""
+
+ __context_type__ = "Kubernetes"
+
+ def __init__(self):
+ self.name = ''
+ self.ssh_key = ''
+ self.key_path = ''
+ self.public_key_path = ''
+ self.template = None
+
+ super(KubernetesContext, self).__init__()
+
+ def init(self, attrs):
+ self.name = attrs.get('name', '')
+
+ template_cfg = attrs.get('servers', {})
+ self.template = KubernetesTemplate(self.name, template_cfg)
+
+ self.ssh_key = '{}-key'.format(self.name)
+
+ self.key_path = self._get_key_path()
+ self.public_key_path = '{}.pub'.format(self.key_path)
+
+ def deploy(self):
+ LOG.info('Creating ssh key')
+ self._set_ssh_key()
+
+ LOG.info('Launch containers')
+ self._create_rcs()
+ time.sleep(1)
+ self.template.get_rc_pods()
+
+ self._wait_until_running()
+
+ def undeploy(self):
+ self._delete_ssh_key()
+ self._delete_rcs()
+ self._delete_pods()
+
+ super(KubernetesContext, self).undeploy()
+
+ def _wait_until_running(self):
+ while not all(self._check_pod_status(p) for p in self.template.pods):
+ time.sleep(1)
+
+ def _check_pod_status(self, pod):
+ status = k8s_utils.read_pod_status(pod)
+ LOG.debug('%s:%s', pod, status)
+ if status == 'Failed':
+ LOG.error('Pod %s status is failed', pod)
+ raise RuntimeError
+ if status != 'Running':
+ return False
+ return True
+
+ def _create_rcs(self):
+ for obj in self.template.k8s_objs:
+ self._create_rc(obj.get_template())
+
+ def _create_rc(self, template):
+ k8s_utils.create_replication_controller(template)
+
+ def _delete_rcs(self):
+ for rc in self.template.rcs:
+ self._delete_rc(rc)
+
+ def _delete_rc(self, rc):
+ k8s_utils.delete_replication_controller(rc)
+
+ def _delete_pods(self):
+ for pod in self.template.pods:
+ self._delete_pod(pod)
+
+ def _delete_pod(self, pod):
+ k8s_utils.delete_pod(pod)
+
+ def _get_key_path(self):
+ task_id = self.name.split('-')[-1]
+ k = 'files/yardstick_key-{}'.format(task_id)
+ return pkg_resources.resource_filename('yardstick.resources', k)
+
+ def _set_ssh_key(self):
+ rsa_key = paramiko.RSAKey.generate(bits=BITS_LENGTH)
+
+ LOG.info('Writing private key')
+ rsa_key.write_private_key_file(self.key_path)
+
+ LOG.info('Writing public key')
+ key = '{} {}\n'.format(rsa_key.get_name(), rsa_key.get_base64())
+ with open(self.public_key_path, 'w') as f:
+ f.write(key)
+
+ LOG.info('Create configmap for ssh key')
+ k8s_utils.create_config_map(self.ssh_key, {'authorized_keys': key})
+
+ def _delete_ssh_key(self):
+ k8s_utils.delete_config_map(self.ssh_key)
+ utils.remove_file(self.key_path)
+ utils.remove_file(self.public_key_path)
+
+ def _get_server(self, name):
+ resp = k8s_utils.get_pod_list()
+ hosts = ({'name': n.metadata.name,
+ 'ip': n.status.pod_ip,
+ 'user': 'root',
+ 'key_filename': self.key_path,
+ 'private_ip': n.status.pod_ip}
+ for n in resp.items if n.metadata.name.startswith(name))
+
+ return next(hosts, None)
+
+ def _get_network(self, attr_name):
+ return None
diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py
index 5077a9786..ec474321f 100644
--- a/yardstick/benchmark/contexts/model.py
+++ b/yardstick/benchmark/contexts/model.py
@@ -104,15 +104,29 @@ class Network(Object):
self.stack_name = context.name + "-" + self.name
self.subnet_stack_name = self.stack_name + "-subnet"
self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
+ self.enable_dhcp = attrs.get('enable_dhcp', 'true')
self.router = None
self.physical_network = attrs.get('physical_network', 'physnet1')
- self.provider = attrs.get('provider', None)
- self.segmentation_id = attrs.get('segmentation_id', None)
+ 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')
+ self.allowed_address_pairs = attrs.get('allowed_address_pairs', [])
+ try:
+ # we require 'null' or '' to disable setting gateway_ip
+ self.gateway_ip = attrs['gateway_ip']
+ except KeyError:
+ # default to explicit None
+ self.gateway_ip = None
+ else:
+ # null is None in YAML, so we have to convert back to string
+ if self.gateway_ip is None:
+ self.gateway_ip = "null"
if "external_network" in attrs:
self.router = Router("router", self.name,
context, attrs["external_network"])
- self.vld_id = attrs.get("vld_id", "")
+ self.vld_id = attrs.get("vld_id")
Network.list.append(self)
@@ -233,10 +247,16 @@ class Server(Object): # pragma: no cover
for network in networks:
port_name = server_name + "-" + network.name + "-port"
self.ports[network.name] = {"stack_name": port_name}
- template.add_port(port_name, network.stack_name,
- network.subnet_stack_name,
- sec_group_id=self.secgroup_name,
- provider=network.provider)
+ # we can't use secgroups if port_security_enabled is False
+ if network.port_security_enabled:
+ sec_group_id = self.secgroup_name
+ else:
+ sec_group_id = None
+ # 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,
+ allowed_address_pairs=network.allowed_address_pairs)
port_name_list.append(port_name)
if self.floating_ip:
@@ -247,7 +267,7 @@ class Server(Object): # pragma: no cover
external_network,
port_name,
network.router.stack_if_name,
- self.secgroup_name)
+ sec_group_id)
self.floating_ip_assoc["stack_name"] = \
server_name + "-fip-assoc"
template.add_floating_ip_association(
diff --git a/yardstick/benchmark/contexts/node.py b/yardstick/benchmark/contexts/node.py
index baa1cf5d6..b3f0aca0e 100644
--- a/yardstick/benchmark/contexts/node.py
+++ b/yardstick/benchmark/contexts/node.py
@@ -33,6 +33,7 @@ class NodeContext(Context):
self.name = None
self.file_path = None
self.nodes = []
+ self.networks = {}
self.controllers = []
self.computes = []
self.baremetals = []
@@ -77,6 +78,9 @@ class NodeContext(Context):
self.env = attrs.get('env', {})
LOG.debug("Env: %r", self.env)
+ # add optional static network definition
+ self.networks.update(cfg.get("networks", {}))
+
def deploy(self):
config_type = self.env.get('type', '')
if config_type == 'ansible':
@@ -141,6 +145,32 @@ class NodeContext(Context):
node["name"] = attr_name
return node
+ def _get_network(self, attr_name):
+ if not isinstance(attr_name, collections.Mapping):
+ network = self.networks.get(attr_name)
+
+ else:
+ # Don't generalize too much Just support vld_id
+ vld_id = attr_name.get('vld_id')
+ if vld_id is None:
+ return None
+
+ network = next((n for n in self.networks.values() if
+ n.get("vld_id") == vld_id), None)
+
+ if network is None:
+ return None
+
+ result = {
+ # name is required
+ "name": network["name"],
+ "vld_id": network.get("vld_id"),
+ "segmentation_id": network.get("segmentation_id"),
+ "network_type": network.get("network_type"),
+ "physical_network": network.get("physical_network"),
+ }
+ return result
+
def _execute_script(self, node_name, info):
if node_name == 'local':
self._execute_local_script(info)
diff --git a/yardstick/benchmark/contexts/ovsdpdk.py b/yardstick/benchmark/contexts/ovsdpdk.py
new file mode 100644
index 000000000..cf5529d89
--- /dev/null
+++ b/yardstick/benchmark/contexts/ovsdpdk.py
@@ -0,0 +1,369 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os
+import yaml
+import time
+import glob
+import itertools
+import logging
+from yardstick import ssh
+from yardstick.benchmark.contexts.standalone import StandaloneContext
+
+BIN_PATH = "/opt/isb_bin/"
+DPDK_NIC_BIND = "dpdk_nic_bind.py"
+
+log = logging.getLogger(__name__)
+
+VM_TEMPLATE = """
+<domain type='kvm'>
+ <name>vm1</name>
+ <uuid>18230c0c-635d-4c50-b2dc-a213d30acb34</uuid>
+ <memory unit='KiB'>20971520</memory>
+ <currentMemory unit="KiB">20971520</currentMemory>
+ <memoryBacking>
+ <hugepages/>
+ </memoryBacking>
+ <vcpu placement='static'>20</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <features>
+ <acpi/>
+ <apic/>
+ </features>
+ <cpu match="exact" mode='host-model'>
+ <model fallback='allow'/>
+ <topology sockets='1' cores='10' threads='2'/>
+ </cpu>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='qcow2' cache='none'/>
+ <source file="{vm_image}"/>
+ <target dev='vda' bus='virtio'/>
+ <address bus="0x00" domain="0x0000"
+ function="0x0" slot="0x04" type="pci" />
+ </disk>
+ <!--disk type='dir' device='disk'>
+ <driver name='qemu' type='fat'/>
+ <source dir='/opt/isb_bin/dpdk'/>
+ <target dev='vdb' bus='virtio'/>
+ <readonly/>
+ </disk-->
+ <interface type="bridge">
+ <mac address="00:00:00:ab:cd:ef" />
+ <source bridge="br-int" />
+ </interface>
+ <interface type='vhostuser'>
+ <mac address='00:00:00:00:00:01'/>
+ <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser0' mode='client'/>
+ <model type='virtio'/>
+ <driver queues='4'>
+ <host mrg_rxbuf='off'/>
+ </driver>
+ </interface>
+ <interface type='vhostuser'>
+ <mac address='00:00:00:00:00:02'/>
+ <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser1' mode='client'/>
+ <model type='virtio'/>
+ <driver queues='4'>
+ <host mrg_rxbuf='off'/>
+ </driver>
+ </interface>
+ <serial type='pty'>
+ <target port='0'/>
+ </serial>
+ <console type='pty'>
+ <target type='serial' port='0'/>
+ </console>
+ <graphics autoport="yes" listen="0.0.0.0" port="1" type="vnc" />
+ </devices>
+</domain>
+"""
+
+
+class Ovsdpdk(StandaloneContext):
+ def __init__(self):
+ self.name = None
+ self.file_path = None
+ self.nodes = []
+ self.vm_deploy = False
+ self.ovs = []
+ self.first_run = True
+ self.dpdk_nic_bind = BIN_PATH + DPDK_NIC_BIND
+ self.user = ""
+ self.ssh_ip = ""
+ self.passwd = ""
+ self.ssh_port = ""
+ self.auth_type = ""
+
+ def init(self):
+ '''initializes itself'''
+ log.debug("In init")
+ self.parse_pod_and_get_data()
+
+ def parse_pod_and_get_data(self, file_path):
+ self.file_path = file_path
+ print("parsing pod file: {0}".format(self.file_path))
+ try:
+ with open(self.file_path) as stream:
+ cfg = yaml.load(stream)
+ except IOError:
+ print("File {0} does not exist".format(self.file_path))
+ raise
+
+ self.ovs.extend([node for node in cfg["nodes"]
+ if node["role"] == "Ovsdpdk"])
+ self.user = self.ovs[0]['user']
+ self.ssh_ip = self.ovs[0]['ip']
+ if self.ovs[0]['auth_type'] == "password":
+ self.passwd = self.ovs[0]['password']
+ else:
+ self.ssh_port = self.ovs[0]['ssh_port']
+ self.key_filename = self.ovs[0]['key_filename']
+
+ def ssh_remote_machine(self):
+ if self.ovs[0]['auth_type'] == "password":
+ self.connection = ssh.SSH(
+ self.user,
+ self.ssh_ip,
+ password=self.passwd)
+ self.connection.wait()
+ else:
+ if self.ssh_port is not None:
+ ssh_port = self.ssh_port
+ else:
+ ssh_port = ssh.DEFAULT_PORT
+ self.connection = ssh.SSH(
+ self.user,
+ self.ssh_ip,
+ port=ssh_port,
+ key_filename=self.key_filename)
+ self.connection.wait()
+
+ def get_nic_details(self):
+ nic_details = {}
+ nic_details['interface'] = {}
+ nic_details['pci'] = self.ovs[0]['phy_ports']
+ nic_details['phy_driver'] = self.ovs[0]['phy_driver']
+ nic_details['vports_mac'] = self.ovs[0]['vports_mac']
+ # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe
+ for i, _ in enumerate(nic_details['pci']):
+ err, out, _ = self.connection.execute(
+ "{dpdk_nic_bind} --force -b {driver} {port}".format(
+ dpdk_nic_bind=self.dpdk_nic_bind,
+ driver=self.ovs[0]['phy_driver'],
+ port=self.ovs[0]['phy_ports'][i]))
+ err, out, _ = self.connection.execute(
+ "lshw -c network -businfo | grep '{port}'".format(
+ port=self.ovs[0]['phy_ports'][i]))
+ a = out.split()[1]
+ err, out, _ = self.connection.execute(
+ "ip -s link show {interface}".format(
+ interface=out.split()[1]))
+ nic_details['interface'][i] = str(a)
+ print("{0}".format(nic_details))
+ return nic_details
+
+ def install_req_libs(self):
+ if self.first_run:
+ err, out, _ = self.connection.execute("apt-get update")
+ print("{0}".format(out))
+ err, out, _ = self.connection.execute(
+ "apt-get -y install qemu-kvm libvirt-bin")
+ print("{0}".format(out))
+ err, out, _ = self.connection.execute(
+ "apt-get -y install libvirt-dev bridge-utils numactl")
+ print("{0}".format(out))
+ self.first_run = False
+
+ def setup_ovs(self, vpcis):
+ self.connection.execute("/usr/bin/chmod 0666 /dev/vfio/*")
+ self.connection.execute("/usr/bin/chmod a+x /dev/vfio")
+ self.connection.execute("pkill -9 ovs")
+ self.connection.execute("ps -ef | grep ovs | grep -v grep | "
+ "awk '{print $2}' | xargs -r kill -9")
+ self.connection.execute("killall -r 'ovs*'")
+ self.connection.execute(
+ "mkdir -p {0}/etc/openvswitch".format(self.ovs[0]["vpath"]))
+ self.connection.execute(
+ "mkdir -p {0}/var/run/openvswitch".format(self.ovs[0]["vpath"]))
+ self.connection.execute(
+ "rm {0}/etc/openvswitch/conf.db".format(self.ovs[0]["vpath"]))
+ self.connection.execute(
+ "ovsdb-tool create {0}/etc/openvswitch/conf.db "
+ "{0}/share/openvswitch/"
+ "vswitch.ovsschema".format(self.ovs[0]["vpath"]))
+ self.connection.execute("modprobe vfio-pci")
+ self.connection.execute("chmod a+x /dev/vfio")
+ self.connection.execute("chmod 0666 /dev/vfio/*")
+ for vpci in vpcis:
+ self.connection.execute(
+ "/opt/isb_bin/dpdk_nic_bind.py "
+ "--bind=vfio-pci {0}".format(vpci))
+
+ def start_ovs_serverswitch(self):
+ self.connection.execute("mkdir -p /usr/local/var/run/openvswitch")
+ self.connection.execute(
+ "ovsdb-server --remote=punix:"
+ "/usr/local/var/run/openvswitch/db.sock --pidfile --detach")
+ self.connection.execute(
+ "ovs-vsctl --no-wait set "
+ "Open_vSwitch . other_config:dpdk-init=true")
+ self.connection.execute(
+ "ovs-vsctl --no-wait set "
+ "Open_vSwitch . other_config:dpdk-lcore-mask=0x3")
+ self.connection.execute(
+ "ovs-vsctl --no-wait set "
+ "Open_vSwitch . other_config:dpdk-socket-mem='2048,0'")
+ self.connection.execute(
+ "ovs-vswitchd unix:{0}/"
+ "var/run/openvswitch/db.sock --pidfile --detach "
+ "--log-file=/var/log/openvswitch/"
+ "ovs-vswitchd.log".format(
+ self.ovs[0]["vpath"]))
+ self.connection.execute(
+ "ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=2C")
+
+ def setup_ovs_bridge(self):
+ self.connection.execute("ovs-vsctl del-br br0")
+ self.connection.execute(
+ "rm -rf /usr/local/var/run/openvswitch/dpdkvhostuser*")
+ self.connection.execute(
+ "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev")
+ self.connection.execute(
+ "ovs-vsctl add-port br0 dpdk0 -- set Interface dpdk0 type=dpdk")
+ self.connection.execute(
+ "ovs-vsctl add-port br0 dpdk1 -- set Interface dpdk1 type=dpdk")
+ self.connection.execute(
+ "ovs-vsctl add-port br0 dpdkvhostuser0 -- set Interface "
+ "dpdkvhostuser0 type=dpdkvhostuser")
+ self.connection.execute("ovs-vsctl add-port br0 dpdkvhostuser1 "
+ "-- set Interface dpdkvhostuser1 "
+ "type=dpdkvhostuser")
+ self.connection.execute(
+ "chmod 0777 {0}/var/run/"
+ "openvswitch/dpdkvhostuser*".format(self.ovs[0]["vpath"]))
+
+ def add_oflows(self):
+ self.connection.execute("ovs-ofctl del-flows br0")
+ for flow in self.ovs[0]["flow"]:
+ self.connection.execute(flow)
+ self.connection.execute("ovs-ofctl dump-flows br0")
+ self.connection.execute(
+ "ovs-vsctl set Interface dpdk0 options:n_rxq=4")
+ self.connection.execute(
+ "ovs-vsctl set Interface dpdk1 options:n_rxq=4")
+
+ def setup_ovs_context(self, pcis, nic_details, host_driver):
+
+ ''' 1: Setup vm_ovs.xml to launch VM.'''
+ cfg_ovs = '/tmp/vm_ovs.xml'
+ vm_ovs_xml = VM_TEMPLATE.format(vm_image=self.ovs[0]["images"])
+ with open(cfg_ovs, 'w') as f:
+ f.write(vm_ovs_xml)
+
+ ''' 2: Create and start the VM'''
+ self.connection.put(cfg_ovs, cfg_ovs)
+ time.sleep(10)
+ err, out = self.check_output("virsh list --name | grep -i vm1")
+ if out == "vm1":
+ print("VM is already present")
+ else:
+ ''' FIXME: launch through libvirt'''
+ print("virsh create ...")
+ err, out, _ = self.connection.execute(
+ "virsh create /tmp/vm_ovs.xml")
+ time.sleep(10)
+ print("err : {0}".format(err))
+ print("{0}".format(_))
+ print("out : {0}".format(out))
+
+ ''' 3: Tuning for better performace.'''
+ self.pin_vcpu(pcis)
+ self.connection.execute(
+ "echo 1 > /sys/module/kvm/parameters/"
+ "allow_unsafe_assigned_interrupts")
+ self.connection.execute(
+ "echo never > /sys/kernel/mm/transparent_hugepage/enabled")
+ print("After tuning performance ...")
+
+ ''' This is roughly compatible with check_output function in subprocess
+ module which is only available in python 2.7.'''
+ def check_output(self, cmd, stderr=None):
+ '''Run a command and capture its output'''
+ err, out, _ = self.connection.execute(cmd)
+ return err, out
+
+ def read_from_file(self, filename):
+ data = ""
+ with open(filename, 'r') as the_file:
+ data = the_file.read()
+ return data
+
+ def write_to_file(self, filename, content):
+ with open(filename, 'w') as the_file:
+ the_file.write(content)
+
+ def pin_vcpu(self, pcis):
+ nodes = self.get_numa_nodes()
+ print("{0}".format(nodes))
+ num_nodes = len(nodes)
+ for i in range(0, 10):
+ self.connection.execute(
+ "virsh vcpupin vm1 {0} {1}".format(
+ 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*")
+ nodes = {}
+ for node_sysfs in nodes_sysfs:
+ num = os.path.basename(node_sysfs).replace("node", "")
+ with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file:
+ cpulist = cpulist_file.read().strip()
+ print("cpulist: {0}".format(cpulist))
+ nodes[num] = self.split_cpu_list(cpulist)
+ print("nodes: {0}".format(nodes))
+ return nodes
+
+ def split_cpu_list(self, cpu_list):
+ if cpu_list:
+ ranges = cpu_list.split(',')
+ bounds = ([int(b) for b in r.split('-')] for r in ranges)
+ range_objects =\
+ (range(bound[0], bound[1] + 1 if len(bound) == 2
+ else bound[0] + 1) for bound in bounds)
+
+ return sorted(itertools.chain.from_iterable(range_objects))
+ else:
+ return []
+
+ def destroy_vm(self):
+ host_driver = self.ovs[0]['phy_driver']
+ err, out = self.check_output("virsh list --name | grep -i vm1")
+ print("{0}".format(out))
+ if err == 0:
+ self.connection.execute("virsh shutdown vm1")
+ self.connection.execute("virsh destroy vm1")
+ self.check_output("rmmod {0}".format(host_driver))[1].splitlines()
+ self.check_output("modprobe {0}".format(host_driver))[
+ 1].splitlines()
+ else:
+ print("error : ", err)
diff --git a/yardstick/benchmark/contexts/sriov.py b/yardstick/benchmark/contexts/sriov.py
new file mode 100644
index 000000000..fe27d2579
--- /dev/null
+++ b/yardstick/benchmark/contexts/sriov.py
@@ -0,0 +1,431 @@
+# Copyright (c) 2016-2017 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os
+import yaml
+import re
+import time
+import glob
+import uuid
+import random
+import logging
+import itertools
+import xml.etree.ElementTree as ET
+from yardstick import ssh
+from yardstick.network_services.utils import get_nsb_option
+from yardstick.network_services.utils import provision_tool
+from yardstick.benchmark.contexts.standalone import StandaloneContext
+
+log = logging.getLogger(__name__)
+
+VM_TEMPLATE = """
+<domain type="kvm">
+ <name>vm1</name>
+ <uuid>{random_uuid}</uuid>
+ <memory unit="KiB">102400</memory>
+ <currentMemory unit="KiB">102400</currentMemory>
+ <memoryBacking>
+ <hugepages />
+ </memoryBacking>
+ <vcpu placement="static">20</vcpu>
+ <os>
+ <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type>
+ <boot dev="hd" />
+ </os>
+ <features>
+ <acpi />
+ <apic />
+ <pae />
+ </features>
+ <cpu match="exact" mode="custom">
+ <model fallback="allow">SandyBridge</model>
+ <topology cores="10" sockets="1" threads="2" />
+ </cpu>
+ <clock offset="utc">
+ <timer name="rtc" tickpolicy="catchup" />
+ <timer name="pit" tickpolicy="delay" />
+ <timer name="hpet" present="no" />
+ </clock>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>restart</on_crash>
+ <devices>
+ <emulator>/usr/bin/kvm-spice</emulator>
+ <disk device="disk" type="file">
+ <driver name="qemu" type="qcow2" />
+ <source file="{vm_image}"/>
+ <target bus="virtio" dev="vda" />
+ <address bus="0x00" domain="0x0000"
+function="0x0" slot="0x04" type="pci" />
+ </disk>
+ <controller index="0" model="ich9-ehci1" type="usb">
+ <address bus="0x00" domain="0x0000"
+function="0x7" slot="0x05" type="pci" />
+ </controller>
+ <controller index="0" model="ich9-uhci1" type="usb">
+ <master startport="0" />
+ <address bus="0x00" domain="0x0000" function="0x0"
+multifunction="on" slot="0x05" type="pci" />
+ </controller>
+ <controller index="0" model="ich9-uhci2" type="usb">
+ <master startport="2" />
+ <address bus="0x00" domain="0x0000"
+function="0x1" slot="0x05" type="pci" />
+ </controller>
+ <controller index="0" model="ich9-uhci3" type="usb">
+ <master startport="4" />
+ <address bus="0x00" domain="0x0000"
+function="0x2" slot="0x05" type="pci" />
+ </controller>
+ <controller index="0" model="pci-root" type="pci" />
+ <serial type="pty">
+ <target port="0" />
+ </serial>
+ <console type="pty">
+ <target port="0" type="serial" />
+ </console>
+ <input bus="usb" type="tablet" />
+ <input bus="ps2" type="mouse" />
+ <input bus="ps2" type="keyboard" />
+ <graphics autoport="yes" listen="0.0.0.0" port="-1" type="vnc" />
+ <video>
+ <model heads="1" type="cirrus" vram="16384" />
+ <address bus="0x00" domain="0x0000"
+function="0x0" slot="0x02" type="pci" />
+ </video>
+ <memballoon model="virtio">
+ <address bus="0x00" domain="0x0000"
+function="0x0" slot="0x06" type="pci" />
+ </memballoon>
+ <interface type="bridge">
+ <mac address="{mac_addr}" />
+ <source bridge="virbr0" />
+ </interface>
+ </devices>
+</domain>
+"""
+
+
+class Sriov(StandaloneContext):
+ def __init__(self):
+ self.name = None
+ self.file_path = None
+ self.nodes = []
+ self.vm_deploy = False
+ self.sriov = []
+ self.first_run = True
+ self.dpdk_nic_bind = ""
+ self.user = ""
+ self.ssh_ip = ""
+ self.passwd = ""
+ self.ssh_port = ""
+ self.auth_type = ""
+
+ def init(self):
+ log.debug("In init")
+ self.parse_pod_and_get_data(self.file_path)
+
+ def parse_pod_and_get_data(self, file_path):
+ self.file_path = file_path
+ log.debug("parsing pod file: {0}".format(self.file_path))
+ try:
+ with open(self.file_path) as stream:
+ cfg = yaml.load(stream)
+ except IOError:
+ log.error("File {0} does not exist".format(self.file_path))
+ raise
+
+ self.sriov.extend([node for node in cfg["nodes"]
+ if node["role"] == "Sriov"])
+ self.user = self.sriov[0]['user']
+ self.ssh_ip = self.sriov[0]['ip']
+ if self.sriov[0]['auth_type'] == "password":
+ self.passwd = self.sriov[0]['password']
+ else:
+ self.ssh_port = self.sriov[0]['ssh_port']
+ self.key_filename = self.sriov[0]['key_filename']
+
+ def ssh_remote_machine(self):
+ if self.sriov[0]['auth_type'] == "password":
+ self.connection = ssh.SSH(
+ self.user,
+ self.ssh_ip,
+ password=self.passwd)
+ self.connection.wait()
+ else:
+ if self.ssh_port is not None:
+ ssh_port = self.ssh_port
+ else:
+ ssh_port = ssh.DEFAULT_PORT
+ self.connection = ssh.SSH(
+ self.user,
+ self.ssh_ip,
+ port=ssh_port,
+ key_filename=self.key_filename)
+ self.connection.wait()
+ self.dpdk_nic_bind = provision_tool(
+ self.connection,
+ os.path.join(get_nsb_option("bin_path"), "dpdk_nic_bind.py"))
+
+ def get_nic_details(self):
+ nic_details = {}
+ nic_details = {
+ 'interface': {},
+ 'pci': self.sriov[0]['phy_ports'],
+ 'phy_driver': self.sriov[0]['phy_driver'],
+ 'vf_macs': self.sriov[0]['vf_macs']
+ }
+ # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe
+ for i, _ in enumerate(nic_details['pci']):
+ err, out, _ = self.connection.execute(
+ "{dpdk_nic_bind} --force -b {driver} {port}".format(
+ dpdk_nic_bind=self.dpdk_nic_bind,
+ driver=self.sriov[0]['phy_driver'],
+ port=self.sriov[0]['phy_ports'][i]))
+ err, out, _ = self.connection.execute(
+ "lshw -c network -businfo | grep '{port}'".format(
+ port=self.sriov[0]['phy_ports'][i]))
+ a = out.split()[1]
+ err, out, _ = self.connection.execute(
+ "ip -s link show {interface}".format(
+ interface=out.split()[1]))
+ nic_details['interface'][i] = str(a)
+ log.info("{0}".format(nic_details))
+ return nic_details
+
+ def install_req_libs(self):
+ if self.first_run:
+ log.info("Installing required libraries...")
+ err, out, _ = self.connection.execute("apt-get update")
+ log.debug("{0}".format(out))
+ err, out, _ = self.connection.execute(
+ "apt-get -y install qemu-kvm libvirt-bin")
+ log.debug("{0}".format(out))
+ err, out, _ = self.connection.execute(
+ "apt-get -y install libvirt-dev bridge-utils numactl")
+ log.debug("{0}".format(out))
+ self.first_run = False
+
+ def configure_nics_for_sriov(self, host_driver, nic_details):
+ vf_pci = [[], []]
+ self.connection.execute(
+ "rmmod {0}".format(host_driver))[1].splitlines()
+ self.connection.execute(
+ "modprobe {0} num_vfs=1".format(host_driver))[1].splitlines()
+ nic_details['vf_pci'] = {}
+ for i in range(len(nic_details['pci'])):
+ self.connection.execute(
+ "echo 1 > /sys/bus/pci/devices/{0}/sriov_numvfs".format(
+ nic_details['pci'][i]))
+ err, out, _ = self.connection.execute(
+ "ip link set {interface} vf 0 mac {mac}".format(
+ interface=nic_details['interface'][i],
+ mac=nic_details['vf_macs'][i]))
+ time.sleep(3)
+ vf_pci[i] = self.get_vf_datas(
+ 'vf_pci',
+ nic_details['pci'][i],
+ nic_details['vf_macs'][i])
+ nic_details['vf_pci'][i] = vf_pci[i]
+ log.debug("NIC DETAILS : {0}".format(nic_details))
+ return nic_details
+
+ def setup_sriov_context(self, pcis, nic_details, host_driver):
+ blacklist = "/etc/modprobe.d/blacklist.conf"
+
+ # 1 : Blacklist the vf driver in /etc/modprobe.d/blacklist.conf
+ vfnic = "{0}vf".format(host_driver)
+ lines = self.read_from_file(blacklist)
+ if vfnic not in lines:
+ vfblacklist = "blacklist {vfnic}".format(vfnic=vfnic)
+ self.connection.execute(
+ "echo {vfblacklist} >> {blacklist}".format(
+ vfblacklist=vfblacklist,
+ blacklist=blacklist))
+
+ # 2 : modprobe host_driver with num_vfs
+ nic_details = self.configure_nics_for_sriov(host_driver, nic_details)
+
+ # 3: Setup vm_sriov.xml to launch VM
+ cfg_sriov = '/tmp/vm_sriov.xml'
+ mac = [0x00, 0x24, 0x81,
+ random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)]
+ mac_address = ':'.join(map(lambda x: "%02x" % x, mac))
+ vm_sriov_xml = VM_TEMPLATE.format(
+ random_uuid=uuid.uuid4(),
+ mac_addr=mac_address,
+ vm_image=self.sriov[0]["images"])
+ with open(cfg_sriov, 'w') as f:
+ f.write(vm_sriov_xml)
+
+ vf = nic_details['vf_pci']
+ for index in range(len(nic_details['vf_pci'])):
+ self.add_sriov_interface(
+ index,
+ vf[index]['vf_pci'],
+ mac_address,
+ "/tmp/vm_sriov.xml")
+ self.connection.execute(
+ "ifconfig {interface} up".format(
+ interface=nic_details['interface'][index]))
+
+ # 4: Create and start the VM
+ self.connection.put(cfg_sriov, cfg_sriov)
+ time.sleep(10)
+ err, out = self.check_output("virsh list --name | grep -i vm1")
+ try:
+ if out == "vm1":
+ log.info("VM is already present")
+ else:
+ # FIXME: launch through libvirt
+ log.info("virsh create ...")
+ err, out, _ = self.connection.execute(
+ "virsh create /tmp/vm_sriov.xml")
+ time.sleep(10)
+ log.error("err : {0}".format(err))
+ log.error("{0}".format(_))
+ log.debug("out : {0}".format(out))
+ except ValueError:
+ raise
+
+ # 5: Tunning for better performace
+ self.pin_vcpu(pcis)
+ self.connection.execute(
+ "echo 1 > /sys/module/kvm/parameters/"
+ "allow_unsafe_assigned_interrupts")
+ self.connection.execute(
+ "echo never > /sys/kernel/mm/transparent_hugepage/enabled")
+
+ def add_sriov_interface(self, index, vf_pci, vfmac, xml):
+ root = ET.parse(xml)
+ pattern = "0000:(\d+):(\d+).(\d+)"
+ m = re.search(pattern, vf_pci, re.MULTILINE)
+ device = root.find('devices')
+
+ interface = ET.SubElement(device, 'interface')
+ interface.set('managed', 'yes')
+ interface.set('type', 'hostdev')
+
+ mac = ET.SubElement(interface, 'mac')
+ mac.set('address', vfmac)
+ source = ET.SubElement(interface, 'source')
+
+ addr = ET.SubElement(source, "address")
+ addr.set('domain', "0x0")
+ addr.set('bus', "{0}".format(m.group(1)))
+ addr.set('function', "{0}".format(m.group(3)))
+ addr.set('slot', "{0}".format(m.group(2)))
+ addr.set('type', "pci")
+
+ vf_pci = ET.SubElement(interface, 'address')
+ vf_pci.set('type', 'pci')
+ vf_pci.set('domain', '0x0000')
+ vf_pci.set('bus', '0x00')
+ vf_pci.set('slot', '0x0{0}'.format(index + 7))
+ vf_pci.set('function', '0x00')
+
+ root.write(xml)
+
+ # This is roughly compatible with check_output function in subprocess
+ # module which is only available in python 2.7
+ def check_output(self, cmd, stderr=None):
+ # Run a command and capture its output
+ err, out, _ = self.connection.execute(cmd)
+ return err, out
+
+ def get_virtual_devices(self, pci):
+ pf_vfs = {}
+ err, extra_info = self.check_output(
+ "cat /sys/bus/pci/devices/{0}/virtfn0/uevent".format(pci))
+ pattern = "PCI_SLOT_NAME=(?P<name>[0-9:.\s.]+)"
+ m = re.search(pattern, extra_info, re.MULTILINE)
+
+ if m:
+ pf_vfs.update({pci: str(m.group(1).rstrip())})
+ log.info("pf_vfs : {0}".format(pf_vfs))
+ return pf_vfs
+
+ def get_vf_datas(self, key, value, vfmac):
+ vfret = {}
+ pattern = "0000:(\d+):(\d+).(\d+)"
+
+ vfret["mac"] = vfmac
+ vfs = self.get_virtual_devices(value)
+ log.info("vfs: {0}".format(vfs))
+ for k, v in vfs.items():
+ m = re.search(pattern, k, re.MULTILINE)
+ m1 = re.search(pattern, value, re.MULTILINE)
+ if m.group(1) == m1.group(1):
+ vfret["vf_pci"] = str(v)
+ break
+
+ return vfret
+
+ def read_from_file(self, filename):
+ data = ""
+ with open(filename, 'r') as the_file:
+ data = the_file.read()
+ return data
+
+ def write_to_file(self, filename, content):
+ with open(filename, 'w') as the_file:
+ the_file.write(content)
+
+ def pin_vcpu(self, pcis):
+ nodes = self.get_numa_nodes()
+ log.info("{0}".format(nodes))
+ num_nodes = len(nodes)
+ for i in range(0, 10):
+ self.connection.execute(
+ "virsh vcpupin vm1 {0} {1}".format(
+ 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*")
+ nodes = {}
+ for node_sysfs in nodes_sysfs:
+ num = os.path.basename(node_sysfs).replace("node", "")
+ with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file:
+ cpulist = cpulist_file.read().strip()
+ nodes[num] = self.split_cpu_list(cpulist)
+ log.info("nodes: {0}".format(nodes))
+ return nodes
+
+ def split_cpu_list(self, cpu_list):
+ if cpu_list:
+ ranges = cpu_list.split(',')
+ bounds = ([int(b) for b in r.split('-')] for r in ranges)
+ range_objects =\
+ (range(bound[0], bound[1] + 1 if len(bound) == 2
+ else bound[0] + 1) for bound in bounds)
+
+ return sorted(itertools.chain.from_iterable(range_objects))
+ else:
+ return []
+
+ def destroy_vm(self):
+ host_driver = self.sriov[0]["phy_driver"]
+ err, out = self.check_output("virsh list --name | grep -i vm1")
+ log.info("{0}".format(out))
+ if err == 0:
+ self.connection.execute("virsh shutdown vm1")
+ self.connection.execute("virsh destroy vm1")
+ self.check_output("rmmod {0}".format(host_driver))[1].splitlines()
+ self.check_output("modprobe {0}".format(host_driver))[
+ 1].splitlines()
+ else:
+ log.error("error : {0}".format(err))
diff --git a/yardstick/benchmark/contexts/standalone.py b/yardstick/benchmark/contexts/standalone.py
index 78eaac7ee..2bc1f3755 100644
--- a/yardstick/benchmark/contexts/standalone.py
+++ b/yardstick/benchmark/contexts/standalone.py
@@ -18,9 +18,11 @@ import logging
import errno
import collections
import yaml
+import time
from yardstick.benchmark.contexts.base import Context
from yardstick.common.constants import YARDSTICK_ROOT_PATH
+from yardstick.common.utils import import_modules_from_package, itersubclasses
LOG = logging.getLogger(__name__)
@@ -36,8 +38,10 @@ class StandaloneContext(Context):
self.name = None
self.file_path = None
self.nodes = []
+ self.networks = {}
self.nfvi_node = []
- super(StandaloneContext, self).__init__()
+ self.nfvi_obj = None
+ super(self.__class__, self).__init__()
def read_config_file(self):
"""Read from config file"""
@@ -47,6 +51,14 @@ class StandaloneContext(Context):
cfg = yaml.load(stream)
return cfg
+ def get_nfvi_obj(self):
+ print("{0}".format(self.nfvi_node[0]['role']))
+ context_type = self.get_context_impl(self.nfvi_node[0]['role'])
+ nfvi_obj = context_type()
+ nfvi_obj.__init__()
+ nfvi_obj.parse_pod_and_get_data(self.file_path)
+ return nfvi_obj
+
def init(self, attrs):
"""initializes itself from the supplied arguments"""
@@ -63,23 +75,70 @@ class StandaloneContext(Context):
else:
raise
- self.nodes.extend(cfg["nodes"])
- self.nfvi_node.extend([node for node in cfg["nodes"]
- if node["role"] == "nfvi_node"])
+ self.vm_deploy = attrs.get("vm_deploy", True)
+ self.nodes.extend([node for node in cfg["nodes"]
+ if str(node["role"]) != "Sriov" and
+ str(node["role"]) != "Ovsdpdk"])
+ for node in cfg["nodes"]:
+ if str(node["role"]) == "Sriov":
+ self.nfvi_node.extend([node for node in cfg["nodes"]
+ if str(node["role"]) == "Sriov"])
+ if str(node["role"]) == "Ovsdpdk":
+ self.nfvi_node.extend([node for node in cfg["nodes"]
+ if str(node["role"]) == "Ovsdpdk"])
+ LOG.info("{0}".format(node["role"]))
+ else:
+ LOG.debug("Node role is other than SRIOV and OVS")
+ self.nfvi_obj = self.get_nfvi_obj()
+ # add optional static network definition
+ self.networks.update(cfg.get("networks", {}))
+ self.nfvi_obj = self.get_nfvi_obj()
LOG.debug("Nodes: %r", self.nodes)
LOG.debug("NFVi Node: %r", self.nfvi_node)
+ LOG.debug("Networks: %r", self.networks)
def deploy(self):
"""don't need to deploy"""
# Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
- pass
+ if not self.vm_deploy:
+ return
+
+ # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
+ self.nfvi_obj.ssh_remote_machine()
+ if self.nfvi_obj.first_run is True:
+ self.nfvi_obj.install_req_libs()
+
+ nic_details = self.nfvi_obj.get_nic_details()
+ print("{0}".format(nic_details))
+
+ if self.nfvi_node[0]["role"] == "Sriov":
+ self.nfvi_obj.setup_sriov_context(
+ self.nfvi_obj.sriov[0]['phy_ports'],
+ nic_details,
+ self.nfvi_obj.sriov[0]['phy_driver'])
+ if self.nfvi_node[0]["role"] == "Ovsdpdk":
+ self.nfvi_obj.setup_ovs(self.nfvi_obj.ovs[0]["phy_ports"])
+ self.nfvi_obj.start_ovs_serverswitch()
+ time.sleep(5)
+ self.nfvi_obj.setup_ovs_bridge()
+ self.nfvi_obj.add_oflows()
+ self.nfvi_obj.setup_ovs_context(
+ self.nfvi_obj.ovs[0]['phy_ports'],
+ nic_details,
+ self.nfvi_obj.ovs[0]['phy_driver'])
+ pass
def undeploy(self):
"""don't need to undeploy"""
+ if not self.vm_deploy:
+ return
# Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config.
- super(StandaloneContext, self).undeploy()
+ # self.nfvi_obj = self.get_nfvi_obj()
+ self.nfvi_obj.ssh_remote_machine()
+ self.nfvi_obj.destroy_vm()
+ pass
def _get_server(self, attr_name):
"""lookup server info by name from context
@@ -87,16 +146,12 @@ class StandaloneContext(Context):
Keyword arguments:
attr_name -- A name for a server listed in nodes config file
"""
-
if isinstance(attr_name, collections.Mapping):
return None
-
- if self.name.split("-")[0] != attr_name.split(".")[1]:
+ if self.name != attr_name.split(".")[1]:
return None
-
node_name = attr_name.split(".")[0]
matching_nodes = (n for n in self.nodes if n["name"] == node_name)
-
try:
# A clone is created in order to avoid affecting the
# original one.
@@ -111,6 +166,49 @@ class StandaloneContext(Context):
else:
raise ValueError("Duplicate nodes!!! Nodes: %s %s",
(matching_nodes, duplicate))
-
node["name"] = attr_name
return node
+
+ def _get_network(self, attr_name):
+ if not isinstance(attr_name, collections.Mapping):
+ network = self.networks.get(attr_name)
+
+ else:
+ # Don't generalize too much Just support vld_id
+ vld_id = attr_name.get('vld_id')
+ if vld_id is None:
+ return None
+ try:
+ network = next(n for n in self.networks.values() if
+ n.get("vld_id") == vld_id)
+ except StopIteration:
+ return None
+
+ if network is None:
+ return None
+
+ result = {
+ # name is required
+ "name": network["name"],
+ "vld_id": network.get("vld_id"),
+ "segmentation_id": network.get("segmentation_id"),
+ "network_type": network.get("network_type"),
+ "physical_network": network.get("physical_network"),
+ }
+ return result
+
+ def get_context_impl(self, nfvi_type):
+ """ Find the implementing class from vnf_model["vnf"]["name"] field
+
+ :param vnf_model: dictionary containing a parsed vnfd
+ :return: subclass of GenericVNF
+ """
+ import_modules_from_package(
+ "yardstick.benchmark.contexts")
+ expected_name = nfvi_type
+ impl = [c for c in itersubclasses(StandaloneContext)
+ if c.__name__ == expected_name]
+ try:
+ return next(iter(impl))
+ except StopIteration:
+ raise ValueError("No implementation for %s", expected_name)
diff --git a/yardstick/benchmark/core/plugin.py b/yardstick/benchmark/core/plugin.py
index 7f67a04b3..c8d0865d1 100644
--- a/yardstick/benchmark/core/plugin.py
+++ b/yardstick/benchmark/core/plugin.py
@@ -84,8 +84,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)
@@ -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 0e85e6316..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."""
@@ -58,12 +64,20 @@ class Task(object): # pragma: no cover
check_environment()
- output_config = utils.parse_ini_file(config_file)
+ try:
+ output_config = utils.parse_ini_file(config_file)
+ except Exception:
+ # all error will be ignore, the default value is {}
+ output_config = {}
+
self._init_output_config(output_config)
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)
@@ -193,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"""
@@ -322,6 +337,8 @@ class Task(object): # pragma: no cover
if "nodes" in scenario_cfg:
context_cfg["nodes"] = parse_nodes_with_context(scenario_cfg)
+ context_cfg["networks"] = get_networks_from_nodes(
+ context_cfg["nodes"])
runner = base_runner.Runner.get(runner_cfg)
print("Starting runner of type '%s'" % runner_cfg["type"])
@@ -518,7 +535,7 @@ class TaskParser(object): # pragma: no cover
cfg_schema))
def _check_precondition(self, cfg):
- """Check if the envrionment meet the preconditon"""
+ """Check if the environment meet the precondition"""
if "precondition" in cfg:
precondition = cfg["precondition"]
@@ -573,14 +590,26 @@ def _is_background_scenario(scenario):
def parse_nodes_with_context(scenario_cfg):
- """paras the 'nodes' fields in scenario """
+ """parse the 'nodes' fields in scenario """
nodes = scenario_cfg["nodes"]
-
- nodes_cfg = {}
- for nodename in nodes:
- nodes_cfg[nodename] = Context.get_server(nodes[nodename])
-
- return nodes_cfg
+ return {nodename: Context.get_server(node) for nodename, node in nodes.items()}
+
+
+def get_networks_from_nodes(nodes):
+ """parse the 'nodes' fields in scenario """
+ networks = {}
+ for node in nodes.values():
+ if not node:
+ continue
+ for interface in node['interfaces'].values():
+ vld_id = interface.get('vld_id')
+ # mgmt network doesn't have vld_id
+ if not vld_id:
+ continue
+ network = Context.get_network({"vld_id": vld_id})
+ if network:
+ networks[network['name']] = network
+ return networks
def runner_join(runner):
diff --git a/yardstick/benchmark/core/testsuite.py b/yardstick/benchmark/core/testsuite.py
new file mode 100644
index 000000000..e3940a0ba
--- /dev/null
+++ b/yardstick/benchmark/core/testsuite.py
@@ -0,0 +1,42 @@
+##############################################################################
+# 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
+##############################################################################
+
+""" Handler for yardstick command 'testcase' """
+from __future__ import absolute_import
+from __future__ import print_function
+
+import os
+import logging
+
+from yardstick.common import constants as consts
+
+LOG = logging.getLogger(__name__)
+
+
+class Testsuite(object):
+ """Testcase commands.
+
+ Set of commands to discover and display test cases.
+ """
+
+ def list_all(self, args):
+ """List existing test cases"""
+
+ testsuite_list = self._get_testsuite_file_list()
+
+ return testsuite_list
+
+ def _get_testsuite_file_list(self):
+ try:
+ testsuite_files = sorted(os.listdir(consts.TESTSUITE_DIR))
+ except OSError:
+ LOG.exception('Failed to list dir:\n%s\n', consts.TESTSUITE_DIR)
+ raise
+
+ return testsuite_files
diff --git a/yardstick/benchmark/scenarios/availability/actionplayers.py b/yardstick/benchmark/scenarios/availability/actionplayers.py
index 420626413..c5e199ba6 100644
--- a/yardstick/benchmark/scenarios/availability/actionplayers.py
+++ b/yardstick/benchmark/scenarios/availability/actionplayers.py
@@ -29,8 +29,10 @@ class AttackerPlayer(ActionPlayer):
class OperationPlayer(ActionPlayer):
- def __init__(self, operation):
+ def __init__(self, operation, intermediate_variables):
self.underlyingOperation = operation
+ self.underlyingOperation.intermediate_variables \
+ = intermediate_variables
def action(self):
self.underlyingOperation.run()
diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py
index 22de0b645..50d44c1ca 100644
--- a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py
+++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py
@@ -9,7 +9,6 @@
from __future__ import absolute_import
import logging
import subprocess
-import traceback
import yardstick.ssh as ssh
from yardstick.benchmark.scenarios.availability.attacker.baseattacker import \
@@ -26,9 +25,7 @@ def _execute_shell_command(command, stdin=None):
output = subprocess.check_output(command, stdin=stdin, shell=True)
except Exception:
exitcode = -1
- output = traceback.format_exc()
- LOG.error("exec command '%s' error:\n ", command)
- LOG.error(traceback.format_exc())
+ LOG.error("exec command '%s' error:\n ", command, exc_info=True)
return exitcode, output
diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py
index f7ab23dcd..cb171eafa 100644
--- a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py
+++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py
@@ -47,11 +47,11 @@ class ProcessAttacker(BaseAttacker):
stdin=stdin_file)
if stdout:
- LOG.info("check the envrioment success!")
+ LOG.info("check the environment success!")
return int(stdout.strip('\n'))
else:
LOG.error(
- "the host envrioment is error, stdout:%s, stderr:%s",
+ "the host environment is error, stdout:%s, stderr:%s",
stdout, stderr)
return False
diff --git a/yardstick/benchmark/scenarios/availability/attacker_conf.yaml b/yardstick/benchmark/scenarios/availability/attacker_conf.yaml
index b8c34ad44..aa144ab50 100644
--- a/yardstick/benchmark/scenarios/availability/attacker_conf.yaml
+++ b/yardstick/benchmark/scenarios/availability/attacker_conf.yaml
@@ -16,6 +16,11 @@ kill-process:
inject_script: ha_tools/fault_process_kill.bash
recovery_script: ha_tools/start_service.bash
+kill-lxc-process:
+ check_script: ha_tools/check_lxc_process_python.bash
+ inject_script: ha_tools/fault_lxc_process_kill.bash
+ recovery_script: ha_tools/start_lxc_service.bash
+
bare-metal-down:
check_script: ha_tools/check_host_ping.bash
recovery_script: ha_tools/ipmi_power.bash
@@ -34,4 +39,4 @@ stress-cpu:
block-io:
inject_script: ha_tools/disk/block_io.bash
- recovery_script: ha_tools/disk/recovery_disk_io.bash \ No newline at end of file
+ recovery_script: ha_tools/disk/recovery_disk_io.bash
diff --git a/yardstick/benchmark/scenarios/availability/director.py b/yardstick/benchmark/scenarios/availability/director.py
index e0d05ebf5..c9187c34d 100644
--- a/yardstick/benchmark/scenarios/availability/director.py
+++ b/yardstick/benchmark/scenarios/availability/director.py
@@ -65,7 +65,9 @@ class Director(object):
self.resultCheckerMgr = baseresultchecker.ResultCheckerMgr()
self.resultCheckerMgr.init_ResultChecker(result_check_cfgs, nodes)
- def createActionPlayer(self, type, key):
+ def createActionPlayer(self, type, key, intermediate_variables=None):
+ if intermediate_variables is None:
+ intermediate_variables = {}
LOG.debug(
"the type of current action is %s, the key is %s", type, key)
if type == ActionType.ATTACKER:
@@ -76,7 +78,8 @@ class Director(object):
return actionplayers.ResultCheckerPlayer(
self.resultCheckerMgr[key])
if type == ActionType.OPERATION:
- return actionplayers.OperationPlayer(self.operationMgr[key])
+ return actionplayers.OperationPlayer(self.operationMgr[key],
+ intermediate_variables)
LOG.debug("something run when creatactionplayer")
def createActionRollbacker(self, type, key):
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash b/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash
new file mode 100755
index 000000000..6d2f4dd51
--- /dev/null
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+##############################################################################
+# 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
+##############################################################################
+
+# check the status of a service
+
+set -e
+
+NOVA_API_PROCESS_1="nova-api-os-compute"
+NOVA_API_PROCESS_2="nova-api-metadata"
+NOVA_API_LXC_FILTER_1="nova_api_os_compute"
+NOVA_API_LXC_FILTER_2="nova_api_metadata"
+
+process_name=$1
+
+lxc_filter=$(echo "${process_name}" | sed 's/-/_/g')
+
+if [ "${lxc_filter}" = "glance_api" ]; then
+ lxc_filter="glance"
+fi
+
+if [ "${process_name}" = "nova-api" ]; then
+ container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}")
+ container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}")
+
+ echo $(($(lxc-attach -n "${container_1}" -- ps aux | grep -e "${NOVA_API_PROCESS_1}" | grep -v grep | grep -cv /bin/sh) + $(lxc-attach -n "${container_2}" -- ps aux | grep -e "${NOVA_API_PROCESS_2}" | grep -v grep | grep -cv /bin/sh)))
+else
+ container=$(lxc-ls -1 --filter="${lxc_filter}")
+
+ if [ "${process_name}" = "haproxy" ]; then
+ ps aux | grep -e "/usr/.*/${process_name}" | grep -v grep | grep -cv /bin/sh
+ else
+ lxc-attach -n "${container}" -- ps aux | grep -e "${process_name}" | grep -v grep | grep -cv /bin/sh
+ fi
+fi
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash b/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash
new file mode 100755
index 000000000..b0b86ab65
--- /dev/null
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+##############################################################################
+# 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
+##############################################################################
+
+# Stop process by process name
+
+set -e
+
+NOVA_API_PROCESS_1="nova-api-os-compute"
+NOVA_API_PROCESS_2="nova-api-metadata"
+NOVA_API_LXC_FILTER_1="nova_api_os_compute"
+NOVA_API_LXC_FILTER_2="nova_api_metadata"
+
+process_name=$1
+
+lxc_filter=$(echo "${process_name}" | sed 's/-/_/g')
+
+if [ "${lxc_filter}" = "glance_api" ]; then
+ lxc_filter="glance"
+fi
+
+if [ "${process_name}" = "nova-api" ]; then
+ container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}")
+ container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}")
+
+ pids_1=$(lxc-attach -n "${container_1}" -- pgrep -f "/openstack/.*/${NOVA_API_PROCESS_1}")
+ for pid in ${pids_1};
+ do
+ lxc-attach -n "${container_1}" -- kill -9 "${pid}"
+ done
+
+ pids_2=$(lxc-attach -n "${container_2}" -- pgrep -f "/openstack/.*/${NOVA_API_PROCESS_2}")
+ for pid in ${pids_2};
+ do
+ lxc-attach -n "${container_2}" -- kill -9 "${pid}"
+ done
+else
+ container=$(lxc-ls -1 --filter="${lxc_filter}")
+
+ if [ "${process_name}" = "haproxy" ]; then
+ for pid in $(pgrep -cf "/usr/.*/${process_name}");
+ do
+ kill -9 "${pid}"
+ done
+ elif [ "${process_name}" = "keystone" ]; then
+ pids=$(lxc-attach -n "${container}" -- ps aux | grep "keystone" | grep -iv heartbeat | grep -iv monitor | grep -v grep | grep -v /bin/sh | awk '{print $2}')
+ for pid in ${pids};
+ do
+ lxc-attach -n "${container}" -- kill -9 "${pid}"
+ done
+ else
+ pids=$(lxc-attach -n "${container}" -- pgrep -f "/openstack/.*/${process_name}")
+ for pid in ${pids};
+ do
+ lxc-attach -n "${container}" -- kill -9 "${pid}"
+ done
+ fi
+fi
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash
index aee516ea9..7408409a9 100644
--- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash
@@ -20,4 +20,4 @@ else
SECURE=""
fi
-openstack "${SECURE}" flavor create $1 --id $2 --ram $3 --disk $4 --vcpus $5
+openstack ${SECURE} flavor create $1 --id $2 --ram $3 --disk $4 --vcpus $5
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash
index d39926fc5..7240476f7 100644
--- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash
@@ -20,4 +20,4 @@ else
SECURE=""
fi
-openstack "${SECURE}" flavor delete $1
+openstack ${SECURE} flavor delete $1
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash
index bd61ba9bb..e679fdb9e 100644
--- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash
@@ -19,4 +19,4 @@ else
SECURE=""
fi
-openstack "${SECURE}" flavor list
+openstack ${SECURE} flavor list
diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash b/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash
new file mode 100755
index 000000000..36a673977
--- /dev/null
+++ b/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash
@@ -0,0 +1,70 @@
+#!/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
+##############################################################################
+
+# Start a service and check the service is started
+
+set -e
+
+NOVA_API_SERVICE_1="nova-api-os-compute"
+NOVA_API_SERVICE_2="nova-api-metadata"
+NOVA_API_LXC_FILTER_1="nova_api_os_compute"
+NOVA_API_LXC_FILTER_2="nova_api_metadata"
+
+service_name=$1
+
+if [ "${service_name}" = "haproxy" ]; then
+ if which systemctl 2>/dev/null; then
+ systemctl start $service_name
+ else
+ service $service_name start
+ fi
+else
+ lxc_filter=${service_name//-/_}
+
+ if [ "${lxc_filter}" = "glance_api" ]; then
+ lxc_filter="glance"
+ fi
+
+ if [ "${service_name}" = "nova-api" ]; then
+ container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}")
+ container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}")
+
+ if lxc-attach -n "${container_1}" -- which systemctl 2>/dev/null; then
+ lxc-attach -n "${container_1}" -- systemctl start "${NOVA_API_SERVICE_1}"
+ else
+ lxc-attach -n "${container_1}" -- service "${NOVA_API_SERVICE_1}" start
+ fi
+
+ if lxc-attach -n "${container_2}" -- which systemctl 2>/dev/null; then
+ lxc-attach -n "${container_2}" -- systemctl start "${NOVA_API_SERVICE_2}"
+ else
+ lxc-attach -n "${container_2}" -- service "${NOVA_API_SERVICE_2}" start
+ fi
+ else
+ container=$(lxc-ls -1 --filter="${lxc_filter}")
+
+ Distributor=$(lxc-attach -n "${container}" -- lsb_release -a | grep "Distributor ID" | awk '{print $3}')
+
+ if [ "${Distributor}" != "Ubuntu" -a "${service_name}" != "keystone" -a "${service_name}" != "neutron-server" ]; then
+ service_name="openstack-"${service_name}
+ elif [ "${Distributor}" = "Ubuntu" -a "${service_name}" = "keystone" ]; then
+ service_name="apache2"
+ elif [ "${service_name}" = "keystone" ]; then
+ service_name="httpd"
+ fi
+
+ if lxc-attach -n "${container}" -- which systemctl 2>/dev/null; then
+ lxc-attach -n "${container}" -- systemctl start "${service_name}"
+ else
+ lxc-attach -n "${container}" -- service "${service_name}" start
+ fi
+ fi
+fi
diff --git a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py
index a0777f94e..a9488cc30 100644
--- a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py
+++ b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py
@@ -11,7 +11,6 @@ from __future__ import absolute_import
import os
import logging
import subprocess
-import traceback
import yardstick.ssh as ssh
from yardstick.benchmark.scenarios.availability.monitor import basemonitor
@@ -27,9 +26,7 @@ def _execute_shell_command(command):
output = subprocess.check_output(command, shell=True)
except Exception:
exitcode = -1
- output = traceback.format_exc()
- LOG.error("exec command '%s' error:\n ", command)
- LOG.error(traceback.format_exc())
+ LOG.error("exec command '%s' error:\n ", command, exc_info=True)
return exitcode, output
diff --git a/yardstick/benchmark/scenarios/availability/monitor_conf.yaml b/yardstick/benchmark/scenarios/availability/monitor_conf.yaml
index 511449221..a08347d2d 100644
--- a/yardstick/benchmark/scenarios/availability/monitor_conf.yaml
+++ b/yardstick/benchmark/scenarios/availability/monitor_conf.yaml
@@ -13,6 +13,8 @@ schema: "yardstick:task:0.1"
process-status:
monitor_script: ha_tools/check_process_python.bash
+lxc_process-status:
+ monitor_script: ha_tools/check_lxc_process_python.bash
nova-image-list:
monitor_script: ha_tools/nova_image_list.bash
service-status:
diff --git a/yardstick/benchmark/scenarios/availability/operation/baseoperation.py b/yardstick/benchmark/scenarios/availability/operation/baseoperation.py
index be286b8fd..88ca9e2bb 100644
--- a/yardstick/benchmark/scenarios/availability/operation/baseoperation.py
+++ b/yardstick/benchmark/scenarios/availability/operation/baseoperation.py
@@ -58,6 +58,7 @@ class BaseOperation(object):
self.key = ''
self._config = config
self._context = context
+ self.intermediate_variables = {}
@staticmethod
def get_operation_cls(type):
diff --git a/yardstick/benchmark/scenarios/availability/operation/operation_general.py b/yardstick/benchmark/scenarios/availability/operation/operation_general.py
index 8fd387e47..af1ae7469 100644
--- a/yardstick/benchmark/scenarios/availability/operation/operation_general.py
+++ b/yardstick/benchmark/scenarios/availability/operation/operation_general.py
@@ -15,7 +15,8 @@ from yardstick.benchmark.scenarios.availability.operation.baseoperation \
import yardstick.ssh as ssh
from yardstick.benchmark.scenarios.availability.util \
- import buildshellparams, execute_shell_command
+ import buildshellparams, execute_shell_command, \
+ read_stdout_item, build_shell_command
LOG = logging.getLogger(__name__)
@@ -39,11 +40,7 @@ class GeneralOperaion(BaseOperation):
self.operation_key = self._config['operation_key']
if "action_parameter" in self._config:
- actionParameter = self._config['action_parameter']
- str = buildshellparams(
- actionParameter, True if self.connection else False)
- l = list(item for item in actionParameter.values())
- self.action_param = str.format(*l)
+ self.actionParameter_config = self._config['action_parameter']
if "rollback_parameter" in self._config:
rollbackParameter = self._config['rollback_parameter']
@@ -61,6 +58,11 @@ class GeneralOperaion(BaseOperation):
def run(self):
if "action_parameter" in self._config:
+ self.action_param = \
+ build_shell_command(
+ self.actionParameter_config,
+ True if self.connection else False,
+ self.intermediate_variables)
if self.connection:
with open(self.action_script, "r") as stdin_file:
exit_status, stdout, stderr = self.connection.execute(
@@ -83,6 +85,12 @@ class GeneralOperaion(BaseOperation):
if exit_status == 0:
LOG.debug("success,the operation's output is: %s", stdout)
+ if "return_parameter" in self._config:
+ returnParameter = self._config['return_parameter']
+ for key, item in returnParameter.items():
+ value = read_stdout_item(stdout, key)
+ LOG.debug("intermediate variables %s: %s", item, value)
+ self.intermediate_variables[item] = value
else:
LOG.error(
"the operation's error, stdout:%s, stderr:%s",
diff --git a/yardstick/benchmark/scenarios/availability/scenario_general.py b/yardstick/benchmark/scenarios/availability/scenario_general.py
index 28bec8aff..17ad79f29 100644
--- a/yardstick/benchmark/scenarios/availability/scenario_general.py
+++ b/yardstick/benchmark/scenarios/availability/scenario_general.py
@@ -25,6 +25,7 @@ class ScenarioGeneral(base.Scenario):
"scenario_cfg:%s context_cfg:%s", scenario_cfg, context_cfg)
self.scenario_cfg = scenario_cfg
self.context_cfg = context_cfg
+ self.intermediate_variables = {}
def setup(self):
self.director = Director(self.scenario_cfg, self.context_cfg)
@@ -38,7 +39,8 @@ class ScenarioGeneral(base.Scenario):
orderedSteps.index(step) + 1)
try:
actionPlayer = self.director.createActionPlayer(
- step['actionType'], step['actionKey'])
+ step['actionType'], step['actionKey'],
+ self.intermediate_variables)
actionPlayer.action()
actionRollbacker = self.director.createActionRollbacker(
step['actionType'], step['actionKey'])
diff --git a/yardstick/benchmark/scenarios/availability/util.py b/yardstick/benchmark/scenarios/availability/util.py
index eadbfa53b..6fef622bd 100644
--- a/yardstick/benchmark/scenarios/availability/util.py
+++ b/yardstick/benchmark/scenarios/availability/util.py
@@ -14,13 +14,8 @@ LOG = logging.getLogger(__name__)
def buildshellparams(param, remote=True):
- i = 0
- values = []
result = '/bin/bash -s' if remote else ''
- for key in param.keys():
- values.append(param[key])
- result += " {%d}" % i
- i = i + 1
+ result += "".join(" {%d}" % i for i in range(len(param)))
return result
@@ -36,5 +31,29 @@ def execute_shell_command(command):
output = traceback.format_exc()
LOG.error("exec command '%s' error:\n ", command)
LOG.error(traceback.format_exc())
-
return exitcode, output
+
+PREFIX = '$'
+
+
+def build_shell_command(param_config, remote=True, intermediate_variables=None):
+ param_template = '/bin/bash -s' if remote else ''
+ if intermediate_variables:
+ for key, val in param_config.items():
+ if str(val).startswith(PREFIX):
+ try:
+ param_config[key] = intermediate_variables[val]
+ except KeyError:
+ pass
+ result = param_template + "".join(" {}".format(v) for v in param_config.values())
+ LOG.debug("THE RESULT OF build_shell_command IS: %s", result)
+ return result
+
+
+def read_stdout_item(stdout, key):
+ for item in stdout.splitlines():
+ if key in item:
+ attributes = item.split("|")
+ if attributes[1].lstrip().startswith(key):
+ return attributes[2].strip()
+ return None
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/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/benchmark/scenarios/networking/iperf3.py b/yardstick/benchmark/scenarios/networking/iperf3.py
index 3135af9bd..a3d273750 100644
--- a/yardstick/benchmark/scenarios/networking/iperf3.py
+++ b/yardstick/benchmark/scenarios/networking/iperf3.py
@@ -50,6 +50,17 @@ For more info see http://software.es.net/iperf
type: int
unit: bytes
default: -
+ length - length of buffer to read or write,
+ (default 128 KB for TCP, 8 KB for UDP)
+ type: int
+ unit: k
+ default: -
+ window - set window size / socket buffer size
+ set TCP windows size. for UDP way to test, this will set to accept UDP
+ packet buffer size, limit the max size of acceptable data packet.
+ type: int
+ unit: k
+ default: -
"""
__scenario_type__ = "Iperf3"
@@ -122,6 +133,12 @@ For more info see http://software.es.net/iperf
elif "blockcount" in options:
cmd += " --blockcount %d" % options["blockcount"]
+ if "length" in options:
+ cmd += " --length %s" % options["length"]
+
+ if "window" in options:
+ cmd += " --window %s" % options["window"]
+
LOG.debug("Executing command: %s", cmd)
status, stdout, stderr = self.host.execute(cmd)
diff --git a/yardstick/benchmark/scenarios/networking/pktgen.py b/yardstick/benchmark/scenarios/networking/pktgen.py
index e6aa7e5fb..8ca1ca60e 100644
--- a/yardstick/benchmark/scenarios/networking/pktgen.py
+++ b/yardstick/benchmark/scenarios/networking/pktgen.py
@@ -9,6 +9,7 @@
from __future__ import absolute_import
from __future__ import print_function
+import os
import logging
import pkg_resources
@@ -19,6 +20,9 @@ from yardstick.benchmark.scenarios import base
LOG = logging.getLogger(__name__)
+VNIC_TYPE_LIST = ["ovs", "sriov"]
+SRIOV_DRIVER_LIST = ["ixgbevf", "i40evf"]
+
class Pktgen(base.Scenario):
"""Execute pktgen between two hosts
@@ -44,7 +48,11 @@ class Pktgen(base.Scenario):
def __init__(self, scenario_cfg, context_cfg):
self.scenario_cfg = scenario_cfg
self.context_cfg = context_cfg
+ self.vnic_name = "eth0"
+ self.vnic_type = "ovs"
+ self.queue_number = 1
self.setup_done = False
+ self.multiqueue_setup_done = False
def setup(self):
"""scenario setup"""
@@ -67,6 +75,212 @@ class Pktgen(base.Scenario):
self.setup_done = True
+ def multiqueue_setup(self):
+ # one time setup stuff
+ cmd = "sudo sysctl -w net.core.netdev_budget=3000"
+ self.server.send_command(cmd)
+ self.client.send_command(cmd)
+
+ cmd = "sudo sysctl -w net.core.netdev_max_backlog=100000"
+ self.server.send_command(cmd)
+ self.client.send_command(cmd)
+
+ """multiqueue setup"""
+ if not self._is_irqbalance_disabled():
+ self._disable_irqbalance()
+
+ vnic_driver_name = self._get_vnic_driver_name()
+ if vnic_driver_name in SRIOV_DRIVER_LIST:
+ self.vnic_type = "sriov"
+
+ # one time setup stuff
+ cmd = "sudo ethtool -G %s rx 4096 tx 4096" % self.vnic_name
+ self.server.send_command(cmd)
+ self.client.send_command(cmd)
+
+ self.queue_number = self._get_sriov_queue_number()
+ self._setup_irqmapping_sriov(self.queue_number)
+ else:
+ self.vnic_type = "ovs"
+ self.queue_number = self._enable_ovs_multiqueue()
+ self._setup_irqmapping_ovs(self.queue_number)
+
+ self.multiqueue_setup_done = True
+
+ def _get_vnic_driver_name(self):
+ cmd = "readlink /sys/class/net/%s/device/driver" % self.vnic_name
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ return os.path.basename(stdout.strip())
+
+ def _is_irqbalance_disabled(self):
+ """Did we disable irqbalance already in the guest?"""
+ is_disabled = False
+ cmd = "grep ENABLED /etc/default/irqbalance"
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ if "0" in stdout:
+ is_disabled = True
+
+ return is_disabled
+
+ def _disable_irqbalance(self):
+ cmd = "sudo sed -i -e 's/ENABLED=\"1\"/ENABLED=\"0\"/g' " \
+ "/etc/default/irqbalance"
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "sudo service irqbalance stop"
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "sudo service irqbalance disable"
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ def _setup_irqmapping_ovs(self, queue_number):
+ cmd = "grep 'virtio0-input.0' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'"
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "grep 'virtio0-output.0' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'"
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ if queue_number == 1:
+ return
+
+ for i in range(1, queue_number):
+ cmd = "grep 'virtio0-input.%s' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'" % (i)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \
+ % (1 << i, int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "grep 'virtio0-output.%s' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'" % (i)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \
+ % (1 << i, int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ def _setup_irqmapping_sriov(self, queue_number):
+ cmd = "grep '%s-TxRx-0' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'" % self.vnic_name
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ if queue_number == 1:
+ return
+
+ for i in range(1, queue_number):
+ cmd = "grep '%s-TxRx-%s' /proc/interrupts |" \
+ "awk '{match($0,/ +[0-9]+/)} " \
+ "{print substr($1,RSTART,RLENGTH-1)}'" % (self.vnic_name, i)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \
+ % (1 << i, int(stdout))
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+
+ def _get_sriov_queue_number(self):
+ """Get queue number from server as both VMs are the same"""
+ cmd = "grep %s-TxRx- /proc/interrupts | wc -l" % self.vnic_name
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ return int(stdout)
+
+ def _get_available_queue_number(self):
+ """Get queue number from client as both VMs are the same"""
+ cmd = "sudo ethtool -l %s | grep Combined | head -1 |" \
+ "awk '{printf $2}'" % self.vnic_name
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ return int(stdout)
+
+ def _get_usable_queue_number(self):
+ """Get queue number from client as both VMs are the same"""
+ cmd = "sudo ethtool -l %s | grep Combined | tail -1 |" \
+ "awk '{printf $2}'" % self.vnic_name
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.server.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ return int(stdout)
+
+ def _enable_ovs_multiqueue(self):
+ available_queue_number = self._get_available_queue_number()
+ usable_queue_number = self._get_usable_queue_number()
+ if available_queue_number > 1 and \
+ available_queue_number != usable_queue_number:
+ cmd = "sudo ethtool -L %s combined %s" % \
+ (self.vnic_name, available_queue_number)
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.server.execute(cmd)
+ status, stdout, stderr = self.client.execute(cmd)
+ if status:
+ raise RuntimeError(stderr)
+ return available_queue_number
+
def _iptables_setup(self):
"""Setup iptables on server to monitor for received packets"""
cmd = "sudo iptables -F; " \
@@ -99,6 +313,14 @@ class Pktgen(base.Scenario):
options = self.scenario_cfg['options']
packetsize = options.get("packetsize", 60)
self.number_of_ports = options.get("number_of_ports", 10)
+ self.vnic_name = options.get("vnic_name", "eth0")
+ ovs_dpdk = options.get("ovs_dpdk", False)
+ pps = options.get("pps", 1000000)
+ multiqueue = options.get("multiqueue", False)
+
+ if multiqueue and not self.multiqueue_setup_done:
+ self.multiqueue_setup()
+
# if run by a duration runner
duration_time = self.scenario_cfg["runner"].get("duration", None) \
if "runner" in self.scenario_cfg else None
@@ -114,8 +336,18 @@ class Pktgen(base.Scenario):
self._iptables_setup()
- cmd = "sudo bash pktgen.sh %s %s %s %s" \
- % (ipaddr, self.number_of_ports, packetsize, duration)
+ queue_number = self.queue_number
+
+ # For native OVS, half of vCPUs are used by vhost kernel threads
+ # hence set the queue_number to half number of vCPUs
+ # e.g. set queue_number to 2 if there are 4 vCPUs
+ if self.vnic_type == "ovs" and not ovs_dpdk and self.queue_number > 1:
+ queue_number = self.queue_number / 2
+
+ cmd = "sudo bash pktgen.sh %s %s %s %s %s %s" \
+ % (ipaddr, self.number_of_ports, packetsize,
+ duration, queue_number, pps)
+
LOG.debug("Executing command: %s", cmd)
status, stdout, stderr = self.client.execute(cmd)
@@ -131,12 +363,15 @@ class Pktgen(base.Scenario):
sent = result['packets_sent']
received = result['packets_received']
ppm = 1000000 * (sent - received) / sent
+ # if ppm is 1, then 11 out of 10 million is no pass
+ ppm += (sent - received) % sent > 0
+ LOG.debug("Lost packets %d - Lost ppm %d", (sent - received), ppm)
sla_max_ppm = int(self.scenario_cfg["sla"]["max_ppm"])
assert ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " \
% (ppm, sla_max_ppm)
-def _test():
+def _test(): # pragma: no cover
"""internal test function"""
key_filename = pkg_resources.resource_filename('yardstick.resources',
'files/yardstick_key')
@@ -165,6 +400,5 @@ def _test():
p.run(result)
print(result)
-
if __name__ == '__main__':
_test()
diff --git a/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash b/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash
index 4224c5abf..e338a1b09 100644
--- a/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash
+++ b/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash
@@ -16,6 +16,8 @@ DST_IP=$1 # destination IP address
NUM_PORTS=$2 # number of source ports
PKT_SIZE=$3 # packet size
DURATION=$4 # test duration (seconds)
+TRXQUEUE=$5 # number of RX/TX queues to use
+PPS=$6 # packets per second to send
# Configuration
UDP_SRC_MIN=1000 # UDP source port min
@@ -37,62 +39,100 @@ pgset()
fi
}
+# remove all devices from thread
+pgclean()
+{
+ COUNTER=0
+ while [ ${COUNTER} -lt ${TRXQUEUE} ]; do
+ #
+ # Thread commands
+ #
+
+ PGDEV=/proc/net/pktgen/kpktgend_${COUNTER}
+
+ # Remove all devices from this thread
+ pgset "rem_device_all"
+ let COUNTER=COUNTER+1
+ done
+}
+
# configure pktgen (see pktgen doc for details)
pgconfig()
{
- #
- # Thread commands
- #
+ pps=$(( PPS / TRXQUEUE ))
+ COUNTER=0
+ while [ ${COUNTER} -lt ${TRXQUEUE} ]; do
+ #
+ # Thread commands
+ #
- PGDEV=/proc/net/pktgen/kpktgend_0
+ PGDEV=/proc/net/pktgen/kpktgend_${COUNTER}
- # Remove all devices from this thread
- pgset "rem_device_all"
+ # Add device to thread
+ pgset "add_device $DEV@${COUNTER}"
- # Add device to thread
- pgset "add_device $DEV"
+ #
+ # Device commands
+ #
- #
- # Device commands
- #
+ PGDEV=/proc/net/pktgen/$DEV@${COUNTER}
- PGDEV=/proc/net/pktgen/$DEV
+ # 0 means continious sends untill explicitly stopped
+ pgset "count 0"
- # 0 means continious sends untill explicitly stopped
- pgset "count 0"
+ # set pps count to test with an explicit number. if 0 will try with bandwidth
+ if [ ${pps} -gt 0 ]
+ then
+ pgset "ratep ${pps}"
+ fi
- # use single SKB for all transmits
- pgset "clone_skb 0"
+ pgset "clone_skb 10"
- # packet size, NIC adds 4 bytes CRC
- pgset "pkt_size $PKT_SIZE"
+ # use different queue per thread
+ pgset "queue_map_min ${COUNTER}"
+ pgset "queue_map_max ${COUNTER}"
- # random address within the min-max range
- pgset "flag IPDST_RND UDPSRC_RND UDPDST_RND"
+ # packet size, NIC adds 4 bytes CRC
+ pgset "pkt_size $PKT_SIZE"
- # destination IP
- pgset "dst_min $DST_IP"
- pgset "dst_max $DST_IP"
+ # random address within the min-max range
+ pgset "flag UDPDST_RND"
+ pgset "flag UDPSRC_RND"
+ pgset "flag IPDST_RND"
- # destination MAC address
- pgset "dst_mac $MAC"
+ # destination IP
+ pgset "dst_min $DST_IP"
+ pgset "dst_max $DST_IP"
+
+ # destination MAC address
+ pgset "dst_mac $MAC"
+
+ # source UDP port range
+ pgset "udp_src_min $UDP_SRC_MIN"
+ pgset "udp_src_max $UDP_SRC_MAX"
- # source UDP port range
- pgset "udp_src_min $UDP_SRC_MIN"
- pgset "udp_src_max $UDP_SRC_MAX"
+ # destination UDP port range
+ pgset "udp_dst_min $UDP_DST_MIN"
+ pgset "udp_dst_max $UDP_DST_MAX"
- # destination UDP port range
- pgset "udp_dst_min $UDP_DST_MIN"
- pgset "udp_dst_max $UDP_DST_MAX"
+ let COUNTER=COUNTER+1
+
+ done
}
# run pktgen
pgrun()
{
- # Time to run, result can be vieved in /proc/net/pktgen/$DEV
+ # Time to run, result can be viewed in /proc/net/pktgen/$DEV
PGDEV=/proc/net/pktgen/pgctrl
# Will hang, Ctrl-C or SIGINT to stop
pgset "start" start
+
+ COUNTER=0
+ while [ ${COUNTER} -lt ${TRXQUEUE} ]; do
+ taskset -c ${COUNTER} kpktgend_${COUNTER}
+ let COUNTER=COUNTER+1
+ done
}
# run pktgen for ${DURATION} seconds
@@ -111,19 +151,28 @@ run_test()
# write the result to stdout in json format
output_json()
{
- sent=$(awk '/^Result:/{print $5}' <$PGDEV)
- pps=$(awk 'match($0,/'\([0-9]+\)pps'/, a) {print a[1]}' <$PGDEV)
- errors=$(awk '/errors:/{print $5}' <$PGDEV)
+ sent=0
+ result_pps=0
+ errors=0
+ PGDEV=/proc/net/pktgen/$DEV@
+ COUNTER=0
+ while [ ${COUNTER} -lt ${TRXQUEUE} ]; do
+ sent=$(($sent + $(awk '/^Result:/{print $5}' <$PGDEV${COUNTER})))
+ result_pps=$(($result_pps + $(awk 'match($0,/'\([0-9]+\)pps'/, a) {print a[1]}' <$PGDEV${COUNTER})))
+ errors=$(($errors + $(awk '/errors:/{print $5}' <$PGDEV${COUNTER})))
+ let COUNTER=COUNTER+1
+ done
flows=$(( NUM_PORTS * (NUM_PORTS + 1) ))
- echo { '"packets_sent"':$sent , '"packets_per_second"':$pps, '"flows"':$flows, '"errors"':$errors }
+ echo '{ "packets_sent"':${sent} , '"packets_per_second"':${result_pps}, '"flows"':${flows}, '"errors"':${errors} '}'
}
# main entry
main()
{
modprobe pktgen
+ pgclean
ping -c 3 $DST_IP >/dev/null
@@ -137,16 +186,20 @@ main()
pgconfig
# run the test
- run_test >/dev/null
+ run_test
- PGDEV=/proc/net/pktgen/$DEV
+ PGDEV=/proc/net/pktgen/$DEV@
# check result
- result=$(cat $PGDEV | fgrep "Result: OK:")
- if [ "$result" = "" ]; then
- cat $PGDEV | fgrep Result: >/dev/stderr
- exit 1
- fi
+ COUNTER=0
+ while [ ${COUNTER} -lt ${TRXQUEUE} ]; do
+ result=$(cat $PGDEV${COUNTER} | fgrep "Result: OK:")
+ if [ "$result" = "" ]; then
+ cat $PGDEV${COUNTER} | fgrep Result: >/dev/stderr
+ exit 1
+ fi
+ let COUNTER=COUNTER+1
+ done
# output result
output_json
diff --git a/yardstick/benchmark/scenarios/networking/vnf_generic.py b/yardstick/benchmark/scenarios/networking/vnf_generic.py
index 594edeaa8..9607e3005 100644
--- a/yardstick/benchmark/scenarios/networking/vnf_generic.py
+++ b/yardstick/benchmark/scenarios/networking/vnf_generic.py
@@ -164,38 +164,60 @@ class NetworkServiceTestCase(base.Scenario):
for vnfd in topology["constituent-vnfd"]
if vnf_id == vnfd["member-vnf-index"]), None)
+ @staticmethod
+ def get_vld_networks(networks):
+ return {n['vld_id']: n for n in networks.values()}
+
def _resolve_topology(self, context_cfg, topology):
for vld in topology["vld"]:
- if len(vld["vnfd-connection-point-ref"]) > 2:
+ try:
+ node_0, node_1 = vld["vnfd-connection-point-ref"]
+ except (TypeError, ValueError):
raise IncorrectConfig("Topology file corrupted, "
- "too many endpoint for connection")
-
- node_0, node_1 = vld["vnfd-connection-point-ref"]
+ "wrong number of endpoints for connection")
- node0 = self._find_vnf_name_from_id(topology,
- node_0["member-vnf-index-ref"])
- node1 = self._find_vnf_name_from_id(topology,
- node_1["member-vnf-index-ref"])
+ node_0_name = self._find_vnf_name_from_id(topology,
+ node_0["member-vnf-index-ref"])
+ node_1_name = self._find_vnf_name_from_id(topology,
+ node_1["member-vnf-index-ref"])
- if0 = node_0["vnfd-connection-point-ref"]
- if1 = node_1["vnfd-connection-point-ref"]
+ node_0_ifname = node_0["vnfd-connection-point-ref"]
+ node_1_ifname = node_1["vnfd-connection-point-ref"]
+ node_0_if = context_cfg["nodes"][node_0_name]["interfaces"][node_0_ifname]
+ node_1_if = context_cfg["nodes"][node_1_name]["interfaces"][node_1_ifname]
try:
- nodes = context_cfg["nodes"]
- nodes[node0]["interfaces"][if0]["vld_id"] = vld["id"]
- nodes[node1]["interfaces"][if1]["vld_id"] = vld["id"]
-
- nodes[node0]["interfaces"][if0]["dst_mac"] = \
- nodes[node1]["interfaces"][if1]["local_mac"]
- nodes[node0]["interfaces"][if0]["dst_ip"] = \
- nodes[node1]["interfaces"][if1]["local_ip"]
-
- nodes[node1]["interfaces"][if1]["dst_mac"] = \
- nodes[node0]["interfaces"][if0]["local_mac"]
- nodes[node1]["interfaces"][if1]["dst_ip"] = \
- nodes[node0]["interfaces"][if0]["local_ip"]
+ vld_networks = self.get_vld_networks(context_cfg["networks"])
+
+ node_0_if["vld_id"] = vld["id"]
+ node_1_if["vld_id"] = vld["id"]
+
+ # set peer name
+ node_0_if["peer_name"] = node_1_name
+ node_1_if["peer_name"] = node_0_name
+
+ # set peer interface name
+ node_0_if["peer_ifname"] = node_1_ifname
+ node_1_if["peer_ifname"] = node_0_ifname
+
+ # just load the whole network dict
+ node_0_if["network"] = vld_networks.get(vld["id"], {})
+ node_1_if["network"] = vld_networks.get(vld["id"], {})
+
+ node_0_if["dst_mac"] = node_1_if["local_mac"]
+ node_0_if["dst_ip"] = node_1_if["local_ip"]
+
+ node_1_if["dst_mac"] = node_0_if["local_mac"]
+ node_1_if["dst_ip"] = node_0_if["local_ip"]
+
+ # add peer interface dict, but remove circular link
+ # TODO: don't waste memory
+ node_0_copy = node_0_if.copy()
+ node_1_copy = node_1_if.copy()
+ node_0_if["peer_intf"] = node_1_copy
+ node_1_if["peer_intf"] = node_0_copy
except KeyError:
- raise IncorrectConfig("Required interface not found,"
+ raise IncorrectConfig("Required interface not found, "
"topology file corrupted")
@classmethod
@@ -308,21 +330,36 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
return dict(network_devices)
@classmethod
- def get_vnf_impl(cls, vnf_model):
+ def get_vnf_impl(cls, vnf_model_id):
""" Find the implementing class from vnf_model["vnf"]["name"] field
- :param vnf_model: dictionary containing a parsed vnfd
+ :param vnf_model_id: parsed vnfd model ID field
:return: subclass of GenericVNF
"""
import_modules_from_package(
"yardstick.network_services.vnf_generic.vnf")
- expected_name = vnf_model['id']
- impl = (c for c in itersubclasses(GenericVNF)
- if c.__name__ == expected_name)
+ expected_name = vnf_model_id
+ classes_found = []
+
+ def impl():
+ for name, class_ in ((c.__name__, c) for c in itersubclasses(GenericVNF)):
+ if name == expected_name:
+ yield class_
+ classes_found.append(name)
+
try:
- return next(impl)
+ return next(impl())
except StopIteration:
- raise IncorrectConfig("No implementation for %s", expected_name)
+ pass
+
+ raise IncorrectConfig("No implementation for %s found in %s" %
+ (expected_name, classes_found))
+
+ @staticmethod
+ def update_interfaces_from_node(vnfd, node):
+ for intf in vnfd["vdu"][0]["external-interface"]:
+ node_intf = node['interfaces'][intf['name']]
+ intf['virtual-interface'].update(node_intf)
def load_vnf_models(self, scenario_cfg, context_cfg):
""" Create VNF objects based on YAML descriptors
@@ -339,8 +376,11 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
scenario_cfg['task_path']) as stream:
vnf_model = stream.read()
vnfd = vnfdgen.generate_vnfd(vnf_model, node)
- vnf_impl = self.get_vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
- vnf_instance = vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
+ # TODO: here add extra context_cfg["nodes"] regardless of template
+ vnfd = vnfd["vnfd:vnfd-catalog"]["vnfd"][0]
+ self.update_interfaces_from_node(vnfd, node)
+ vnf_impl = self.get_vnf_impl(vnfd['id'])
+ vnf_instance = vnf_impl(vnfd)
vnf_instance.name = node_name
vnfs.append(vnf_instance)
diff --git a/yardstick/benchmark/scenarios/storage/fio.py b/yardstick/benchmark/scenarios/storage/fio.py
index ad34817a7..b99e34270 100644
--- a/yardstick/benchmark/scenarios/storage/fio.py
+++ b/yardstick/benchmark/scenarios/storage/fio.py
@@ -40,10 +40,26 @@ class Fio(base.Scenario):
type: string
unit: na
default: write
+ rwmixwrite - percentage of a mixed workload that should be writes
+ type: int
+ unit: percentage
+ default: 50
ramp_time - run time before logging any performance
type: int
unit: seconds
default: 20
+ direct - whether use non-buffered I/O or not
+ type: boolean
+ unit: na
+ default: 1
+ size - total size of I/O for this job.
+ type: string
+ unit: na
+ default: 1g
+ numjobs - number of clones (processes/threads performing the same workload) of this job
+ type: int
+ unit: na
+ default: 1
Read link below for more fio args description:
http://www.bluestop.org/fio/HOWTO.txt
@@ -74,8 +90,8 @@ class Fio(base.Scenario):
def run(self, result):
"""execute the benchmark"""
- default_args = "-ioengine=libaio -direct=1 -group_reporting " \
- "-numjobs=1 -time_based --output-format=json"
+ default_args = "-ioengine=libaio -group_reporting -time_based -time_based " \
+ "--output-format=json"
if not self.setup_done:
self.setup()
@@ -86,6 +102,10 @@ class Fio(base.Scenario):
iodepth = options.get("iodepth", "1")
rw = options.get("rw", "write")
ramp_time = options.get("ramp_time", 20)
+ size = options.get("size", "1g")
+ direct = options.get("direct", "1")
+ numjobs = options.get("numjobs", "1")
+ rwmixwrite = options.get("rwmixwrite", 50)
name = "yardstick-fio"
# if run by a duration runner
duration_time = self.scenario_cfg["runner"].get("duration", None) \
@@ -99,10 +119,10 @@ class Fio(base.Scenario):
else:
runtime = 30
- cmd_args = "-filename=%s -bs=%s -iodepth=%s -rw=%s -ramp_time=%s " \
- "-runtime=%s -name=%s %s" \
- % (filename, bs, iodepth, rw, ramp_time, runtime, name,
- default_args)
+ cmd_args = "-filename=%s -direct=%s -bs=%s -iodepth=%s -rw=%s -rwmixwrite=%s " \
+ "-size=%s -ramp_time=%s -numjobs=%s -runtime=%s -name=%s %s" \
+ % (filename, direct, bs, iodepth, rw, rwmixwrite, size, ramp_time, numjobs,
+ runtime, name, default_args)
cmd = "sudo bash fio.sh %s %s" % (filename, cmd_args)
LOG.debug("Executing command: %s", cmd)
# Set timeout, so that the cmd execution does not exit incorrectly
diff --git a/yardstick/benchmark/scenarios/storage/storperf.py b/yardstick/benchmark/scenarios/storage/storperf.py
index c10118ad1..f0b2361d6 100644
--- a/yardstick/benchmark/scenarios/storage/storperf.py
+++ b/yardstick/benchmark/scenarios/storage/storperf.py
@@ -87,8 +87,9 @@ class StorPerf(base.Scenario):
def setup(self):
"""Set the configuration."""
env_args = {}
- env_args_payload_list = ["agent_count", "public_network",
- "agent_image", "volume_size"]
+ env_args_payload_list = ["agent_count", "agent_flavor",
+ "public_network", "agent_image",
+ "volume_size"]
for env_argument in env_args_payload_list:
try:
@@ -206,7 +207,7 @@ class StorPerf(base.Scenario):
# terminate_res = requests.delete('http://%s:5000/api/v1.0
# /jobs' % self.target)
# else:
- # time.sleep(int(est_time)/2)
+ # time.sleep(int(esti_time)/2)
result_res = requests.get('http://%s:5000/api/v1.0/jobs?id=%s' %
(self.target, job_id))
diff --git a/yardstick/cmd/NSBperf.py b/yardstick/cmd/NSBperf.py
index 3a6d604cd..4e7590ea5 100755
--- a/yardstick/cmd/NSBperf.py
+++ b/yardstick/cmd/NSBperf.py
@@ -39,13 +39,11 @@ if not PYTHONPATH or not VIRTUAL_ENV:
raise SystemExit(1)
-def handler():
+def sigint_handler(*args, **kwargs):
""" Capture ctrl+c and exit cli """
subprocess.call(["pkill", "-9", "yardstick"])
raise SystemExit(1)
-signal.signal(signal.SIGINT, handler)
-
class YardstickNSCli(object):
""" This class handles yardstick network serivce testing """
@@ -215,5 +213,6 @@ class YardstickNSCli(object):
self.run_test(args, test_path)
if __name__ == "__main__":
+ signal.signal(signal.SIGINT, sigint_handler)
NS_CLI = YardstickNSCli()
NS_CLI.main()
diff --git a/yardstick/common/constants.py b/yardstick/common/constants.py
index d251341fc..8e8114fbb 100644
--- a/yardstick/common/constants.py
+++ b/yardstick/common/constants.py
@@ -26,7 +26,15 @@ except KeyError:
SERVER_IP = '172.17.0.1'
else:
with IPDB() as ip:
- SERVER_IP = ip.routes['default'].gateway
+ try:
+ SERVER_IP = ip.routes['default'].gateway
+ except KeyError:
+ # during unittests ip.routes['default'] can be invalid
+ SERVER_IP = '127.0.0.1'
+
+if not SERVER_IP:
+ SERVER_IP = '127.0.0.1'
+
# dir
CONF_DIR = get_param('dir.conf', '/etc/yardstick')
@@ -40,12 +48,15 @@ SAMPLE_CASE_DIR = join(REPOS_DIR, 'samples')
TESTCASE_DIR = join(YARDSTICK_ROOT_PATH, 'tests/opnfv/test_cases/')
TESTSUITE_DIR = join(YARDSTICK_ROOT_PATH, 'tests/opnfv/test_suites/')
DOCS_DIR = join(REPOS_DIR, 'docs/testing/user/userguide/')
+OPENSTACK_CONF_DIR = '/etc/openstack'
# file
OPENRC = get_param('file.openrc', '/etc/yardstick/openstack.creds')
ETC_HOSTS = get_param('file.etc_hosts', '/etc/hosts')
CONF_FILE = join(CONF_DIR, 'yardstick.conf')
POD_FILE = join(CONF_DIR, 'pod.yaml')
+CLOUDS_CONF = join(OPENSTACK_CONF_DIR, 'clouds.yml')
+K8S_CONF_FILE = join(CONF_DIR, 'admin.conf')
CONF_SAMPLE_FILE = join(CONF_SAMPLE_DIR, 'yardstick.conf.sample')
FETCH_SCRIPT = get_param('file.fetch_script', 'utils/fetch_os_creds.sh')
FETCH_SCRIPT = join(RELENG_DIR, FETCH_SCRIPT)
@@ -66,6 +77,7 @@ INFLUXDB_PASS = get_param('influxdb.password', 'root')
INFLUXDB_DB_NAME = get_param('influxdb.db_name', 'yardstick')
INFLUXDB_IMAGE = get_param('influxdb.image', 'tutum/influxdb')
INFLUXDB_TAG = get_param('influxdb.tag', '0.13')
+INFLUXDB_DASHBOARD_PORT = 8083
# grafana
GRAFANA_IP = get_param('grafana.ip', SERVER_IP)
@@ -74,8 +86,10 @@ GRAFANA_USER = get_param('grafana.username', 'admin')
GRAFANA_PASS = get_param('grafana.password', 'admin')
GRAFANA_IMAGE = get_param('grafana.image', 'grafana/grafana')
GRAFANA_TAG = get_param('grafana.tag', '3.1.1')
+GRAFANA_MAPPING_PORT = 1948
# api
+API_PORT = 5000
DOCKER_URL = 'unix://var/run/docker.sock'
INSTALLERS = ['apex', 'compass', 'fuel', 'joid']
SQLITE = 'sqlite:////tmp/yardstick.db'
diff --git a/yardstick/common/kubernetes_utils.py b/yardstick/common/kubernetes_utils.py
new file mode 100644
index 000000000..e4c232830
--- /dev/null
+++ b/yardstick/common/kubernetes_utils.py
@@ -0,0 +1,137 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+
+from kubernetes import client
+from kubernetes import config
+from kubernetes.client.rest import ApiException
+
+from yardstick.common import constants as consts
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+
+def get_core_api(): # pragma: no cover
+ try:
+ config.load_kube_config(config_file=consts.K8S_CONF_FILE)
+ except IOError:
+ LOG.exception('config file not found')
+ raise
+
+ return client.CoreV1Api()
+
+
+def create_replication_controller(template,
+ namespace='default',
+ wait=False,
+ **kwargs): # pragma: no cover
+
+ core_v1_api = get_core_api()
+ try:
+ core_v1_api.create_namespaced_replication_controller(namespace,
+ template,
+ **kwargs)
+ except ApiException:
+ LOG.exception('Create replication controller failed')
+ raise
+
+
+def delete_replication_controller(name,
+ namespace='default',
+ wait=False,
+ **kwargs): # pragma: no cover
+
+ core_v1_api = get_core_api()
+ body = kwargs.get('body', client.V1DeleteOptions())
+ kwargs.pop('body', None)
+ try:
+ core_v1_api.delete_namespaced_replication_controller(name,
+ namespace,
+ body,
+ **kwargs)
+ except ApiException:
+ LOG.exception('Delete replication controller failed')
+ raise
+
+
+def delete_pod(name,
+ namespace='default',
+ wait=False,
+ **kwargs): # pragma: no cover
+
+ core_v1_api = get_core_api()
+ body = kwargs.get('body', client.V1DeleteOptions())
+ kwargs.pop('body', None)
+ try:
+ core_v1_api.delete_namespaced_pod(name,
+ namespace,
+ body,
+ **kwargs)
+ except ApiException:
+ LOG.exception('Delete pod failed')
+ raise
+
+
+def read_pod(name,
+ namespace='default',
+ **kwargs): # pragma: no cover
+ core_v1_api = get_core_api()
+ try:
+ resp = core_v1_api.read_namespaced_pod(name, namespace, **kwargs)
+ except ApiException:
+ LOG.exception('Read pod failed')
+ raise
+ else:
+ return resp
+
+
+def read_pod_status(name, namespace='default', **kwargs): # pragma: no cover
+ return read_pod(name).status.phase
+
+
+def create_config_map(name,
+ data,
+ namespace='default',
+ wait=False,
+ **kwargs): # pragma: no cover
+ core_v1_api = get_core_api()
+ metadata = client.V1ObjectMeta(name=name)
+ body = client.V1ConfigMap(data=data, metadata=metadata)
+ try:
+ core_v1_api.create_namespaced_config_map(namespace, body, **kwargs)
+ except ApiException:
+ LOG.exception('Create config map failed')
+ raise
+
+
+def delete_config_map(name,
+ namespace='default',
+ wait=False,
+ **kwargs): # pragma: no cover
+ core_v1_api = get_core_api()
+ body = kwargs.get('body', client.V1DeleteOptions())
+ kwargs.pop('body', None)
+ try:
+ core_v1_api.delete_namespaced_config_map(name,
+ namespace,
+ body,
+ **kwargs)
+ except ApiException:
+ LOG.exception('Delete config map failed')
+ raise
+
+
+def get_pod_list(namespace='default'): # pragma: no cover
+ core_v1_api = get_core_api()
+ try:
+ return core_v1_api.list_namespaced_pod(namespace=namespace)
+ except ApiException:
+ LOG.exception('Get pod list failed')
+ raise
diff --git a/yardstick/common/utils.py b/yardstick/common/utils.py
index 7633777ae..7a64b8ca2 100644
--- a/yardstick/common/utils.py
+++ b/yardstick/common/utils.py
@@ -24,10 +24,14 @@ import os
import subprocess
import sys
import collections
-import six
+import socket
+import random
from functools import reduce
+from contextlib import closing
import yaml
+import six
+from flask import jsonify
from six.moves import configparser
from oslo_utils import importutils
from oslo_serialization import jsonutils
@@ -123,6 +127,14 @@ def makedirs(d):
raise
+def remove_file(path):
+ try:
+ os.remove(path)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
def execute_command(cmd):
exec_msg = "Executing command: '%s'" % cmd
logger.debug(exec_msg)
@@ -160,7 +172,15 @@ def write_file(path, data, mode='w'):
def parse_ini_file(path):
parser = configparser.ConfigParser()
- parser.read(path)
+
+ try:
+ files = parser.read(path)
+ except configparser.MissingSectionHeaderError:
+ logger.exception('invalid file type')
+ raise
+ else:
+ if not files:
+ raise RuntimeError('file not exist')
try:
default = {k: v for k, v in parser.items('DEFAULT')}
@@ -197,7 +217,8 @@ def flatten_dict_key(data):
next_data = {}
# use list, because iterable is too generic
- if not any(isinstance(v, (collections.Mapping, list)) for v in data.values()):
+ if not any(isinstance(v, (collections.Mapping, list))
+ for v in data.values()):
return data
for k, v in six.iteritems(data):
@@ -212,3 +233,52 @@ def flatten_dict_key(data):
next_data[k] = v
return flatten_dict_key(next_data)
+
+
+def translate_to_str(obj):
+ if isinstance(obj, collections.Mapping):
+ return {str(k): translate_to_str(v) for k, v in obj.items()}
+ elif isinstance(obj, list):
+ return [translate_to_str(ele) for ele in obj]
+ elif isinstance(obj, six.text_type):
+ return str(obj)
+ return obj
+
+
+def result_handler(status, data):
+ result = {
+ 'status': status,
+ 'result': data
+ }
+ return jsonify(result)
+
+
+def change_obj_to_dict(obj):
+ dic = {}
+ for k, v in vars(obj).items():
+ try:
+ vars(v)
+ except TypeError:
+ dic.update({k: v})
+ return dic
+
+
+def set_dict_value(dic, keys, value):
+ return_dic = dic
+
+ for key in keys.split('.'):
+
+ return_dic.setdefault(key, {})
+ if key == keys.split('.')[-1]:
+ return_dic[key] = value
+ else:
+ return_dic = return_dic[key]
+ return dic
+
+
+def get_free_port(ip):
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+ while True:
+ port = random.randint(5000, 10000)
+ if s.connect_ex((ip, port)) != 0:
+ return port
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/network_services/vnf_generic/vnf/base.py b/yardstick/network_services/vnf_generic/vnf/base.py
index 1d770f724..2df6037f3 100644
--- a/yardstick/network_services/vnf_generic/vnf/base.py
+++ b/yardstick/network_services/vnf_generic/vnf/base.py
@@ -96,7 +96,6 @@ class GenericVNF(object):
return address.version
def _ip_to_hex(self, ip_addr):
- ip_to_convert = ip_addr.split(".")
ip_x = ip_addr
if self.get_ip_version(ip_addr) == 4:
ip_to_convert = ip_addr.split(".")
diff --git a/yardstick/network_services/vnf_generic/vnfdgen.py b/yardstick/network_services/vnf_generic/vnfdgen.py
index 40cc14a49..b56a91915 100644
--- a/yardstick/network_services/vnf_generic/vnfdgen.py
+++ b/yardstick/network_services/vnf_generic/vnfdgen.py
@@ -48,7 +48,7 @@ def generate_vnfd(vnf_model, node):
rendered_vnfd = render(vnf_model, **node)
# This is done to get rid of issues with serializing node
del node["get"]
- filled_vnfd = yaml.load(rendered_vnfd)
+ filled_vnfd = yaml.safe_load(rendered_vnfd)
return filled_vnfd
diff --git a/yardstick/orchestrator/heat.py b/yardstick/orchestrator/heat.py
index 7958b1cfb..95ca0ad2e 100644
--- a/yardstick/orchestrator/heat.py
+++ b/yardstick/orchestrator/heat.py
@@ -231,13 +231,15 @@ name (i.e. %s).\
}
def add_network(self, name, physical_network='physnet1', provider=None,
- segmentation_id=None):
+ 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:
self.resources[name] = {
'type': 'OS::Neutron::Net',
- 'properties': {'name': name}
+ 'properties': {
+ 'name': name,
+ }
}
else:
self.resources[name] = {
@@ -245,12 +247,15 @@ name (i.e. %s).\
'properties': {
'name': name,
'network_type': 'vlan',
- 'physical_network': physical_network
- }
+ 'physical_network': physical_network,
+ },
}
if segmentation_id:
- seg_id_dit = {'segmentation_id': segmentation_id}
- self.resources[name]["properties"].update(seg_id_dit)
+ 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"""
@@ -262,8 +267,9 @@ name (i.e. %s).\
'policies': policies}
}
- def add_subnet(self, name, network, cidr):
- """add to the template a Neutron Subnet"""
+ def add_subnet(self, name, network, cidr, enable_dhcp='true', gateway_ip=None):
+ """add to the template a Neutron Subnet
+ """
log.debug("adding Neutron::Subnet '%s' in network '%s', cidr '%s'",
name, network, cidr)
self.resources[name] = {
@@ -272,9 +278,12 @@ name (i.e. %s).\
'properties': {
'name': name,
'cidr': cidr,
- 'network_id': {'get_resource': network}
+ 'network_id': {'get_resource': network},
+ 'enable_dhcp': enable_dhcp,
}
}
+ if gateway_ip is not None:
+ self.resources[name]['properties']['gateway_ip'] = gateway_ip
self._template['outputs'][name] = {
'description': 'subnet %s ID' % name,
@@ -316,9 +325,10 @@ name (i.e. %s).\
}
}
- def add_port(self, name, network_name, subnet_name, sec_group_id=None,
- provider=None):
- """add to the template a named Neutron Port"""
+ def add_port(self, name, network_name, subnet_name, 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)
self.resources[name] = {
@@ -341,6 +351,10 @@ name (i.e. %s).\
self.resources[name]['properties']['security_groups'] = \
[sec_group_id]
+ if allowed_address_pairs:
+ self.resources[name]['properties'][
+ 'allowed_address_pairs'] = allowed_address_pairs
+
self._template['outputs'][name] = {
'description': 'Address for interface %s' % name,
'value': {'get_attr': [name, 'fixed_ips', 0, 'ip_address']}
@@ -534,6 +548,7 @@ name (i.e. %s).\
}
HEAT_WAIT_LOOP_INTERVAL = 2
+ HEAT_CREATE_COMPLETE_STATUS = u'CREATE_COMPLETE'
def create(self, block=True, timeout=3600):
"""
@@ -558,10 +573,13 @@ name (i.e. %s).\
if not block:
self.outputs = stack.outputs = {}
+ end_time = time.time()
+ log.info("Created stack '%s' in %.3e secs",
+ self.name, end_time - start_time)
return stack
time_limit = start_time + timeout
- for status in iter(self.status, u'CREATE_COMPLETE'):
+ for status in iter(self.status, self.HEAT_CREATE_COMPLETE_STATUS):
log.debug("stack state %s", status)
if status == u'CREATE_FAILED':
stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason
@@ -574,7 +592,7 @@ name (i.e. %s).\
end_time = time.time()
outputs = heat_client.stacks.get(self.uuid).outputs
- log.info("Created stack '%s' in %d secs",
+ log.info("Created stack '%s' in %.3e secs",
self.name, end_time - start_time)
# keep outputs as unicode
diff --git a/yardstick/orchestrator/kubernetes.py b/yardstick/orchestrator/kubernetes.py
new file mode 100644
index 000000000..6d7045f58
--- /dev/null
+++ b/yardstick/orchestrator/kubernetes.py
@@ -0,0 +1,130 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+from yardstick.common import utils
+from yardstick.common import kubernetes_utils as k8s_utils
+
+
+class KubernetesObject(object):
+
+ def __init__(self, name, **kwargs):
+ super(KubernetesObject, self).__init__()
+ self.name = name
+ self.image = kwargs.get('image', 'openretriever/yardstick')
+ self.command = [kwargs.get('command', '/bin/bash')]
+ self.args = kwargs.get('args', [])
+ self.ssh_key = kwargs.get('ssh_key', 'yardstick_key')
+
+ self.volumes = []
+
+ self.template = {
+ "apiVersion": "v1",
+ "kind": "ReplicationController",
+ "metadata": {
+ "name": ""
+ },
+ "spec": {
+ "replicas": 1,
+ "template": {
+ "metadata": {
+ "labels": {
+ "app": ""
+ }
+ },
+ "spec": {
+ "containers": [],
+ "volumes": []
+ }
+ }
+ }
+ }
+
+ self._change_value_according_name(name)
+ self._add_containers()
+ self._add_ssh_key_volume()
+ self._add_volumes()
+
+ def get_template(self):
+ return self.template
+
+ def _change_value_according_name(self, name):
+ utils.set_dict_value(self.template, 'metadata.name', name)
+
+ utils.set_dict_value(self.template,
+ 'spec.template.metadata.labels.app',
+ name)
+
+ def _add_containers(self):
+ containers = [self._add_container()]
+ utils.set_dict_value(self.template,
+ 'spec.template.spec.containers',
+ containers)
+
+ def _add_container(self):
+ container_name = '{}-container'.format(self.name)
+ ssh_key_mount_path = "/root/.ssh/"
+
+ container = {
+ "args": self.args,
+ "command": self.command,
+ "image": self.image,
+ "name": container_name,
+ "volumeMounts": [
+ {
+ "mountPath": ssh_key_mount_path,
+ "name": self.ssh_key
+ }
+ ]
+ }
+
+ return container
+
+ def _add_volumes(self):
+ utils.set_dict_value(self.template,
+ 'spec.template.spec.volumes',
+ self.volumes)
+
+ def _add_volume(self, volume):
+ self.volumes.append(volume)
+
+ def _add_ssh_key_volume(self):
+ key_volume = {
+ "configMap": {
+ "name": self.ssh_key
+ },
+ "name": self.ssh_key
+ }
+ self._add_volume(key_volume)
+
+
+class KubernetesTemplate(object):
+
+ def __init__(self, name, template_cfg):
+ self.name = name
+ self.ssh_key = '{}-key'.format(name)
+
+ self.rcs = [self._get_rc_name(rc) for rc in template_cfg]
+ self.k8s_objs = [KubernetesObject(self._get_rc_name(rc),
+ ssh_key=self.ssh_key,
+ **cfg)
+ for rc, cfg in template_cfg.items()]
+ self.pods = []
+
+ def _get_rc_name(self, rc_name):
+ return '{}-{}'.format(rc_name, self.name)
+
+ def get_rc_pods(self):
+ resp = k8s_utils.get_pod_list()
+ self.pods = [p.metadata.name for p in resp.items for s in self.rcs
+ if p.metadata.name.startswith(s)]
+
+ return self.pods
diff --git a/yardstick/vTC/apexlake/tests/deployment_unit_test.py b/yardstick/vTC/apexlake/tests/deployment_unit_test.py
index 5a9178f53..1ff4225d6 100644
--- a/yardstick/vTC/apexlake/tests/deployment_unit_test.py
+++ b/yardstick/vTC/apexlake/tests/deployment_unit_test.py
@@ -130,6 +130,7 @@ class DummyDeploymentUnit(mut.DeploymentUnit):
raise Exception
+@mock.patch("experimental_framework.deployment_unit.time")
class TestDeploymentUnit(unittest.TestCase):
def setUp(self):
@@ -140,7 +141,7 @@ class TestDeploymentUnit(unittest.TestCase):
@mock.patch('experimental_framework.heat_manager.HeatManager',
side_effect=DummyHeatManager)
- def test_constructor_for_sanity(self, mock_heat_manager):
+ def test_constructor_for_sanity(self, mock_heat_manager, mock_time):
du = mut.DeploymentUnit(dict())
self.assertTrue(isinstance(du.heat_manager, DummyHeatManager))
mock_heat_manager.assert_called_once_with(dict())
@@ -150,7 +151,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManager)
@mock.patch('os.path.isfile')
def test_deploy_heat_template_for_failure(self, mock_os_is_file,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
mock_os_is_file.return_value = False
du = mut.DeploymentUnit(dict())
template_file = ''
@@ -163,7 +164,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManager)
@mock.patch('os.path.isfile')
def test_deploy_heat_template_for_success(self, mock_os_is_file,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
mock_os_is_file.return_value = True
du = mut.DeploymentUnit(dict())
template_file = ''
@@ -178,7 +179,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManagerComplete)
@mock.patch('os.path.isfile')
def test_deploy_heat_template_2_for_success(self, mock_os_is_file,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
mock_os_is_file.return_value = True
du = mut.DeploymentUnit(dict())
template_file = ''
@@ -196,7 +197,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyDeploymentUnit)
def test_deploy_heat_template_3_for_success(self, mock_dep_unit,
mock_os_is_file,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
mock_os_is_file.return_value = True
du = mut.DeploymentUnit(dict())
template_file = ''
@@ -212,7 +213,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManagerFailed)
@mock.patch('os.path.isfile')
def test_deploy_heat_template_for_success_2(self, mock_os_is_file,
- mock_heat_manager, mock_log):
+ mock_heat_manager, mock_log, mock_time):
mock_os_is_file.return_value = True
du = DummyDeploymentUnit(dict())
template_file = ''
@@ -226,7 +227,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManagerDestroy)
@mock.patch('experimental_framework.common.LOG')
def test_destroy_heat_template_for_success(self, mock_log,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
openstack_credentials = dict()
du = mut.DeploymentUnit(openstack_credentials)
du.deployed_stacks = ['stack']
@@ -238,14 +239,14 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManagerDestroyException)
@mock.patch('experimental_framework.common.LOG')
def test_destroy_heat_template_for_success_2(self, mock_log,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
openstack_credentials = dict()
du = mut.DeploymentUnit(openstack_credentials)
du.deployed_stacks = ['stack']
stack_name = 'stack'
self.assertFalse(du.destroy_heat_template(stack_name))
- def test_destroy_all_deployed_stacks_for_success(self):
+ def test_destroy_all_deployed_stacks_for_success(self, mock_time):
du = DeploymentUnitDestroy()
du.destroy_all_deployed_stacks()
self.assertTrue(du.destroy_heat_template())
@@ -254,7 +255,7 @@ class TestDeploymentUnit(unittest.TestCase):
side_effect=DummyHeatManagerReiteration)
@mock.patch('os.path.isfile')
def test_deploy_heat_template_for_success_3(self, mock_os_is_file,
- mock_heat_manager):
+ mock_heat_manager, mock_time):
mock_os_is_file.return_value = True
du = mut.DeploymentUnit(dict())
template = 'template_reiteration'
diff --git a/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py b/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py
index 96ead5ef7..9fa860ab4 100644
--- a/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py
+++ b/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py
@@ -359,6 +359,7 @@ class MockRunCommand:
return MockRunCommand.ret_val_finalization
+@mock.patch('experimental_framework.packet_generators.dpdk_packet_generator.time')
class TestDpdkPacketGenOthers(unittest.TestCase):
def setUp(self):
@@ -370,7 +371,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
@mock.patch('experimental_framework.packet_generators.'
'dpdk_packet_generator.DpdkPacketGenerator.'
'_cores_configuration')
- def test__get_core_nics_for_failure(self, mock_cores_configuration):
+ def test__get_core_nics_for_failure(self, mock_cores_configuration, mock_time):
mock_cores_configuration.return_value = None
self.assertRaises(ValueError, mut.DpdkPacketGenerator._get_core_nics,
'', '')
@@ -379,7 +380,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
'dpdk_packet_generator.DpdkPacketGenerator.'
'_cores_configuration')
def test__get_core_nics_one_nic_for_success(self,
- mock_cores_configuration):
+ mock_cores_configuration, mock_time):
mock_cores_configuration.return_value = 'ret_val'
expected = 'ret_val'
output = mut.DpdkPacketGenerator._get_core_nics(1, 'coremask')
@@ -390,7 +391,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
'dpdk_packet_generator.DpdkPacketGenerator.'
'_cores_configuration')
def test__get_core_nics_two_nics_for_success(self,
- mock_cores_configuration):
+ mock_cores_configuration, mock_time):
mock_cores_configuration.return_value = 'ret_val'
expected = 'ret_val'
output = mut.DpdkPacketGenerator._get_core_nics(2, 'coremask')
@@ -398,7 +399,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
mock_cores_configuration.assert_called_once_with('coremask', 1, 2, 2)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_success(self, mock_is_file):
+ def test__init_input_validation_for_success(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_file_0 = 'pcap_file_0'
@@ -419,7 +420,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
variables), None)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_failure(self, mock_is_file):
+ def test__init_input_validation_for_failure(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_file_0 = 'pcap_file_0'
@@ -440,7 +441,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_failure_2(self, mock_is_file):
+ def test__init_input_validation_for_failure_2(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_directory = None
@@ -461,7 +462,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_failure_3(self, mock_is_file):
+ def test__init_input_validation_for_failure_3(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_directory = 'directory'
@@ -482,7 +483,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_failure_4(self, mock_is_file):
+ def test__init_input_validation_for_failure_4(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_directory = 'directory'
@@ -503,7 +504,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile')
- def test__init_input_validation_for_failure_5(self, mock_is_file):
+ def test__init_input_validation_for_failure_5(self, mock_is_file, mock_time):
mock_is_file.return_value = True
pcap_directory = 'directory'
@@ -524,7 +525,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile', side_effect=[False])
- def test__init_input_validation_for_failure_6(self, mock_is_file):
+ def test__init_input_validation_for_failure_6(self, mock_is_file, mock_time):
# mock_is_file.return_value = False
pcap_directory = 'directory'
@@ -545,7 +546,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile', side_effect=[True, False])
- def test__init_input_validation_for_failure_7(self, mock_is_file):
+ def test__init_input_validation_for_failure_7(self, mock_is_file, mock_time):
pcap_directory = 'directory'
pcap_file_0 = 'pcap_file_0'
pcap_file_1 = 'pcap_file_1'
@@ -564,7 +565,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.path.isfile', side_effect=[True, True, False])
- def test__init_input_validation_for_failure_8(self, mock_is_file):
+ def test__init_input_validation_for_failure_8(self, mock_is_file, mock_time):
pcap_directory = 'directory'
pcap_file_0 = 'pcap_file_0'
pcap_file_1 = 'pcap_file_1'
@@ -583,13 +584,13 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
lua_script, pcap_directory, lua_directory, variables)
@mock.patch('os.chdir')
- def test__chdir_for_success(self, mock_os_chdir):
+ def test__chdir_for_success(self, mock_os_chdir, mock_time):
mut.DpdkPacketGenerator._chdir('directory')
mock_os_chdir.assert_called_once_with('directory')
@mock.patch('experimental_framework.common.run_command',
side_effect=MockRunCommand.mock_run_command)
- def test__init_physical_nics_for_success(self, mock_run_command):
+ def test__init_physical_nics_for_success(self, mock_run_command, mock_time):
dpdk_interfaces = 1
dpdk_vars = dict()
@@ -608,7 +609,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
@mock.patch('experimental_framework.common.run_command',
side_effect=MockRunCommand.mock_run_command)
- def test__init_physical_nics_for_success_2(self, mock_run_command):
+ def test__init_physical_nics_for_success_2(self, mock_run_command, mock_time):
dpdk_interfaces = 2
dpdk_vars = dict()
@@ -626,7 +627,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
[True, True, True, True, True, True])
@mock.patch('experimental_framework.common.run_command')
- def test__init_physical_nics_for_failure(self, mock_run_command):
+ def test__init_physical_nics_for_failure(self, mock_run_command, mock_time):
dpdk_interfaces = 3
dpdk_vars = dict()
self.assertRaises(ValueError, self.mut._init_physical_nics,
@@ -634,7 +635,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
@mock.patch('experimental_framework.common.run_command',
side_effect=MockRunCommand.mock_run_command_finalization)
- def test__finalize_physical_nics_for_success(self, mock_run_command):
+ def test__finalize_physical_nics_for_success(self, mock_run_command, mock_time):
dpdk_interfaces = 1
dpdk_vars = dict()
dpdk_vars[conf_file.CFSP_DPDK_DPDK_DIRECTORY] = 'dpdk_directory/'
@@ -652,7 +653,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
@mock.patch('experimental_framework.common.run_command',
side_effect=MockRunCommand.mock_run_command_finalization)
- def test__finalize_physical_nics_for_success_2(self, mock_run_command):
+ def test__finalize_physical_nics_for_success_2(self, mock_run_command, mock_time):
dpdk_interfaces = 2
dpdk_vars = dict()
dpdk_vars[conf_file.CFSP_DPDK_DPDK_DIRECTORY] = 'dpdk_directory/'
@@ -668,34 +669,34 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
self.assertEqual(MockRunCommand.mock_run_command_finalization(),
[True, True, True, True, True, True])
- def test__finalize_physical_nics_for_failure(self):
+ def test__finalize_physical_nics_for_failure(self, mock_time):
dpdk_interfaces = 0
dpdk_vars = dict()
self.assertRaises(ValueError, self.mut._finalize_physical_nics,
dpdk_interfaces, dpdk_vars)
- def test__cores_configuration_for_success(self):
+ def test__cores_configuration_for_success(self, mock_time):
coremask = '1f'
expected = '[2:1].0,[4:3].1'
output = mut.DpdkPacketGenerator._cores_configuration(coremask,
1, 2, 2)
self.assertEqual(expected, output)
- def test__cores_configuration_for_success_2(self):
+ def test__cores_configuration_for_success_2(self, mock_time):
coremask = '1f'
expected = '2.0,[4:3].1'
output = mut.DpdkPacketGenerator._cores_configuration(coremask,
1, 1, 2)
self.assertEqual(expected, output)
- def test__cores_configuration_for_success_3(self):
+ def test__cores_configuration_for_success_3(self, mock_time):
coremask = '1f'
expected = '[3:2].0,4.1'
output = mut.DpdkPacketGenerator._cores_configuration(coremask,
1, 2, 1)
self.assertEqual(expected, output)
- def test__cores_configuration_for_failure(self):
+ def test__cores_configuration_for_failure(self, mock_time):
coremask = '1'
self.assertRaises(ValueError,
mut.DpdkPacketGenerator._cores_configuration,
@@ -703,7 +704,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase):
@mock.patch('experimental_framework.common.LOG')
@mock.patch('experimental_framework.common.run_command')
- def test__change_vlan_for_success(self, mock_run_command, mock_log):
+ def test__change_vlan_for_success(self, mock_run_command, mock_log, mock_time):
mut.DpdkPacketGenerator._change_vlan('/directory/', 'pcap_file', '10')
expected_param = '/directory/vlan_tag.sh /directory/pcap_file 10'
mock_run_command.assert_called_with(expected_param)
diff --git a/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py b/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py
index 2bd8b7b38..69c5d745e 100644
--- a/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py
+++ b/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py
@@ -257,6 +257,7 @@ class InstantiationValidationInitTest(unittest.TestCase):
self.assertEqual(dummy_os_kill('', '', True), [1, 1])
self.assertEqual(dummy_run_command('', True), [1, 1, 0, 0, 0])
+ @mock.patch('experimental_framework.benchmarks.instantiation_validation_benchmark.time')
@mock.patch('os.chdir')
@mock.patch('experimental_framework.common.run_command',
side_effect=dummy_run_command_2)
@@ -265,7 +266,7 @@ class InstantiationValidationInitTest(unittest.TestCase):
'InstantiationValidationBenchmark._get_pids')
@mock.patch('os.kill', side_effect=dummy_os_kill)
def test__init_packet_checker_for_success(self, mock_kill, mock_pids,
- mock_run_command, mock_chdir):
+ mock_run_command, mock_chdir, mock_time):
global command_counter
command_counter = [0, 0, 0, 0, 0]
mock_pids.return_value = [1234, 4321]
@@ -314,13 +315,14 @@ class InstantiationValidationInitTest(unittest.TestCase):
self.assertEqual(dummy_replace_in_file('', '', '', True),
[0, 0, 0, 1, 1, 1])
+ @mock.patch('experimental_framework.benchmarks.instantiation_validation_benchmark.time')
@mock.patch('experimental_framework.common.LOG')
@mock.patch('experimental_framework.packet_generators.'
'dpdk_packet_generator.DpdkPacketGenerator',
side_effect=DummyDpdkPacketGenerator)
@mock.patch('experimental_framework.common.get_dpdk_pktgen_vars')
def test_run_for_success(self, mock_common_get_vars, mock_pktgen,
- mock_log):
+ mock_log, mock_time):
rval = dict()
rval[cfs.CFSP_DPDK_BUS_SLOT_NIC_2] = 'bus_2'
rval[cfs.CFSP_DPDK_NAME_IF_2] = 'if_2'