Merge "Testing live migration using qemu"
authorRex Lee <limingjiang@huawei.com>
Mon, 31 Jul 2017 06:10:06 +0000 (06:10 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Mon, 31 Jul 2017 06:10:06 +0000 (06:10 +0000)
260 files changed:
.gitignore
ansible/inventory.ini
ansible/migrate_pinning_setup.yaml [new file with mode: 0644]
ansible/migrate_pinning_teardown.yaml [new file with mode: 0644]
ansible/roles/backup_nova_conf/tasks/main.yaml [moved from tests/ci/scp_storperf_admin-rc.sh with 56% similarity]
ansible/roles/create_flavor/tasks/main.yaml [new file with mode: 0644]
ansible/roles/delete_flavor/tasks/main.yaml [new file with mode: 0644]
ansible/roles/recover_nova_conf/tasks/main.yaml [new file with mode: 0644]
ansible/roles/restart_nova_service/tasks/main.yaml [new file with mode: 0644]
ansible/roles/set_flavor_property/tasks/main.yaml [new file with mode: 0644]
ansible/roles/set_nova_conf/tasks/main.yaml [new file with mode: 0644]
api/__init__.py
api/base.py [deleted file]
api/database/v2/__init__.py [new file with mode: 0644]
api/database/v2/handlers.py [new file with mode: 0644]
api/database/v2/models.py [new file with mode: 0644]
api/resources/asynctask.py [deleted file]
api/resources/case_docs.py [deleted file]
api/resources/env_action.py [deleted file]
api/resources/release_action.py [deleted file]
api/resources/results.py [deleted file]
api/resources/samples_action.py [deleted file]
api/resources/testcases.py [deleted file]
api/resources/testsuites_action.py [deleted file]
api/resources/v1/__init__.py [new file with mode: 0644]
api/resources/v1/asynctasks.py [new file with mode: 0644]
api/resources/v1/env.py [new file with mode: 0644]
api/resources/v1/results.py [new file with mode: 0644]
api/resources/v1/testcases.py [new file with mode: 0644]
api/resources/v1/testsuites.py [new file with mode: 0644]
api/resources/v2/__init__.py [new file with mode: 0644]
api/resources/v2/containers.py [new file with mode: 0644]
api/resources/v2/environments.py [new file with mode: 0644]
api/resources/v2/images.py [new file with mode: 0644]
api/resources/v2/openrcs.py [new file with mode: 0644]
api/resources/v2/pods.py [new file with mode: 0644]
api/resources/v2/projects.py [new file with mode: 0644]
api/resources/v2/tasks.py [new file with mode: 0644]
api/resources/v2/testcases.py [new file with mode: 0644]
api/resources/v2/testsuites.py [new file with mode: 0644]
api/resources/write_hosts.py [new file with mode: 0644]
api/server.py
api/urls.py
api/utils/common.py [deleted file]
api/utils/thread.py
api/views.py [deleted file]
docker/Dockerfile
docker/nginx.sh [new file with mode: 0755]
docker/supervisor.sh [new file with mode: 0755]
docker/uwsgi.sh [moved from api/api-prepare.sh with 59% similarity]
docs/release/release-notes/release-notes.rst
etc/yardstick/nodes/compass_sclab_virtual/pod.yaml
etc/yardstick/yardstick.conf.sample
gui/Gruntfile.js [new file with mode: 0644]
gui/app/404.html [new file with mode: 0644]
gui/app/favicon.ico [new file with mode: 0644]
gui/app/images/back.png [new file with mode: 0644]
gui/app/images/checkno.png [new file with mode: 0644]
gui/app/images/checkyes.png [new file with mode: 0644]
gui/app/images/close.png [new file with mode: 0644]
gui/app/images/loading.gif [new file with mode: 0644]
gui/app/images/loading2.gif [new file with mode: 0644]
gui/app/images/statusno.png [new file with mode: 0644]
gui/app/images/statusyes.png [new file with mode: 0644]
gui/app/images/url.json [new file with mode: 0644]
gui/app/images/yeoman.png [new file with mode: 0644]
gui/app/index.html [new file with mode: 0644]
gui/app/robots.txt [new file with mode: 0644]
gui/app/scripts/app.js [new file with mode: 0644]
gui/app/scripts/controllers/container.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/content.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/detail.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/image.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/main.js [new file with mode: 0644]
gui/app/scripts/controllers/pod.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/project.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/projectDetail.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/report.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/suitecreate.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/suitedetail.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/task.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/taskModify.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/testcase.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/testcasedetail.controller.js [new file with mode: 0644]
gui/app/scripts/controllers/testsuit.controller.js [new file with mode: 0644]
gui/app/scripts/factory/main.factory.js [new file with mode: 0644]
gui/app/scripts/router.config.js [new file with mode: 0644]
gui/app/styles/main.css [new file with mode: 0644]
gui/app/views/container.html [new file with mode: 0644]
gui/app/views/content.html [new file with mode: 0644]
gui/app/views/environmentDetail.html [new file with mode: 0644]
gui/app/views/environmentList.html [new file with mode: 0644]
gui/app/views/layout/footer.html [new file with mode: 0644]
gui/app/views/layout/header.html [new file with mode: 0644]
gui/app/views/layout/sideNav.html [new file with mode: 0644]
gui/app/views/layout/sideNav2.html [new file with mode: 0644]
gui/app/views/main.html [new file with mode: 0644]
gui/app/views/main2.html [new file with mode: 0644]
gui/app/views/modal/chooseContainer.html [new file with mode: 0644]
gui/app/views/modal/deleteConfirm.html [new file with mode: 0644]
gui/app/views/modal/environmentDialog.html [new file with mode: 0644]
gui/app/views/modal/projectCreate.html [new file with mode: 0644]
gui/app/views/modal/suiteName.html [new file with mode: 0644]
gui/app/views/modal/taskCreate.html [new file with mode: 0644]
gui/app/views/podupload.html [new file with mode: 0644]
gui/app/views/projectList.html [new file with mode: 0644]
gui/app/views/projectdetail.html [new file with mode: 0644]
gui/app/views/report.html [new file with mode: 0644]
gui/app/views/suite.html [new file with mode: 0644]
gui/app/views/suitedetail.html [new file with mode: 0644]
gui/app/views/taskList.html [new file with mode: 0644]
gui/app/views/taskmodify.html [new file with mode: 0644]
gui/app/views/testcasechoose.html [new file with mode: 0644]
gui/app/views/testcasedetail.html [new file with mode: 0644]
gui/app/views/testcaselist.html [new file with mode: 0644]
gui/app/views/uploadImage.html [new file with mode: 0644]
gui/bower.json [new file with mode: 0644]
gui/gui.sh [new file with mode: 0755]
gui/package.json [new file with mode: 0644]
gui/test/.jshintrc [new file with mode: 0644]
gui/test/karma.conf.js [new file with mode: 0644]
gui/test/spec/controllers/main.js [new file with mode: 0644]
install.sh
plugin/CI/storperf.yaml
requirements.txt
samples/fio_volume.yaml [new file with mode: 0644]
samples/ping.yaml
samples/ping_k8s.yaml [new file with mode: 0644]
samples/storperf.yaml
samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml [new file with mode: 0644]
tests/ci/prepare_env.sh
tests/ci/scp_storperf_files.sh [new file with mode: 0644]
tests/ci/yardstick-verify
tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml
tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml [new file with mode: 0644]
tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml
tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml
tests/opnfv/test_suites/opnfv_os-odl_l2-nofeature-ha_daily.yaml
tests/opnfv/test_suites/opnfv_os-odl_l2-nofeature-noha_daily.yaml
tests/opnfv/test_suites/opnfv_os-odl_l3-nofeature-ha_daily.yaml
tests/opnfv/test_suites/opnfv_os-odl_l3-nofeature-noha_daily.yaml
tests/opnfv/test_suites/opnfv_os-odl_l3-ovs-ha_daily.yaml
tests/unit/apiserver/utils/test_common.py [deleted file]
tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/nodes_sample_new.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/nodes_sample_ovs.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/ovs_sample_password.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt [new file with mode: 0644]
tests/unit/benchmark/contexts/sriov_sample_password.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml [new file with mode: 0644]
tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt [new file with mode: 0644]
tests/unit/benchmark/contexts/test_heat.py
tests/unit/benchmark/contexts/test_kubernetes.py [new file with mode: 0644]
tests/unit/benchmark/contexts/test_model.py
tests/unit/benchmark/contexts/test_node.py
tests/unit/benchmark/contexts/test_ovsdpdk.py [new file with mode: 0644]
tests/unit/benchmark/contexts/test_sriov.py [new file with mode: 0644]
tests/unit/benchmark/contexts/test_standalone.py
tests/unit/benchmark/core/test_task.py
tests/unit/benchmark/runner/test_base.py
tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py
tests/unit/benchmark/scenarios/availability/test_monitor_command.py
tests/unit/benchmark/scenarios/availability/test_monitor_multi.py
tests/unit/benchmark/scenarios/availability/test_util.py
tests/unit/benchmark/scenarios/compute/test_lmbench.py
tests/unit/benchmark/scenarios/compute/test_ramspeed.py
tests/unit/benchmark/scenarios/lib/__init__.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_add_memory_load.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_check_numa_info.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_check_value.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_get_numa_info.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_get_server.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/lib/test_get_server_ip.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/networking/test_iperf3.py
tests/unit/benchmark/scenarios/networking/test_nstat.py
tests/unit/benchmark/scenarios/networking/test_ping.py
tests/unit/benchmark/scenarios/networking/test_pktgen.py
tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py
tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py
tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
tests/unit/benchmark/scenarios/storage/test_storperf.py
tests/unit/cmd/test_NSBperf.py
tests/unit/common/test_utils.py
tests/unit/dispatcher/test_influxdb.py
tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py
tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py
tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py
tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py
tests/unit/orchestrator/test_heat.py
tests/unit/orchestrator/test_kubernetes.py [new file with mode: 0644]
yardstick/benchmark/contexts/base.py
yardstick/benchmark/contexts/dummy.py
yardstick/benchmark/contexts/heat.py
yardstick/benchmark/contexts/kubernetes.py [new file with mode: 0644]
yardstick/benchmark/contexts/model.py
yardstick/benchmark/contexts/node.py
yardstick/benchmark/contexts/ovsdpdk.py [new file with mode: 0644]
yardstick/benchmark/contexts/sriov.py [new file with mode: 0644]
yardstick/benchmark/contexts/standalone.py
yardstick/benchmark/core/plugin.py
yardstick/benchmark/core/task.py
yardstick/benchmark/core/testsuite.py [new file with mode: 0644]
yardstick/benchmark/scenarios/availability/actionplayers.py
yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py
yardstick/benchmark/scenarios/availability/attacker/attacker_process.py
yardstick/benchmark/scenarios/availability/attacker_conf.yaml
yardstick/benchmark/scenarios/availability/director.py
yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash [new file with mode: 0755]
yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash [new file with mode: 0755]
yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash
yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash
yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash
yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash [new file with mode: 0755]
yardstick/benchmark/scenarios/availability/monitor/monitor_command.py
yardstick/benchmark/scenarios/availability/monitor_conf.yaml
yardstick/benchmark/scenarios/availability/operation/baseoperation.py
yardstick/benchmark/scenarios/availability/operation/operation_general.py
yardstick/benchmark/scenarios/availability/scenario_general.py
yardstick/benchmark/scenarios/availability/util.py
yardstick/benchmark/scenarios/base.py
yardstick/benchmark/scenarios/compute/lmbench.py
yardstick/benchmark/scenarios/compute/ramspeed.py
yardstick/benchmark/scenarios/lib/__init__.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/add_memory_load.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/check_numa_info.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/check_value.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/get_migrate_target_host.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/get_numa_info.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/get_server.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/get_server_ip.py [new file with mode: 0644]
yardstick/benchmark/scenarios/lib/migrate.py [new file with mode: 0644]
yardstick/benchmark/scenarios/networking/iperf3.py
yardstick/benchmark/scenarios/networking/ping.py
yardstick/benchmark/scenarios/networking/pktgen.py
yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash
yardstick/benchmark/scenarios/networking/vnf_generic.py
yardstick/benchmark/scenarios/storage/fio.py
yardstick/benchmark/scenarios/storage/storperf.py
yardstick/cmd/NSBperf.py
yardstick/cmd/commands/task.py
yardstick/common/constants.py
yardstick/common/kubernetes_utils.py [new file with mode: 0644]
yardstick/common/openstack_utils.py
yardstick/common/utils.py
yardstick/dispatcher/base.py
yardstick/dispatcher/influxdb.py
yardstick/network_services/vnf_generic/vnf/base.py
yardstick/network_services/vnf_generic/vnfdgen.py
yardstick/orchestrator/heat.py
yardstick/orchestrator/kubernetes.py [new file with mode: 0644]
yardstick/resources/scripts/install/storperf.bash
yardstick/resources/scripts/remove/storperf.bash
yardstick/vTC/apexlake/tests/deployment_unit_test.py
yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py
yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py

index f2c8fd9..d98b4a0 100644 (file)
@@ -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?
index 440e625..79a6ee0 100644 (file)
@@ -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 (file)
index 0000000..ee5eef3
--- /dev/null
@@ -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 (file)
index 0000000..13dd611
--- /dev/null
@@ -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"
similarity index 56%
rename from tests/ci/scp_storperf_admin-rc.sh
rename to ansible/roles/backup_nova_conf/tasks/main.yaml
index 7c3896d..ca95bac 100644 (file)
@@ -1,7 +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
@@ -9,8 +8,11 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
-# 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.200.1:/root/ &> /dev/null
+- 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 (file)
index 0000000..9b776c6
--- /dev/null
@@ -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 (file)
index 0000000..dc9fc88
--- /dev/null
@@ -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 (file)
index 0000000..44919d2
--- /dev/null
@@ -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 (file)
index 0000000..2bdce65
--- /dev/null
@@ -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 (file)
index 0000000..f989887
--- /dev/null
@@ -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 (file)
index 0000000..ae665c5
--- /dev/null
@@ -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
index c6cbbf1..c5aefff 100644 (file)
@@ -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 (file)
index 0f1e76a..0000000
+++ /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 (file)
index 0000000..e69de29
diff --git a/api/database/v2/handlers.py b/api/database/v2/handlers.py
new file mode 100644 (file)
index 0000000..1bc32bf
--- /dev/null
@@ -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 (file)
index 0000000..1e85559
--- /dev/null
@@ -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 (file)
index 39b47c0..0000000
+++ /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 (file)
index 289410d..0000000
+++ /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 (file)
index 2ea64ef..0000000
+++ /dev/null
@@ -1,424 +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 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')
-    hosts_list = ['\n{} {}'.format(ip, host_name)
-                  for host_name, ip in hosts_ip.items()]
-    LOG.debug('Writing: %s', hosts_list)
-    with open(consts.ETC_HOSTS, 'a') as f:
-        f.writelines(hosts_list)
-    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 (file)
index 9871c1f..0000000
+++ /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 (file)
index 692e00c..0000000
+++ /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 (file)
index 10b9980..0000000
+++ /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 (file)
index 6ee15ef..0000000
+++ /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 (file)
index e37eacc..0000000
+++ /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 (file)
index 0000000..e69de29
diff --git a/api/resources/v1/asynctasks.py b/api/resources/v1/asynctasks.py
new file mode 100644 (file)
index 0000000..759df21
--- /dev/null
@@ -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 (file)
index 0000000..8943db3
--- /dev/null
@@ -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 (file)
index 0000000..0493b43
--- /dev/null
@@ -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 (file)
index 0000000..f159472
--- /dev/null
@@ -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 (file)
index 0000000..5f72c2e
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/api/resources/v2/containers.py b/api/resources/v2/containers.py
new file mode 100644 (file)
index 0000000..66dc941
--- /dev/null
@@ -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 (file)
index 0000000..f021a3c
--- /dev/null
@@ -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 (file)
index 0000000..8359e10
--- /dev/null
@@ -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 (file)
index 0000000..cb506d0
--- /dev/null
@@ -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 (file)
index 0000000..f2316d3
--- /dev/null
@@ -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 (file)
index 0000000..2ff61d0
--- /dev/null
@@ -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 (file)
index 0000000..885a190
--- /dev/null
@@ -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 (file)
index 0000000..b47a8f6
--- /dev/null
@@ -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 (file)
index 0000000..56ad473
--- /dev/null
@@ -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/resources/write_hosts.py b/api/resources/write_hosts.py
new file mode 100644 (file)
index 0000000..e4b6984
--- /dev/null
@@ -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
+##############################################################################
+from __future__ import absolute_import
+
+import sys
+import json
+
+
+def write_hosts(hosts_ip):
+    hosts_list = ('\n{} {}'.format(ip, host_name)
+                  for host_name, ip in hosts_ip.items())
+    with open("/etc/hosts", 'a') as f:
+        f.writelines(hosts_list)
+        f.write("\n")
+
+if __name__ == "__main__":
+    write_hosts(json.load(sys.stdin))
index d39c445..158b8a5 100644 (file)
@@ -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')
index 13c6c76..3fef91a 100644 (file)
@@ -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 (file)
index eda9c17..0000000
+++ /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
index 2106548..20bd07a 100644 (file)
@@ -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 (file)
index 9c9ca4e..0000000
+++ /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)
index 96a5d77..b48a550 100644 (file)
@@ -22,6 +22,7 @@ ENV IMAGE_DIR /home/opnfv/images/
 # Yardstick repo
 ENV YARDSTICK_REPO_DIR ${REPOS_DIR}/yardstick
 ENV RELENG_REPO_DIR ${REPOS_DIR}/releng
+ENV STORPERF_REPO_DIR ${REPOS_DIR}/storperf
 
 RUN apt-get update && apt-get install -y git python-setuptools python-pip
 RUN easy_install -U setuptools==30.0.0
@@ -32,9 +33,11 @@ RUN mkdir -p ${REPOS_DIR}
 RUN git config --global http.sslVerify false
 RUN git clone --depth 1 -b $BRANCH https://gerrit.opnfv.org/gerrit/yardstick ${YARDSTICK_REPO_DIR}
 RUN git clone --depth 1 https://gerrit.opnfv.org/gerrit/releng ${RELENG_REPO_DIR}
+RUN git clone --depth 1 -b $BRANCH https://gerrit.opnfv.org/gerrit/storperf ${STORPERF_REPO_DIR}
 
 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 (executable)
index 0000000..26937d1
--- /dev/null
@@ -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 (executable)
index 0000000..b67de22
--- /dev/null
@@ -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
similarity index 59%
rename from api/api-prepare.sh
rename to docker/uwsgi.sh
index 7632d9d..cf46123 100755 (executable)
@@ -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
 
 # 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
index d89f9ed..6d55ada 100644 (file)
@@ -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     |
index 5c55740..e306d0d 100644 (file)
 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
index 5720511..1157b9d 100644 (file)
@@ -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 (file)
index 0000000..171d65a
--- /dev/null
@@ -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 (file)
index 0000000..899828a
--- /dev/null
@@ -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 (file)
index 0000000..6527905
Binary files /dev/null and b/gui/app/favicon.ico differ
diff --git a/gui/app/images/back.png b/gui/app/images/back.png
new file mode 100644 (file)
index 0000000..917c86e
Binary files /dev/null and b/gui/app/images/back.png differ
diff --git a/gui/app/images/checkno.png b/gui/app/images/checkno.png
new file mode 100644 (file)
index 0000000..7c68419
Binary files /dev/null and b/gui/app/images/checkno.png differ
diff --git a/gui/app/images/checkyes.png b/gui/app/images/checkyes.png
new file mode 100644 (file)
index 0000000..ef60283
Binary files /dev/null and b/gui/app/images/checkyes.png differ
diff --git a/gui/app/images/close.png b/gui/app/images/close.png
new file mode 100644 (file)
index 0000000..0d2c142
Binary files /dev/null and b/gui/app/images/close.png differ
diff --git a/gui/app/images/loading.gif b/gui/app/images/loading.gif
new file mode 100644 (file)
index 0000000..b04dd11
Binary files /dev/null and b/gui/app/images/loading.gif differ
diff --git a/gui/app/images/loading2.gif b/gui/app/images/loading2.gif
new file mode 100644 (file)
index 0000000..9d15344
Binary files /dev/null and b/gui/app/images/loading2.gif differ
diff --git a/gui/app/images/statusno.png b/gui/app/images/statusno.png
new file mode 100644 (file)
index 0000000..ace4a45
Binary files /dev/null and b/gui/app/images/statusno.png differ
diff --git a/gui/app/images/statusyes.png b/gui/app/images/statusyes.png
new file mode 100644 (file)
index 0000000..d88a99e
Binary files /dev/null and b/gui/app/images/statusyes.png differ
diff --git a/gui/app/images/url.json b/gui/app/images/url.json
new file mode 100644 (file)
index 0000000..f16c4e0
--- /dev/null
@@ -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 (file)
index 0000000..92497ad
Binary files /dev/null and b/gui/app/images/yeoman.png differ
diff --git a/gui/app/index.html b/gui/app/index.html
new file mode 100644 (file)
index 0000000..5592656
--- /dev/null
@@ -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 (file)
index 0000000..4d521f9
--- /dev/null
@@ -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 (file)
index 0000000..ecb642c
--- /dev/null
@@ -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 (file)
index 0000000..6c2ccd8
--- /dev/null
@@ -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 (file)
index 0000000..d2bc19e
--- /dev/null
@@ -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 (file)
index 0000000..3e2eaa1
--- /dev/null
@@ -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 (file)
index 0000000..53acff4
--- /dev/null
@@ -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 (file)
index 0000000..e3e880e
--- /dev/null
@@ -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 (file)
index 0000000..3ef2368
--- /dev/null
@@ -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 (file)
index 0000000..0a7b8b9
--- /dev/null
@@ -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 (file)
index 0000000..4ab4a05
--- /dev/null
@@ -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 (file)
index 0000000..9b6b595
--- /dev/null
@@ -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 (file)
index 0000000..4a7b6fe
--- /dev/null
@@ -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 (file)
index 0000000..0dd39c3
--- /dev/null
@@ -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 (file)
index 0000000..05546f9
--- /dev/null
@@ -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 (file)
index 0000000..757d658
--- /dev/null
@@ -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 (file)
index 0000000..616ceb4
--- /dev/null
@@ -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 (file)
index 0000000..4e824ca
--- /dev/null
@@ -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 (file)
index 0000000..abc9095
--- /dev/null
@@ -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 (file)
index 0000000..f8e9df9
--- /dev/null
@@ -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 (file)
index 0000000..b429542
--- /dev/null
@@ -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 (file)
index 0000000..e13a66b
--- /dev/null
@@ -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 (file)
index 0000000..b3d78bf
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/gui/app/views/environmentDetail.html b/gui/app/views/environmentDetail.html
new file mode 100644 (file)
index 0000000..4d5f21c
--- /dev/null
@@ -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 (file)
index 0000000..29273a7
--- /dev/null
@@ -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 (file)
index 0000000..cfdf74a
--- /dev/null
@@ -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 (file)
index 0000000..033322a
--- /dev/null
@@ -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 (file)
index 0000000..42dcbbc
--- /dev/null
@@ -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 (file)
index 0000000..104a9c6
--- /dev/null
@@ -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 (file)
index 0000000..d5f7a3a
--- /dev/null
@@ -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 (file)
index 0000000..661d604
--- /dev/null
@@ -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 (file)
index 0000000..4b857b2
--- /dev/null
@@ -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 (file)
index 0000000..1659b88
--- /dev/null
@@ -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 (file)
index 0000000..a5b88d2
--- /dev/null
@@ -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 (file)
index 0000000..74839e7
--- /dev/null
@@ -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 (file)
index 0000000..981d242
--- /dev/null
@@ -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 (file)
index 0000000..e7812cf
--- /dev/null
@@ -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 (file)
index 0000000..99e83ac
--- /dev/null
@@ -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 (file)
index 0000000..ea6e63d
--- /dev/null
@@ -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 (file)
index 0000000..ff61c5f
--- /dev/null
@@ -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 (file)
index 0000000..78ac6a0
--- /dev/null
@@ -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 (file)
index 0000000..8e13483
--- /dev/null
@@ -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 (file)
index 0000000..6122f65
--- /dev/null
@@ -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 (file)
index 0000000..159fed5
--- /dev/null
@@ -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 (file)
index 0000000..a4593f7
--- /dev/null
@@ -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 (file)
index 0000000..12bdb83
--- /dev/null
@@ -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 (file)
index 0000000..43a5153
--- /dev/null
@@ -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 (file)
index 0000000..3e8cfcc
--- /dev/null
@@ -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 (file)
index 0000000..17ccfdb
--- /dev/null
@@ -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 (file)
index 0000000..6da3bee
--- /dev/null
@@ -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 (executable)
index 0000000..12a1492
--- /dev/null
@@ -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 (file)
index 0000000..b85c754
--- /dev/null
@@ -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 (file)
index 0000000..b2ce4ef
--- /dev/null
@@ -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 (file)
index 0000000..a9ab3a8
--- /dev/null
@@ -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 (file)
index 0000000..27e0a5a
--- /dev/null
@@ -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);
+  });
+});
index ad14b8e..e82ae02 100755 (executable)
@@ -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
index 7ac920a..70915f6 100644 (file)
@@ -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.200.1
+  ip: local
   user: root
   password: root
index f283b99..2bcc4df 100644 (file)
@@ -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/fio_volume.yaml b/samples/fio_volume.yaml
new file mode 100644 (file)
index 0000000..edb3837
--- /dev/null
@@ -0,0 +1,74 @@
+##############################################################################
+# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+---
+# Sample benchmark task config file
+# measure storage performance using fio
+#
+# For this sample just like running the command below on the test vm and
+# getting benchmark info back to the yardstick.
+#
+# sudo fio -filename=/home/ubuntu/data.raw -bs=4k -ipdepth=1 -rw=rw \
+#          -ramp_time=10 -runtime=60 -name=yardstick-fio -ioengine=libaio \
+#          -direct=1 -group_reporting -numjobs=1 -time_based \
+#          --output-format=json
+
+schema: "yardstick:task:0.1"
+
+{% set rw = rw or "randrw" %}
+{% set bs = bs or "8k" %}
+{% set size = size or "100g" %}
+{% set rwmixwrite = rwmixwrite or "50" %}
+{% set numjobs = numjobs or "1" %}
+{% set direct = direct or "1" %}
+
+scenarios:
+-
+  type: Fio
+  options:
+    filename: /dev/vdb
+    bs: {{bs}}
+    rw: {{rw}}
+    size: {{size}}
+    rwmixwrite: {{rwmixwrite}}
+    numjobs: {{numjobs}}
+    direct: {{direct}}
+    ramp_time: 10
+
+  host: fio.fio_volume
+
+  runner:
+    type: Duration
+    duration: 60
+    interval: 1
+
+  sla:
+    read_bw: 6000
+    read_iops: 1500
+    read_lat: 500.1
+    write_bw: 6000
+    write_iops: 1500
+    write_lat: 500.1
+    action: monitor
+
+context:
+  name: fio_volume
+  image: yardstick-image
+  flavor: yardstick-flavor
+  user: ubuntu
+  servers:
+    fio:
+      volume:
+        name: fio-volume
+        size: 200
+      volume_mountpoint: "/dev/vdb"
+      floating_ip: true
+  networks:
+    test:
+      cidr: "10.0.1.0/24"
+      port_security_enabled: true
index 0c1783c..6a19d26 100644 (file)
@@ -12,6 +12,9 @@
 
 schema: "yardstick:task:0.1"
 
+{% set provider = provider or none %}
+{% set physical_network = physical_network or 'physnet1' %}
+{% set segmentation_id = segmentation_id or none %}
 scenarios:
 -
   type: Ping
@@ -49,4 +52,10 @@ context:
   networks:
     test:
       cidr: '10.0.1.0/24'
-
+      {% if provider == "vlan" %}
+      provider: {{provider}}
+      physical_network: {{physical_network}}
+        {% if segmentation_id %}
+      segmentation_id: {{segmentation_id}}
+        {% endif %}
+      {% endif %}
diff --git a/samples/ping_k8s.yaml b/samples/ping_k8s.yaml
new file mode 100644 (file)
index 0000000..503fe6a
--- /dev/null
@@ -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']
index 5000759..2ea0221 100644 (file)
@@ -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 (file)
index 0000000..7654b0f
--- /dev/null
@@ -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
index e8f287b..c3ee4c7 100755 (executable)
@@ -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_admin-rc.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
new file mode 100644 (file)
index 0000000..71306eb
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+##############################################################################
+# 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
+##############################################################################
+
+# Copy storperf_admin-rc to deployment location.
+
+ssh_options="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
+
+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
index 096ea53..16598df 100755 (executable)
@@ -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"
index 4c7fdab..f5ccb25 100644 (file)
@@ -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 (file)
index 0000000..2804f25
--- /dev/null
@@ -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'
index 326fdf5..ef4f02c 100644 (file)
@@ -21,6 +21,7 @@ scenarios:
   options:
     agent_count: 1
     agent_image: "Ubuntu-16.04"
+    agent_flavor: "storperf"
     public_network: {{public_network}}
     volume_size: 4
     block_sizes: "4096"
index ba1a93c..dea44c8 100644 (file)
@@ -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:
index 674c9a8..b8b8c46 100644 (file)
@@ -22,6 +22,8 @@ test_cases:
   file_name: opnfv_yardstick_tc010.yaml
 -
   file_name: opnfv_yardstick_tc011.yaml
+  constraint:
+      installer: compass
 -
   file_name: opnfv_yardstick_tc012.yaml
 -
index 0f00bd7..cd352cc 100644 (file)
@@ -22,6 +22,8 @@ test_cases:
   file_name: opnfv_yardstick_tc010.yaml
 -
   file_name: opnfv_yardstick_tc011.yaml
+  constraint:
+      installer: compass
 -
   file_name: opnfv_yardstick_tc012.yaml
 -
index 0459a13..a503db9 100644 (file)
@@ -22,6 +22,8 @@ test_cases:
   file_name: opnfv_yardstick_tc010.yaml
 -
   file_name: opnfv_yardstick_tc011.yaml
+  constraint:
+      installer: compass
 -
   file_name: opnfv_yardstick_tc012.yaml
 -
index d7429b1..e83aaec 100644 (file)
@@ -22,6 +22,8 @@ test_cases:
   file_name: opnfv_yardstick_tc010.yaml
 -
   file_name: opnfv_yardstick_tc011.yaml
+  constraint:
+      installer: compass
 -
   file_name: opnfv_yardstick_tc012.yaml
 -
index 4411b2c..4165c54 100644 (file)
@@ -22,6 +22,8 @@ test_cases:
   file_name: opnfv_yardstick_tc010.yaml
 -
   file_name: opnfv_yardstick_tc011.yaml
+  constraint:
+      installer: compass
 -
   file_name: opnfv_yardstick_tc012.yaml
 -
diff --git a/tests/unit/apiserver/utils/test_common.py b/tests/unit/apiserver/utils/test_common.py
deleted file mode 100644 (file)
index ad81cb7..0000000
+++ /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 (file)
index 0000000..306915c
--- /dev/null
@@ -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 (file)
index 0000000..65449c9
--- /dev/null
@@ -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 (file)
index 0000000..a400bec
--- /dev/null
@@ -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 (file)
index 0000000..55ff2e7
--- /dev/null
@@ -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 (file)
index 0000000..b1da1ea
--- /dev/null
@@ -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 (file)
index 0000000..c02849a
--- /dev/null
@@ -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 (file)
index 0000000..b1da1ea
--- /dev/null
@@ -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 (file)
index 0000000..896ec33
--- /dev/null
@@ -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 (file)
index 0000000..f0eec86
--- /dev/null
@@ -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 (file)
index 0000000..4f60e46
--- /dev/null
@@ -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 (file)
index 0000000..faa4967
--- /dev/null
@@ -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 (file)
index 0000000..f0eec86
--- /dev/null
@@ -0,0 +1 @@
+some content
\ No newline at end of file
index 3dadd48..ae57402 100644 (file)
@@ -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 (file)
index 0000000..f47c07a
--- /dev/null
@@ -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()
index 3fb186b..5444c2b 100644 (file)
@@ -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,13 @@ 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.vnic_type = 'normal'
         mock_network.subnet_stack_name = 'some-network-stack-subnet'
         mock_network.provider = 'sriov'
         mock_network.external_network = 'ext_net'
@@ -231,8 +250,10 @@ class ServerTestCase(unittest.TestCase):
             'some-server-some-network-port',
             mock_network.stack_name,
             mock_network.subnet_stack_name,
+            mock_network.vnic_type,
             sec_group_id=self.mock_context.secgroup_name,
-            provider=mock_network.provider)
+            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 +311,13 @@ 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.vnic_type = 'normal'
+        mock_network.configure_mock(name='some-network', stack_name='some-network-stack',
+                                    subnet_stack_name='some-network-stack-subnet',
+                                    provider='some-provider')
 
         test_server._add_instance(mock_template, 'ServerFlavor-2',
                                   [mock_network], 'hints')
@@ -303,8 +326,10 @@ class ServerTestCase(unittest.TestCase):
             'ServerFlavor-2-some-network-port',
             mock_network.stack_name,
             mock_network.subnet_stack_name,
+            mock_network.vnic_type,
             provider=mock_network.provider,
-            sec_group_id=self.mock_context.secgroup_name)
+            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',
index 4b35ca4..d5ce8c5 100644 (file)
@@ -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 (file)
index 0000000..ac25ec8
--- /dev/null
@@ -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 (file)
index 0000000..a8641a2
--- /dev/null
@@ -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()
index 687ef73..1fc7403 100644 (file)
 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()
+
index b64bb8e..7f61753 100644 (file)
@@ -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):
index 6e72fa5..0313ef8 100644 (file)
@@ -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)
 
index 28b27c7..cc17960 100644 (file)
@@ -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()
index 2ed4be7..6a9b3b1 100644 (file)
@@ -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()
 
index f8d12bd..b59ec6c 100644 (file)
@@ -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}
         }
index bb0e6bc..2e4fff4 100644 (file)
@@ -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')
index 08f5da3..65939c6 100644 (file)
@@ -68,8 +68,7 @@ class LmbenchTestCase(unittest.TestCase):
         sample_output = '[{"latency": 4.944, "size": 0.00049}]'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         l.run(self.result)
-        expected_result = jsonutils.loads(
-            '{"latencies": ' + sample_output + "}")
+        expected_result = {"latencies0.latency": 4.944, "latencies0.size": 0.00049}
         self.assertEqual(self.result, expected_result)
 
     def test_successful_bandwidth_run_no_sla(self, mock_ssh):
@@ -105,8 +104,7 @@ class LmbenchTestCase(unittest.TestCase):
         sample_output = '[{"latency": 4.944, "size": 0.00049}]'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         l.run(self.result)
-        expected_result = jsonutils.loads(
-            '{"latencies": ' + sample_output + "}")
+        expected_result = {"latencies0.latency": 4.944, "latencies0.size": 0.00049}
         self.assertEqual(self.result, expected_result)
 
     def test_successful_bandwidth_run_sla(self, mock_ssh):
@@ -191,3 +189,10 @@ class LmbenchTestCase(unittest.TestCase):
 
         mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR')
         self.assertRaises(RuntimeError, l.run, self.result)
+
+
+def main():
+    unittest.main()
+
+if __name__ == '__main__':
+    main()
index 85d4964..4f71fbb 100644 (file)
@@ -18,6 +18,7 @@ import unittest
 import mock
 from oslo_serialization import jsonutils
 
+from yardstick.common import utils
 from yardstick.benchmark.scenarios.compute import ramspeed
 
 
@@ -77,7 +78,7 @@ class RamspeedTestCase(unittest.TestCase):
  "Block_size(kb)": 32768, "Bandwidth(MBps)": 8340.85}]}'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         r.run(self.result)
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         self.assertEqual(self.result, expected_result)
 
     def test_ramspeed_successful_run_sla(self, mock_ssh):
@@ -113,7 +114,7 @@ class RamspeedTestCase(unittest.TestCase):
  "Block_size(kb)": 32768, "Bandwidth(MBps)": 8340.85}]}'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         r.run(self.result)
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         self.assertEqual(self.result, expected_result)
 
     def test_ramspeed_unsuccessful_run_sla(self, mock_ssh):
@@ -179,7 +180,7 @@ class RamspeedTestCase(unittest.TestCase):
  "Bandwidth(MBps)": 9401.58}]}'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         r.run(self.result)
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         self.assertEqual(self.result, expected_result)
 
     def test_ramspeed_mem_successful_run_sla(self, mock_ssh):
@@ -200,7 +201,7 @@ class RamspeedTestCase(unittest.TestCase):
  "Bandwidth(MBps)": 9401.58}]}'
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
         r.run(self.result)
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         self.assertEqual(self.result, expected_result)
 
     def test_ramspeed_mem_unsuccessful_run_sla(self, mock_ssh):
diff --git a/tests/unit/benchmark/scenarios/lib/__init__.py b/tests/unit/benchmark/scenarios/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
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 (file)
index 0000000..bda07f7
--- /dev/null
@@ -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 (file)
index 0000000..bdf1e66
--- /dev/null
@@ -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 (file)
index 0000000..21e83f8
--- /dev/null
@@ -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 (file)
index 0000000..f046c92
--- /dev/null
@@ -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 (file)
index 0000000..e7ba3ca
--- /dev/null
@@ -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 (file)
index 0000000..aebbf54
--- /dev/null
@@ -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 (file)
index 0000000..3d20d54
--- /dev/null
@@ -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()
index 45ff1b7..3312453 100644 (file)
@@ -19,6 +19,7 @@ import unittest
 import mock
 from oslo_serialization import jsonutils
 
+from yardstick.common import utils
 from yardstick.benchmark.scenarios.networking import iperf3
 
 
@@ -81,7 +82,7 @@ class IperfTestCase(unittest.TestCase):
 
         sample_output = self._read_sample_output(self.output_name_tcp)
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         p.run(result)
         self.assertEqual(result, expected_result)
 
@@ -100,7 +101,7 @@ class IperfTestCase(unittest.TestCase):
 
         sample_output = self._read_sample_output(self.output_name_tcp)
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         p.run(result)
         self.assertEqual(result, expected_result)
 
@@ -135,7 +136,7 @@ class IperfTestCase(unittest.TestCase):
 
         sample_output = self._read_sample_output(self.output_name_udp)
         mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '')
-        expected_result = jsonutils.loads(sample_output)
+        expected_result = utils.flatten_dict_key(jsonutils.loads(sample_output))
         p.run(result)
         self.assertEqual(result, expected_result)
 
index 1317167..fe44cfd 100644 (file)
@@ -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
index 5269309..0635324 100644 (file)
@@ -45,7 +45,7 @@ class PingTestCase(unittest.TestCase):
 
         mock_ssh.SSH.from_node().execute.return_value = (0, '100', '')
         p.run(result)
-        self.assertEqual(result, {'rtt': {'ares': 100.0}})
+        self.assertEqual(result, {'rtt.ares': 100.0})
 
     @mock.patch('yardstick.benchmark.scenarios.networking.ping.ssh')
     def test_ping_successful_sla(self, mock_ssh):
@@ -61,7 +61,7 @@ class PingTestCase(unittest.TestCase):
 
         mock_ssh.SSH.from_node().execute.return_value = (0, '100', '')
         p.run(result)
-        self.assertEqual(result, {'rtt': {'ares': 100.0}})
+        self.assertEqual(result, {'rtt.ares': 100.0})
 
     @mock.patch('yardstick.benchmark.scenarios.networking.ping.ssh')
     def test_ping_unsuccessful_sla(self, mock_ssh):
index d4eb124..2914c8e 100644 (file)
@@ -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()
index e6998e4..b4b8752 100644 (file)
@@ -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},
index 0178165..d340970 100644 (file)
@@ -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},
         }
index 111e781..c9cd7fe 100644 (file)
@@ -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)
index 00054d5..7b16bb3 100644 (file)
@@ -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
         }
 
index 5bd248a..e1b4da7 100644 (file)
@@ -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):
index 8f52b53..e21e5fa 100644 (file)
@@ -109,6 +109,92 @@ class GetParaFromYaml(unittest.TestCase):
         return file_path
 
 
+class CommonUtilTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.data = {
+            "benchmark": {
+                "data": {
+                    "mpstat": {
+                        "cpu0": {
+                            "%sys": "0.00",
+                            "%idle": "99.00"
+                        },
+                        "loadavg": [
+                            "1.09",
+                            "0.29"
+                        ]
+                    },
+                    "rtt": "1.03"
+                }
+            }
+        }
+
+    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'
+        # need to sort for assert to work
+        line = ",".join(sorted(line.split(',')))
+        flattened_data = utils.flatten_dict_key(
+            self.data['benchmark']['data'])
+        result = ",".join(
+            ("=".join(item) for item in sorted(flattened_data.items())))
+        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()
 
index a5d9b07..7ebe8c9 100644 (file)
@@ -76,23 +76,6 @@ class InfluxdbDispatcherTestCase(unittest.TestCase):
             },
             "runner_id": 8921
         }
-        self.data3 = {
-            "benchmark": {
-                "data": {
-                    "mpstat": {
-                        "cpu0": {
-                            "%sys": "0.00",
-                            "%idle": "99.00"
-                        },
-                        "loadavg": [
-                            "1.09",
-                            "0.29"
-                        ]
-                    },
-                    "rtt": "1.03"
-                }
-            }
-        }
 
         self.yardstick_conf = {'dispatcher_influxdb': {}}
 
@@ -113,18 +96,6 @@ class InfluxdbDispatcherTestCase(unittest.TestCase):
         }
         self.assertEqual(influxdb.flush_result_data(data), 0)
 
-    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'
-        # need to sort for assert to work
-        line = ",".join(sorted(line.split(',')))
-        influxdb = InfluxdbDispatcher(self.yardstick_conf)
-        flattened_data = influxdb._dict_key_flatten(
-            self.data3['benchmark']['data'])
-        result = ",".join(
-            [k + "=" + v for k, v in sorted(flattened_data.items())])
-        self.assertEqual(result, line)
-
     def test__get_nano_timestamp(self):
         influxdb = InfluxdbDispatcher(self.yardstick_conf)
         results = {'timestamp': '1451461248.925574'}
index 88df778..0c88ee8 100644 (file)
@@ -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)
index 4ea1808..bca0780 100644 (file)
@@ -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):
index ca84219..a1d4ca1 100644 (file)
@@ -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"
index b69e537..54934c2 100644 (file)
 #
 
 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())
index 3b38733..1510704 100644 (file)
@@ -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
@@ -125,9 +135,9 @@ class HeatTemplateTestCase(unittest.TestCase):
         heat_template.add_subnet("subnet2", "network2", "cidr2")
         heat_template.add_router("router1", "gw1", "subnet1")
         heat_template.add_router_interface("router_if1", "router1", "subnet1")
-        heat_template.add_port("port1", "network1", "subnet1")
-        heat_template.add_port("port2", "network2", "subnet2", sec_group_id="sec_group1",provider="not-sriov")
-        heat_template.add_port("port3", "network2", "subnet2", sec_group_id="sec_group1",provider="sriov")
+        heat_template.add_port("port1", "network1", "subnet1", "normal")
+        heat_template.add_port("port2", "network2", "subnet2", "normal", sec_group_id="sec_group1",provider="not-sriov")
+        heat_template.add_port("port3", "network2", "subnet2", "normal", sec_group_id="sec_group1",provider="sriov")
         heat_template.add_floating_ip("floating_ip1", "network1", "port1", "router_if1")
         heat_template.add_floating_ip("floating_ip2", "network2", "port2", "router_if2", "foo-secgroup")
         heat_template.add_floating_ip_association("floating_ip1_association", "floating_ip1", "port1")
@@ -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 (file)
index 0000000..51718ab
--- /dev/null
@@ -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()
index 0be2eee..e362c6a 100644 (file)
@@ -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)
index c658d32..8ae4b65 100644 (file)
@@ -37,3 +37,6 @@ class DummyContext(Context):
 
     def _get_server(self, attr_name):
         return None
+
+    def _get_network(self, attr_name):
+        return None
index fed8fc3..d5349ea 100644 (file)
@@ -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 (file)
index 0000000..a39f631
--- /dev/null
@@ -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
index 5077a97..aed1a3f 100644 (file)
@@ -104,15 +104,30 @@ 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.vnic_type = attrs.get('vnic_type', 'normal')
+        self.allowed_address_pairs = attrs.get('allowed_address_pairs', [])
+        try:
+            # we require 'null' or '' to disable setting gateway_ip
+            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)
 
@@ -170,6 +185,14 @@ class Server(Object):     # pragma: no cover
             self.placement_groups.append(pg)
             pg.add_member(self.stack_name)
 
+        self.volume = None
+        if "volume" in attrs:
+            self.volume = attrs.get("volume")
+
+        self.volume_mountpoint = None
+        if "volume_mountpoint" in attrs:
+            self.volume_mountpoint = attrs.get("volume_mountpoint")
+
         # support servergroup attr
         self.server_group = None
         sg = attrs.get("server_group")
@@ -233,10 +256,17 @@ 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,
+                              network.vnic_type, sec_group_id=sec_group_id,
+                              provider=network.provider,
+                              allowed_address_pairs=network.allowed_address_pairs)
             port_name_list.append(port_name)
 
             if self.floating_ip:
@@ -247,7 +277,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(
@@ -263,6 +293,17 @@ class Server(Object):     # pragma: no cover
             else:
                 self.flavor_name = self.flavor
 
+        if self.volume:
+            if isinstance(self.volume, dict):
+                self.volume["name"] = \
+                    self.volume.setdefault("name", server_name + "-volume")
+                template.add_volume(**self.volume)
+                template.add_volume_attachment(server_name, self.volume["name"],
+                                               mountpoint=self.volume_mountpoint)
+            else:
+                template.add_volume_attachment(server_name, self.volume,
+                                               mountpoint=self.volume_mountpoint)
+
         template.add_server(server_name, self.image, flavor=self.flavor_name,
                             flavors=self.context.flavors,
                             ports=port_name_list,
index baa1cf5..b3f0aca 100644 (file)
@@ -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 (file)
index 0000000..cf5529d
--- /dev/null
@@ -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 (file)
index 0000000..fe27d25
--- /dev/null
@@ -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))
index 78eaac7..2bc1f37 100644 (file)
@@ -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)
index 7f67a04..c8d0865 100644 (file)
@@ -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)
index a985c86..b2da7a2 100644 (file)
@@ -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"""
@@ -255,11 +270,7 @@ class Task(object):     # pragma: no cover
 
             self.outputs.update(runner.get_output())
             result.extend(runner.get_result())
-
-            if status != 0:
-                raise RuntimeError
             print("Background task ended")
-
         return result
 
     def atexit_handler(self):
@@ -326,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"])
@@ -375,10 +388,10 @@ class TaskParser(object):       # pragma: no cover
                 tc_fit_installer = constraint.get('installer', None)
                 LOG.info("cur_pod:%s, cur_installer:%s,tc_constraints:%s",
                          cur_pod, cur_installer, constraint)
-                if cur_pod and tc_fit_pod and cur_pod not in tc_fit_pod:
+                if (cur_pod is None) or (tc_fit_pod and cur_pod not in tc_fit_pod):
                     return False
-                if cur_installer and tc_fit_installer and \
-                        cur_installer not in tc_fit_installer:
+                if (cur_installer is None) or (tc_fit_installer and cur_installer
+                                               not in tc_fit_installer):
                     return False
         return True
 
@@ -522,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"]
@@ -577,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 (file)
index 0000000..e3940a0
--- /dev/null
@@ -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
index 4206264..c5e199b 100644 (file)
@@ -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()
index 22de0b6..50d44c1 100644 (file)
@@ -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
 
index f7ab23d..cb171ea 100644 (file)
@@ -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
 
index b8c34ad..aa144ab 100644 (file)
@@ -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
index e0d05eb..c9187c3 100644 (file)
@@ -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 (executable)
index 0000000..6d2f4dd
--- /dev/null
@@ -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 (executable)
index 0000000..b0b86ab
--- /dev/null
@@ -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
index aee516e..7408409 100644 (file)
@@ -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/start_lxc_service.bash b/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash
new file mode 100755 (executable)
index 0000000..36a6739
--- /dev/null
@@ -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
index a0777f9..a9488cc 100644 (file)
@@ -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
 
index 5114492..a08347d 100644 (file)
@@ -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:
index be286b8..88ca9e2 100644 (file)
@@ -58,6 +58,7 @@ class BaseOperation(object):
         self.key = ''
         self._config = config
         self._context = context
+        self.intermediate_variables = {}
 
     @staticmethod
     def get_operation_cls(type):
index 8fd387e..af1ae74 100644 (file)
@@ -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",
index 28bec8a..17ad79f 100644 (file)
@@ -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'])
index eadbfa5..6fef622 100644 (file)
@@ -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
index 5d3c36c..3cb138d 100644 (file)
@@ -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
index c99fc98..801f7fa 100644 (file)
@@ -15,6 +15,7 @@ import pkg_resources
 from oslo_serialization import jsonutils
 
 import yardstick.ssh as ssh
+from yardstick.common import utils
 from yardstick.benchmark.scenarios import base
 
 LOG = logging.getLogger(__name__)
@@ -127,30 +128,32 @@ class Lmbench(base.Scenario):
         if status:
             raise RuntimeError(stderr)
 
+        lmbench_result = {}
         if test_type == 'latency':
-            result.update(
+            lmbench_result.update(
                 {"latencies": jsonutils.loads(stdout)})
         else:
-            result.update(jsonutils.loads(stdout))
+            lmbench_result.update(jsonutils.loads(stdout))
+        result.update(utils.flatten_dict_key(lmbench_result))
 
         if "sla" in self.scenario_cfg:
             sla_error = ""
             if test_type == 'latency':
                 sla_max_latency = int(self.scenario_cfg['sla']['max_latency'])
-                for t_latency in result["latencies"]:
+                for t_latency in lmbench_result["latencies"]:
                     latency = t_latency['latency']
                     if latency > sla_max_latency:
                         sla_error += "latency %f > sla:max_latency(%f); " \
                             % (latency, sla_max_latency)
             elif test_type == 'bandwidth':
                 sla_min_bw = int(self.scenario_cfg['sla']['min_bandwidth'])
-                bw = result["bandwidth(MBps)"]
+                bw = lmbench_result["bandwidth(MBps)"]
                 if bw < sla_min_bw:
                     sla_error += "bandwidth %f < " \
                                  "sla:min_bandwidth(%f)" % (bw, sla_min_bw)
             elif test_type == 'latency_for_cache':
                 sla_latency = float(self.scenario_cfg['sla']['max_latency'])
-                cache_latency = float(result['L1cache'])
+                cache_latency = float(lmbench_result['L1cache'])
                 if sla_latency < cache_latency:
                     sla_error += "latency %f > sla:max_latency(%f); " \
                         % (cache_latency, sla_latency)
index 850ee59..ca64935 100644 (file)
@@ -14,6 +14,7 @@ import pkg_resources
 from oslo_serialization import jsonutils
 
 import yardstick.ssh as ssh
+from yardstick.common import utils
 from yardstick.benchmark.scenarios import base
 
 LOG = logging.getLogger(__name__)
@@ -128,12 +129,13 @@ class Ramspeed(base.Scenario):
         if status:
             raise RuntimeError(stderr)
 
-        result.update(jsonutils.loads(stdout))
+        ramspeed_result = jsonutils.loads(stdout)
+        result.update(utils.flatten_dict_key(ramspeed_result))
 
         if "sla" in self.scenario_cfg:
             sla_error = ""
             sla_min_bw = int(self.scenario_cfg['sla']['min_bandwidth'])
-            for i in result["Result"]:
+            for i in ramspeed_result["Result"]:
                 bw = i["Bandwidth(MBps)"]
                 if bw < sla_min_bw:
                     sla_error += "Bandwidth %f < " \
diff --git a/yardstick/benchmark/scenarios/lib/__init__.py b/yardstick/benchmark/scenarios/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/yardstick/benchmark/scenarios/lib/add_memory_load.py b/yardstick/benchmark/scenarios/lib/add_memory_load.py
new file mode 100644 (file)
index 0000000..26cf140
--- /dev/null
@@ -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 (file)
index 0000000..59a4754
--- /dev/null
@@ -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 (file)
index 0000000..7590760
--- /dev/null
@@ -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 (file)
index 0000000..c19d96d
--- /dev/null
@@ -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 (file)
index 0000000..4e4a44d
--- /dev/null
@@ -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 (file)
index 0000000..fcf47c8
--- /dev/null
@@ -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 (file)
index 0000000..1eeeb7f
--- /dev/null
@@ -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 (file)
index 0000000..116bae6
--- /dev/null
@@ -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
index 334f3a9..a3d2737 100644 (file)
@@ -19,6 +19,7 @@ import pkg_resources
 from oslo_serialization import jsonutils
 
 import yardstick.ssh as ssh
+from yardstick.common import utils
 from yardstick.benchmark.scenarios import base
 
 LOG = logging.getLogger(__name__)
@@ -49,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"
 
@@ -121,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)
@@ -131,8 +149,8 @@ For more info see http://software.es.net/iperf
         # Note: convert all ints to floats in order to avoid
         # schema conflicts in influxdb. We probably should add
         # a format func in the future.
-        result.update(
-            jsonutils.loads(stdout, parse_int=float))
+        iperf_result = jsonutils.loads(stdout, parse_int=float)
+        result.update(utils.flatten_dict_key(iperf_result))
 
         if "sla" in self.scenario_cfg:
             sla_iperf = self.scenario_cfg["sla"]
@@ -141,7 +159,7 @@ For more info see http://software.es.net/iperf
 
                 # convert bits per second to bytes per second
                 bit_per_second = \
-                    int(result["end"]["sum_received"]["bits_per_second"])
+                    int(iperf_result["end"]["sum_received"]["bits_per_second"])
                 bytes_per_second = bit_per_second / 8
                 assert bytes_per_second >= sla_bytes_per_second, \
                     "bytes_per_second %d < sla:bytes_per_second (%d); " % \
@@ -149,7 +167,7 @@ For more info see http://software.es.net/iperf
             else:
                 sla_jitter = float(sla_iperf["jitter"])
 
-                jitter_ms = float(result["end"]["sum"]["jitter_ms"])
+                jitter_ms = float(iperf_result["end"]["sum"]["jitter_ms"])
                 assert jitter_ms <= sla_jitter, \
                     "jitter_ms  %f > sla:jitter %f; " % \
                     (jitter_ms, sla_jitter)
index a929e53..6a7927d 100644 (file)
@@ -15,6 +15,7 @@ import pkg_resources
 import logging
 
 import yardstick.ssh as ssh
+from yardstick.common import utils
 from yardstick.benchmark.scenarios import base
 
 LOG = logging.getLogger(__name__)
@@ -57,8 +58,8 @@ class Ping(base.Scenario):
         destination = self.context_cfg['target'].get('ipaddr', '127.0.0.1')
         dest_list = [s.strip() for s in destination.split(',')]
 
-        result["rtt"] = {}
-        rtt_result = result["rtt"]
+        rtt_result = {}
+        ping_result = {"rtt": rtt_result}
 
         for pos, dest in enumerate(dest_list):
             if 'targets' in self.scenario_cfg:
@@ -88,6 +89,7 @@ class Ping(base.Scenario):
                         (rtt_result[target_vm_name], sla_max_rtt)
             else:
                 LOG.error("ping '%s' '%s' timeout", options, target_vm)
+        result.update(utils.flatten_dict_key(ping_result))
 
 
 def _test():    # pragma: no cover
index e6aa7e5..8ca1ca6 100644 (file)
@@ -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()
index 4224c5a..e338a1b 100644 (file)
@@ -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
index 594edea..9607e30 100644 (file)
@@ -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)
 
index ad34817..b99e342 100644 (file)
@@ -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
index c10118a..f0b2361 100644 (file)
@@ -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))
index f158d57..4e7590e 100755 (executable)
@@ -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 """
@@ -117,10 +115,10 @@ class YardstickNSCli(object):
         and generates final report in rst format.
         """
 
+        tc_name = os.path.splitext(test_case)[0]
         report_caption = '{}\n{} ({})\n{}\n\n'.format(
             '================================================================',
-            'Performance report for',
-            os.path.splitext(test_case)[0].upper(),
+            'Performance report for', tc_name.upper(),
             '================================================================')
         print(report_caption)
         if os.path.isfile("/tmp/yardstick.out"):
@@ -129,9 +127,10 @@ class YardstickNSCli(object):
                 lines = jsonutils.load(infile)
 
             if lines:
-                lines = lines['result']
+                lines = \
+                    lines['result']["testcases"][tc_name]["tc_data"]
                 tc_res = lines.pop(len(lines) - 1)
-                for key, value in tc_res["benchmark"]["data"].items():
+                for key, value in tc_res["data"].items():
                     self.generate_kpi_results(key, value)
                     self.generate_nfvi_results(value)
 
@@ -158,7 +157,7 @@ class YardstickNSCli(object):
                 testcases = os.listdir(test_path + vnf)
                 print(("VNF :(%s)" % vnf))
                 print("================")
-                for testcase in [tc for tc in testcases if "tc" in tc]:
+                for testcase in [tc for tc in testcases if "tc_" in tc]:
                     print('%s' % testcase)
                 print(os.linesep)
             raise SystemExit(0)
@@ -214,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()
index 0f98cab..03f6b1b 100644 (file)
@@ -11,6 +11,8 @@
 from __future__ import print_function
 from __future__ import absolute_import
 
+import logging
+
 from yardstick.benchmark.core.task import Task
 from yardstick.common.utils import cliargs
 from yardstick.common.utils import write_json_to_file
@@ -19,6 +21,9 @@ from yardstick.cmd.commands import change_osloobj_to_paras
 output_file_default = "/tmp/yardstick.out"
 
 
+LOG = logging.getLogger(__name__)
+
+
 class TaskCommands(object):     # pragma: no cover
     """Task commands.
 
@@ -49,7 +54,7 @@ class TaskCommands(object):     # pragma: no cover
             Task().start(param, **kwargs)
         except Exception as e:
             self._write_error_data(e)
-            raise
+            LOG.exception("")
 
     def _write_error_data(self, error):
         data = {'status': 2, 'result': str(error)}
index d251341..8e8114f 100644 (file)
@@ -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 (file)
index 0000000..e4c2328
--- /dev/null
@@ -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
index 8787e60..f027b79 100644 (file)
@@ -15,6 +15,7 @@ import logging
 
 from keystoneauth1 import loading
 from keystoneauth1 import session
+from cinderclient import client as cinderclient
 from novaclient import client as novaclient
 from glanceclient import client as glanceclient
 from neutronclient.neutron import client as neutronclient
@@ -108,6 +109,21 @@ def get_heat_api_version():     # pragma: no cover
         return api_version
 
 
+def get_cinder_client_version():      # pragma: no cover
+    try:
+        api_version = os.environ['OS_VOLUME_API_VERSION']
+    except KeyError:
+        return DEFAULT_API_VERSION
+    else:
+        log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
+        return api_version
+
+
+def get_cinder_client():      # pragma: no cover
+    sess = get_session()
+    return cinderclient.Client(get_cinder_client_version(), session=sess)
+
+
 def get_nova_client_version():      # pragma: no cover
     try:
         api_version = os.environ['OS_COMPUTE_API_VERSION']
@@ -430,3 +446,11 @@ def get_port_id_by_ip(neutron_client, ip_address):      # pragma: no cover
 def get_image_id(glance_client, image_name):    # pragma: no cover
     images = glance_client.images.list()
     return next((i.id for i in images if i.name == image_name), None)
+
+
+# *********************************************
+#   CINDER
+# *********************************************
+def get_volume_id(volume_name):    # pragma: no cover
+    volumes = get_cinder_client().volumes.list()
+    return next((v.id for v in volumes if v.name == volume_name), None)
index 7aab469..7a64b8c 100644 (file)
@@ -23,9 +23,15 @@ import logging
 import os
 import subprocess
 import sys
+import collections
+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
@@ -121,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)
@@ -158,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')}
@@ -189,3 +211,74 @@ def get_port_ip(sshclient, port):
     if status:
         raise RuntimeError(stderr)
     return stdout.rstrip()
+
+
+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()):
+        return data
+
+    for k, v in six.iteritems(data):
+        if isinstance(v, collections.Mapping):
+            for n_k, n_v in six.iteritems(v):
+                next_data["%s.%s" % (k, n_k)] = n_v
+        # use list because iterable is too generic
+        elif isinstance(v, list):
+            for index, item in enumerate(v):
+                next_data["%s%d" % (k, index)] = item
+        else:
+            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
index e77249c..1fc0a2f 100644 (file)
@@ -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):
index 373aae1..f157e91 100644 (file)
@@ -12,10 +12,9 @@ from __future__ import absolute_import
 import logging
 import time
 
-import collections
 import requests
-import six
 
+from yardstick.common import utils
 from third_party.influxdb.influxdb_line_protocol import make_lines
 from yardstick.dispatcher.base import Base as DispatchBase
 
@@ -80,7 +79,7 @@ class InfluxdbDispatcher(DispatchBase):
         msg = {}
         point = {
             "measurement": case,
-            "fields": self._dict_key_flatten(data["data"]),
+            "fields": utils.flatten_dict_key(data["data"]),
             "time": self._get_nano_timestamp(data),
             "tags": self._get_extended_tags(criteria),
         }
@@ -89,27 +88,6 @@ class InfluxdbDispatcher(DispatchBase):
 
         return make_lines(msg).encode('utf-8')
 
-    def _dict_key_flatten(self, data):
-        next_data = {}
-
-        # use list, because iterable is too generic
-        if not [v for v in data.values() if
-                isinstance(v, (collections.Mapping, list))]:
-            return data
-
-        for k, v in six.iteritems(data):
-            if isinstance(v, collections.Mapping):
-                for n_k, n_v in six.iteritems(v):
-                    next_data["%s.%s" % (k, n_k)] = n_v
-            # use list because iterable is too generic
-            elif isinstance(v, list):
-                for index, item in enumerate(v):
-                    next_data["%s%d" % (k, index)] = item
-            else:
-                next_data[k] = v
-
-        return self._dict_key_flatten(next_data)
-
     def _get_nano_timestamp(self, results):
         try:
             timestamp = results["timestamp"]
index 1d770f7..2df6037 100644 (file)
@@ -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(".")
index 40cc14a..b56a919 100644 (file)
@@ -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
 
 
index fd6c4f6..beb63b4 100644 (file)
@@ -230,14 +230,50 @@ name (i.e. %s).\
             'value': {'get_resource': name}
         }
 
+    def add_volume(self, name, size=10):
+        """add to the template a volume description"""
+        log.debug("adding Cinder::Volume '%s' size '%d' ", name, size)
+
+        self.resources[name] = {
+            'type': 'OS::Cinder::Volume',
+            'properties': {'name': name,
+                           'size': size}
+        }
+
+        self._template['outputs'][name] = {
+            'description': 'Volume %s ID' % name,
+            'value': {'get_resource': name}
+        }
+
+    def add_volume_attachment(self, server_name, volume_name, mountpoint=None):
+        """add to the template an association of volume to instance"""
+        log.debug("adding Cinder::VolumeAttachment server '%s' volume '%s' ", server_name,
+                  volume_name)
+
+        name = "%s-%s" % (server_name, volume_name)
+
+        volume_id = op_utils.get_volume_id(volume_name)
+        if not volume_id:
+            volume_id = {'get_resource': volume_name}
+        self.resources[name] = {
+            'type': 'OS::Cinder::VolumeAttachment',
+            'properties': {'instance_uuid': {'get_resource': server_name},
+                           'volume_id': volume_id}
+        }
+
+        if mountpoint:
+            self.resources[name]['properties']['mountpoint'] = mountpoint
+
     def add_network(self, name, physical_network='physnet1', provider=None,
-                    segmentation_id=None):
+                    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 +281,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 +301,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 +312,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,16 +359,18 @@ 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"""
-        log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', "
-                  "secgroup:%s", name, network_name, subnet_name, sec_group_id)
+    def add_port(self, name, network_name, subnet_name, vnic_type, sec_group_id=None,
+                 provider=None, allowed_address_pairs=None):
+        """add to the template a named Neutron Port
+        """
+        log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', vnic_type:'%s', "
+                  "secgroup:%s", name, network_name, subnet_name, vnic_type, sec_group_id)
         self.resources[name] = {
             'type': 'OS::Neutron::Port',
             'depends_on': [subnet_name],
             'properties': {
                 'name': name,
+                'binding:vnic_type': vnic_type,
                 'fixed_ips': [{'subnet': {'get_resource': subnet_name}}],
                 'network_id': {'get_resource': network_name},
                 'replacement_policy': 'AUTO',
@@ -341,6 +386,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 +583,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,14 +608,18 @@ 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':
-                raise RuntimeError(
-                    heat_client.stacks.get(self.uuid).stack_status_reason)
+                stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason
+                heat_client.stacks.delete(self.uuid)
+                raise RuntimeError(stack_status_reason)
             if time.time() > time_limit:
                 raise RuntimeError("Heat stack create timeout")
 
@@ -573,7 +627,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 (file)
index 0000000..6d7045f
--- /dev/null
@@ -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
index 9d20a5a..4974bac 100644 (file)
 # StorPerf plugin installation script
 # After installation, it will run StorPerf container on Jump Host
 # Requirements:
-#     1. docker has been installed on the Jump Host
-#     2. Openstack environment file for storperf, '~/storperf_admin-rc', is ready.
+#     1. docker and docker-compose have been installed on the Jump Host
+#     2. Openstack environment file for storperf, '~/storperf_admin-rc', is ready
+#     3. Jump Host must have internet connectivity for downloading docker image
+#     4. Jump Host has access to the OpenStack Controller API
+#     5. Enough OpenStack floating IPs must be available to match your agent count
+#     6. The following ports are exposed if you use the supplied docker-compose.yaml file:
+#        * 5000 for StorPerf ReST API and Swagger UI
+#        * 8000 for StorPerf's Graphite Web Server
 
 set -e
 
-mkdir -p /tmp/storperf-yardstick
+WWW_DATA_UID=33
+WWW_DATA_GID=33
 
-docker pull opnfv/storperf
+export TAG=${DOCKER_TAG:-latest}
+export ENV_FILE=~/storperf_admin-rc
+export CARBON_DIR=~/carbon
 
-STORPERF_DIR=/tmp/storperf-yardstick/carbon
-docker run -t \
---env-file ~/storperf_admin-rc \
--p 5000:5000 -p 8000:8000 \
--v $STORPERF_DIR:/opt/graphite/storage/whisper \
---name storperf-yardstick opnfv/storperf &
+sudo install --owner=${WWW_DATA_UID} --group=${WWW_DATA_GID} -d "${CARBON_DIR}"
 
-chown www-data:www-data $STORPERF_DIR
+docker-compose -f ~/docker-compose.yaml pull
+docker-compose -f ~/docker-compose.yaml up -d
index a8eb51c..b241d18 100644 (file)
 
 set -e
 
-docker stop storperf-yardstick
-docker rm -f storperf-yardstick
-docker rmi opnfv/storperf
+export TAG=${DOCKER_TAG:-latest}
+export ENV_FILE=~/storperf_admin-rc
+export CARBON_DIR=~/carbon
 
-rm -rf /tmp/storperf-yardstick
+rm -rf "${CARBON_DIR}"
+
+docker-compose down
+
+for container_name in storperf swagger-ui http-front-end
+do
+    container=$(docker ps -a -q -f name=$container_name)
+    if [[ ! -z $container ]]
+    then
+        echo "Stopping any existing $container_name container"
+        docker rm -fv $container
+    fi
+done
index 5a9178f..1ff4225 100644 (file)
@@ -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'
index 96ead5e..9fa860a 100644 (file)
@@ -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)
index 2bd8b7b..69c5d74 100644 (file)
@@ -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'