Upload the contribution of vstf as bottleneck network framework. 07/5107/1
authorYiting.Li <liyiting@huawei.com>
Wed, 23 Dec 2015 01:11:12 +0000 (17:11 -0800)
committerYiting.Li <liyiting@huawei.com>
Wed, 23 Dec 2015 01:11:12 +0000 (17:11 -0800)
    End to End Performance test

    JIRA:BOTTLENECK-29

Change-Id: Ib2c553c8b60d6cda9e7a7b52b737c9139f706ebd
Signed-off-by: Yiting.Li <liyiting@huawei.com>
266 files changed:
vstf/README.rst [new file with mode: 0755]
vstf/__init__.py [new file with mode: 0755]
vstf/etc/vstf/amqp/amqp.ini [new file with mode: 0755]
vstf/etc/vstf/default/reporters.html-settings [new file with mode: 0755]
vstf/etc/vstf/default/reporters.mail.mail-settings [new file with mode: 0755]
vstf/etc/vstf/default/sw_perf.tool-settings [new file with mode: 0755]
vstf/etc/vstf/env/Ti.json [new file with mode: 0755]
vstf/etc/vstf/env/Tn.json [new file with mode: 0755]
vstf/etc/vstf/env/Tnv.json [new file with mode: 0755]
vstf/etc/vstf/env/Tu.json [new file with mode: 0755]
vstf/etc/vstf/env/eth2eth.json [new file with mode: 0755]
vstf/etc/vstf/env/images.json [new file with mode: 0755]
vstf/etc/vstf/env/src.json [new file with mode: 0755]
vstf/etc/vstf/env/tester.json [new file with mode: 0755]
vstf/etc/vstf/perf/sw_perf.batch-settings [new file with mode: 0755]
vstf/etc/vstf/perf/sw_perf.cpu-settings [new file with mode: 0755]
vstf/etc/vstf/perf/sw_perf.device-settings [new file with mode: 0755]
vstf/etc/vstf/perf/sw_perf.flownodes-settings [new file with mode: 0755]
vstf/etc/vstf/perf/sw_perf.forwarding-settings [new file with mode: 0755]
vstf/etc/vstf/reporter/reporter.pdf.story-show [new file with mode: 0755]
vstf/etc/vstf/reporter/reporters.html.data-settings [new file with mode: 0755]
vstf/etc/vstf/spirent/optimize.ini [new file with mode: 0755]
vstf/etc/vstf/spirent/strategy.ini [new file with mode: 0755]
vstf/etc/vstf/user/reporters.html-settings [new file with mode: 0755]
vstf/etc/vstf/user/reporters.mail.mail-settings [new file with mode: 0755]
vstf/etc/vstf/user/sw_perf.tool-settings [new file with mode: 0755]
vstf/install.sh [new file with mode: 0755]
vstf/pylint.conf [new file with mode: 0644]
vstf/requirements.txt [new file with mode: 0755]
vstf/setup.cfg [new file with mode: 0755]
vstf/setup.py [new file with mode: 0644]
vstf/tool_package/install.sh [new file with mode: 0755]
vstf/tool_package/netperf-2.7.0.tar.gz [new file with mode: 0755]
vstf/tool_package/qperf-0.4.9.tar.gz [new file with mode: 0755]
vstf/tool_package/vnstat-1.14.tar.gz [new file with mode: 0755]
vstf/vstf/__init__.py [new file with mode: 0755]
vstf/vstf/agent/__init__.py [new file with mode: 0755]
vstf/vstf/agent/agent.py [new file with mode: 0755]
vstf/vstf/agent/env/Readme [new file with mode: 0755]
vstf/vstf/agent/env/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/Readme [new file with mode: 0755]
vstf/vstf/agent/env/basic/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/collect.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/commandline.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/device_manager.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/image_manager.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/source_manager.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/vm9pfs.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/vm_manager.py [new file with mode: 0755]
vstf/vstf/agent/env/basic/vm_xml_help.py [new file with mode: 0755]
vstf/vstf/agent/env/builder.py [new file with mode: 0755]
vstf/vstf/agent/env/driver_plugins/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/driver_plugins/manager.py [new file with mode: 0755]
vstf/vstf/agent/env/driver_plugins/model.py [new file with mode: 0755]
vstf/vstf/agent/env/driver_plugins/origin_driver.py [new file with mode: 0755]
vstf/vstf/agent/env/fsmonitor/FSMonitor.py [new file with mode: 0755]
vstf/vstf/agent/env/fsmonitor/Readme [new file with mode: 0755]
vstf/vstf/agent/env/fsmonitor/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/fsmonitor/constant.py [new file with mode: 0755]
vstf/vstf/agent/env/fsmonitor/utils.py [new file with mode: 0755]
vstf/vstf/agent/env/plugins/Readme [new file with mode: 0755]
vstf/vstf/agent/env/plugins/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/plugins/libvirt_plugin.py [new file with mode: 0755]
vstf/vstf/agent/env/plugins/model.py [new file with mode: 0755]
vstf/vstf/agent/env/plugins/tester_env_plugin.py [new file with mode: 0755]
vstf/vstf/agent/env/vswitch_plugins/__init__.py [new file with mode: 0755]
vstf/vstf/agent/env/vswitch_plugins/bridge_plugin.py [new file with mode: 0755]
vstf/vstf/agent/env/vswitch_plugins/manager.py [new file with mode: 0755]
vstf/vstf/agent/env/vswitch_plugins/model.py [new file with mode: 0755]
vstf/vstf/agent/env/vswitch_plugins/ovs_plugin.py [new file with mode: 0755]
vstf/vstf/agent/equalizer/README [new file with mode: 0755]
vstf/vstf/agent/equalizer/__init__.py [new file with mode: 0755]
vstf/vstf/agent/equalizer/equalizer.py [new file with mode: 0755]
vstf/vstf/agent/equalizer/get_info.py [new file with mode: 0755]
vstf/vstf/agent/equalizer/optimize.py [new file with mode: 0755]
vstf/vstf/agent/perf/__init__.py [new file with mode: 0755]
vstf/vstf/agent/perf/affctl.py [new file with mode: 0755]
vstf/vstf/agent/perf/ethtool.py [new file with mode: 0755]
vstf/vstf/agent/perf/iperf.py [new file with mode: 0755]
vstf/vstf/agent/perf/netmap.py [new file with mode: 0755]
vstf/vstf/agent/perf/netns.py [new file with mode: 0755]
vstf/vstf/agent/perf/netperf.py [new file with mode: 0755]
vstf/vstf/agent/perf/pktgen.py [new file with mode: 0755]
vstf/vstf/agent/perf/qperf.py [new file with mode: 0755]
vstf/vstf/agent/perf/sar.py [new file with mode: 0755]
vstf/vstf/agent/perf/utils.py [new file with mode: 0755]
vstf/vstf/agent/perf/vnstat.py [new file with mode: 0755]
vstf/vstf/agent/perf/vstfperf.py [new file with mode: 0755]
vstf/vstf/agent/softagent.py [new file with mode: 0755]
vstf/vstf/agent/spirent/__init__.py [new file with mode: 0755]
vstf/vstf/agent/spirent/spirent.py [new file with mode: 0755]
vstf/vstf/agent/spirent/tools.py [new file with mode: 0755]
vstf/vstf/agent/spirentagent.py [new file with mode: 0755]
vstf/vstf/agent/unittest/__init__.py [new file with mode: 0755]
vstf/vstf/agent/unittest/configuration.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/Readme [new file with mode: 0755]
vstf/vstf/agent/unittest/env/__init__.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/configuration/Ti.json [new file with mode: 0755]
vstf/vstf/agent/unittest/env/configuration/Tn-ovs.json [new file with mode: 0755]
vstf/vstf/agent/unittest/env/configuration/Tn.json [new file with mode: 0755]
vstf/vstf/agent/unittest/env/configuration/Tn1v.json [new file with mode: 0755]
vstf/vstf/agent/unittest/env/configuration/Tu.json [new file with mode: 0755]
vstf/vstf/agent/unittest/env/model.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/run_test.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_bridge_plugin.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_builder.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_devicemanager.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_drivermanager.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_origin_driver.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_sourcemanager.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_vm9pfs.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_vm_manager.py [new file with mode: 0755]
vstf/vstf/agent/unittest/env/test_vs_plugin_manager.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/Readme [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/__init__.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/model.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/run_test.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_ethtool.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_netns.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_netperf.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_pktgen.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_qperf.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_utils.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_vnstat.py [new file with mode: 0755]
vstf/vstf/agent/unittest/perf/test_vstfperf.py [new file with mode: 0755]
vstf/vstf/agent/unittest/test_callback.py [new file with mode: 0755]
vstf/vstf/common/__init__.py [new file with mode: 0755]
vstf/vstf/common/cfgparser.py [new file with mode: 0755]
vstf/vstf/common/check.py [new file with mode: 0755]
vstf/vstf/common/cliutil.py [new file with mode: 0755]
vstf/vstf/common/cmds.py [new file with mode: 0755]
vstf/vstf/common/constants.py [new file with mode: 0755]
vstf/vstf/common/daemon.py [new file with mode: 0755]
vstf/vstf/common/decorator.py [new file with mode: 0755]
vstf/vstf/common/excepts.py [new file with mode: 0755]
vstf/vstf/common/input.py [new file with mode: 0755]
vstf/vstf/common/log.py [new file with mode: 0755]
vstf/vstf/common/message.py [new file with mode: 0755]
vstf/vstf/common/perfmark.py [new file with mode: 0755]
vstf/vstf/common/pyhtml.py [new file with mode: 0755]
vstf/vstf/common/rsync.py [new file with mode: 0755]
vstf/vstf/common/saltstack.py [new file with mode: 0755]
vstf/vstf/common/ssh.py [new file with mode: 0755]
vstf/vstf/common/test_func.py [new file with mode: 0755]
vstf/vstf/common/unix.py [new file with mode: 0755]
vstf/vstf/common/utils.py [new file with mode: 0755]
vstf/vstf/common/vstfcli.py [new file with mode: 0755]
vstf/vstf/controller/__init__.py [new file with mode: 0755]
vstf/vstf/controller/api_server.py [new file with mode: 0755]
vstf/vstf/controller/database/__init__.py [new file with mode: 0755]
vstf/vstf/controller/database/constants.py [new file with mode: 0755]
vstf/vstf/controller/database/dbinterface.py [new file with mode: 0755]
vstf/vstf/controller/database/tables.py [new file with mode: 0755]
vstf/vstf/controller/env_build/README [new file with mode: 0755]
vstf/vstf/controller/env_build/__init__.py [new file with mode: 0755]
vstf/vstf/controller/env_build/cfg_intent_parse.py [new file with mode: 0755]
vstf/vstf/controller/env_build/env_build.py [new file with mode: 0755]
vstf/vstf/controller/env_build/env_collect.py [new file with mode: 0755]
vstf/vstf/controller/fabricant.py [new file with mode: 0755]
vstf/vstf/controller/reporters/README [new file with mode: 0755]
vstf/vstf/controller/reporters/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/mail/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/mail/mail.py [new file with mode: 0755]
vstf/vstf/controller/reporters/mail/sendmail.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/data_factory.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/html/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/html/html_base.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/html/html_text.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/html/htmlcreator.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/element.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/pdfcreator.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/pdftemplate.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/story.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/pdf/styles.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/provider/__init__.py [new file with mode: 0755]
vstf/vstf/controller/reporters/report/provider/html_provider.py [new file with mode: 0755]
vstf/vstf/controller/reporters/reporter.py [new file with mode: 0755]
vstf/vstf/controller/res/Traffic-types.gif [new file with mode: 0755]
vstf/vstf/controller/res/Traffic-types.jpg [new file with mode: 0755]
vstf/vstf/controller/res/__init__.py [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Ti-direct.gif [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Ti-direct.jpg [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Ti.gif [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Ti.jpg [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tn.gif [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tn.jpg [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tnv.gif [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tnv.jpg [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tu.gif [new file with mode: 0755]
vstf/vstf/controller/res/deployment/Tu.jpg [new file with mode: 0755]
vstf/vstf/controller/res/iperf/Ti-3.gif [new file with mode: 0755]
vstf/vstf/controller/res/iperf/Ti-3.jpg [new file with mode: 0755]
vstf/vstf/controller/res/logo.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-1.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-1.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-2.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-2.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-direct-1.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-direct-1.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-direct-2.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Ti-direct-2.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-1.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-1.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-1v.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-1v.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-2.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-2.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-2v.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tn-2v.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-1.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-1.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-2.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-2.jpg [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-3.gif [new file with mode: 0755]
vstf/vstf/controller/res/pktgen/Tu-3.jpg [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-1.gif [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-1.jpg [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-1v.gif [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-1v.jpg [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-2.gif [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-2.jpg [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-2v.gif [new file with mode: 0755]
vstf/vstf/controller/res/spirent/Tn-2v.jpg [new file with mode: 0755]
vstf/vstf/controller/settings/README [new file with mode: 0755]
vstf/vstf/controller/settings/__init__.py [new file with mode: 0755]
vstf/vstf/controller/settings/cpu_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/data_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/device_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/flows_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/forwarding_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/html_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/mail_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/perf_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/settings_input.py [new file with mode: 0755]
vstf/vstf/controller/settings/tester_settings.py [new file with mode: 0755]
vstf/vstf/controller/settings/tool_settings.py [new file with mode: 0755]
vstf/vstf/controller/spirent/__init__.py [new file with mode: 0755]
vstf/vstf/controller/spirent/appliance.py [new file with mode: 0755]
vstf/vstf/controller/spirent/common/__init__.py [new file with mode: 0755]
vstf/vstf/controller/spirent/common/model.py [new file with mode: 0755]
vstf/vstf/controller/spirent/common/result_analysis.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/README [new file with mode: 0755]
vstf/vstf/controller/sw_perf/__init__.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/flow_producer.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/model.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/perf_provider.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/performance.py [new file with mode: 0755]
vstf/vstf/controller/sw_perf/raw_data.py [new file with mode: 0755]
vstf/vstf/controller/unittest/README [new file with mode: 0755]
vstf/vstf/controller/unittest/__init__.py [new file with mode: 0755]
vstf/vstf/controller/unittest/configuration.py [new file with mode: 0755]
vstf/vstf/controller/unittest/model.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_cfg_intent_parse.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_collect.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_driver_function.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_env_build.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_perf.py [new file with mode: 0755]
vstf/vstf/controller/unittest/test_ssh.py [new file with mode: 0755]
vstf/vstf/controller/vstfadm.py [new file with mode: 0755]
vstf/vstf/rpc_frame_work/__init__.py [new file with mode: 0755]
vstf/vstf/rpc_frame_work/constant.py [new file with mode: 0755]
vstf/vstf/rpc_frame_work/rpc_consumer.py [new file with mode: 0755]
vstf/vstf/rpc_frame_work/rpc_producer.py [new file with mode: 0755]

diff --git a/vstf/README.rst b/vstf/README.rst
new file mode 100755 (executable)
index 0000000..0e4e83c
--- /dev/null
@@ -0,0 +1,2 @@
+welcome to open vstf(Virtual Switch Test Framwork
+)
diff --git a/vstf/__init__.py b/vstf/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/vstf/etc/vstf/amqp/amqp.ini b/vstf/etc/vstf/amqp/amqp.ini
new file mode 100755 (executable)
index 0000000..8f355e6
--- /dev/null
@@ -0,0 +1,9 @@
+[rabbit]
+user=guest
+passwd=guest
+host=192.168.188.10
+port=5672
+id="local"
+[spirent]
+package='/home/Spirent_TestCenter_4.46/Spirent_TestCenter_Application_Linux'
+
diff --git a/vstf/etc/vstf/default/reporters.html-settings b/vstf/etc/vstf/default/reporters.html-settings
new file mode 100755 (executable)
index 0000000..de75f70
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "style":{
+        "table":{
+            "font-family":"\"Trebuchet MS\", Arial, Helvetica, sans-serif",
+            "border":"1px solid green",
+            "border-collapse":"collapse",
+            "padding":"8px",
+            "text-align":"center"
+        },
+        "td":{
+            "border":"1px solid green",
+            "padding":"8px",
+            "word-wrap":"break-all"
+        },
+        "th":{
+            "background-color":"#EAF2D3",
+            "border":"1px solid green",
+            "padding":"8px"
+        }
+    }
+    
+}
diff --git a/vstf/etc/vstf/default/reporters.mail.mail-settings b/vstf/etc/vstf/default/reporters.mail.mail-settings
new file mode 100755 (executable)
index 0000000..89bbcb5
--- /dev/null
@@ -0,0 +1,21 @@
+//Place your settings in the file "user/reporters.mail.mail-settings",
+//which overrides the settings in here.
+{
+    "server": 
+    {
+        "host": "localhost",
+        "username": null, 
+        "password": null
+    },
+    "body":
+    {
+        "from": ["vstf_from@vstf.com"],
+        "to": ["vstf_to@vstf.com"],
+        "cc": ["vstf_cc@vstf.com"],
+        "bcc": ["vstf_bcc@vstf.com"],
+        "subject": "Virtual Switching Performance Test Report"
+    }
+}
+
+
+
diff --git a/vstf/etc/vstf/default/sw_perf.tool-settings b/vstf/etc/vstf/default/sw_perf.tool-settings
new file mode 100755 (executable)
index 0000000..e72a0bc
--- /dev/null
@@ -0,0 +1,29 @@
+//Place your settings in the file "user/sw_perf.tool-settings",
+//which overrides the settings in here.
+{
+    "pktgen":{
+        "threads": 2,
+        "wait": 5,
+        "time": 20
+    },
+    "netperf":{
+        "threads": 2,
+        "wait": 5,
+        "time": 20
+    },
+    "qperf":{
+        "threads": 1,
+        "wait": 5,
+        "time": 2
+    },
+    "iperf":{
+        "threads": 1,
+        "wait": 5,
+        "time": 20
+    },
+    "netmap":{
+        "threads": 1,
+        "wait": 12,
+        "time": 20
+    }
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/env/Ti.json b/vstf/etc/vstf/env/Ti.json
new file mode 100755 (executable)
index 0000000..d122ddd
--- /dev/null
@@ -0,0 +1,63 @@
+{
+    "env-build": [
+        {
+            "ip": "192.168.188.14",
+            "drivers": [
+                "ixgbe"
+            ]
+        },
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe",
+                "vhost_net"
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200,300,400"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04:00.0",
+                            "outport": "tap0"
+                        }
+                    ]
+                }
+            ],
+            "vms": [
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap0",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a2",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "ctrl_mac": "16:6f:44:a5:3f:a2",
+                    "ctrl_br": "br0"
+                }
+            ]
+        }
+    ]
+}
diff --git a/vstf/etc/vstf/env/Tn.json b/vstf/etc/vstf/env/Tn.json
new file mode 100755 (executable)
index 0000000..f731457
--- /dev/null
@@ -0,0 +1,36 @@
+{
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe"
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        },
+                        {
+                            "bdf": "04:00.1",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04:00.0",
+                            "outport": "04:00.1"
+                        }
+                    ]
+                }
+            ],
+            "vms": []
+        }
+    ]
+}
diff --git a/vstf/etc/vstf/env/Tnv.json b/vstf/etc/vstf/env/Tnv.json
new file mode 100755 (executable)
index 0000000..3697dbb
--- /dev/null
@@ -0,0 +1,84 @@
+{
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe",
+                "vhost_net"
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04:00.0",
+                            "outport": "tap0"
+                        }
+                    ]
+                },
+                {
+                    "type": "ovs",
+                    "name": "ovs2",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.1",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04:00.1",
+                            "outport": "tap1"
+                        }
+                    ]
+                }
+            ],
+            "vms": [
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23",
+                        "tap_pktloop_config": "dpdk",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap0",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a2",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        },
+                        {
+                            "tap_name": "tap1",
+                            "br_type": "ovs",
+                            "br_name": "ovs2",
+                            "tap_mac": "56:6f:44:a5:3f:a3",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "ctrl_br": "br0",
+                    "ctrl_model": "rtl8139"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/env/Tu.json b/vstf/etc/vstf/env/Tu.json
new file mode 100755 (executable)
index 0000000..95bb81f
--- /dev/null
@@ -0,0 +1,70 @@
+{
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "vhost_net"
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "tap0",
+                            "outport": "tap1"
+                        }
+                    ]
+                }
+            ],
+            "vms": [
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap0",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a2",
+                            "vlan_mode": "access",
+                            "vlan_id": "100"
+                        }
+                    ]
+                },
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test2",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test2.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.201/23",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap1",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a4",
+                            "vlan_mode": "access",
+                            "vlan_id": "100"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/vstf/etc/vstf/env/eth2eth.json b/vstf/etc/vstf/env/eth2eth.json
new file mode 100755 (executable)
index 0000000..5662ae1
--- /dev/null
@@ -0,0 +1,16 @@
+{
+    "env-build": [
+        {
+            "ip": "hostA",
+            "drivers": ["ixgbe"],
+            "bridges": [],
+            "vms": []
+        },
+        {
+            "ip": "hostB",
+            "drivers": ["ixgbe"],
+            "bridges": [],
+            "vms": []
+        }      
+    ]
+}
diff --git a/vstf/etc/vstf/env/images.json b/vstf/etc/vstf/env/images.json
new file mode 100755 (executable)
index 0000000..a1c187c
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "parent_image": "/mnt/sdb/ubuntu_salt_master.img",
+    "dst_location": "/mnt/sdb",
+    "full_clone":false,
+    "type": "qcow2",
+    "names": ["test1","test2","test3","test4"]
+}
diff --git a/vstf/etc/vstf/env/src.json b/vstf/etc/vstf/env/src.json
new file mode 100755 (executable)
index 0000000..391b084
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "vnx-bin":{
+        "install":true,
+        "url": "root@192.168.188.10:/root/src/vnx-bin",
+        "repo_type": "git"
+    }
+}
diff --git a/vstf/etc/vstf/env/tester.json b/vstf/etc/vstf/env/tester.json
new file mode 100755 (executable)
index 0000000..213658d
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "drivers": ["ixgbe"]
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/perf/sw_perf.batch-settings b/vstf/etc/vstf/perf/sw_perf.batch-settings
new file mode 100755 (executable)
index 0000000..7926877
--- /dev/null
@@ -0,0 +1,90 @@
+{
+    "Tn":[
+        {
+            "case": "Tn-1",
+            "tool": "netperf",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Tn-2",
+            "tool": "pktgen",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        }
+    ],
+    "Ti":[
+        {
+            "case": "Ti-1",
+            "tool": "netperf",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Ti-2",
+            "tool": "pktgen",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Ti-3",
+            "tool": "pktgen",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        }
+    ],
+    "Tnv":[
+        {
+            "case": "Tnv-1",
+            "tool": "netmap",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Tnv-2",
+            "tool": "netperf",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        }
+    ],
+    "Tu":[
+        {
+            "case": "Tu-1",
+            "tool": "netperf",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Tu-2",
+            "tool": "pktgen",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        },
+        {
+            "case": "Tu-3",
+            "tool": "netperf",
+            "protocol": "udp",
+            "profile": "rdp",
+            "type": "frameloss",
+            "sizes": [64, 128, 512, 1024]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/perf/sw_perf.cpu-settings b/vstf/etc/vstf/perf/sw_perf.cpu-settings
new file mode 100755 (executable)
index 0000000..77b9304
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "affctl":{
+        "policy": 2
+    }
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/perf/sw_perf.device-settings b/vstf/etc/vstf/perf/sw_perf.device-settings
new file mode 100755 (executable)
index 0000000..a91340b
--- /dev/null
@@ -0,0 +1,56 @@
+{
+    "tester":{
+        "agent": "192.168.188.14",
+        "devs": [
+            {
+                "bdf": "04:00.0"
+            },
+            {
+                "bdf": "04:00.1"
+            }
+        ]
+    },
+    "host":{
+        "agent": "192.168.188.16",
+        "devs": [
+            {
+                "bdf": "04:00.0"
+            },
+            {
+                "bdf": "04:00.1"
+            },
+            {
+                "iface": "tap0"
+            },
+            {
+                "iface": "tap1"
+            }
+        ]
+    },
+    "vm-200":{
+        "agent": "192.168.188.200",
+        "devs": [
+            {
+                "mac": "56:6f:44:a5:3f:a2"
+            },
+            {
+                "mac": "56:6f:44:a5:3f:a3"
+            }
+        ]
+    },
+     "vm-201":{
+        "agent": "192.168.188.201",
+        "devs": [
+            {
+                "mac": "56:6f:44:a5:3f:a4"
+            },
+            {
+                "mac": "56:6f:44:a5:3f:a5"
+            }
+        ]
+    },
+    "tables":{
+        "vm-201": "host",
+        "vm-200": "host"
+    }
+}
diff --git a/vstf/etc/vstf/perf/sw_perf.flownodes-settings b/vstf/etc/vstf/perf/sw_perf.flownodes-settings
new file mode 100755 (executable)
index 0000000..c9cc75b
--- /dev/null
@@ -0,0 +1,99 @@
+{
+    "cpu_listens":[
+        {
+            "agent":"192.168.188.16",
+            "affctl":{
+                "policy": 2
+            }
+        }
+    ],
+    "flows":2,
+    "namespaces":[
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.0",
+                "iface":"eth4",
+                "ip":"192.168.1.100",
+                "mac":"90:e2:ba:20:1f:d8",
+                "namespace":"vstf-space-1"
+            }
+        },
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.1",
+                "iface":"p57p2",
+                "ip":"192.168.1.101",
+                "mac":"90:e2:ba:20:1f:d9",
+                "namespace":"vstf-space-2"
+            }
+        }
+    ],
+    "receivers":[
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.1",
+                "iface":"p57p2",
+                "ip":"192.168.1.101",
+                "mac":"90:e2:ba:20:1f:d9",
+                "namespace":"vstf-space-2"
+            }
+        },
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.0",
+                "iface":"eth4",
+                "ip":"192.168.1.100",
+                "mac":"90:e2:ba:20:1f:d8",
+                "namespace":"vstf-space-1"
+            }
+        }
+    ],
+    "senders":[
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.0",
+                "iface":"eth4",
+                "ip":"192.168.1.100",
+                "mac":"90:e2:ba:20:1f:d8",
+                "namespace":"vstf-space-1"
+            }
+        },
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.1",
+                "iface":"p57p2",
+                "ip":"192.168.1.101",
+                "mac":"90:e2:ba:20:1f:d9",
+                "namespace":"vstf-space-2"
+            }
+        }
+    ],
+    "watchers":[
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.0",
+                "iface":"eth4",
+                "ip":"192.168.1.100",
+                "mac":"90:e2:ba:20:1f:d8",
+                "namespace":"vstf-space-1"
+            }
+        },
+        {
+            "agent":"192.168.188.14",
+            "dev":{
+                "bdf":"04:00.1",
+                "iface":"p57p2",
+                "ip":"192.168.1.101",
+                "mac":"90:e2:ba:20:1f:d9",
+                "namespace":"vstf-space-2"
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/perf/sw_perf.forwarding-settings b/vstf/etc/vstf/perf/sw_perf.forwarding-settings
new file mode 100755 (executable)
index 0000000..17f6643
--- /dev/null
@@ -0,0 +1,51 @@
+{
+    "head": {
+        "ip": "192.168.1.100",
+        "namespace": "vstf-space-1"
+    },
+
+    "tail": {
+        "ip": "192.168.1.101",
+        "namespace": "vstf-space-2"
+    },
+
+    "Tnv":{
+        "flows":[
+            ["tester", 0],
+            ["host", 0],
+            ["host", 2],
+            ["host", 3],
+            ["host", 1],
+            ["tester", 1]
+        ],
+        "watchers": [1, 4]
+    },
+    "Tn":{
+        "flows":[
+            ["tester", 0],
+            ["host", 0],
+            ["host", 1],
+            ["tester", 1]
+        ],
+        "watchers": [1, 2]
+    },
+    "Tu":{
+        "flows":[
+            ["vm-200", 0],
+            ["host", 2],
+            ["host", 3],
+            ["vm-201", 0]
+
+        ],
+        "watchers": [1, 2]
+    },
+    "Ti":{
+        "flows":[
+            ["tester", 1],
+            ["host", 0],
+            ["host", 2],
+            ["vm-200", 0]
+        ],
+        "watchers": [1, 2]
+    }
+}
\ No newline at end of file
diff --git a/vstf/etc/vstf/reporter/reporter.pdf.story-show b/vstf/etc/vstf/reporter/reporter.pdf.story-show
new file mode 100755 (executable)
index 0000000..514a0cc
--- /dev/null
@@ -0,0 +1,32 @@
+[
+   {
+      "cover":{
+         "title":[],
+         "logo":[],
+         "header": [],
+         "footer": [],
+         "note": [],
+      }
+   },
+   {
+      "chapter":
+      {
+          "title":[],
+          "type":[],
+          "content":[
+              "paragraph":{
+                  "type": [],
+                  "content":[]
+              }
+              "section"
+
+
+          }
+
+
+      }
+
+   }
+
+
+]
\ No newline at end of file
diff --git a/vstf/etc/vstf/reporter/reporters.html.data-settings b/vstf/etc/vstf/reporter/reporters.html.data-settings
new file mode 100755 (executable)
index 0000000..5c4e32b
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    "ovs":{
+        "content":{
+            "version":3.0
+        },
+        "title":"Ovs info"
+    },
+    "result":{
+        "content":{},
+        "title":"Performance Result"
+    },
+    "subject":"ATF Performance Test"
+}
diff --git a/vstf/etc/vstf/spirent/optimize.ini b/vstf/etc/vstf/spirent/optimize.ini
new file mode 100755 (executable)
index 0000000..23fefea
--- /dev/null
@@ -0,0 +1,14 @@
+[common]
+tester_ip = 9.31.1.215
+[send]
+nic=enp129s0f1
+port=2/5
+vlans=100 300 500 700
+macs = 68:05:CA:30:4F:01 68:05:CA:30:4F:02 68:05:CA:30:4F:03 68:05:CA:30:4F:04
+ip_sections=193.168.100.1 194.168.100.1 195.168.100.1 196.168.100.1
+[recv]
+nic=enp129s0f0
+port=2/9
+macs = 68:05:CA:30:4F:05 68:05:CA:30:4F:06 68:05:CA:30:4F:07 68:05:CA:30:4F:08
+vlans=200 400 600 800
+ip_sections=193.168.100.2 194.168.100.2 195.168.100.2 196.168.100.2
diff --git a/vstf/etc/vstf/spirent/strategy.ini b/vstf/etc/vstf/spirent/strategy.ini
new file mode 100755 (executable)
index 0000000..0873150
--- /dev/null
@@ -0,0 +1,21 @@
+[strategy1]
+qemu_numa=1
+src_vhost_numa=1
+dst_vhost_numa=1
+src_irq_numa=1
+dst_irq_numa=1
+loan_numa=0
+[strategy2]
+qemu_numa=0
+src_vhost_numa=0
+dst_vhost_numa=0
+src_irq_numa=0
+dst_irq_numa=0
+loan_numa=1
+[strategy3]
+qemu_numa=1
+src_vhost_numa=0
+dst_vhost_numa=0
+src_irq_numa=0
+dst_irq_numa=0
+loan_numa=1
diff --git a/vstf/etc/vstf/user/reporters.html-settings b/vstf/etc/vstf/user/reporters.html-settings
new file mode 100755 (executable)
index 0000000..deaa69b
--- /dev/null
@@ -0,0 +1,3 @@
+{
+   
+}
diff --git a/vstf/etc/vstf/user/reporters.mail.mail-settings b/vstf/etc/vstf/user/reporters.mail.mail-settings
new file mode 100755 (executable)
index 0000000..8716cd4
--- /dev/null
@@ -0,0 +1,18 @@
+{
+    "server": {
+        "host": "localhost",
+        "username": null,
+        "password": null
+    },
+    "body": {
+        "from": [
+            "hangzhou_vstf_ci@huawei.com"
+        ],
+        "to": [
+            "Yiting.Li@gerrit.opnfv.org",
+        ],
+        "cc": [ ],
+        "bcc": [],
+        "subject": "Virtual Switching Performance Test Report"
+    }
+}
diff --git a/vstf/etc/vstf/user/sw_perf.tool-settings b/vstf/etc/vstf/user/sw_perf.tool-settings
new file mode 100755 (executable)
index 0000000..2c63c08
--- /dev/null
@@ -0,0 +1,2 @@
+{
+}
diff --git a/vstf/install.sh b/vstf/install.sh
new file mode 100755 (executable)
index 0000000..2aae6f5
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+SERVER=$2
+PORT=$3
+if [ -e $SERVER ]
+then
+    SERVER=""
+    echo "Use Default server."
+fi
+
+if [ -e $PORT ]
+then
+    PORT=""
+    echo "Use Default port."
+fi
+
+
+killall vnstat
+killall netserver
+killall netperf
+killall sar
+rm -rf vstf.egg-info || exit 1
+rm -rf build/ || exit 1
+rm -rf /usr/local/lib/python2.7/dist-packages/vstf* || exit 1
+python setup.py install --force
+if [ $1 == "manager" ]; then
+    vstf-agent stop
+    vstf-manager stop
+    if [ "${SERVER}x" == "x" ]
+    then
+        vstf-manager start
+    else
+        vstf-manager start --monitor ${SERVER} --port ${PORT}
+    fi
+elif [ $1 == "agent" ];then
+    vstf-manager stop
+    vstf-agent stop
+    vstf-agent start --config_file=/etc/vstf/amqp/amqp.ini
+fi
diff --git a/vstf/pylint.conf b/vstf/pylint.conf
new file mode 100644 (file)
index 0000000..6514d3c
--- /dev/null
@@ -0,0 +1,280 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+#disable=
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct attribute names in class
+# bodies
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=__.*__
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/vstf/requirements.txt b/vstf/requirements.txt
new file mode 100755 (executable)
index 0000000..3d7e803
--- /dev/null
@@ -0,0 +1,13 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+pbr>=0.6,!=0.7,<1.0
+pika
+clize
+sqlalchemy
+stevedore
+paramiko
+reportlab
+sigtools
+six>=1.7.0,<=1.9.0
+oslo.config>=1.4.0,<=1.6.0 # Apache-2.0
diff --git a/vstf/setup.cfg b/vstf/setup.cfg
new file mode 100755 (executable)
index 0000000..4dce398
--- /dev/null
@@ -0,0 +1,82 @@
+[metadata]
+name = vstf
+version = 2014.2.3
+summary = Open Virtual Switch Test Framework
+description-file = 
+       README.rst
+author = OpenNFV
+author-email = openatf@huawei.com
+home-page = https://12.234.32.89/mediawiki/
+classifier = 
+       Environment :: OpenATF
+       Intended Audience :: Information Technology
+       Intended Audience :: System Administrators
+       License :: OSI Approved :: Apache Software License
+       Operating System :: POSIX :: Linux
+       Programming Language :: Python
+       Programming Language :: Python :: 2
+       Programming Language :: Python :: 2.7
+       Programming Language :: Python :: 2.6
+
+[files]
+packages = 
+       vstf
+data_files = 
+    etc/vstf/=
+    etc/vstf/amqp=
+    etc/vstf/amqp/amqp.ini
+    etc/vstf/driver=
+    etc/vstf/driver/drivertest.conf
+[global]
+setup-hooks = 
+       pbr.hooks.setup_hook
+
+[entry_points]
+console_scripts = 
+    vstf-manager  = vstf.controller.api_server:main
+    vstf-agent    = vstf.agent.agent:main
+    vstfadm       = vstf.controller.vstfadm:main
+    vstf-settings = vstf.controller.settings.settings_input:main
+    vstf-driver   = vstf.controller.functiontest.driver.drivertest:main
+    vstf-switch   = vstf.controller.functiontest.switch.switchtest:main
+
+agent.plugins = 
+    soft = vstf.agent.softagent:softAgent
+    spirent = vstf.agent.spirentagent:spirentAgent
+
+
+spirent.model.plugins = 
+    Tnv = vstf.spirent.common.model:Tnv_Model
+    
+perf.plugins = 
+    spirent = vstf.controller.spirent.appliance:spirentSTC
+
+vswitch.plugins =
+       ovs = vstf.agent.env.vswitch_plugins.ovs_plugin:OvsPlugin
+       bridge = vstf.agent.env.vswitch_plugins.bridge_plugin:BridgePlugin
+
+drivers.plugins = 
+       origin = vstf.agent.env.driver_plugins.origin_driver:OriginDriverPlugin
+
+env_build.plugins =
+       libvirt =  vstf.agent.env.plugins.libvirt_plugin:Plugin
+       transmitter =  vstf.agent.env.plugins.tester_env_plugin:Plugin
+[build_sphinx]
+#all_files = 1
+#build-dir = doc/build
+#source-dir = doc/source
+
+[extract_messages]
+
+[compile_catalog]
+
+[update_catalog]
+
+[wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/vstf/setup.py b/vstf/setup.py
new file mode 100644 (file)
index 0000000..7363757
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
+
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+    import multiprocessing  # noqa
+except ImportError:
+    pass
+
+setuptools.setup(
+    setup_requires=['pbr'],
+    pbr=True)
diff --git a/vstf/tool_package/install.sh b/vstf/tool_package/install.sh
new file mode 100755 (executable)
index 0000000..471457d
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+function usage()
+{
+    echo -e "install:
+    install [path];
+    -->ops:
+        path: path of the souce code, default : './'
+    "
+}
+
+function install()
+{
+    local file=$1
+    local dir=${file%.tar.gz}
+    tar -zxvf $file
+    cd $dir
+    if [ -e "configure" ]; then
+        ./configure
+    fi
+    if [ -e "Makefile" ]; then
+        make && make install
+        echo "install $dir successfully"
+    else
+        echo "install $dir failed"
+    fi
+
+    cd .. && rm $dir -r
+
+}
+
+if [ $# -gt  2 ]; then
+    usage
+    exit -1
+fi
+
+code_path="./"
+if [ $# -eq  2 ]; then
+    code_path=$1
+fi
+cd $code_path
+for file in $(ls *.tar.gz);do
+    install $file
+done
+
diff --git a/vstf/tool_package/netperf-2.7.0.tar.gz b/vstf/tool_package/netperf-2.7.0.tar.gz
new file mode 100755 (executable)
index 0000000..666945a
Binary files /dev/null and b/vstf/tool_package/netperf-2.7.0.tar.gz differ
diff --git a/vstf/tool_package/qperf-0.4.9.tar.gz b/vstf/tool_package/qperf-0.4.9.tar.gz
new file mode 100755 (executable)
index 0000000..12ec727
Binary files /dev/null and b/vstf/tool_package/qperf-0.4.9.tar.gz differ
diff --git a/vstf/tool_package/vnstat-1.14.tar.gz b/vstf/tool_package/vnstat-1.14.tar.gz
new file mode 100755 (executable)
index 0000000..f8b0bcf
Binary files /dev/null and b/vstf/tool_package/vnstat-1.14.tar.gz differ
diff --git a/vstf/vstf/__init__.py b/vstf/vstf/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/__init__.py b/vstf/vstf/agent/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/agent.py b/vstf/vstf/agent/agent.py
new file mode 100755 (executable)
index 0000000..396b571
--- /dev/null
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# coding=utf-8
+import logging
+import argparse
+import signal
+
+from oslo.config import cfg
+
+from vstf.rpc_frame_work import rpc_consumer
+from vstf.common.log import setup_logging
+from vstf.common import daemon
+from vstf.common.cfgparser import CfgParser
+
+LOG = logging.getLogger(__name__)
+
+server_opts = [
+    cfg.StrOpt('user', default='guest', help="the rabbit's user, default guest"),
+    cfg.StrOpt('passwd', default='guest', help="the rabbit's passwd, default guest"),
+    cfg.StrOpt('host', default='localhost', help="tell the process wich interface to listen"),
+    cfg.StrOpt('port', default=5672, help=""),
+    cfg.StrOpt('id', default='agent', help="")
+
+]
+
+stc_opts = [
+    cfg.StrOpt('package', default='', help="the STC python path")
+]
+
+
+class Client(daemon.Daemon):
+    """This is a consumer of vstf-agent which will create two channel to the
+    rabbitmq-server, one for direct call, one for fan call.
+    
+    agent start with a config file which record rabbitmq's ip, port and user passwd
+    also each agent has its own id.
+    
+    """
+
+    def __init__(self, agent, config_file):
+        """Record the config file, init the daemon.
+        
+        :param str config_file: the config of a VSTF agent.
+        
+        """
+        super(Client, self).__init__('/tmp/esp_rpc_client.pid')
+        self.config_file = config_file
+        self.agent = agent
+        self.config = None
+        self.proxy = None
+        self.run_flag = True
+
+    def init_config(self):
+        """Use olso.config to analyse the config file
+        
+        """
+        parser = CfgParser(self.config_file)
+        parser.register_my_opts(server_opts, "rabbit")
+        parser.register_my_opts(stc_opts, "spirent")
+        self.config = parser.parse()
+
+    def loop_thread(self):
+        LOG.info("Try to create direct proxy...")
+        self.proxy = rpc_consumer.VstfConsumer(self.agent,
+                                                self.config.rabbit.user,
+                                                self.config.rabbit.passwd,
+                                                self.config.rabbit.host,
+                                                self.config.rabbit.port,
+                                                self.config.rabbit.id)
+        self.proxy.run()
+
+    def run(self):
+        """Run the rabbitmq consumers as a daemon.
+        
+        """
+        signal.signal(signal.SIGTERM, self.process_exit)
+        self.loop_thread()
+        LOG.info("agent start ok!")
+
+    def process_exit(self, signum, frame):
+        """This function try to stop the agent after running agent stop.
+        When we call vstf-agent stop which will send a signal SIGTERM to agent
+        When the agent catch the SIGTERM signal will call this function.
+        
+        """
+        LOG.info("daemon catch the signalterm, start to stop the process.")
+        self.run_flag = False
+        if self.proxy:
+            self.proxy.stop()
+
+    def start_agent(self):
+        self.init_config()
+        self.start()
+
+    def stop_agent(self):
+        """Notice that: this function just kill the agent by pid file, it has
+        none vars of the agent.
+        
+        """
+        LOG.info("call daemon stop.")
+        # kill the main thread
+        self.stop()
+
+
+def main():
+    setup_logging(level=logging.INFO, log_file="/var/log/vstf/vstf-agent.log")
+    parser = argparse.ArgumentParser(description='agent option')
+    parser.add_argument('action', choices=('start', 'stop', "restart"),
+                        default="start", help="start or stop agent")
+    parser.add_argument('--agent_type', action='store',
+                        default="soft",
+                        choices=["soft", "spirent"],
+                        help="the agent type, as now, just soft and spirent")
+    parser.add_argument('--config_file', action='store',
+                        default="/etc/vstf/amqp/amqp.ini",
+                        help="some env_build params recorded in the config file")
+
+    args = parser.parse_args()
+
+    client = Client(args.agent_type, args.config_file)
+    if "start" == args.action:
+        client.start_agent()
+    elif "stop" == args.action:
+        client.stop_agent()
+    elif "restart" == args.action:
+        client.stop_agent()
+        client.start_agent()
+    else:
+        raise Exception("only support actions: start/stop/restart")
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/agent/env/Readme b/vstf/vstf/agent/env/Readme
new file mode 100755 (executable)
index 0000000..d178a6f
--- /dev/null
@@ -0,0 +1,9 @@
+Modules in the directory are for "Environment Building".
+"Environment Building" is for creating "virtual network" for Performance Evaluation.
+The builder.py contains the quick test code for creates a "virtual network" from a config file.
+
+Usage:
+    python builder.py --config /etc/vstf/env/Tn.json
+
+Please modify example config file "/etc/vstf/model/Tn.json" to suit your own Environment.
+
diff --git a/vstf/vstf/agent/env/__init__.py b/vstf/vstf/agent/env/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/env/basic/Readme b/vstf/vstf/agent/env/basic/Readme
new file mode 100755 (executable)
index 0000000..cf7a877
--- /dev/null
@@ -0,0 +1,39 @@
+the modules in this directory include some basic common purpose functions that can be used for other plugins.
+you can see the modules in this directory as libraries that you can import for ease of creating your own plugin.
+
+the main functions of the modules are listed as follows:
+ImageManger:
+       role:
+               virtual machine images manager. used by VmManager.
+               wraps 'qemu-img' command as underlying mechanism.
+       features:
+               provides a function to create child image from a parent image.
+               provides a function to delete child image.
+       
+SourceManager:
+       role:
+               source code manager.
+               using 'pramiko' as underlying mechanism.
+       features:
+               provides a function to download source code 
+               provides a function which wrap 'make clean;make;make install' to compile and install.
+       
+VmManager:
+       role:
+               virtual machine manager which can create vm and configure vm on bootstrap.
+               use libvirt as underlying mechanism. wrap 'virsh' command for creating vm.
+       features:
+               provides a function to create vm from a vm configuration context.
+               provides functions to detect if vm boots up successfully and onfigure bootstrap options for vm like ip and rabbitmq conf file.
+
+vm9pfs:
+       used by VmManager.
+       host side 9pfs operation (mainly file operations) to communicate with vm by libvirt 9pfs.
+       see as communication protocol on client side. The vm runs a agent for receiving the commands. 
+                       
+VmXmlHelp:
+       used by VmManager.
+       just some divided libvirt xml string templates for building a complete libvirt xml file on demand by VmManager.
+
+
diff --git a/vstf/vstf/agent/env/basic/__init__.py b/vstf/vstf/agent/env/basic/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/env/basic/collect.py b/vstf/vstf/agent/env/basic/collect.py
new file mode 100755 (executable)
index 0000000..bc4f1ee
--- /dev/null
@@ -0,0 +1,101 @@
+import os
+import platform
+import logging
+from collections import OrderedDict
+
+from vstf.agent.env.basic.commandline import CommandLine
+from vstf.common import constants as const
+
+log = logging.getLogger(__name__)
+CMD = CommandLine()
+
+
+class Collect(object):
+    """collect host information such as _cpu, memory and so on"""
+
+    def __init__(self):
+        super(Collect, self).__init__()
+        self._system = self._system()
+        self._cpu = self._cpu()
+
+    def _system(self):
+        """the base _system info
+        {'os info':{'_system':'ubuntu', 'kernel': '3.13.3'}}"""
+        return {const.OS_INFO:
+            {
+                '_system': open('/etc/issue.net').readline().strip(),
+                'kernel': platform.uname()[2]
+            }
+        }
+
+    def _memery(self):
+        """ Return the information in /proc/meminfo
+        as a dictionary """
+        meminfo = OrderedDict()
+        with open('/proc/meminfo') as f:
+            for line in f:
+                meminfo[line.split(':')[0]] = line.split(':')[1].strip()
+
+        return {const.MEMORY_INFO:
+            {
+                "Mem Total": meminfo['MemTotal'],
+                "Mem Swap": meminfo['SwapTotal']
+            }
+        }
+
+    def _lscpu(self):
+        ret = {}
+        with os.popen("lscpu") as f:
+            for line in f:
+                ret[line.split(':')[0].strip()] = line.split(':')[1].strip()
+        return ret
+
+    def _cpu(self):
+        ret = []
+        with open('/proc/cpuinfo') as f:
+            cpuinfo = OrderedDict()
+            for line in f:
+                if not line.strip():
+                    ret.append(cpuinfo)
+                    cpuinfo = OrderedDict()
+                elif len(line.split(':')) == 2:
+                    cpuinfo[line.split(':')[0].strip()] = line.split(':')[1].strip()
+                else:
+                    log.error("_cpu info unknow format <%(c)s>", {'c': line})
+        return {const.CPU_INFO:
+            dict(
+                {
+                    "Model Name": ret[0]['model name'],
+                    "Address sizes": ret[0]['address sizes']
+                },
+                **(self._lscpu())
+            )
+        }
+
+    def _hw_sysinfo(self):
+        cmdline = "dmidecode | grep  -A 2 'System Information' | grep -v 'System Information'"
+        ret, output = CMD.execute(cmdline, shell=True)
+        if ret:
+            result = {}
+            # del the stderr
+            for tmp in output.strip().split('\n'):
+                if tmp is None or tmp is "":
+                    continue
+                # split the items 
+                tmp = tmp.split(":")
+                if len(tmp) >= 2:
+                    # first item as key, and the other as value
+                    result[tmp[0].strip("\t")] = ";".join(tmp[1:])
+            return {const.HW_INFO: result}
+        else:
+            return {const.HW_INFO: "get hw info failed. check the host by cmd: dmidecode"}
+
+    def collect_host_info(self):
+        return [self._system, self._cpu, self._memery(), self._hw_sysinfo()]
+
+
+if __name__ == "__main__":
+    c = Collect()
+    import json
+
+    print json.dumps(c.collect_host_info(), indent=4)
diff --git a/vstf/vstf/agent/env/basic/commandline.py b/vstf/vstf/agent/env/basic/commandline.py
new file mode 100755 (executable)
index 0000000..0df037d
--- /dev/null
@@ -0,0 +1,46 @@
+import subprocess
+import threading
+import logging
+from vstf.common import constants
+
+LOG = logging.getLogger(__name__)
+
+
+class CommandLine(object):
+    def __init__(self):
+        super(CommandLine, self).__init__()
+        self.proc = None
+        self.is_timeout = False
+
+    def __kill_proc(self):
+        self.is_timeout = True
+        self.proc.kill()
+
+    def execute(self, cmd, timeout=constants.TIMEOUT, shell=False):
+        """this func call subprocess.Popen(),
+        here setup a timer to deal with timeout.
+        :param cmd: cmd list like ['ls', 'home']
+        :param timeout: for timer count for timeout
+        :return: (ret, output) the output (stdout+'\n'+stderr)
+        """
+        # reset the timeout flag
+        self.is_timeout = False
+        self.proc = subprocess.Popen(cmd,
+                                     stdout=subprocess.PIPE,
+                                     stderr=subprocess.PIPE,
+                                     shell=shell)
+
+        timer = threading.Timer(timeout, self.__kill_proc, [])
+        timer.start()
+        stdout, stderr = self.proc.communicate()
+        timer.cancel()
+
+        if self.proc.returncode or self.is_timeout:
+            if self.is_timeout:
+                LOG.error("run cmd<%(cmd)s> timeout", {"cmd": cmd})
+            ret = False
+            output = "".join([stderr, stdout])
+        else:
+            ret = True
+            output = stdout
+        return ret, output
diff --git a/vstf/vstf/agent/env/basic/device_manager.py b/vstf/vstf/agent/env/basic/device_manager.py
new file mode 100755 (executable)
index 0000000..3209d7c
--- /dev/null
@@ -0,0 +1,144 @@
+"""
+Created on 2015-9-25
+
+@author: y00228926
+"""
+
+import re
+import logging
+from vstf.agent.perf import netns
+from vstf.common.utils import check_output, get_device_name, my_sleep, check_call, call, IPCommandHelper
+
+LOG = logging.getLogger(__name__)
+
+default_drivers = {
+    '82599': 'ixgbe',
+    '82576': 'igb',
+}
+
+
+class LspciHelper(object):
+    def __init__(self):
+        self.bdf_desc_map = {}
+        self.bdf_device_map = {}
+        self.device_bdf_map = {}
+        self.bdf_ip_map = {}
+        self.bdf_driver_map = {}
+        self.mac_bdf_map = {}
+        self.bdf_mac_map = {}
+        self._get_bdfs()
+        self._get_devices()
+        self._get_drivers()
+        self._get_ip_macs()
+
+    def _get_bdfs(self):
+        self.bdf_desc_map = {}
+        out = check_output('lspci |grep Eth', shell=True)
+        for line in out.splitlines():
+            bdf, desc = line.split(' ', 1)
+            self.bdf_desc_map[bdf] = desc
+
+    def _get_devices(self):
+        for bdf, desc in self.bdf_desc_map.items():
+            device = get_device_name(bdf)
+            if device is None:
+                LOG.info("cann't find device name for bdf:%s, no driver is available.", bdf)
+                try:
+                    self._load_driver(desc)
+                except:
+                    LOG.warn("!!!unable to load_driver for device:%s", bdf)
+                my_sleep(0.2)
+                device = get_device_name(bdf)
+            self.bdf_device_map[bdf] = device
+            if device:
+                self.device_bdf_map[device] = bdf
+                check_call("ip link set dev %s up" % device, shell=True)
+
+    def _get_drivers(self):
+        for device, bdf in self.device_bdf_map.items():
+            buf = check_output('ethtool -i %s | head -n1' % device, shell=True)
+            driver = buf.split()[1]
+            self.bdf_driver_map[bdf] = driver
+
+    def _get_ip_macs(self):
+        for device, bdf in self.device_bdf_map.items():
+            buf = check_output("ip addr show dev %s" % device, shell=True)
+            macs = re.compile("[A-F0-9]{2}(?::[A-F0-9]{2}){5}", re.IGNORECASE | re.MULTILINE)
+            for mac in macs.findall(buf):
+                if mac.lower() in ('00:00:00:00:00:00', 'ff:ff:ff:ff:ff:ff'):
+                    continue
+                else:
+                    break
+            ips = re.compile(r"inet (\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}/\d{1,2})", re.MULTILINE)
+            ip = ips.findall(buf)
+            if ip:
+                self.bdf_ip_map[bdf] = ip[0]
+            else:
+                self.bdf_ip_map[bdf] = None
+            self.bdf_mac_map[bdf] = mac
+            self.mac_bdf_map[mac] = bdf
+
+    def _load_driver(self, desc):
+        for key in default_drivers:
+            if key in desc:
+                driver = default_drivers[key]
+                LOG.info("try to load default driver [%s]", driver)
+                check_call('modprobe %s' % driver, shell=True)
+                break
+        else:
+            LOG.warn("unsupported nic type:%s", desc)
+
+
+class DeviceManager(object):
+    def __init__(self):
+        super(DeviceManager, self).__init__()
+        mgr = netns.NetnsManager()
+        mgr.clean_all_namespace()
+        self.lspci_helper = LspciHelper()
+
+    def _get_device_detail(self, bdf):
+        device = self.lspci_helper.bdf_device_map[bdf]
+        mac = self.lspci_helper.bdf_mac_map[bdf]
+        ip = self.lspci_helper.bdf_ip_map[bdf]
+        desc = self.lspci_helper.bdf_desc_map[bdf]
+        driver = self.lspci_helper.bdf_driver_map[bdf]
+        detail = {
+            'bdf': bdf,
+            'device': device,
+            'mac': mac,
+            'ip': ip,
+            'desc': desc,
+            'driver': driver
+        }
+        return detail
+
+    def get_device_detail(self, identity):
+        """
+        Gets the detail of a network card.
+
+        :param identity: be it the mac address, bdf, device name of a network card.
+        :return: device detail of a network card.
+        """
+        if identity in self.lspci_helper.bdf_device_map:
+            bdf = identity
+        elif identity in self.lspci_helper.device_bdf_map:
+            bdf = self.lspci_helper.device_bdf_map[identity]
+        elif identity in self.lspci_helper.mac_bdf_map:
+            bdf = self.lspci_helper.mac_bdf_map[identity]
+        else:
+            raise Exception("cann't find the device by identity:%s" % identity)
+        return self._get_device_detail(bdf)
+
+    def get_device_verbose(self, identity):
+        return IPCommandHelper().get_device_verbose(identity)
+
+    def list_nic_devices(self):
+        """
+        Get all the details of network devices in the host.
+        :return: a list of network card detail.
+        """
+        device_list = []
+        for bdf in self.lspci_helper.bdf_device_map.keys():
+            detail = self._get_device_detail(bdf)
+            device_list.append(detail)
+        return device_list
diff --git a/vstf/vstf/agent/env/basic/image_manager.py b/vstf/vstf/agent/env/basic/image_manager.py
new file mode 100755 (executable)
index 0000000..6c7df70
--- /dev/null
@@ -0,0 +1,124 @@
+"""
+Created on 2015-7-28
+
+@author: y00228926
+"""
+from vstf.common.utils import check_call
+import os
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+class _ImageManager(object):
+    """
+    A qemu-img wrapper to create qcow2 child image from a parent image.
+
+    """
+    def __init__(self, parent_image_path, child_image_dir):
+        """
+        :param parent_image_path    str: the parent image path.
+        :param child_image_dir      str: the destination path to put child images.
+        """
+        self._create_child_str = 'qemu-img create -f %(image_type)s %(child_path)s -o backing_file=%(parent_path)s'
+        self._convert_str = "qemu-img convert -O %(image_type)s %(parent_path)s %(child_path)s"
+        self.child_image_dir = child_image_dir
+        self.parent_image_path = parent_image_path
+        assert os.path.isfile(self.parent_image_path)
+        assert os.path.isdir(self.child_image_dir)
+
+    def create_child_image(self, child_name, full_clone=False, image_type='qcow2'):
+        """
+        create a child image and put it in self.child_image_dir.
+
+        :param child_name:  the image name to be created..
+        :return: return the path of child image.
+        """
+
+        image_path = os.path.join(self.child_image_dir, child_name) + '.' + image_type
+        if full_clone:
+            cmd = self._convert_str % {'image_type': image_type, 'child_path': image_path, 'parent_path': self.parent_image_path}
+        else:
+            cmd = self._create_child_str % {'child_path': image_path, 'parent_path': self.parent_image_path, 'image_type':image_type}
+        check_call(cmd.split())
+        return image_path
+
+
+class ImageManager(object):
+    def __init__(self, cfg):
+        """
+        ImageManager creates images from configuration context.
+
+        :param cfg: dict, example:
+        {
+            'parent_image': "/mnt/sdb/ubuntu_salt_master.img",
+            'dst_location': "/mnt/sdb",
+            'full_clone':False,
+            'type': "qcow2",
+            'names': ['vm1','vm2','vm3','vm4']
+        }
+        :return:
+        """
+        super(ImageManager, self).__init__()
+        cfg = self._check_cfg(cfg)
+        self.parent_image = cfg['parent_image']
+        self.image_dir = cfg['dst_location']
+        self.full_clone = cfg['full_clone']
+        self.image_type = cfg['type']
+        self.names = cfg['names']
+        self.mgr = _ImageManager(self.parent_image, self.image_dir)
+
+    @staticmethod
+    def _check_cfg(cfg):
+        for key in ('parent_image', 'dst_location', 'full_clone', 'type', 'names'):
+            if key not in cfg:
+                raise Exception("does't find %s config" % key)
+        if cfg['type'] not in ('raw', 'qcow2'):
+            raise Exception("type:%s not supported, only support 'raw' and 'qcow2'" % cfg['type'])
+        if not cfg['full_clone'] and cfg['type'] == 'raw':
+            raise Exception("only support 'qcow2' for not full_clone image creation" % cfg['type'])
+        return cfg
+
+    def create_all(self):
+        """
+        create images by configuration context.
+
+        :return: True for success, False for failure.
+        """
+        for name in self.names:
+            image = self.mgr.create_child_image(name, self.full_clone, self.image_type)
+            LOG.info("image: %s created", image)
+        return True
+
+    def clean_all(self):
+        """
+        remove all the images created in one go.
+
+        :return: True for success. Raise exception otherwise.
+        """
+        for name in self.names:
+            image_path = os.path.join(self.image_dir, name + '.' + self.image_type)
+            try:
+                os.unlink(image_path)
+                LOG.info("remove:%s successfully", image_path)
+            except Exception:
+                LOG.info("cann't find path:%s", image_path)
+        return True
+
+
+if __name__ == '__main__':
+    import argparse
+    import json
+    parser = argparse.ArgumentParser()
+    parser.add_argument('action', choices = ('create','clean'), help='action:create|clean')
+    parser.add_argument('--config', help='config file to parse')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    image_cfg = json.load(open(args.config))
+    mgr = ImageManager(image_cfg)
+    if args.action == 'create':
+        mgr.create_all()
+    if args.action == 'clean':
+        mgr.clean_all()
+
+
diff --git a/vstf/vstf/agent/env/basic/source_manager.py b/vstf/vstf/agent/env/basic/source_manager.py
new file mode 100755 (executable)
index 0000000..4267cbd
--- /dev/null
@@ -0,0 +1,74 @@
+"""
+Created on 2015-8-27
+
+@author: y00228926
+"""
+import os
+import logging
+import contextlib
+from subprocess import CalledProcessError
+from vstf.common.utils import check_call
+
+LOG = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def my_chdir(file_path):
+    old_cwd = os.path.realpath(os.curdir)
+    os.chdir(file_path)
+    LOG.info("cd %s", file_path)
+    yield
+    os.chdir(old_cwd)
+    LOG.info("cd %s", old_cwd)
+
+
+class SourceCodeManager(object):
+    def __init__(self):
+        super(SourceCodeManager, self).__init__()
+        self.base_path = '/opt/vstf/'
+
+    @staticmethod
+    def _git_pull(url, dest):
+        if not os.path.isdir(dest):
+            check_call("git clone %s %s" % (url, dest), shell=True)
+        else:
+            with my_chdir(dest):
+                check_call("git pull", shell=True)
+
+    @staticmethod
+    def _install(dest):
+        with my_chdir(dest):
+            try:
+                check_call("make && make install", shell=True)
+            except CalledProcessError:
+                LOG.info("retry make again")
+                check_call("make clean; make && make install", shell=True)
+
+    def src_install(self, cfg):
+        for key, item in cfg.items():
+            repo_type = item['repo_type']
+            url = item['url']
+            install = item['install']
+            if install is True:
+                LOG.info("installing src repo:%s", key)
+                if repo_type == "git":
+                    target = self.base_path + key
+                    self._git_pull(url, target)
+                    self._install(target)
+                else:
+                    raise Exception("unsupported repo type:%s" % repo_type)
+            else:
+                LOG.info("skip src repo:%s", key)
+        return True
+
+
+if __name__ == '__main__':
+    import argparse
+    import json
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--config', help='config file to parse')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    cfg = json.load(open(args.config))
+    mgr = SourceCodeManager()
+    mgr.src_install(cfg)
diff --git a/vstf/vstf/agent/env/basic/vm9pfs.py b/vstf/vstf/agent/env/basic/vm9pfs.py
new file mode 100755 (executable)
index 0000000..f3a2c2c
--- /dev/null
@@ -0,0 +1,154 @@
+"""
+Created on 2015-8-27
+
+@author: y00228926
+"""
+import os
+import logging
+import textwrap
+from vstf.common.utils import my_sleep
+from vstf.agent.env.fsmonitor import constant
+
+LOG = logging.getLogger(__name__)
+
+
+class VMConfigBy9pfs(object):
+    """
+    host side implemetation of a self-defined communication protocol using libvirt 9pfs to give commands to the Virtual Machine.
+
+    """
+
+    def __init__(self, vm_9p_path):
+        """
+        :param vm_9p_path: The host path of libvirt 9pfs for a vm.
+        :return:
+        """
+        self.vm_9p_path = vm_9p_path
+
+    def clean(self):
+        self._unlink(self._path(constant.VM_CMD_RETURN_CODE_FILE))
+        self._unlink(self._path(constant.VM_CMD_DONE_FLAG_FILE))
+
+    def _path(self, relative_path):
+        return os.path.join(self.vm_9p_path, relative_path)
+
+    def _unlink(self, file_path):
+        os.unlink(file_path)
+        LOG.info("os.unlink(%s)", file_path)
+
+    def _read(self, filename):
+        filepath = self._path(filename)
+        with open(filepath, 'r') as f:
+            ret = f.read()
+            LOG.info("read(%s) -> %s", filepath, ret)
+        return ret
+
+    def _write(self, filename, cmd):
+        filepath = self._path(filename)
+        with open(filepath, 'w') as f:
+            f.write("%s" % cmd)
+            LOG.info("write(%s) <- %s", filepath, cmd)
+
+    def _wait_flag_file_to_exist(self, filename, timeout):
+        filepath = self._path(filename)
+        while timeout > 0:
+            if os.path.exists(filepath):
+                LOG.info("wait and find file:%s", filepath)
+                return True
+            my_sleep(1)
+            timeout -= 1
+            LOG.info("waiting file to exist:%s", filepath)
+        return False
+
+    def _get_cmd_return_code(self):
+        ret = self._read(constant.VM_CMD_RETURN_CODE_FILE)
+        return ret == constant.VM_CMD_EXCUTE_SUCCES_FLAG_CONTENT
+
+    def _wait_command_done(self):
+        done = self._wait_flag_file_to_exist(constant.VM_CMD_DONE_FLAG_FILE, constant.VM_COMMON_CMD_EXCUTE_TIME_OUT)
+        if done:
+            return self._get_cmd_return_code()
+        else:
+            return 'timeout'
+
+    def _set_cmd(self, cmd):
+        self._write(constant.VM_CMD_CONTENT_FILE, cmd)
+        self._write(constant.VM_CMD_SET_FLAG_FILE, '')
+        ret = self._wait_command_done()
+        if ret:
+            self.clean()
+            return ret
+        else:
+            raise Exception("9pfs command failure: timeout.")
+
+    def wait_up(self):
+        return self._wait_flag_file_to_exist(constant.VM_UP_Flag_FILE, constant.VM_UP_TIME_OUT)
+
+    def config_ip(self, mac, ip):
+        cmd = 'config_ip %s %s' % (mac, ip)
+        return self._set_cmd(cmd)
+
+    def config_gw(self, ip):
+        cmd = 'config_gw %s' % ip
+        return self._set_cmd(cmd)
+
+    def set_pktloop_dpdk(self, macs):
+        """
+        To connect two network devices together in the vm and loop the packets received to another.
+        Use dpdk testpmd to loop the packets. See FSMonitor.
+
+        :param macs: the mac address list of network cards of the vm.
+        :return: True for success, Exception for Failure.
+        """
+        mac_str = ' '.join(macs)
+        cmd = 'set_pktloop_dpdk ' + mac_str
+        return self._set_cmd(cmd)
+
+    def recover_nic_binding(self, macs):
+        """
+        in contrast to set_pktloop_dpdk, disconnect the looping.
+        :param macs:  the mac address list of network cards of the vm.
+        :return: True for success, Exception for Failure.
+        """
+        mac_str = ' '.join(macs)
+        cmd = 'recover_nic_binding ' + mac_str
+        return self._set_cmd(cmd)
+
+    def config_amqp(self, identity, server, port=5672, user="guest", passwd="guest"):
+        data = {
+            'server': server,
+            'port': port,
+            'id': identity,
+            'user': user,
+            'passwd': passwd
+        }
+        header = "[rabbit]"
+        content = '''
+        user=%(user)s
+        passwd=%(passwd)s
+        host=%(server)s
+        port=%(port)s
+        id=%(id)s''' % data
+        file_name = "amqp.ini"
+        dedented_text = textwrap.dedent(content)
+        self._write(file_name, header+dedented_text)
+        cmd = 'config_amqp %s' % file_name
+        return self._set_cmd(cmd)
+
+    def stop_vstf(self):
+        cmd = "stop_vstf"
+        return self._set_cmd(cmd)
+
+    def __repr__(self):
+        return self.__class__.__name__ + ':' + self.vm_9p_path
+
+
+if __name__ == '__main__':
+    fs = VMConfigBy9pfs('/tmp/tmp4T6p7L')
+    print os.listdir(os.curdir)
+    print fs.config_ip('56:6f:44:a5:3f:a4', '192.168.188.200/23')
+    print fs.config_gw('192.168.188.1')
+    print fs.set_pktloop_dpdk(['56:6f:44:a5:3f:a2', '56:6f:44:a5:3f:a3'])
+    print fs.recover_nic_binding(['56:6f:44:a5:3f:a2', '56:6f:44:a5:3f:a3'])
+    print fs.config_amqp('192.168.188.200', '192.168.188.10')
+    print os.listdir(os.curdir)
diff --git a/vstf/vstf/agent/env/basic/vm_manager.py b/vstf/vstf/agent/env/basic/vm_manager.py
new file mode 100755 (executable)
index 0000000..e59d6c7
--- /dev/null
@@ -0,0 +1,218 @@
+"""
+Created on 2015-8-27
+
+@author: y00228926
+"""
+import os
+import shutil
+import logging
+from vstf.common.utils import check_and_kill, randomMAC, my_mkdir, check_call, check_output, my_sleep
+from vstf.agent.env.basic.vm9pfs import VMConfigBy9pfs
+
+LOG = logging.getLogger(__name__)
+
+
+class VMControlOperation(object):
+    """
+    a libivrt virsh wrapper for creating virtual machine.
+    """
+
+    def __init__(self):
+        """
+        all tmp files will be created under '/tmp/atf_vm_manager'
+
+        """
+        work_dir = '/tmp/atf_vm_manager'
+        shutil.rmtree(work_dir, ignore_errors=True)
+        my_mkdir(work_dir)
+        self.work_dir = work_dir
+        self.vnc_index = 0
+        self.pci_index = 3
+        self.net_index = 0
+        self.vm_9p_controllers = {}
+        self.vm_configs = {}
+        self.image_mgr = None
+
+    @staticmethod
+    def composite_xml(context):
+        """
+        composit a libvirt xml configuration for creating vm from context.
+
+        :param context: a dict containing all necessary options for creating a vm.
+        :return: libvirt xml configuration string
+        """
+        from vm_xml_help import xml_head, xml_disk, xml_ovs, xml_pci, xml_9p, xml_tail, xml_ctrl_br, xml_br
+        xml = ''
+        tmp = xml_head.replace('VM_NAME', context['vm_name'])
+        tmp = tmp.replace('VM_MEMORY', str(context['vm_memory']))
+        tmp = tmp.replace('CPU_NUM', str(context['vm_cpu']))
+        xml += tmp
+        tmp = xml_disk.replace('IMAGE_TYPE', context['image_type'])
+        tmp = tmp.replace('IMAGE_PATH', context['image_path'])
+        xml += tmp
+
+        if context['9p_path']:
+            tmp = xml_9p.replace('9P_PATH', context['9p_path'])
+            xml += tmp
+
+        if context['eth_pci']:
+            for pci in context['eth_pci']:
+                bus = pci[:2]
+                slot = pci[3:5]
+                func = pci[6:7]
+                tmp = xml_pci.replace('BUS', bus)
+                tmp = tmp.replace('SLOT', slot)
+                tmp = tmp.replace('FUNCTION', func)
+                xml += tmp
+
+        if context['ctrl_br']:
+            tmp = xml_ctrl_br.replace('CTRL_BR', context['ctrl_br'])
+            tmp = tmp.replace('CTRL_MAC', context['ctrl_mac'])
+            tmp = tmp.replace('CTRL_MODEL', context['ctrl_model'])
+            xml += tmp
+
+        for tap_cfg in context['taps']:
+            if tap_cfg['br_type'] == "ovs":
+                br_type = "openvswitch"
+            else:
+                br_type = tap_cfg['br_type']
+            if br_type == 'bridge':
+                xml_ovs = xml_br
+            tmp = xml_ovs.replace('BR_TYPE', br_type)
+            tmp = tmp.replace('TAP_MAC', tap_cfg['tap_mac'])
+            tmp = tmp.replace('TAP_NAME', tap_cfg['tap_name'])
+            tmp = tmp.replace('BR_NAME', tap_cfg['br_name'])
+            xml += tmp
+
+        xml += xml_tail
+        return xml
+
+    @staticmethod
+    def check_required_options(context):
+        for key in ('vm_name', 'vm_memory', 'vm_cpu', 'image_path', 'image_type', 'taps'):
+            if not context.has_key(key):
+                raise Exception("vm config error, must set %s option" % key)
+
+    def set_vm_defaults(self, context):
+        vm_9p_path = '%s/%s' % (self.work_dir, context['vm_name'])
+        shutil.rmtree(vm_9p_path, ignore_errors=True)
+        my_mkdir(vm_9p_path)
+        default = {'vm_memory': 4194304,
+                   'vm_cpu': 4,
+                   'image_type': 'qcow2',
+                   'br_type': 'ovs',
+                   '9p_path': vm_9p_path,
+                   'eth_pci': None,
+                   'ctrl_br': 'br0',
+                   'ctrl_mac': randomMAC(),
+                   'ctrl_model': 'virtio',
+                   'ctrl_ip_setting': '192.168.100.100/24',
+                   'ctrl_gw': '192.168.100.1'
+                   }
+        for k, v in default.items():
+            context.setdefault(k, v)
+
+    def _shutdown_vm(self):
+        out = check_output("virsh list | sed 1,2d | awk '{print $2}'", shell=True)
+        vm_set = set(out.split())
+        for vm in vm_set:
+            check_call("virsh shutdown %s" % vm, shell=True)
+        timeout = 60
+        # wait for gracefully shutdown
+        while timeout > 0:
+            out = check_output("virsh list | sed 1,2d | awk '{print $2}'", shell=True)
+            vm_set = set(out.split())
+            if len(vm_set) == 0:
+                break
+            timeout -= 2
+            my_sleep(2)
+            LOG.info("waiting for vms:%s to shutdown gracefully", vm_set)
+        # destroy by force
+        for vm in vm_set:
+            check_call("virsh destroy %s" % vm, shell=True)
+        # undefine all
+        out = check_output("virsh list --all | sed 1,2d | awk '{print $2}'", shell=True)
+        vm_set = set(out.split())
+        for vm in vm_set:
+            check_call("virsh undefine %s" % vm, shell=True)
+        # kill all qemu
+        check_and_kill('qemu-system-x86_64')
+
+    def clean_all_vms(self):
+        self._shutdown_vm()
+        for _, ctrl in self.vm_9p_controllers.items():
+            LOG.debug("remove vm9pfs dir:%s", ctrl.vm_9p_path)
+            shutil.rmtree(ctrl.vm_9p_path, ignore_errors=True)
+        self.vm_9p_controllers = {}
+        self.vm_configs = {}
+        # shutil.rmtree(self.work_dir, ignore_errors=True)
+        self.vnc_index = 0
+        self.pci_index = 3
+        self.net_index = 0
+        self.vms = []
+        return True
+
+    def create_vm(self, context):
+        self.set_vm_defaults(context)
+        self.check_required_options(context)
+        xml = self.composite_xml(context)
+        vm_name = context['vm_name']
+        file_name = os.path.join(self.work_dir, vm_name + '.xml')
+        with open(file_name, 'w') as f:
+            f.write(xml)
+        check_call('virsh define %s' % file_name, shell=True)
+        check_call('virsh start %s' % vm_name, shell=True)
+        vm_name = context['vm_name']
+        vm_9pfs = context['9p_path']
+        self.vm_9p_controllers[vm_name] = VMConfigBy9pfs(vm_9pfs)
+        self.vm_configs[vm_name] = context
+        LOG.debug("%s's vm_9pfs path:%s", vm_name, vm_9pfs)
+        return True
+
+    def wait_vm(self, vm_name):
+        vm9pctrl = self.vm_9p_controllers[vm_name]
+        ret = vm9pctrl.wait_up()
+        if ret not in (True,):
+            raise Exception('vm running but stuck in boot process, please manully check.')
+        LOG.debug('waitVM %s up ok, ret:%s', vm_name, ret)
+        return True
+
+    def init_config_vm(self, vm_name):
+        """
+        using libvirt 9pfs to config boot up options like network ip/gw.
+
+        :param vm_name: the vm to be config with.
+        :return: True if succeed, Exception if fail.
+        """
+        vm_cfg = self.vm_configs[vm_name]
+        vm9pctrl = self.vm_9p_controllers[vm_name]
+        # print self.vm_9p_controllers
+        init_cfg = vm_cfg['init_config']
+        if "ctrl_ip_setting" in init_cfg:
+            ret = vm9pctrl.config_ip(vm_cfg['ctrl_mac'], init_cfg['ctrl_ip_setting'])
+            assert ret == True
+            LOG.info('initConfigVM config ip ok')
+        if 'ctrl_gw' in init_cfg:
+            ret = vm9pctrl.config_gw(init_cfg['ctrl_gw'])
+            assert ret == True
+            LOG.info('initConfigVM ctrl_gw ok')
+        if "ctrl_ip_setting" in init_cfg and "amqp_server" in init_cfg:
+            identity = init_cfg['ctrl_ip_setting'].split('/')[0]
+            if init_cfg['amqp_id'].strip():
+                identity = init_cfg['amqp_id'].strip()
+            server = init_cfg['amqp_server']
+            port = init_cfg['amqp_port']
+            user = init_cfg['amqp_user']
+            passwd = init_cfg['amqp_passwd']
+            ret = vm9pctrl.config_amqp(identity, server, port, user, passwd)
+            assert ret == True
+            LOG.info('initConfigVM config_amqp ok')
+        if 'tap_pktloop_config' in init_cfg:
+            taps = vm_cfg['taps']
+            macs = []
+            for tap in taps:
+                macs.append(tap['tap_mac'])
+            ret = vm9pctrl.set_pktloop_dpdk(macs)
+            assert ret == True
+            LOG.info('initConfigVM set_pktloop_dpdk ok')
+        return True
diff --git a/vstf/vstf/agent/env/basic/vm_xml_help.py b/vstf/vstf/agent/env/basic/vm_xml_help.py
new file mode 100755 (executable)
index 0000000..d311625
--- /dev/null
@@ -0,0 +1,81 @@
+"""
+Created on 2015-7-2
+
+@author: y00228926
+"""
+xml_head = '''
+<domain type='kvm'>
+  <name>VM_NAME</name>
+  <memory unit='KiB'>VM_MEMORY</memory>
+  <currentMemory unit='KiB'>VM_MEMORY</currentMemory>
+  <!--numatune>
+       <memory mode='strict' nodeset='0'/>
+  </numatune-->
+  <vcpu placement='static'>CPU_NUM</vcpu>
+  <cpu mode='host-passthrough'>
+  </cpu>
+  <os>
+    <type arch='x86_64' >hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>'''
+xml_disk = '''
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='IMAGE_TYPE' cache='none' io='native'/>
+      <source file='IMAGE_PATH'/>
+      <target dev='vda' bus='virtio'/>
+    </disk>'''
+    
+xml_ctrl_br = '''
+<interface type='bridge'>
+  <mac address='CTRL_MAC'/>
+  <source bridge='CTRL_BR'/>
+  <model type='CTRL_MODEL'/>
+</interface>
+'''
+xml_ovs = '''
+    <interface type='bridge'>
+      <mac address='TAP_MAC'/>
+      <source bridge='BR_NAME'/>
+      <virtualport type='BR_TYPE'>
+      </virtualport>
+      <model type='virtio'/>
+      <driver name='vhost' queues='4'/>
+      <target dev='TAP_NAME'/>
+    </interface>'''
+xml_br = '''
+    <interface type='bridge'>
+      <mac address='TAP_MAC'/>
+      <source bridge='BR_NAME'/>
+      <model type='virtio'/>
+      <target dev='TAP_NAME'/>
+    </interface>'''
+    
+xml_pci = '''
+    <hostdev mode='subsystem' type='pci' managed='yes'>
+      <driver name='kvm'/>
+      <source>
+        <address domain='0x0000' bus='0xBUS' slot='0xSLOT' function='0xFUNCTION' />
+      </source>
+    </hostdev>'''
+xml_9p = '''
+    <filesystem type='mount' accessmode='passthrough'>
+      <source dir='9P_PATH'/>
+      <target dir='9pfs'/>
+    </filesystem>'''
+xml_tail = '''
+    <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>
+      <listen type='address' address='0.0.0.0'/>
+    </graphics>
+  </devices>
+</domain>'''
+
diff --git a/vstf/vstf/agent/env/builder.py b/vstf/vstf/agent/env/builder.py
new file mode 100755 (executable)
index 0000000..ebd3d3d
--- /dev/null
@@ -0,0 +1,51 @@
+"""
+Created on 2015-7-8
+
+@author: y00228926
+"""
+import logging
+
+import stevedore
+
+LOG = logging.getLogger(__name__)
+
+
+class PluginManager(object):
+    def __init__(self):
+        self.instance = None
+        self.saved = {}
+
+    def build(self, cfg):
+        scheme = cfg["scheme"]
+        if scheme in self.saved:
+            # reuse old instance
+            self.instance = self.saved[scheme]
+        else:
+            mgr = stevedore.driver.DriverManager(namespace="env_build.plugins",
+                                                 name=scheme,
+                                                 invoke_on_load=False)
+            self.instance = mgr.driver()
+            self.saved[scheme] = self.instance
+
+        self.instance.clean()
+        return self.instance.build(cfg)
+
+    def clean(self):
+        if self.instance:
+            self.instance.clean()
+        self.instance = None
+
+
+if __name__ == "__main__":
+    import argparse
+    from vstf.controller.env_build.env_build import IntentParser
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--config', help='config file to parse')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    parser = IntentParser(args.config)
+    cfg_intent = parser.parse_cfg_file()
+    for host_cfg in cfg_intent['env-build']:
+        tn = PluginManager()
+        tn.build(host_cfg)
diff --git a/vstf/vstf/agent/env/driver_plugins/__init__.py b/vstf/vstf/agent/env/driver_plugins/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/vstf/vstf/agent/env/driver_plugins/manager.py b/vstf/vstf/agent/env/driver_plugins/manager.py
new file mode 100755 (executable)
index 0000000..3bb3fad
--- /dev/null
@@ -0,0 +1,40 @@
+"""
+Created on 2015-9-15
+
+@author: y00228926
+"""
+import stevedore
+
+
+class DriverPluginManager(object):
+    def __init__(self):
+        self.plugins = {}
+        self.mgr = stevedore.extension.ExtensionManager(namespace="drivers.plugins", invoke_on_load=True)
+
+    def load(self, drivers):
+        plugin = self.determine_driver_type(drivers)
+        ext = self.mgr[plugin]
+        ext.obj.load(drivers)
+        return True
+
+    def clean(self):
+        self.mgr.map(self._clean)
+        return True
+
+    def _clean(self, ext, *args, **kwargs):
+        ext.obj.clean()
+
+    def get_all_supported_drivers(self):
+        if not self.plugins:
+            for ext_name in self.mgr.names():
+                ext = self.mgr[ext_name]
+                self.plugins[ext_name] = ext.obj.get_supported_drivers()
+        return self.plugins
+
+    def determine_driver_type(self, drivers):
+        s = set(drivers)
+        for plugin, supported in self.get_all_supported_drivers().items():
+            if not (s - set(supported)):
+                return plugin
+        else:
+            raise Exception('unspported drivers: %s' % drivers)
diff --git a/vstf/vstf/agent/env/driver_plugins/model.py b/vstf/vstf/agent/env/driver_plugins/model.py
new file mode 100755 (executable)
index 0000000..3a0b184
--- /dev/null
@@ -0,0 +1,38 @@
+"""
+Created on 2015-9-15
+
+@author: y00228926
+"""
+from abc import ABCMeta
+from abc import abstractmethod
+
+
+class DriverPlugin:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __init__(self):
+        """don't pass in any args for __init__.
+        """
+
+    @abstractmethod
+    def clean(self):
+        """implement this clean function to clean environment before and after calling any other functions.
+        
+        """
+        pass
+
+    @abstractmethod
+    def load(self, drivers):
+        """load driver modules.
+        
+        :param list    drivers:list of modules to be inserted. for example:[ixgbe,vhost_net]
+        
+        """
+        pass
+
+    @abstractmethod
+    def get_supported_drivers(self):
+        """return a list of supported driver modules.
+        """
+        pass
diff --git a/vstf/vstf/agent/env/driver_plugins/origin_driver.py b/vstf/vstf/agent/env/driver_plugins/origin_driver.py
new file mode 100755 (executable)
index 0000000..850d785
--- /dev/null
@@ -0,0 +1,46 @@
+"""
+Created on 2015-10-12
+
+@author: y00228926
+"""
+from vstf.agent.env.driver_plugins import model
+from vstf.common.utils import check_and_rmmod, check_call
+
+
+class OriginDriverPlugin(model.DriverPlugin):
+    """
+    implement for operating linux origin driver modules.
+    """
+
+    def __init__(self):
+        """
+        list all origin drivers in self.origin_drivers
+        """
+        self.origin_drivers = ['ixgbe', 'bnx2x', 'i40e', 'be2net', 'vhost_net']
+
+    def clean(self):
+        """clean drivers list in self.origin_drivers.
+        
+        """
+        for mod in self.origin_drivers:
+            check_and_rmmod(mod)
+
+        check_and_rmmod('tun')
+        return True
+
+    def load(self, drivers):
+        """insmod drivers
+        
+        :param list    drivers:list of drivers link ['ixgbe','vhost_net']
+        """
+        # load implicit 'tun' module dependency for vhost_net
+        if 'vhost_net' in drivers:
+            check_call("modprobe tun", shell=True)
+
+        for drv in drivers:
+            check_call("modprobe %s" % drv, shell=True)
+
+        return True
+
+    def get_supported_drivers(self):
+        return self.origin_drivers
diff --git a/vstf/vstf/agent/env/fsmonitor/FSMonitor.py b/vstf/vstf/agent/env/fsmonitor/FSMonitor.py
new file mode 100755 (executable)
index 0000000..7102970
--- /dev/null
@@ -0,0 +1,215 @@
+"""
+Created on 2015-7-13
+
+@author: y00228926
+"""
+import os
+import time
+import logging
+import subprocess
+import sys
+
+import constant
+from utils import IPCommandHelper, umount, check_and_rmmod, check_output, check_call, call
+
+LOG_FILE = '/tmp/fsmonitor.log'
+PID_FILE = '/tmp/fsmonitor.pid'
+LOG = logging.getLogger('__name__')
+
+
+class VMOperation(object):
+    def __init__(self):
+        self.RTE_SDK = '/home/dpdk-2.0.0'
+        self.RTE_TARGET = 'x86_64-native-linuxapp-gcc'
+        self.nr_hugepages = '512'
+        self.pid = 0
+        self.ip_helper = IPCommandHelper()
+
+    def config_ip(self, mac, ip):
+        device = self.ip_helper.mac_device_map[mac]
+        check_call("ifconfig %s %s up" % (device, ip), shell=True)
+
+    def config_gw(self, ip):
+        call("route del default", shell=True)
+        check_call("route add default gw %s" % ip, shell=True)
+
+    def recover_nic_binding(self, *tap_macs):
+        if self.pid:
+            os.kill(self.pid, 9)
+            self.pid = None
+        bdf_str = ''
+        for mac in tap_macs:
+            bdf = self.ip_helper.mac_bdf_map[mac]
+            bdf_str = bdf_str + ' ' + bdf
+        cmd = 'python %s/tools/dpdk_nic_bind.py --bind=virtio-pci %s' % (self.RTE_SDK, bdf_str)
+        LOG.debug("recover_nic_binding runs cmd = %s", cmd)
+        check_call(cmd, shell=True)
+
+    def set_pktloop_dpdk(self, *tap_macs):
+        RTE_SDK = self.RTE_SDK
+        RTE_TARGET = self.RTE_TARGET
+        umount("/mnt/huge")
+        with open('/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages', 'w') as f:
+            f.write(self.nr_hugepages)
+        check_call("mkdir -p /mnt/huge", shell=True)
+        check_call("mount -t hugetlbfs nodev /mnt/huge", shell=True)
+        check_call("modprobe uio", shell=True)
+        check_and_rmmod('igb_uio')
+        check_call("insmod %s/%s/kmod/igb_uio.ko" % (RTE_SDK, RTE_TARGET), shell=True)
+
+        bdf_str = ''
+        for mac in tap_macs:
+            bdf = self.ip_helper.mac_bdf_map[mac]
+            bdf_str = bdf_str + ' ' + bdf
+
+        check_call('python %s/tools/dpdk_nic_bind.py --bind=igb_uio %s' % (RTE_SDK, bdf_str), shell=True)
+        cpu_num = int(check_output('cat /proc/cpuinfo | grep processor | wc -l', shell=True))
+        cpu_bit_mask = 0
+        i = cpu_num
+        while i:
+            cpu_bit_mask = (cpu_bit_mask << 1) + 1
+            i -= 1
+        cpu_bit_mask = hex(cpu_bit_mask)
+        cmd = "%s/%s/app/testpmd -c %s -n %d -- --disable-hw-vlan --disable-rss --nb-cores=%d --rxq=%d --txq=%d --rxd=4096 --txd=4096" % (
+            RTE_SDK,
+            RTE_TARGET,
+            cpu_bit_mask,
+            cpu_num / 2,
+            cpu_num - 1,
+            (cpu_num - 1) / 2,
+            (cpu_num - 1) / 2
+        )
+        LOG.info("set_pktloop_dpdk runs cmd = %s", cmd)
+        p = subprocess.Popen(cmd.split())
+        if not p.poll():
+            self.pid = p.pid
+            return True
+        else:
+            raise Exception("start testpmd failed")
+
+    def config_amqp(self, file_name):
+        if not os.path.isfile(file_name):
+            raise Exception("file: %s not exists." % file_name)
+        check_call("cp %s /etc/vstf/amqp/amqp.ini" % file_name, shell=True)
+        check_call("vstf-agent restart", shell=True)
+        return True
+
+    def stop_vstf(self):
+        check_call("vstf-agent stop", shell=True)
+        return True
+
+
+class FSMonitor(object):
+    def __init__(self, pidfile=None, interval=1):
+        if pidfile:
+            self.pidfile = pidfile
+        else:
+            self.pidfile = PID_FILE
+        self.interval = interval
+        self.handlers = []
+        self.kill_old()
+        umount(constant.FS_MOUNT_POINT)
+        check_call("mkdir -p %s" % constant.FS_MOUNT_POINT, shell=True)
+        check_call("mount -t 9p 9pfs %s" % constant.FS_MOUNT_POINT, shell=True)
+        os.chdir(constant.FS_MOUNT_POINT)
+        with open(constant.VM_UP_Flag_FILE, 'w'):
+            pass
+
+    def kill_old(self):
+        out = check_output("ps -ef | grep -v grep | egrep 'python.*%s' | awk '{print $2}'" % sys.argv[0],
+                                      shell=True)
+        if out:
+            for pid in out.split():
+                if int(pid) != os.getpid():
+                    os.kill(int(pid), 9)
+                    LOG.debug("found daemon:pid=%s and kill.", pid)
+
+    def set_fail(self, failed_reason):
+        with open(constant.VM_CMD_RETURN_CODE_FILE, 'w') as f:
+            f.writelines([constant.VM_CMD_EXCUTE_FAILED_FLAG_CONTENT, '\n', failed_reason])
+        with open(constant.VM_CMD_DONE_FLAG_FILE, 'w') as f:
+            pass
+
+    def set_success(self):
+        with open(constant.VM_CMD_RETURN_CODE_FILE, 'w') as f:
+            f.write(constant.VM_CMD_EXCUTE_SUCCES_FLAG_CONTENT)
+        with open(constant.VM_CMD_DONE_FLAG_FILE, 'w') as f:
+            pass
+
+    def register_handler(self, obj):
+        self.handlers.append(obj)
+
+    def daemonize(self):
+        try:
+            pid = os.fork()
+            if pid > 0:
+                sys.exit(0)
+        except OSError, e:
+            sys.stderr.write('fork #1 failed:%d,(%s)\n' % (e.errno, e.strerror))
+            sys.exit(1)
+        os.setsid()
+        os.umask(0)
+        try:
+            pid = os.fork()
+            if pid > 0:
+                sys.exit(0)
+        except OSError, e:
+            sys.stderr.write('fork #2 failed:%d,(%s)\n' % (e.errno, e.strerror))
+            sys.exit(1)
+        LOG.debug("pid:%d,ppid:%d,sid:%d", os.getpid(), os.getppid(), os.getsid(os.getpid()))
+        old = open('/dev/null', 'r')
+        os.dup2(old.fileno(), sys.stdin.fileno())
+        old = open('/dev/null', 'a+')
+        os.dup2(old.fileno(), sys.stdout.fileno())
+        old = open('/dev/null', 'a+', 0)
+        os.dup2(old.fileno(), sys.stderr.fileno())
+        pid = str(os.getpid())
+        file(self.pidfile, 'w+').write('%s\n' % pid)
+
+    def run_forever(self):
+        # todo:resolve handlers name space conflict
+        self.daemonize()
+        while True:
+            time.sleep(self.interval)
+            files = os.listdir(constant.FS_MOUNT_POINT)
+            if constant.VM_CMD_SET_FLAG_FILE in files and constant.VM_CMD_CONTENT_FILE in files:
+                with open(constant.VM_CMD_CONTENT_FILE, 'r') as f:
+                    out = f.read().strip()
+                LOG.debug("new command arrived:%s", out)
+                cmd_param = out.split()
+                cmd = cmd_param[0]
+                param = cmd_param[1:]
+                for obj in self.handlers:
+                    if hasattr(obj, cmd) and callable(getattr(obj, cmd)):
+                        LOG.debug("method:%s found!", cmd)
+                        method = getattr(obj, cmd)
+                        try:
+                            method(*param)
+                            self.set_success()
+                            LOG.debug("cmd sucessfully done")
+                        except Exception, e:
+                            LOG.debug('failed to run:%s %s,reason:%s', cmd, param, str(e))
+                            self.set_fail(str(e))
+                        break
+                else:
+                    LOG.debug("method:%s not found!", cmd)
+                    self.set_fail(constant.VM_CMD_NOT_FOUND)
+                os.remove(constant.VM_CMD_SET_FLAG_FILE)
+                os.remove(constant.VM_CMD_CONTENT_FILE)
+
+
+if __name__ == '__main__':
+    # echo "set_pktloop_dpdk" > command;touch command_set
+    # echo "recover_nic_binding" > command;touch command_set
+    # echo "config_ip 56:6f:44:a5:3f:a2 192.168.188.200/23" > command;touch command_set
+    # echo "config_gw 192.168.188.1" > command;touch command_set
+    # echo set_pktloop_dpdk 56:6f:44:a5:3f:a2 56:6f:44:a5:3f:a3 > command;touch command_set
+    # echo recover_nic_binding 56:6f:44:a5:3f:a2 56:6f:44:a5:3f:a3 > command;touch command_set
+    import os
+    logging.basicConfig(level=logging.DEBUG, filename=LOG_FILE, filemode='w')
+    os.environ['PATH'] = os.environ["PATH"] + ":/usr/local/bin"
+    LOG.info(os.environ['PATH'])
+    vm_op = VMOperation()
+    agent = FSMonitor()
+    agent.register_handler(vm_op)
+    agent.run_forever()
diff --git a/vstf/vstf/agent/env/fsmonitor/Readme b/vstf/vstf/agent/env/fsmonitor/Readme
new file mode 100755 (executable)
index 0000000..f2bf9a8
--- /dev/null
@@ -0,0 +1,13 @@
+FSMonitor is a daemon process which runs in the vm.
+FSMonitor receive "commands" from 'VmManager' by libvirt 9pfs. 
+
+Basically the process works like this: 
+       1.The 'VmManager' writes 'command string' to 'command' file on the libvirt 9pfs used by vm.
+       2.The FSMonitor constantly detects file changes on the libvirt 9pfs. 
+       3.The FSMonitor finds the newly created file, it then reads the file and execute the command. 
+
+All the dependencies of FSMonitor should be satisfied by modules in this directory.
+
+When deploying FSMonitor, just copy this directory into the vm.
+
+
diff --git a/vstf/vstf/agent/env/fsmonitor/__init__.py b/vstf/vstf/agent/env/fsmonitor/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/env/fsmonitor/constant.py b/vstf/vstf/agent/env/fsmonitor/constant.py
new file mode 100755 (executable)
index 0000000..d30bb43
--- /dev/null
@@ -0,0 +1,17 @@
+"""
+Created on 2015-8-27
+
+@author: y00228926
+"""
+VM_UP_Flag_FILE = 'up'
+VM_CMD_DONE_FLAG_FILE = 'command_done'
+VM_CMD_RESULT_FILE = 'command_result_data'
+VM_CMD_SET_FLAG_FILE = 'command_set'
+VM_CMD_CONTENT_FILE = 'command'
+VM_CMD_RETURN_CODE_FILE = 'command_result'
+VM_CMD_EXCUTE_SUCCES_FLAG_CONTENT = 'success'
+VM_CMD_EXCUTE_FAILED_FLAG_CONTENT = 'fail'
+VM_CMD_NOT_FOUND = 'comamnd_not_found'
+VM_UP_TIME_OUT = 120
+VM_COMMON_CMD_EXCUTE_TIME_OUT = 10
+FS_MOUNT_POINT = '/mnt/9pfs'
\ No newline at end of file
diff --git a/vstf/vstf/agent/env/fsmonitor/utils.py b/vstf/vstf/agent/env/fsmonitor/utils.py
new file mode 100755 (executable)
index 0000000..e6eb153
--- /dev/null
@@ -0,0 +1,110 @@
+"""
+Created on 2015-7-8
+
+@author: y00228926
+"""
+import subprocess
+from StringIO import StringIO
+import re
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def call(cmd, shell=False):
+    if shell:
+        LOG.info(cmd)
+    else:
+        LOG.info(' '.join(cmd))
+    return subprocess.call(cmd, shell=shell)
+
+
+def check_call(cmd, shell=False):
+    if shell:
+        LOG.info(cmd)
+    else:
+        LOG.info(' '.join(cmd))
+    subprocess.check_call(cmd, shell=shell)
+
+
+def check_output(cmd, shell=False):
+    if shell:
+        LOG.info(cmd)
+    else:
+        LOG.info(' '.join(cmd))
+    return subprocess.check_output(cmd, shell=shell)
+
+
+def check_and_kill(process):
+    cmd = "ps -ef | grep -v grep | awk '{print $8}' | grep -w %s | wc -l" % process
+    out = check_output(cmd, shell=True)
+    if int(out):
+        check_call(['killall', process])
+
+
+def check_and_rmmod(mod):
+    cmd = "lsmod | awk '{print $1}' | grep -w %s | wc -l" % mod
+    out = check_output(cmd, shell=True)
+    if int(out):
+        check_call(['rmmod', mod])
+
+
+def umount(path):
+    mount_path_set = set()
+    out = check_output("cat /proc/mounts", shell=True)
+    f = StringIO(out)
+    line = f.readline()
+    while line:
+        line = f.readline()
+        if line:
+            mpath = line.split()[1]
+            mount_path_set.add(mpath)
+    if path in mount_path_set:
+        ret = call("umount %s" % path, shell=True)
+        return ret == 0
+    return True
+
+
+class IPCommandHelper(object):
+    def __init__(self):
+        self.devices = []
+        self.macs = []
+        self.device_mac_map = {}
+        self.mac_device_map = {}
+        self.bdf_device_map = {}
+        self.device_bdf_map = {}
+        self.mac_bdf_map = {}
+        self.bdf_mac_map = {}
+        buf = check_output("ip link", shell=True)
+        macs = re.compile("[A-F0-9]{2}(?::[A-F0-9]{2}){5}", re.IGNORECASE | re.MULTILINE)
+        for mac in macs.findall(buf):
+            if mac.lower() in ('00:00:00:00:00:00', 'ff:ff:ff:ff:ff:ff'):
+                continue
+            self.macs.append(mac)
+        sio = StringIO(buf)
+        for line in sio:
+            m = re.match(r'^\d+:(.*):.*', line)
+            if m and m.group(1).strip() != 'lo':
+                self.devices.append(m.group(1).strip())
+        for device, mac in zip(self.devices, self.macs):
+            self.device_mac_map[device] = mac
+            self.mac_device_map[mac] = device
+        for device in self.devices:
+            buf = check_output("ethtool -i %s" % device, shell=True)
+            bdfs = re.findall(r'^bus-info: \d{4}:(\d{2}:\d{2}\.\d*)$', buf, re.MULTILINE)
+            if bdfs:
+                self.bdf_device_map[bdfs[0]] = device
+                self.device_bdf_map[device] = bdfs[0]
+                mac = self.device_mac_map[device]
+                self.mac_bdf_map[mac] = bdfs[0]
+                self.bdf_mac_map[bdfs[0]] = mac
+
+
+if __name__ == '__main__':
+    ip_helper = IPCommandHelper()
+    print ip_helper.device_mac_map
+    print ip_helper.mac_device_map
+    print ip_helper.bdf_device_map
+    print ip_helper.device_bdf_map
+    print ip_helper.mac_bdf_map
+    print ip_helper.bdf_mac_map
diff --git a/vstf/vstf/agent/env/plugins/Readme b/vstf/vstf/agent/env/plugins/Readme
new file mode 100755 (executable)
index 0000000..a2879ba
--- /dev/null
@@ -0,0 +1,49 @@
+All the plugins should subclass EnvBuilderPlugin from "model.py".
+
+The EnvBuilderPlugin is a template class with a template algorithm:
+
+       def __init__(self, ):
+               pass
+       @abstractmethod    
+    def clean(self):
+        #clean Environment before goes further.
+    @abstractmethod        
+    def install(self):
+        #install network virtualization software from source code.
+    @abstractmethod
+    def load_drivers(self):
+        #loads drivers for network card.
+    @abstractmethod    
+    def create_brs(self):         
+        #creates virtual switches.         
+    @abstractmethod
+    def config_br_ports(self):
+        #config the vlan property for vswitch ports.
+    def create_vms(self):
+        #create vms         
+    def wait_vms(self):
+        #wait vm to boot up and config vm for ips and other configurations.
+    def check_vm_connectivity(self):
+        #check if the vms correctly setup the control panel ips.
+    def build(self, cfg_intent):
+        self.host_cfg = cfg_intent #please retrieve options from self.host_cfg for your use in other methods.
+        self.clean()
+        self.download_and_compile()
+        self.load_drivers()
+        self.create_brs()
+        self.create_vms()
+        self.wait_vms()
+        self.config_tap_vlans()
+        self.check_vm_connectivity()
+        
+You should implements the abstract methods left empty, however you can make some methods do nothing to skip steps..
+
+The plugin receives a "cfg_intent", The "cfg_intent" is a python dict parsed from a env-build configuration file.
+
+It contains the detail configurations for the plugin to build a "virtual network" for testing.
+
+There are some example json config files for building different type of "virtual network" under "etc/vstf/env" that you can refer to.
+
+Before you creates a new plugin, you should make sure you understand these json config file properly.   
+
+
diff --git a/vstf/vstf/agent/env/plugins/__init__.py b/vstf/vstf/agent/env/plugins/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/env/plugins/libvirt_plugin.py b/vstf/vstf/agent/env/plugins/libvirt_plugin.py
new file mode 100755 (executable)
index 0000000..e7fefb6
--- /dev/null
@@ -0,0 +1,66 @@
+"""
+Created on 2015-7-8
+
+@author: y00228926
+"""
+import logging
+
+from vstf.common.utils import ping, my_sleep
+from vstf.agent.env.plugins.model import EnvBuilderPlugin
+from vstf.agent.env.basic.source_manager import SourceCodeManager
+from vstf.agent.env.basic.vm_manager import VMControlOperation
+from vstf.agent.env.vswitch_plugins.manager import VswitchPluginManager
+from vstf.agent.env.driver_plugins.manager import DriverPluginManager
+
+LOG = logging.getLogger(__name__)
+
+
+class Plugin(EnvBuilderPlugin):
+    def __init__(self):
+        super(Plugin, self).__init__()
+        self.vm_mgr = VMControlOperation()
+        self.vs_mgr = VswitchPluginManager()
+        self.dr_mgr = DriverPluginManager()
+
+    def clean(self):
+        self.vm_mgr.clean_all_vms()
+        self.vs_mgr.clean()
+        self.dr_mgr.clean()
+
+    def load_drivers(self):
+        drivers = self.host_cfg['drivers']
+        self.dr_mgr.load(drivers)
+
+    def create_brs(self):
+        for br_cfg in self.host_cfg['bridges']:
+            plugin = self.vs_mgr.get_vs_plugin(br_cfg['type'])
+            plugin.create_br(br_cfg)
+
+    def config_br_ports(self):
+        for vm_cfg in self.host_cfg['vms']:
+            for tap_cfg in vm_cfg['taps']:
+                plugin = self.vs_mgr.get_vs_plugin(tap_cfg['br_type'])
+                plugin.set_tap_vid(tap_cfg)
+        for br_cfg in self.host_cfg['bridges']:
+            plugin = self.vs_mgr.get_vs_plugin(br_cfg['type'])
+            plugin.set_fastlink(br_cfg)
+
+    def create_vms(self):
+        for vm_cfg in self.host_cfg['vms']:
+            self.vm_mgr.create_vm(vm_cfg)
+
+    def wait_vms(self):
+        for vm_cfg in self.host_cfg['vms']:
+            self.vm_mgr.wait_vm(vm_cfg['vm_name'])
+            self.vm_mgr.init_config_vm(vm_cfg['vm_name'])
+
+    def check_vm_connectivity(self):
+        for vm_cfg in self.host_cfg['vms']:
+            vm_ip = vm_cfg['init_config']['ctrl_ip_setting'].split('/')[0]
+            for _ in range(3):
+                ret = ping(vm_ip)
+                if ret:
+                    break
+                my_sleep(3)
+            else:
+                raise Exception("ping ip:%s failed." % vm_ip)
diff --git a/vstf/vstf/agent/env/plugins/model.py b/vstf/vstf/agent/env/plugins/model.py
new file mode 100755 (executable)
index 0000000..5485f97
--- /dev/null
@@ -0,0 +1,54 @@
+"""
+Created on 2015-9-15
+
+@author: y00228926
+"""
+from abc import ABCMeta
+from abc import abstractmethod
+
+
+class EnvBuilderPlugin:
+    __metaclass__ = ABCMeta
+
+    def __init__(self):
+        self.host_cfg = None
+        pass
+
+    @abstractmethod
+    def clean(self):
+        pass
+
+    @abstractmethod
+    def load_drivers(self):
+        pass
+
+    @abstractmethod
+    def create_brs(self):
+        pass
+
+    @abstractmethod
+    def config_br_ports(self):
+        pass
+
+    @abstractmethod
+    def create_vms(self):
+        pass
+
+    @abstractmethod
+    def wait_vms(self):
+        pass
+
+    @abstractmethod
+    def check_vm_connectivity(self):
+        pass
+
+    def build(self, cfg_intent):
+        self.host_cfg = cfg_intent
+        self.clean()
+        self.load_drivers()
+        self.create_brs()
+        self.create_vms()
+        self.wait_vms()
+        self.config_br_ports()
+        self.check_vm_connectivity()
+        return True
diff --git a/vstf/vstf/agent/env/plugins/tester_env_plugin.py b/vstf/vstf/agent/env/plugins/tester_env_plugin.py
new file mode 100755 (executable)
index 0000000..0fd4b9a
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/17
+# see license for license details
+
+import logging
+
+from vstf.agent.env.plugins.model import EnvBuilderPlugin
+from vstf.agent.env.driver_plugins.manager import DriverPluginManager
+
+LOG = logging.getLogger(__name__)
+
+
+class Plugin(EnvBuilderPlugin):
+    def __init__(self):
+        super(Plugin, self).__init__()
+        self.dr_mgr = DriverPluginManager()
+
+    def clean(self):
+        self.dr_mgr.clean()
+
+    def install(self):
+        pass
+
+    def load_drivers(self):
+        drivers = self.host_cfg['drivers']
+        self.dr_mgr.load(drivers)
+
+    def create_brs(self):
+        pass
+
+    def config_br_ports(self):
+        pass
+
+    def create_vms(self):
+        pass
+
+    def wait_vms(self):
+        pass
+
+    def check_vm_connectivity(self):
+        pass
diff --git a/vstf/vstf/agent/env/vswitch_plugins/__init__.py b/vstf/vstf/agent/env/vswitch_plugins/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/vstf/vstf/agent/env/vswitch_plugins/bridge_plugin.py b/vstf/vstf/agent/env/vswitch_plugins/bridge_plugin.py
new file mode 100755 (executable)
index 0000000..252f190
--- /dev/null
@@ -0,0 +1,67 @@
+"""
+Created on 2015-10-12
+
+@author: y00228926
+"""
+from vstf.agent.env.vswitch_plugins import model
+from vstf.common.utils import check_call, get_eth_by_bdf, check_output
+
+
+class BridgePlugin(model.VswitchPlugin):
+    def __init__(self):
+        pass
+
+    def clean(self):
+        """clean brs created before.
+
+        """
+        out = check_output(r"brctl show | grep -v '^\s' | awk '{print $1}'|sed '1,1d'", shell=True)
+        print out
+        for br in out.split():
+            if br != 'br0':
+                self._del_br(br)
+
+        return True
+
+    def init(self):
+        pass
+
+    def _del_br(self, name):
+        check_call('ip link set dev %s down' % name, shell=True)
+        check_call('brctl delbr %s' % name, shell=True)
+
+    def create_br(self, br_cfg):
+        """Create a bridge(virtual switch). Return True for success, return False for failure.
+
+        :param dict    br_cfg: configuration for bridge creation like
+                {
+                    "name": "br1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                        },
+                        {
+                            "bdf": "04:00.1",
+                        }
+                    ]
+                }
+
+        """
+        name, uplinks = br_cfg['name'], br_cfg['uplinks']
+        check_call("brctl addbr %s" % name, shell=True)
+        for uplink in uplinks:
+            device = get_eth_by_bdf(uplink['bdf'])
+            check_call("ip link set dev %s up" % device, shell=True)
+            check_call("brctl addif %s %s" % (name, device), shell=True)
+        check_call("ip link set dev %s up" % name, shell=True)
+        return True
+
+    def set_tap_vid(self, tap_cfg):
+        """linux bridge doesn't support vlan id setting.
+        """
+        return True
+
+    def set_fastlink(self, br_cfg):
+        """linux bridge doesn't support openflow protocol.
+        """
+        return True
diff --git a/vstf/vstf/agent/env/vswitch_plugins/manager.py b/vstf/vstf/agent/env/vswitch_plugins/manager.py
new file mode 100755 (executable)
index 0000000..00115df
--- /dev/null
@@ -0,0 +1,31 @@
+"""
+Created on 2015-9-15
+
+@author: y00228926
+"""
+import stevedore
+
+
+class VswitchPluginManager(object):
+    def __init__(self):
+        self.plugin = None
+        self.mgr = stevedore.extension.ExtensionManager(namespace="vswitch.plugins", invoke_on_load=True)
+
+    def clean(self):
+        if self.plugin:
+            self.plugin.clean()
+            self.plugin = None
+        for plugin in self.mgr.names():
+            self.mgr[plugin].obj.clean()
+        return True
+
+    def get_vs_plugin(self, plugin):
+        if plugin in self.mgr.names():
+            ext = self.mgr[plugin]
+            self.plugin = ext.obj
+            return self.plugin
+        else:
+            raise Exception("unsupported vswitch plugin: %s" % plugin)
+
+    def get_supported_plugins(self):
+        return self.mgr.names()
diff --git a/vstf/vstf/agent/env/vswitch_plugins/model.py b/vstf/vstf/agent/env/vswitch_plugins/model.py
new file mode 100755 (executable)
index 0000000..a4d8b3b
--- /dev/null
@@ -0,0 +1,63 @@
+"""
+Created on 2015-9-15
+
+@author: y00228926
+"""
+from abc import ABCMeta
+from abc import abstractmethod
+
+
+class VswitchPlugin:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def clean(self):
+        """implement this clean function to clean environment before and after calling any other functions.
+
+        """
+        pass
+
+    @abstractmethod
+    def init(self):
+        """implements this init function to setup necessary Preconditions.
+
+        """
+        pass
+
+    @abstractmethod
+    def create_br(self, br_cfg):
+        """Create a bridge(virtual switch). Return True for success, return False for failure.
+
+        :param dict    br_cfg: configuration for bridge creation like
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "vtep": {},
+                }
+
+        """
+        pass
+
+    @abstractmethod
+    def set_tap_vid(self, tap_cfg):
+        """set vlan id or vxlan id for tap device(virtual nic for vm).
+
+        :param dict    tap_cfg: dictionary config for tap device like
+                        {
+                            "tap_name": "tap_in",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+
+        """
+        pass
+
+    def set_fastlink(self, br_cfg):
+        return True
\ No newline at end of file
diff --git a/vstf/vstf/agent/env/vswitch_plugins/ovs_plugin.py b/vstf/vstf/agent/env/vswitch_plugins/ovs_plugin.py
new file mode 100755 (executable)
index 0000000..4604595
--- /dev/null
@@ -0,0 +1,183 @@
+"""
+Created on 2015-10-10
+
+@author: y00228926
+"""
+import os
+import shutil
+import logging
+import time
+import re
+
+from vstf.agent.env.vswitch_plugins import model
+from vstf.common.utils import check_and_kill, check_and_rmmod, check_call, check_output, \
+    get_eth_by_bdf, my_mkdir, call
+
+LOG = logging.getLogger(__name__)
+
+
+class OvsPlugin(model.VswitchPlugin):
+
+    def __init__(self):
+        self.daemons = ['ovs-vswitchd', 'ovsdb-server']
+        self.mods = ['openvswitch']
+        self.dirs = {'db': "/usr/local/etc/openvswitch"}
+        self.cmds = []
+        self.cmds.append("mkdir -p /usr/local/etc/openvswitch")
+        self.cmds.append("ovsdb-tool create /usr/local/etc/openvswitch/conf.db")
+        self.cmds.append("ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock \
+             --remote=db:Open_vSwitch,Open_vSwitch,manager_options \
+             --private-key=db:Open_vSwitch,SSL,private_key \
+             --certificate=db:Open_vSwitch,SSL,certificate \
+             --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
+             --pidfile --detach")
+        self.cmds.append("ovs-vsctl --no-wait init")
+        self.cmds.append("ovs-vswitchd --pidfile --detach")
+        self.initialized = False
+
+    def init(self):
+        if not self.initialized:
+            self._start_servers()
+            self.initialized = True
+
+    def clean(self):
+        """clean for ovs. Rmmod openvswitch.ko, kill openvswitch daemon process.
+
+        """
+        for process in self.daemons:
+            check_and_kill(process)
+        for mod in self.mods:
+            check_and_rmmod(mod)
+        for _, directory in self.dirs.items():
+            if os.path.isdir(directory):
+                LOG.info('rm -rf %s', directory)
+                shutil.rmtree(directory, ignore_errors=True)
+        self.initialized = False
+        return True
+
+    def create_br(self, br_cfg):
+        """Create a bridge(virtual switch). Return True for success, return False for failure.
+
+        :param dict    br_cfg: configuration for bridge creation like
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+                    ],
+                    "vtep": {},
+                }
+
+        """
+        self.init()
+        name, uplinks = br_cfg['name'], br_cfg['uplinks']
+
+        check_call("ovs-vsctl add-br %s" % (name), shell=True)
+        if br_cfg['vtep']: # vxlan supports
+            local_ip, remote_ip = br_cfg['vtep']['local_ip'], br_cfg['vtep']['remote_ip']
+            assert len(uplinks) == 1
+            uplink = uplinks[0]
+            device = get_eth_by_bdf(uplink['bdf'])
+            time.sleep(0.5)
+            vtep = 'vx1'
+            check_call("ifconfig %s %s up" % (device, local_ip), shell=True)
+            check_call("ovs-vsctl add-port %s %s" % (name, vtep), shell=True)
+            check_call("ovs-vsctl set interface %s type=vxlan options:remote_ip=%s" % (vtep, remote_ip), shell=True)
+        for uplink in uplinks:
+            device = get_eth_by_bdf(uplink['bdf'])
+            vlan_mode = uplink['vlan_mode']
+            vlan_id = uplink['vlan_id']
+            check_call("ip link set dev %s up" % device, shell=True)
+            call("ethtool -A %s rx off tx off " % device, shell=True)
+            check_call("ovs-vsctl add-port %s %s" % (name, device), shell=True)
+            if vlan_mode == 'trunk':
+                check_call("ovs-vsctl set port %s trunks=%s" % (device, vlan_id), shell=True)
+            elif vlan_mode == 'access':
+                check_call("ovs-vsctl set port %s tag=%s" % (device, vlan_id), shell=True)
+            else:
+                raise Exception("unreconized vlan_mode:%s" % vlan_mode)
+        return True
+
+    def set_tap_vid(self, tap_cfg):
+        """set vlan id or vxlan id for tap device(virtual nic for vm).
+        return True for success, return False for failure.
+
+        :param dict    tap_cfg: dictionary config for tap device like
+                        {
+                            "tap_name": "tap_in",
+                            "vlan_mode": "access",
+                            "vlan_id": "1"
+                        }
+
+        """
+        port, vlan_mode, vlan = tap_cfg['tap_name'], tap_cfg['vlan_mode'], tap_cfg['vlan_id']
+        assert vlan_mode in ('access', 'vxlan')
+        if int(vlan) > '4095':
+            # vxlan setting
+            self.__set_tap_vid(port, "vxlan", vlan)
+        else:
+            # vlan setting
+            self.__set_tap_vid(port, vlan_mode, vlan)
+        return True
+
+    def set_fastlink(self, br_cfg):
+        """connect two ports directly, so that packets comes from any one port be forwarded to the other.
+        return True for success, return False for failure.
+
+        :param dict    br_cfg: dictionary configuration for linking ports.
+                {
+                    "name": "ovs1",
+                    "fastlink": [
+                        {
+                            "inport": "04:00.0",
+                            "outport": "tap_in"
+                        }
+                    ]
+                }
+        """
+        br_name = br_cfg['name']
+        for fast_cfg in br_cfg['fastlink']:
+            p1, p2 = fast_cfg['inport'], fast_cfg['outport']
+        self.__fastlink(br_name, p1, p2)
+        return True
+
+    def _start_servers(self):
+        for _, directory in self.dirs.items():
+            my_mkdir(directory)
+        for mod in self.mods:
+            check_call("modprobe %s" % mod, shell=True)
+        for cmd in self.cmds:
+            check_call(cmd, shell=True)
+        return True
+
+    def __set_tap_vid(self, port, vlan_mode, vlan_id):
+        if vlan_mode == 'vxlan':
+            raise Exception("don't support vxlan setting right now.")
+        elif vlan_mode == 'trunk':
+            check_call("ovs-vsctl set port %s trunks=%s" % (port, vlan_id), shell=True)
+        else:
+            check_call("ovs-vsctl set port %s tag=%s" % (port, vlan_id), shell=True)
+
+    def __fastlink(self, br, p1, p2):
+        LOG.info("_fastlink(%s,%s,%s)", br, p1, p2)
+        p1 = p1.replace(' ', '')
+        p2 = p2.replace(' ', '')
+        bdfs = check_output("lspci |grep Eth | awk '{print $1}'", shell=True).splitlines()
+        if p1 in bdfs:
+            p1 = get_eth_by_bdf(p1)
+        if p2 in bdfs:
+            p2 = get_eth_by_bdf(p2)
+        ovs_port = {}
+        buf = check_output("ovs-ofctl show %s" % br, shell=True)
+        port_info = re.compile(r"[0-9]+\(.*\)", re.IGNORECASE | re.MULTILINE)
+        for s in port_info.findall(buf):
+            port_num, interface = s.replace('(', ' ').replace(')', ' ').split()
+            ovs_port[interface] = port_num
+        pn1, pn2 = ovs_port[p1], ovs_port[p2]
+        check_call("ovs-ofctl add-flow %s in_port=%s,priority=100,action=output:%s" % (br, pn1, pn2), shell=True)
+        check_call("ovs-ofctl add-flow %s in_port=%s,priority=100,action=output:%s" % (br, pn2, pn1), shell=True)
+        return True
diff --git a/vstf/vstf/agent/equalizer/README b/vstf/vstf/agent/equalizer/README
new file mode 100755 (executable)
index 0000000..6c688a1
--- /dev/null
@@ -0,0 +1 @@
+equalizer of the performance of network
diff --git a/vstf/vstf/agent/equalizer/__init__.py b/vstf/vstf/agent/equalizer/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/equalizer/equalizer.py b/vstf/vstf/agent/equalizer/equalizer.py
new file mode 100755 (executable)
index 0000000..8db35df
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# coding=utf-8
+import os
+import re
+import subprocess
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def run_cmd(cmd, shell=True):
+    try:
+        ret = subprocess.check_output(cmd, shell=shell)
+    except subprocess.CalledProcessError as e:
+        raise e
+    return ret
+
+
+class Resource(object):
+    def __init__(self):
+        super(Resource, self).__init__()
+        self.sysfs = "/sys/devices/system/node"
+        self.mapping = {}
+        for node in self._init_numa():
+            self.mapping[node] = {}
+
+            process_mapping = self._get_process_mapping(node)
+            for process_index in xrange(0, len(bin(process_mapping)) - 2):
+                if process_mapping & 1 << process_index != 0:
+                    core = self._get_core_id(node, process_index)
+                    if not self.mapping[node].has_key(core):
+                        self.mapping[node][core] = []
+                    self.mapping[node][core].append(process_index)
+
+    def _get_process_mapping(self, numa_node):
+        ret = run_cmd("cat " + self.sysfs + '/' + numa_node + '/cpumap').replace(',', '').lstrip('0')
+        return int(ret, 16)
+
+    def _get_core_id(self, numa_node, process_index):
+        cmd = "cat " + self.sysfs + '/' + numa_node + '/cpu' + str(process_index) + '/topology/core_id'
+        return run_cmd(cmd).strip('\n')
+
+    def _init_numa(self):
+        """the node name is node0, node1......"""
+        try:
+            node_list = os.listdir(self.sysfs)
+        except Exception as e:
+            raise e
+        ret = []
+        partten = re.compile("^node[0-9]{,}$")
+        for node in node_list:
+            if partten.match(node) is None:
+                continue
+            ret.append(node)
+        return ret
+
+
+class Equalizer(Resource):
+    def __init__(self):
+        super(Equalizer, self).__init__()
+
+    def topology(self):
+        print self.mapping
+
+
+e = Equalizer()
+e.topology()
diff --git a/vstf/vstf/agent/equalizer/get_info.py b/vstf/vstf/agent/equalizer/get_info.py
new file mode 100755 (executable)
index 0000000..873f0ca
--- /dev/null
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+import commands
+
+try:
+    import xml.etree.cElementTree as ET
+except ImportError:
+    import xml.etree.ElementTree as ET
+
+
+class GetPhyInfo(object):
+    def __init__(self):
+        pass
+
+    def _get_range(self, temp):
+        topo = {}
+        phy_core_flag = True
+        for sub in temp.split(','):
+            r_list = []
+            _start = sub.split('-')[0]
+            _end = sub.split('-')[1]
+            r_list.extend(range(int(_start), int(_end) + 1))
+            if phy_core_flag:
+                topo['phy_cores'] = r_list
+            else:
+                topo['virt_cores'] = r_list
+            phy_core_flag = False
+        return topo
+
+    def _get_numa_num(self):
+        flag, num = commands.getstatusoutput('lscpu | grep "NUMA node(s):"')
+        try:
+            num = num.split(':')[1]
+        except:
+            print('get numa %s value failed.' % (num))
+        return num
+
+    def get_numa_core(self):
+        numa = {}
+        num = self._get_numa_num()
+        for numa_id in range(0, int(num)):
+            flag, temp = commands.getstatusoutput('lscpu | grep "NUMA node%s"' % (str(numa_id)))
+            try:
+                temp = temp.split(':')[1].split()[0]
+            except:
+                print('get numa %s range %s failed.' % (str(numa_id), range))
+            topo = self._get_range(temp)
+            numa['node' + str(numa_id)] = topo
+        return str(numa)
+
+    def get_nic_numa(self, nic):
+        result = {}
+        try:
+            flag, id = commands.getstatusoutput('cat /sys/class/net/%s/device/numa_node' % (nic))
+        except:
+            print('get nic numa id failed.')
+        return id
+
+    def _get_main_pid(self, xml_file):
+        try:
+            tree = ET.ElementTree(file=xml_file)
+            root = tree.getroot()
+            _main_pid = root.attrib['pid']
+        except:
+            print('[ERROR]Parse xml file failed, could not get qemu main pid')
+        return _main_pid
+
+    def _get_qemu_threads(self, xml_file):
+        # import pdb
+        # pdb.set_trace()
+        _qemu_threads = []
+        try:
+            tree = ET.ElementTree(file=xml_file)
+            root = tree.getroot()
+            for element in tree.iterfind('vcpus/vcpu'):
+                _qemu_threads.append(element.attrib['pid'])
+        except:
+            print('[ERROR]Parse xml file failed, could not get qemu threads.')
+
+        return _qemu_threads
+
+    def _get_mem_numa(self, xml_file):
+        try:
+            _mem_numa = None
+            tree = ET.ElementTree(file=xml_file)
+            root = tree.getroot()
+            for element in tree.iterfind('domain/numatune/memory'):
+                _mem_numa = element.attrib['nodeset']
+        finally:
+            return _mem_numa
+
+    def _get_vhost_threads(self, xml_file):
+        _vhost = []
+        _main_pid = self._get_main_pid(xml_file)
+
+        # get vhost info
+        proc_name = 'vhost-' + _main_pid
+        flag, temp = commands.getstatusoutput('ps -ef | grep %s | grep -v grep' % (proc_name))
+        for line in temp.split('\n'):
+            try:
+                vhost = line.split()[1]
+                _vhost.append(vhost)
+            except:
+                print('get vhost %s proc id failed' % (line))
+
+        return _vhost
+
+    def get_vm_info(self, vm_name):
+        vm = {}
+        src_path = '/var/run/libvirt/qemu/'
+        xml_file = src_path + vm_name + '.xml'
+
+        # get vm main pid from file
+        _main_pid = self._get_main_pid(xml_file)
+        # get vm vcpu thread from the libvirt file
+        _qemu_threads = self._get_qemu_threads(xml_file)
+        # get vm bind mem numa id
+        _mem_numa = self._get_mem_numa(xml_file)
+        # get vhost thread
+        _vhosts = self._get_vhost_threads(xml_file)
+
+        vm['main_pid'] = _main_pid
+        vm['qemu_thread'] = _qemu_threads
+        vm['mem_numa'] = _mem_numa
+        vm['vhost_thread'] = _vhosts
+        return vm
+
+    def _get_proc_by_irq(self, irq):
+        try:
+            flag, info = commands.getstatusoutput('ps -ef | grep irq/%s | grep -v grep ' % (irq))
+            proc_id = info.split('\n')[0].split()[1]
+        except:
+            print("[ERROR]grep process id failed.")
+        return proc_id
+
+    def get_nic_interrupt_proc(self, nic):
+        _phy_nic_thread = []
+        flag, info = commands.getstatusoutput('cat /proc/interrupts | grep %s' % (nic))
+        for line in info.split('\n'):
+            try:
+                irq_num = line.split(':')[0].split()[0]
+                proc_id = self._get_proc_by_irq(irq_num)
+                _phy_nic_thread.append([irq_num, proc_id])
+            except:
+                print("[ERROR]get irq num failed.")
+        return _phy_nic_thread
+
+    def get_libvirt_vms(self):
+        vm_list = []
+        flag, info = commands.getstatusoutput('virsh list')
+        list = info.split('\n')
+        if list[-1] == '':
+            list.pop()
+        del list[0]
+        del list[0]
+
+        for line in list:
+            try:
+                vm_temp = line.split()[1]
+                vm_list.append(vm_temp)
+            except:
+                print("Get vm name failed from %s" % (line))
+        return vm_list
diff --git a/vstf/vstf/agent/equalizer/optimize.py b/vstf/vstf/agent/equalizer/optimize.py
new file mode 100755 (executable)
index 0000000..941769a
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+import commands
+import re
+
+
+# import pdb
+# pdb.set_trace()
+
+class Optimize(object):
+    def __init__(self):
+        pass
+
+    def bind_cpu(self, cpu_range, thread):
+        flag, num = commands.getstatusoutput('taskset -pc %s %s' % (cpu_range, thread))
+        return flag
+
+    def catch_thread_info(self):
+        thread_info = {'fwd_vhost': None, 'src_recv_irq': None, 'dst_send_irq': None}
+        # top -H get the usage info
+        flag, threads_usages = commands.getstatusoutput('top -bH -n1 -c -w 2000')
+        line_array = threads_usages.split('\n')
+        # get highest vhost line
+        for line in line_array:
+            if re.search('vhost-', line) and self._check_thread_usage(line):
+                thread_info['fwd_vhost'] = line.split()[0]
+                break
+        # get highest irq thread as src_recv_irq thread
+        for line in line_array:
+            if re.search('irq/', line) and self._check_thread_usage(line):
+                thread_info['src_recv_irq'] = line.split()[0]
+                line_array.remove(line)
+                break
+        # get the second highest irq thread as dst_send_irq
+        for line in line_array:
+            if re.search('irq/', line) and self._check_thread_usage(line):
+                thread_info['dst_send_irq'] = line.split()[0]
+                break
+        # check the data valid
+
+        for key in thread_info.keys():
+            if thread_info[key] is None:
+                return False, str(thread_info)
+        return True, str(thread_info)
+
+    def _check_thread_usage(self, line):
+        try:
+            usage = line.split()[8]
+            if float(usage) >= 3.0:
+                return True
+            else:
+                print("[ERROR]The highest thread %s is less than 0.05" % usage)
+                return False
+        except:
+            print("[ERROR]The thread usage get failed.")
diff --git a/vstf/vstf/agent/perf/__init__.py b/vstf/vstf/agent/perf/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/perf/affctl.py b/vstf/vstf/agent/perf/affctl.py
new file mode 100755 (executable)
index 0000000..e9b9692
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/26
+# see license for license details
+
+from vstf.common.utils import check_call, call, check_output
+
+
+def affctl_load(policy):
+    cmd = "affctl load %s" % policy
+    return check_call(cmd, shell=True)
+
+
+def affctl_list():
+    cmd = "affctl list"
+    return check_output(cmd, shell=True)
+
diff --git a/vstf/vstf/agent/perf/ethtool.py b/vstf/vstf/agent/perf/ethtool.py
new file mode 100755 (executable)
index 0000000..c214a56
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/12
+# see license for license details
+
+import vstf.common.utils as utils
+
+__all__ = ["autoneg_on", "autoneg_off", "autoneg_query"]
+
+_para_map = {
+    "Autonegotiate": ("-A", "-a", "autoneg"),
+    "RX": ("-A", "-a", "rx"),
+    "TX": ("-A", "-a", "tx"),
+}
+
+
+def autoneg_on(iface, nspace=None):
+    return _set(nspace, iface, Autonegotiate="on", RX="on", TX="on")
+
+
+def autoneg_off(iface, nspace=None):
+    return _set(nspace, iface, Autonegotiate="off", RX="off", TX="off")
+
+
+def autoneg_query(iface, nspace=None):
+    return _query(nspace, iface, "-a")
+
+
+def _set(nspace, iface, **kwargs):
+    cmds = {}
+    for item, value in kwargs.items():
+        opt, _, key = _para_map[item]
+        cmds.setdefault(opt, [])
+        cmds[opt].append(key)
+        cmds[opt].append(value)
+
+    for key, value in cmds.items():
+        cmd = _namespace(nspace)
+        cmd += ["ethtool", key, iface] + value
+        utils.call(cmd)
+
+    return True
+
+
+def _query(nspace, iface, item):
+    cmd = _namespace(nspace)
+    cmd += ["ethtool", item, iface]
+    return utils.check_output(cmd)
+
+
+def _namespace(nspace):
+    result = ""
+    if nspace:
+        result = "ip netns exec %(namespace)s " % {"namespace": nspace}
+    return result.split()
diff --git a/vstf/vstf/agent/perf/iperf.py b/vstf/vstf/agent/perf/iperf.py
new file mode 100755 (executable)
index 0000000..25728b7
--- /dev/null
@@ -0,0 +1,152 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author:
+# date: 2015-09-15
+# see license for license details
+
+import subprocess
+import signal
+import os
+import time
+import logging
+
+import vstf.common.decorator as deco
+import vstf.agent.perf.utils as utils
+from vstf.common.utils import kill_by_name
+
+LOG = logging.getLogger(__name__)
+
+
+class Iperf(object):
+    def __init__(self):
+        self._send_processes = []
+        self._receive_processes = []
+        self._typemap = {
+            "tcp_bw": "",
+            "udp_bw": " -u ",
+        }
+
+    @deco.check("protocol", choices=['tcp_bw', 'udp_bw'])
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    @deco.check("time", defaults=600)
+    @deco.check("size", defaults=64)
+    @deco.check("threads", defaults=1)
+    def send_start(self, **kwargs):
+
+        cmd = self.format_send_start(**kwargs)
+        LOG.debug("cmd:%s", cmd)
+
+        process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(1)
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start iperf send success"
+            self._send_processes.append(process)
+        else:
+            print ret
+            error_str = "start iperf send failed, %s", (str(kwargs))
+
+        return ret, error_str
+
+    @deco.namespace()
+    def format_send_start(self, **kwargs):
+        cmd = "iperf %(type)s -c %(dst_ip)s -i 1 -l %(pkt_size)s -t %(time)s  -P %(threads)s "
+        context = {
+            'type': self._typemap[kwargs['protocol']],
+            'dst_ip': kwargs['dst'][0]['ip'],
+            'time': kwargs['time'],
+            'pkt_size': kwargs['size'],
+            'threads': kwargs['threads'],
+        }
+        cmd = cmd % context
+        return cmd
+
+    def send_stop(self):
+        results = []
+        for process in self._send_processes:
+            poll = process.poll()
+            if poll is None:
+                process.kill()
+                ret = 0
+                read = "process is stopped by killed"
+                results.append((ret, read))
+
+        self._send_processes = []
+        return results
+
+    @deco.namespace()
+    def format_receive_start(self, **kwargs):
+        cmd = 'iperf %s -s ' % (self._typemap[kwargs['protocol']])
+        return cmd
+
+    @deco.check("protocol", choices=['tcp_bw', 'udp_bw'])
+    @deco.check("namespace", defaults=None)
+    def receive_start(self, **kwargs):
+        cmd = self.format_receive_start(**kwargs)
+        LOG.debug("cmd:%s", cmd)
+
+        process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(1)
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start iperf receive success"
+            self._receive_processes.append(process)
+        else:
+            print ret
+            error_str = "start iperf receive failed, %s" % (str(kwargs))
+        return ret, error_str
+
+    def receive_stop(self):
+        ret = 0
+        for process in self._receive_processes:
+            process.kill()
+            ret = process.wait()
+        self._receive_processes = []
+        return ret
+
+    def receive_kill(self):
+        ret = 0
+        receive_pids = utils.get_pid_by_name('iperf')
+        for pid in receive_pids:
+            os.kill(pid, signal.SIGKILL)
+            time.sleep(0.5)
+        error_str = "stop iperf receive success"
+        LOG.debug(error_str)
+        return ret, error_str
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name('iperf')
+        self._send_processes = []
+        self._receive_processes = []
+        return True
+
+
+def unit_test():
+    perf = Iperf()
+    pro = 'udp_bw'
+    print perf.receive_start(namespace='receive', protocol=pro)
+
+    send = {
+        "namespace": "send",
+        "protocol": "udp_bw",
+        "dst": [
+            {"ip": "192.168.1.102"}
+        ],
+        "size": 64,
+        "time": 5,
+    }
+    print perf.send_start(**send)
+    time.sleep(10)
+    print perf.send_stop()
+    print perf.receive_stop()
+
+
+if __name__ == "__main__":
+    from vstf.common.log import setup_logging
+
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf-iperf.log", clevel=logging.DEBUG)
+    unit_test()
diff --git a/vstf/vstf/agent/perf/netmap.py b/vstf/vstf/agent/perf/netmap.py
new file mode 100755 (executable)
index 0000000..c61d257
--- /dev/null
@@ -0,0 +1,166 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-11-09
+# see license for license details
+
+
+import time
+import subprocess
+import vstf.common.decorator as deco
+from vstf.common.utils import kill_by_name, my_popen
+
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+class Netmap(object):
+    def __init__(self):
+        self._send_processes = []
+        self._receive_processes = []
+
+    @deco.check("protocol", choices=['udp_bw'], defaults='udp_bw')
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    @deco.check("src")
+    @deco.check("size", defaults=64)
+    @deco.check("threads", defaults=1)
+    @deco.check("ratep", defaults=0)
+    def send_start(self, **kwargs):
+        cmd = self.format_send_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+
+        process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        self._send_processes.append(process)
+        time.sleep(0.5)
+
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start netmap send success"
+        else:
+            error_str = "start netmap send failed, %s" % (str(kwargs))
+            process.wait()
+            self._send_processes.remove(process)
+
+        return ret, error_str
+
+    def send_stop(self, **kwargs):
+        LOG.info("send_stop")
+        results = []
+        ret = 0
+        for process in self._send_processes:
+            process.kill()
+            process.wait()
+            error_str = "stop netmap send success"
+            results.append((ret, error_str))
+        self._send_processes = []
+        return results
+
+    def format_send_start(self, **kwargs):
+        cmd = "pkt-gen -i %(src_iface)s -f tx -l %(pkt_size)s -p %(threads)s -D %(dst_mac)s -R %(ratep)s"
+        context = {
+            'src_iface': kwargs['src'][0]['iface'],
+            'dst_mac': kwargs['dst'][0]['mac'],
+            'pkt_size': kwargs['size'],
+            'threads': kwargs['threads'],
+            'ratep': kwargs['ratep']
+        }
+        cmd = cmd % context
+        return cmd
+
+    @deco.namespace()
+    def format_receive_start(self, **kwargs):
+        cmd = "pkt-gen -i %(iface)s -f rx"
+        context = {
+            'iface': kwargs['dst'][0]['iface']
+        }
+        cmd = cmd % context
+        return cmd
+
+    @deco.check("protocol", choices=['udp_bw'], defaults='udp_bw')
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    def receive_start(self, **kwargs):
+
+        cmd = self.format_receive_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+
+        process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        self._receive_processes.append(process)
+        time.sleep(0.5)
+
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start netmap receive success"
+        else:
+            error_str = "start netmap receive failed, %s" % (str(kwargs))
+            process.wait()
+            self._receive_processes.remove(process)
+
+        return ret, error_str
+
+    def receive_stop(self, **kwargs):
+        LOG.info("receive_stop")
+        ret = 0
+        for process in self._receive_processes:
+            process.kill()
+            process.wait()
+        self._receive_processes = []
+        error_str = "stop netmap receive success"
+        self._receive_processes = []
+        return ret, error_str
+
+    def clean(self):
+        self.send_stop()
+        self.receive_stop()
+        return True
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name('pkt-gen')
+        self._send_processes = []
+        self._receive_processes = []
+        return True
+
+
+def unit_test():
+    perf = Netmap()
+    receive = {
+        "protocol": "udp_bw",
+        # "namespace": "receive",
+        "dst": [
+            {"iface": "p57p2"}
+        ],
+    }
+    ret = perf.receive_start(**receive)
+    LOG.info("*********receive_start***********")
+    LOG.info("ret")
+    send = {
+        # "namespace": "send",
+        "protocol": "udp_bw",
+        "src": [
+            {"iface": "eth4", "mac": "90:e2:ba:20:1f:d8"}
+        ],
+        "dst": [
+            {"mac": "90:e2:ba:20:1f:d9"}
+        ],
+        "size": 64,
+        "threads": 1,
+        "ratep": 0
+    }
+    print perf.send_start(**send)
+    print perf._send_processes
+    time.sleep(10)
+
+    print perf.send_stop()
+    print perf.receive_stop()
+
+
+if __name__ == "__main__":
+    from vstf.common.log import setup_logging
+
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-netmap.log", clevel=logging.INFO)
+    unit_test()
diff --git a/vstf/vstf/agent/perf/netns.py b/vstf/vstf/agent/perf/netns.py
new file mode 100755 (executable)
index 0000000..d5552fa
--- /dev/null
@@ -0,0 +1,103 @@
+"""
+Created on 2015-8-6
+
+@author: y00228926
+"""
+import logging
+from vstf.common.utils import IPCommandHelper
+from vstf.agent.perf import ethtool
+from vstf.common.utils import check_call, check_output, ns_cmd, my_popen, my_sleep
+
+LOG = logging.getLogger(__name__)
+
+
+class Netns(object):
+    def __init__(self):
+        super(Netns, self).__init__()
+        self.netns_add_str = "ip netns add %s"
+        self.netns_del_str = " ip netns del %s"
+        self.netns_add_device_str = " ip link set %s netns %s"
+        self.set_link_up_str = "ip link set dev %s up"
+        self.set_link_addr_str = "ip addr replace %s dev %s"
+        self.netns_remove_device_str = "ip netns exec %s ip link set %s netns  1"
+        # self.set_link_addr_str = "ifconfig %s %s up"
+        self.ns_devices = {}
+
+    def clean_all_namespace(self):
+        out = check_output("ip netns list", shell=True)
+        for ns in out.splitlines():
+            self.remove_namespace(ns)
+        return True
+
+    def create_namespace(self, name):
+        if name in (None, 'None', 'none'):
+            return True
+        cmd = self.netns_add_str % name
+        check_call(cmd, shell=True)
+        return True
+
+    def remove_namespace(self, ns):
+        if ns in (None, 'None', 'none'):
+            return True
+        ip_helper = IPCommandHelper(ns)
+        for dev in ip_helper.device_mac_map:
+            cmd = self.netns_remove_device_str % (ns, dev)
+            check_call(cmd, shell=True)
+            self.activate_device(None, dev)
+        cmd = self.netns_del_str % ns
+        check_call(cmd, shell=True)
+        return True
+
+    def add_device(self, ns, device):
+        if ns is None:
+            return True
+        cmd = self.netns_add_device_str % (device, ns)
+        check_call(cmd, shell=True)
+        return True
+
+    def config_ip(self, ns, device, ip):
+        self.activate_device(ns, device)
+        cmd = self.set_link_addr_str % (ip, device)
+        cmd = ns_cmd(ns, cmd)
+        check_call(cmd, shell=True)
+        return True
+
+    def activate_device(self, ns, device):
+        cmd = self.set_link_up_str % device
+        cmd = ns_cmd(ns, cmd)
+        check_call(cmd, shell=True)
+        return True
+
+
+class NetnsManager(object):
+    def __init__(self):
+        super(NetnsManager, self).__init__()
+        self._netns = Netns()
+
+    def config_dev(self, netdev):
+        ns, device, ip = netdev["namespace"], netdev["iface"], netdev['ip_setting'] if "ip_setting" in netdev else \
+            netdev['ip']
+        self._netns.create_namespace(ns)
+        self._netns.add_device(ns, device)
+        self._netns.config_ip(ns, device, ip)
+        my_sleep(1)
+        ethtool.autoneg_off(device, ns)
+        return True
+
+    def recover_dev(self, netdev):
+        ns = netdev["namespace"]
+        return self._netns.remove_namespace(ns)
+
+    def clean_all_namespace(self):
+        return self._netns.clean_all_namespace()
+
+    @staticmethod
+    def ping(ns, ip):
+        cmd = "ping -w2 -c1 %s" % ip
+        cmd = ns_cmd(ns, cmd)
+        child = my_popen(cmd, shell=True)
+        return 0 == child.wait()
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
diff --git a/vstf/vstf/agent/perf/netperf.py b/vstf/vstf/agent/perf/netperf.py
new file mode 100755 (executable)
index 0000000..fab1fc1
--- /dev/null
@@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author:
+# date: 2015-09-15
+# see license for license details
+import time
+import subprocess
+import vstf.common.constants as cst
+import vstf.common.decorator as deco
+from vstf.common import perfmark as mark
+from vstf.common.utils import kill_by_name, my_popen
+
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+class Netperf(object):
+    def __init__(self):
+        self._send_processes = []
+        self._islat = False
+        self._typemap = {
+            "tcp_lat": "TCP_STREAM",
+            "tcp_bw": "TCP_STREAM",
+            "udp_lat": "UDP_STREAM",
+            "udp_bw": "UDP_STREAM",
+        }
+
+    @deco.check("protocol", choices=cst.PROTOCOLS)
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    @deco.check("time", defaults=0)
+    @deco.check("size", defaults=64)
+    @deco.check("threads", defaults=1)
+    def send_start(self, **kwargs):
+        threads = kwargs.pop('threads')
+        kwargs['buf'] = cst.SOCKET_BUF
+        if kwargs['protocol'] in ['tcp_lat', 'udp_lat']:
+            self._islat = True
+        else:
+            kwargs['time'] = 0
+
+        cmd = self.format_send_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+
+        for _ in range(threads):
+            process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            self._send_processes.append(process)
+        time.sleep(0.5)
+        for process in self._send_processes:
+            ret = process.poll()
+            if ret is None:
+                ret = 0
+                error_str = "start netperf send success"
+            else:
+                error_str = "start netperf send failed, %s" % (str(kwargs))
+                process.wait()
+                self._send_processes.remove(process)
+
+        return ret, error_str
+
+    def send_stop(self, **kwargs):
+        LOG.info("send_stop")
+        results = []
+        ret = 0
+        for process in self._send_processes:
+            poll = process.poll()
+            if poll is None:
+                if not self._islat:
+                    process.kill()
+                    read = "process is stopped by killed"
+                else:
+                    ret = process.wait()
+                    read = process.stdout.read()
+                    read = self._parse_data(read)
+                results.append((ret, read))
+        self._send_processes = []
+        self._islat = False
+        return results
+
+    @staticmethod
+    def _parse_data(data):
+        buf = data.splitlines()
+        data = buf[2].strip().split(',')
+        result = {
+            mark.minLatency: float(data[0]),
+            mark.avgLatency: float(data[1]),
+            mark.maxLatency: float(data[2])
+        }
+        return result
+
+    @deco.namespace()
+    def format_send_start(self, **kwargs):
+        #       cmd = "netperf -H %(dst_ip)s -t %(type)s -l %(time)s -- -m %(pkt_size)s "
+        cmd = "netperf -H %(dst_ip)s -t %(type)s -l %(time)s  " \
+              "-- -m %(pkt_size)s -s %(buf)s -S %(buf)s -o  MIN_LATENCY,MEAN_LATENCY,MAX_LATENCY"
+        context = {
+            'dst_ip': kwargs['dst'][0]['ip'],
+            'type': self._typemap[kwargs['protocol']],
+            'time': kwargs['time'],
+            'pkt_size': kwargs['size'],
+            'buf': kwargs['buf'],
+        }
+        cmd = cmd % context
+        return cmd
+
+    @deco.namespace()
+    def format_receive_start(self, **kwargs):
+        cmd = 'netserver'
+        return cmd
+
+    @deco.check("namespace")
+    def receive_start(self, **kwargs):
+
+        cmd = self.format_receive_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+
+        process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(0.5)
+        ret = process.poll()
+        if ret:
+            error_str = "start netserver failed, %s" % (str(kwargs))
+        else:
+            ret = 0
+            error_str = "start netserver success"
+
+        return ret, error_str
+
+    def receive_stop(self, **kwargs):
+        LOG.info("receive_stop")
+        ret = 0
+        kill_by_name('netserver')
+        time.sleep(0.5)
+        error_str = "stop netserver success"
+        return ret, error_str
+
+    def clean(self):
+        self.send_stop()
+        self.receive_stop()
+        return True
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name('netserver')
+        kill_by_name('netperf')
+        self._send_processes = []
+        self._receive_processes = []
+        return True
+
+
+def unit_test():
+    perf = Netperf()
+    ret = perf.receive_start(namespace='receive')
+    print "*********receive_start***********"
+    print ret
+    send = {
+        "namespace": "send",
+        "protocol": "udp_lat",
+        "dst": [
+            {"ip": "192.168.1.102"}
+        ],
+        "size": 64,
+        "threads": 1,
+        "time": 10,
+    }
+    print perf.send_start(**send)
+    print perf._send_processes
+    time.sleep(10)
+    print perf.send_stop()
+    print perf.receive_stop()
+
+
+if __name__ == "__main__":
+    from vstf.common.log import setup_logging
+
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-netperf.log", clevel=logging.DEBUG)
+    unit_test()
diff --git a/vstf/vstf/agent/perf/pktgen.py b/vstf/vstf/agent/perf/pktgen.py
new file mode 100755 (executable)
index 0000000..58c0e6c
--- /dev/null
@@ -0,0 +1,149 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author:
+# date: 2015-09-15
+# see license for license details
+import subprocess
+import time
+import logging
+import vstf.agent.perf.utils as utils
+import vstf.common.decorator as deco
+from vstf.common.utils import my_popen
+
+LOG = logging.getLogger(__name__)
+
+
+class Pktgen(object):
+    def __init__(self):
+        utils.modprobe_pktgen()
+        self._send_processes = []
+
+    def _psetpg(self, dev):
+        self._dev = dev
+
+    def _vsetpg(self, key, value=''):
+        with open(self._dev, 'w') as f:
+            txt = "%(key)s %(value)s\n" % {'key': key, 'value': value}
+            f.write(txt)
+            LOG.info("write(%s) to %s", txt.strip(), self._dev)
+
+    def _start(self):
+        cmd = 'echo start > /proc/net/pktgen/pgctrl'
+        process = my_popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        LOG.info('running pid:%s', process.pid)
+        time.sleep(0.5)
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            self._send_processes.append(process)
+            error_str = "start pktgen send success"
+        else:
+            error_str = "start pktgen send failed, stdout:%s,stderr:%s" % (process.stdout.read(), process.stderr.read())
+            LOG.info(error_str)
+        return ret, error_str
+
+    def _rem_device_all(self):
+        cpu_num = utils.get_cpu_num()
+        for thread in range(0, cpu_num - 1):
+            self._psetpg("/proc/net/pktgen/kpktgend_%s" % thread)
+            self._vsetpg('rem_device_all')
+        return True
+
+    @deco.check("protocol", choices=['udp_bw'], defaults='udp_bw')
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    @deco.check("src")
+    @deco.check("size", defaults=64)
+    @deco.check("threads", defaults=utils.get_default_threads())
+    @deco.check("clone_skb", defaults=1)
+    @deco.check("count", defaults=0)
+    @deco.check("ratep", defaults=0)
+    def send_start(self, **kwargs):
+        # ensure that all sends is exit
+        self.send_stop()
+
+        interface_num = len(kwargs['src'])
+        interfaces = []
+        for i in range(interface_num):
+            device = kwargs['src'][i]['iface']
+            interfaces.append(device)
+            utils.iface_up(device)
+
+        self._rem_device_all()
+
+        threads = kwargs['threads']
+        for i in range(interface_num):
+            dev_min = i * threads
+            dev_max = (i + 1) * threads
+            device = interfaces[i]
+            for dev_id in range(dev_min, dev_max):
+                queue_id = dev_id % threads
+                self._psetpg("/proc/net/pktgen/kpktgend_%s" % dev_id)
+                self._vsetpg('add_device', "%s@%s" % (device, queue_id))
+                self._psetpg("/proc/net/pktgen/%s@%s" % (device, queue_id))
+                self._vsetpg('pkt_size', kwargs['size'])
+                self._vsetpg('clone_skb', kwargs['clone_skb'])
+                self._vsetpg('dst_mac', kwargs['dst'][i]['mac'])
+                self._vsetpg('src_mac', kwargs['src'][i]['mac'])
+                self._vsetpg('count', kwargs['count'])
+                if kwargs['ratep']:
+                    self._vsetpg('ratep', kwargs['ratep'])
+        return self._start()
+
+    def send_stop(self, **kwargs):
+        results = []
+        ret = 0
+        for process in self._send_processes:
+            process.kill()
+            process.wait()
+            LOG.info("process.kill(pktgen:%s)", process.pid)
+            results.append((ret, process.stdout.read()))
+        self._send_processes = []
+        return results
+
+    def receive_start(self, **kwargs):
+        ret = 0
+        error_str = "%s pktgen neednt receive start" % (self.__class__)
+        LOG.debug(error_str)
+        return ret, error_str
+
+    def receive_stop(self, **kwargs):
+        ret = 0
+        error_str = "pktgen neednt receive stop"
+        LOG.debug(error_str)
+        return ret, error_str
+
+    def clean(self):
+        self.send_stop()
+        return True
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        return self.clean()
+
+
+def unit_test():
+    perf = Pktgen()
+    print perf.receive_start()
+    send = {
+        "src": [
+            {"iface": 'eth4', "mac": "90:e2:ba:20:1f:d8"}
+        ],
+        "dst": [
+            {"mac": '90:e2:ba:20:1f:d9'}
+        ],
+        "size": 64,
+        "threads": 1,
+        'ratep': 0
+    }
+    print perf.send_start(**send)
+    time.sleep(30)
+    print perf.send_stop()
+    print perf.receive_stop()
+
+
+if __name__ == "__main__":
+    from vstf.common.log import setup_logging
+
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-pktgen.log", clevel=logging.DEBUG)
+    unit_test()
diff --git a/vstf/vstf/agent/perf/qperf.py b/vstf/vstf/agent/perf/qperf.py
new file mode 100755 (executable)
index 0000000..3cb9eaf
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author:
+# date: 2015-09-15
+# see license for license details
+
+import subprocess
+import time
+import logging
+import vstf.common.decorator as deco
+from vstf.common import perfmark as mark
+from vstf.common.utils import kill_by_name, my_popen
+
+LOG = logging.getLogger(__name__)
+
+
+class Qperf(object):
+    def __init__(self):
+        self._send_processes = []
+        self._receive_processes = []
+
+    @deco.check("protocol", choices=['tcp_lat', 'udp_lat'])
+    @deco.check("namespace", defaults=None)
+    @deco.check("dst")
+    @deco.check("time", defaults=10)
+    @deco.check("size", defaults=64)
+    def send_start(self, **kwargs):
+        cmd = self.format_send_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+        process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(0.5)
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start qperf send success"
+            self._send_processes.append(process)
+        else:
+            print ret
+            error_str = "start qperf send failed, %s" % (str(kwargs))
+            process.wait()
+
+        return ret, error_str
+
+    @deco.namespace()
+    def format_send_start(self, **kwargs):
+        cmd = "qperf %(dst_ip)s -t %(time)s -m %(pkt_size)s -vu %(type)s "
+        context = {
+            'dst_ip': kwargs['dst'][0]['ip'],
+            'type': kwargs['protocol'],
+            'time': kwargs['time'],
+            'pkt_size': kwargs['size'],
+        }
+        cmd = cmd % context
+        return cmd
+
+    def send_stop(self, **kwargs):
+        results = []
+        for process in self._send_processes:
+            process.wait()
+            read = process.stdout.read()
+            read = self._parse_data(read)
+            ret = 0
+            results.append((ret, read))
+        self._send_processes = []
+        return results
+
+    @deco.namespace()
+    def format_receive_start(self, **kwargs):
+        cmd = 'qperf'
+        return cmd
+
+    def receive_start(self, **kwargs):
+        cmd = self.format_receive_start(**kwargs)
+        LOG.info("cmd:%s", cmd)
+
+        process = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(0.5)
+        ret = process.poll()
+        if ret is None:
+            ret = 0
+            error_str = "start qperf receive success"
+            self._receive_processes.append(process)
+        else:
+            print ret
+            error_str = "start qperf receive failed, %s" % (str(kwargs))
+            process.wait()
+            raise Exception(error_str)
+        return ret, error_str
+
+    def receive_stop(self, **kwargs):
+        ret = 0
+        for process in self._receive_processes:
+            process.kill()
+            process.wait()
+        self._receive_processes = []
+        error_str = "stop qperf receive success"
+        return ret, error_str
+
+    def receive_kill(self):
+        kill_by_name('qperf')
+        self._receive_processes = []
+        return True
+
+    def clean(self):
+        for process in self._receive_processes:
+            process.kill()
+            process.wait()
+            LOG.info("process.kill(qperf daemon:%s)", process.pid)
+        for process in self._send_processes:
+            LOG.info("process.wait(qperf client:%s)", process.pid)
+            process.wait()
+        self._receive_processes = []
+        self._send_processes = []
+        return True
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name('qperf')
+        self._send_processes = []
+        self._receive_processes = []
+        return True
+
+    def _parse_data(self, data):
+        LOG.info(data)
+        latency = 0
+        if data:
+            buf = data.splitlines()
+            if "latency" in buf[1]:
+                data = buf[1].strip().split()
+                if data[3] == "us":
+                    latency = float(data[2]) / 1000
+                else:
+                    latency = float(data[2]) / 1000
+        result = {
+            mark.minLatency: latency,
+            mark.avgLatency: latency,
+            mark.maxLatency: latency
+        }
+        return result
+
+
+def unit_test():
+    perf = Qperf()
+    perf.receive_start(namespace='receive')
+
+    send = {
+        "namespace": "send",
+        "protocol": "udp_lat",
+        "dst": [
+            {"ip": "192.168.1.102"}
+        ],
+        "size": 64,
+    }
+    print perf.send_start(**send)
+    time.sleep(10)
+    print perf.send_stop()
+    print perf.receive_stop()
+
+
+if __name__ == "__main__":
+    from vstf.common.log import setup_logging
+
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-qperf.log", clevel=logging.DEBUG)
+    unit_test()
diff --git a/vstf/vstf/agent/perf/sar.py b/vstf/vstf/agent/perf/sar.py
new file mode 100755 (executable)
index 0000000..c4688c9
--- /dev/null
@@ -0,0 +1,78 @@
+"""
+Created on 2015-8-6
+
+@author: y00228926
+"""
+import subprocess
+import logging
+import time
+import os
+from signal import SIGINT
+
+from vstf.common.utils import check_output, my_popen, kill_by_name
+from vstf.agent.env.basic import collect
+
+LOG = logging.getLogger(__name__)
+
+
+class Sar(object):
+    def __init__(self):
+        self.sar_cmd_str = "sar -u %(interval)s"
+        self.child_process = {}
+
+    def start(self, interval=2):
+        cmd = self.sar_cmd_str % {'interval': interval}
+        child = my_popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        time.sleep(1)
+        if child.poll() is not None:
+            print child.poll()
+            raise Exception("start vnstat error, vnstat is not running")
+        self.child_process[child.pid] = child
+        return child.pid
+
+    def stop(self, pid):
+        assert pid in self.child_process
+        os.kill(pid, SIGINT)
+        process = self.child_process.pop(pid)
+        out = process.stdout.read()
+        process.wait()
+        data = {'raw_data': out, 'tool': 'sar', 'type': 'cpu'}
+        cpu_info = collect.Collect().collect_host_info()[1]
+        cpu_num = cpu_info['CPU INFO']['CPU(s)']
+        cpu_mhz = cpu_info['CPU INFO']['CPU MHz']
+        data.update({'cpu_num': float(cpu_num), 'cpu_mhz': float(cpu_mhz)})
+        return data
+
+    def process(self, raw):
+        lines = raw.splitlines()
+        # print lines
+        head = lines[2].split()[3:]
+        average = lines[-1].split()[2:]
+        data = {}
+        for h, d in zip(head, average):
+            data[h.strip('%')] = float(d)
+        cpu_num = check_output('cat /proc/cpuinfo  | grep processor | wc -l', shell=True).strip()
+        data.update({'cpu_num': int(cpu_num)})
+        return data
+
+    def clean(self):
+        for _, process in self.child_process.items():
+            process.kill()
+            process.wait()
+        self.child_process = {}
+        return True
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name("sar")
+        self.child_process = {}
+        return True
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    q = Sar()
+    pid = q.start()
+    time.sleep(10)
+    raw = q.stop(pid)
+    print raw
+    print q.process(raw['raw_data'])
diff --git a/vstf/vstf/agent/perf/utils.py b/vstf/vstf/agent/perf/utils.py
new file mode 100755 (executable)
index 0000000..1fb4b92
--- /dev/null
@@ -0,0 +1,42 @@
+"""
+Created on 2015-8-6
+
+@author: y00228926
+"""
+import logging
+import subprocess
+from vstf.common.utils import check_call, check_output
+
+LOG = logging.getLogger(__name__)
+
+
+def get_pid_by_name(process_name):
+    out = check_output(['ps', '-A'])
+    pids = []
+    for line in out.splitlines():
+        values = line.split()
+        pid, name = values[0], values[3]
+        if process_name == name:
+            pids.append(int(pid))
+    return pids
+
+
+def get_cpu_num():
+    cpu_num = check_output('cat /proc/cpuinfo  | grep processor | wc -l', shell=True).strip()
+    cpu_num = int(cpu_num)
+    return cpu_num
+
+
+def get_default_threads():
+    cpu_num = get_cpu_num()
+    return 2 if cpu_num > 3 * 3 else 1
+
+
+def modprobe_pktgen():
+    check_call('modprobe pktgen', shell=True)
+    return True
+
+
+def iface_up(device):
+    check_call("ifconfig %s up" % device, shell=True)
+    return True
diff --git a/vstf/vstf/agent/perf/vnstat.py b/vstf/vstf/agent/perf/vnstat.py
new file mode 100755 (executable)
index 0000000..7a47af1
--- /dev/null
@@ -0,0 +1,106 @@
+"""
+Created on 2015-8-6
+
+@author: y00228926
+"""
+import subprocess
+import time
+import re
+from signal import SIGINT
+import os
+import logging
+from vstf.common.utils import check_call, my_popen, kill_by_name
+
+LOG = logging.getLogger(__name__)
+
+
+class VnStat(object):
+    def __init__(self):
+        self.netns_exec_str = "ip netns exec %s "
+        self.vnstat_cmd_str = "vnstat -l -i %s"
+        self.child_process = {}
+
+    def run_vnstat(self, device, namespace=None):
+        cmd = self.vnstat_cmd_str
+        if namespace:
+            cmd1 = (self.netns_exec_str + "ifconfig %s") % (namespace, device)
+            check_call(cmd1, shell=True)
+            cmd = self.netns_exec_str + cmd
+            cmd = cmd % (namespace, device)
+        else:
+            cmd = cmd % device
+        check_call("which vnstat", shell=True)
+        child = my_popen(cmd.split(), stdout=subprocess.PIPE)
+        self.child_process[child.pid] = child
+        return child.pid
+
+    def kill_vnstat(self, pid, namespace=None):
+        assert pid in self.child_process
+        os.kill(pid, SIGINT)
+        process = self.child_process.pop(pid)
+        out = process.stdout.read()
+        process.wait()
+        LOG.info("os.kill(pid = %s)", pid)
+        data = {'tool': 'vnstat', 'type': 'nic', 'raw_data': out}
+        return data
+
+    def clean(self):
+        for _, process in self.child_process.items():
+            process.kill()
+            process.wait()
+            LOG.info("process.kill(vnstat:%s)", process.pid)
+        self.child_process = {}
+        return True
+
+    def process(self, raw):
+        buf = raw.splitlines()
+        buf = buf[9:]
+        buf = ' '.join(buf)
+        m = {}
+
+        digits = re.compile(r"\d+\.?\d*")
+        units = re.compile("(?:gib|mib|kib|kbit/s|gbits/s|mbit/s|p/s)", re.IGNORECASE | re.MULTILINE)
+        units_arr = units.findall(buf)
+
+        LOG.debug(units_arr)
+
+        digits_arr = digits.findall(buf)
+
+        for i in range(len(digits_arr)):
+            digits_arr[i] = round(float(digits_arr[i]), 2)
+
+        m['rxpck'], m['txpck'] = digits_arr[8], digits_arr[9]
+        m['time'] = digits_arr[-1]
+        digits_arr = digits_arr[:8] + digits_arr[10:-1]
+        index = 0
+        for unit in units_arr:
+            unit = unit.lower()
+            if unit == 'gib':
+                digits_arr[index] *= 1024
+            elif unit == 'kib':
+                digits_arr[index] /= 1024
+            elif unit == 'gbit/s':
+                digits_arr[index] *= 1000
+            elif unit == 'kbit/s':
+                digits_arr[index] /= 1000
+            else:
+                pass
+            index += 1
+
+        for i in range(len(digits_arr)):
+            digits_arr[i] = round(digits_arr[i], 2)
+
+        m['rxmB'], m['txmB'] = digits_arr[0:2]
+        m['rxmB_max/s'], m['txmB_max/s'] = digits_arr[2:4]
+        m['rxmB/s'], m['txmB/s'] = digits_arr[4:6]
+        m['rxmB_min/s'], m['txmB_min/s'] = digits_arr[6:8]
+        m['rxpck_max/s'], m['txpck_max/s'] = digits_arr[8:10]
+        m['rxpck/s'], m['txpck/s'] = digits_arr[10:12]
+        m['rxpck_min/s'], m['txpck_min/s'] = digits_arr[12:14]
+        return m
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        kill_by_name("vnstat")
+        self.child_process = {}
+        return True
diff --git a/vstf/vstf/agent/perf/vstfperf.py b/vstf/vstf/agent/perf/vstfperf.py
new file mode 100755 (executable)
index 0000000..224380f
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-08
+# see license for license details
+
+
+__doc__ = """
+operation: [start, stop, restart]
+action: [send, receive]
+tool: [pktgen, netperf, qperf, iperf, netmap]
+params:
+    protocol: [tcp_lat, udp_lat, tcp_bw, udp_bw]
+    namespace: None
+    src:[
+       { "iface":"eth0", "ip":"xxx.xxx.xxx.xxx", "mac":"FF:FF:FF:FF:FF:FF"}
+    ]
+    dst:[
+       { "iface":"eth0", "ip":"xxx.xxx.xxx.xxx", "mac":"FF:FF:FF:FF:FF:FF"}
+    ]
+    size: 64
+    threads: 1
+    ratep: 100000  (pps)
+    time: 100  (s)
+"""
+
+import sys
+import logging
+import vstf.common.constants as cst
+import vstf.common.decorator as deco
+import vstf.agent.perf.pktgen as vstf_pktgen
+import vstf.agent.perf.netmap as vstf_netmap
+import vstf.agent.perf.qperf as vstf_qperf
+import vstf.agent.perf.iperf as vstf_iperf
+import vstf.agent.perf.netperf as vstf_netperf
+
+LOG = logging.getLogger(__name__)
+
+
+class Vstfperf(object):
+    def __init__(self):
+        for tool in cst.TOOLS:
+            obj_name = 'vstf_' + tool
+            obj = getattr(sys.modules[__name__], obj_name)
+            cls_name = tool.title()
+            cls = getattr(obj, tool.title())
+            self.__dict__.update({tool: cls()})
+
+    @deco.check("operation", choices=cst.OPERATIONS)
+    @deco.check("action", choices=cst.ACTIONS)
+    @deco.check("tool", choices=cst.TOOLS)
+    @deco.check("params", defaults={})
+    def run(self, **kwargs):
+        print "_run in"
+        operation = kwargs.pop("operation")
+        tool = kwargs.pop("tool")
+        instance = getattr(self, tool)
+        action = kwargs.pop("action")
+        func_name = "%s_%s" % (action, operation)
+        func = getattr(instance, func_name)
+        LOG.info(kwargs['params'])
+        LOG.info(func)
+        ret = func(**kwargs['params'])
+        return ret
+
+    def force_clean(self):
+        LOG.info("%s %s start", self.__class__, self.force_clean.__name__)
+        for tool in cst.TOOLS:
+            instance = getattr(self, tool)
+            instance.force_clean()
+        return True
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-vstfperf.log", clevel=logging.INFO)
+
+    perf = Vstfperf()
+    start = {
+        "operation": "start",
+        "action": "send",
+        "tool": "netperf",
+        "params": {
+            "namespace": "vnet_name1",
+            "protocol": "udp_lat",
+            "dst": [
+                {"ip": "192.168.1.102"}
+            ],
+            "size": 64,
+            "threads": 1,
+            "time": 100,
+        },
+    }
+    perf.run(**start)
+
+    stop = {
+        "operation": "stop",
+        "action": "send",
+        "tool": "netperf",
+    }
+    perf.run(**stop)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/agent/softagent.py b/vstf/vstf/agent/softagent.py
new file mode 100755 (executable)
index 0000000..6f97239
--- /dev/null
@@ -0,0 +1,123 @@
+import logging
+import time
+from vstf.agent.env.basic.image_manager import ImageManager
+from vstf.agent.env.basic.source_manager import SourceCodeManager
+from vstf.agent.env.basic import commandline
+from vstf.agent.env.basic.device_manager import DeviceManager
+from vstf.agent.env.basic import collect as coll
+from vstf.agent.perf import netns, vnstat, vstfperf, sar, ethtool, affctl
+from vstf.agent.env import builder
+from vstf.agent.equalizer.get_info import GetPhyInfo
+from vstf.agent.equalizer.optimize import Optimize
+from vstf.agent.env.driver_plugins.manager import DriverPluginManager
+
+LOG = logging.getLogger(__name__)
+
+
+class ENV(object):
+    def __init__(self):
+        super(ENV, self).__init__()
+        self.builder = builder.PluginManager()
+
+    def build_env(self, cfg_intent):
+        return self.builder.build(cfg_intent)
+
+    def clean_env(self):
+        return self.builder.clean()
+
+    @staticmethod
+    def create_images(cfg):
+        return ImageManager(cfg).create_all()
+
+    @staticmethod
+    def clean_images(cfg):
+        return ImageManager(cfg).clean_all()
+
+
+class Drivers(object):
+    def __init__(self):
+        super(Drivers, self).__init__()
+        self.dr_mgr = DriverPluginManager()
+
+    def install_drivers(self, drivers):
+        LOG.info("install drivers:%s", drivers)
+        self.dr_mgr.clean()
+        ret = self.dr_mgr.load(drivers)
+        return ret
+
+    def clean_drivers(self):
+        return self.dr_mgr.clean()
+
+    def autoneg_on(self, iface, nspace):
+        return ethtool.autoneg_on(iface, nspace)
+
+    def autoneg_off(self, iface, nspace):
+        return ethtool.autoneg_off(iface, nspace)
+
+    def autoneg_query(self, iface, nspace):
+        return ethtool.autoneg_query(iface, nspace)
+
+
+class Cpu(object):
+    def affctl_load(self, policy):
+        return affctl.affctl_load(policy)
+
+    def affctl_list(self):
+        return affctl.affctl_list()
+
+
+class Perf(object):
+    def __init__(self):
+        super(Perf, self).__init__()
+        self._vnstat = vnstat.VnStat()
+        self._vstfperf = vstfperf.Vstfperf()
+        self._sar = sar.Sar()
+   
+    def run_vnstat(self, device, namespace=None):
+        return self._vnstat.run_vnstat(device, namespace)
+
+    def kill_vnstat(self, pid, namespace=None):
+        return self._vnstat.kill_vnstat(pid, namespace)
+
+    def perf_run(self, **kwargs):
+        return self._vstfperf.run(**kwargs)
+
+    def run_cpuwatch(self, interval = 2):
+        return self._sar.start(interval)
+
+    def kill_cpuwatch(self, pid):
+        return self._sar.stop(pid)
+
+    def force_clean(self):
+        self._vstfperf.force_clean()
+        self._sar.force_clean()
+        self._vnstat.force_clean()
+        return True
+
+
+class EqualizerOps(GetPhyInfo, Optimize):
+    def __init__(self):
+        super(EqualizerOps, self).__init__()
+
+
+class BaseAgent(coll.Collect,
+                ENV,
+                Cpu,
+                Drivers,
+                DeviceManager,
+                commandline.CommandLine, 
+                netns.NetnsManager,
+                SourceCodeManager
+                ):
+    def __init__(self):
+        super(BaseAgent, self).__init__()
+
+
+class softAgent(BaseAgent, Perf, EqualizerOps):
+    def __init__(self):
+        super(softAgent, self).__init__()
+
+
+if __name__ == '__main__':
+    softAgent()
+
diff --git a/vstf/vstf/agent/spirent/__init__.py b/vstf/vstf/agent/spirent/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/spirent/spirent.py b/vstf/vstf/agent/spirent/spirent.py
new file mode 100755 (executable)
index 0000000..3b073cb
--- /dev/null
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+import Tkinter
+
+
+def build_cmd(*args):
+    cmd = ''
+    for arg in args:
+        cmd = cmd+str(arg)+' '
+    #import pdb
+    #pdb.set_trace()
+    return cmd
+
+
+class stcPython():
+    def __init__(self):
+        self.tclsh = Tkinter.Tcl()
+        self.stcpkg = '/home/Spirent_TestCenter_4.46/Spirent_TestCenter_Application_Linux'
+        self.tclsh.eval("set auto_path [ linsert $auto_path 0 %s ]" %(self.stcpkg))
+        self.tclsh.eval("package require SpirentTestCenter")
+        
+    def build_cmd(self, *args):
+        cmd = ''
+        for arg in args:
+            cmd = cmd+str(arg)+' '
+        return cmd
+
+    # [ stc base interface ]
+    def stc_init(self, *args):
+        cmd = build_cmd('stc::init', *args)
+        return self.tclsh.eval(cmd)
+    # stc connect
+    def stc_connect(self,*args):
+        cmd = build_cmd('stc::connect', *args)
+        return self.tclsh.eval(cmd)
+    # stc disconnect
+    def stc_disconnect(self,*args):
+        cmd = build_cmd('stc::disconnect', *args)
+        return self.tclsh.eval(cmd)
+    # stc create
+    def stc_create(self,*args):
+        cmd = build_cmd('stc::create', *args)
+        return self.tclsh.eval(cmd)
+    # stc delete
+    def stc_delete(self,*args):
+        cmd = build_cmd('stc::delete', *args)
+        return self.tclsh.eval(cmd)
+    # stc config
+    def stc_config(self,*args):
+        cmd = build_cmd('stc::config', *args)
+        return self.tclsh.eval(cmd)
+    # stc get
+    def stc_get(self,*args):
+        cmd = build_cmd('stc::get', *args)
+        return self.tclsh.eval(cmd)
+    # stc apply
+    def stc_apply(self,*args):
+        cmd = build_cmd('stc::apply', *args)
+        return self.tclsh.eval(cmd)
+    # stc perform
+    def stc_perform(self,*args):
+        cmd = build_cmd('stc::perform', *args)
+        return self.tclsh.eval(cmd)
+    # stc reserve
+    def stc_reserve(self,*args):
+        cmd = build_cmd('stc::reserve', *args)
+        return self.tclsh.eval(cmd)
+    # stc release
+    def stc_release(self,*args):
+        cmd = build_cmd('stc::release', *args)
+        return self.tclsh.eval(cmd)
+    # stc subscribe
+    def stc_subscribe(self,*args):
+        cmd = build_cmd('stc::subscribe',*args)
+        return self.tclsh.eval(cmd)
+    # stc unsubscribe
+    def stc_unsubscribe(self,*args):
+        cmd = build_cmd('stc::unsubscribe', *args)
+        return self.tclsh.eval(cmd)
+    # stc wait until sequencer complete
+    def stc_waituntilcomplete(self,*args):
+        cmd = build_cmd('stc::waituntilcomplete', *args)
+        return self.tclsh.eval(cmd)
+    # stc help
+    def stc_help(self, *args):
+        cmd = build_cmd('stc::help',*args)
+        return self.tclsh.eval(cmd)
+
+    # [ stc expand interface ]
+    # get one dict-key's value
+    # return value
+    def stc_get_value(self,stc_dict,stc_key):
+        cmd = stc_dict+' -'+stc_key
+        return self.stc_get(cmd)
+    # create project
+    # return: project_name
+    def stc_create_project(self):
+        return self.stc_create('project')
+    # create port under project
+    # return: port name
+    def stc_create_port(self,project_name):
+        cmd = 'port -under '+project_name
+        return self.stc_create(cmd)
+    # config port location
+    # return: None
+    def stc_config_port_location(self,port_name,chassisAddress,slot,port):
+        #import pdb
+        #pdb.set_trace()
+        cmd = port_name+' -location //'+chassisAddress+'/'+slot+'/'+port+' -UseDefaultHost False'
+        return self.stc_config(cmd)
+    # create streamblock under port 
+    # return: streamblock name
+    def stc_create_streamblock(self,port_name,vlan_tag,ExpectedRxPort,srcMac,dstMac,sourceAddr,destAddr):
+        #import pdb
+        #pdb.set_trace()
+        if vlan_tag == None or vlan_tag == 'None':
+            frameStruc = '"EthernetII IPv4 Udp"'
+            if ExpectedRxPort == '' :
+                return self.stc_create( 'streamBlock -under ',port_name,
+                                        '-frameConfig ',frameStruc,
+                                        '-frame "EthernetII.srcMac',srcMac,'EthernetII.dstMac',dstMac,
+                                        'IPv4.1.sourceAddr',sourceAddr,'IPv4.1.destAddr',destAddr,'"')
+            else :
+                return self.stc_create( 'streamBlock -under ',port_name,
+                                        '-ExpectedRxPort',ExpectedRxPort,
+                                        '-frameConfig ',frameStruc,
+                                        '-frame "EthernetII.srcMac',srcMac,'EthernetII.dstMac',dstMac,
+                                        'IPv4.1.sourceAddr',sourceAddr,'IPv4.1.destAddr',destAddr,'"')
+        else :
+            frameStruc = '"EthernetII Vlan IPv4 Udp"'
+            if ExpectedRxPort == '' :
+                return self.stc_create( 'streamBlock -under ',port_name,
+                                        '-frameConfig '+frameStruc,
+                                        '-frame "EthernetII.srcMac',srcMac,'EthernetII.dstMac',dstMac,
+                                        'Vlan.1.id',vlan_tag,
+                                        'IPv4.1.sourceAddr',sourceAddr,'IPv4.1.destAddr',destAddr,'"')
+            else :
+                return self.stc_create( 'streamBlock -under ',port_name,
+                                        '-ExpectedRxPort',ExpectedRxPort,
+                                        '-frameConfig '+frameStruc,
+                                        '-frame "EthernetII.srcMac',srcMac,'EthernetII.dstMac',dstMac,
+                                        'Vlan.1.id',vlan_tag,
+                                        'IPv4.1.sourceAddr',sourceAddr,'IPv4.1.destAddr',destAddr,'"')
+    # config streamblock with part arguments
+    # argument list use args dictionary
+    def stc_config_streamblock(self,streamblock_name,args_dict):
+        cmd = ''
+        for key in args_dict.keys() :
+            temp_cmd = '-'+key+' '+str(args_dict[key])
+            cmd = cmd + temp_cmd
+        return self.stc_config(streamblock_name,cmd)
+    # get generator name from port name
+    # return: generator name
+    def stc_get_generator(self,port_name):
+        cmd = port_name+' -children-generator'
+        return self.stc_get(cmd)
+    # config generator with part arguments
+    # argument list use args dictionary
+    # return none
+    def stc_config_generator(self,generator_name,args_dict):
+        cmd = ''
+        for key in args_dict.keys() :
+            temp_cmd = '-'+key+' '+str(args_dict[key])
+            cmd = cmd + temp_cmd
+        return self.stc_config(generator_name,cmd)
+    # attach port
+    # return: port's parent project info
+    def stc_attach_ports(self,portList):
+        cmd = 'AttachPorts -portList {'
+        for port in portList :
+            cmd = cmd+' '+port
+        cmd = cmd+'} -autoConnect TRUE'
+        return self.stc_perform(cmd)
+    # config src mac and dst mac
+    # return: none
+    def stc_config_ethII(self,ethII,src_mac,dst_mac):
+        cmd = ethII+' -srcMac '+src_mac+' -dstMac '+dst_mac
+        return self.stc_config(cmd)
+    # config src ip and dst ip
+    # return: none
+    def stc_config_ethIII(self,ethIII,src_ip,dst_ip):
+        cmd = ethIII+' -sourceAddr '+src_ip+' -destAddr '+dst_ip
+        return self.stc_config(cmd)
+    # start streamblock
+    # return: none
+    def stc_streamblock_start(self,streamblock_list):
+        cmd = 'StreamBlockStart -StreamBlockList {'
+        for streamblock in streamblock_list :
+            cmd = cmd+' '+streamblock
+        cmd = cmd+' } -ExecuteSynchronous TRUE'
+        return self.stc_perform(cmd)
+    # stop streamblock
+    def stc_streamblock_stop(self,streamblock_list):
+        cmd = 'StreamBlockStop -StreamBlockList {'
+        for streamblock in streamblock_list :
+            cmd = cmd+' '+streamblock
+        cmd = cmd+' } -ExecuteSynchronous TRUE'
+        return self.stc_perform(cmd)
+    # start generator
+    # return: none
+    def stc_generator_start(self,generator_List):
+        cmd = 'GeneratorStart -generatorList {'
+        for generator in generator_List :
+            cmd = cmd+' '+generator
+        cmd = cmd+' }'
+        return self.stc_perform(cmd)
+    # stop generator
+    # return: none
+    def stc_generator_stop(self,generator_List):
+        cmd = 'GeneratorStop -generatorList {'
+        for generator in generator_List :
+            cmd = cmd+' '+generator
+        cmd = cmd+' }'
+        return self.stc_perform(cmd)
+    # create rfc2544 throughput test
+    def stc_setup_rfc2544_throughput(self):
+        pass
+    # create rfc2544 frameloss test
+    def stc_setup_rfc2544_frameloss(self):
+        pass
+    # create rfc2544 latency test
+    def stc_setup_rfc2544_latency(self):
+        pass
+    # start Sequence start
+    def stc_sequence_start(self):
+        return self.stc_perform('SequencerStart')
+    # output rfc2544 throughput result
+    def stc_get_rfc2544_throughput_result(self):
+        pass
+    # output rfc2544 frameloss result
+    def stc_get_rfc2544_frameloss_result(self):
+        pass
+    # output rfc2544 latency result
+    def stc_get_rfc2544_latency_result(self):
+        pass
diff --git a/vstf/vstf/agent/spirent/tools.py b/vstf/vstf/agent/spirent/tools.py
new file mode 100755 (executable)
index 0000000..6d0d429
--- /dev/null
@@ -0,0 +1,325 @@
+#!/usr/bin/python
+import time
+from spirent import stcPython
+
+class Spirent_Tools(object):
+    baseAPI = stcPython()
+    def __init__(self):
+        """This class provide API of Spirent
+        
+        """
+        super(Spirent_Tools, self).__init__()
+    
+    def send_packet(self,flow):
+        try:
+            #import pdb
+            #pdb.set_trace()
+            flow = eval(flow)
+            #stc init action
+            self.baseAPI.stc_perform(' ResetConfig -config system1')
+            self.baseAPI.stc_init()
+            #create project
+            project = self.baseAPI.stc_create_project()
+            #create port
+            port_handle = self.baseAPI.stc_create_port(project)
+            #config port
+            slot = flow['send_port'].split('/')[0]
+            port = flow['send_port'].split('/')[1]
+            self.baseAPI.stc_config_port_location(port_handle,flow['tester_ip'],slot,port)
+            #create streamblock
+            streamblock_handle = self.baseAPI.stc_create_streamblock(
+                                                                 port_name  = port_handle,
+                                                                 ExpectedRxPort = '',
+                                                                 vlan_tag = flow['vlan'],
+                                                                 srcMac = flow['src_mac'],
+                                                                 dstMac = flow['dst_mac'],
+                                                                 sourceAddr = flow['src_ip'],
+                                                                 destAddr =flow['dst_ip']
+                                                                 )
+            # attach port
+            port_list = [port_handle]
+            self.baseAPI.stc_attach_ports(port_list)
+            #start streamblock
+            streamblock_list = [streamblock_handle]
+            flag = self.baseAPI.stc_streamblock_start(streamblock_list)
+            return str(streamblock_list).strip('[]')
+        except :
+            print("[ERROR]create stream block and send packet failed.")
+            return False
+
+    def mac_learning(self,flowA,flowB):
+        try:
+            #import pdb
+            #pdb.set_trace()
+            flowA = eval(flowA)
+            flowB = eval(flowB)
+            port_list = []
+            streamblock_list = []
+            #stc init action
+            self.baseAPI.stc_perform(' ResetConfig -config system1')
+            self.baseAPI.stc_init()
+            #create project
+            project = self.baseAPI.stc_create_project()
+            #create port and config port 
+            for flow in [ flowA,flowB ]:
+                flow['port_handle'] = self.baseAPI.stc_create_port(project)
+                tmp_test_ip = flow['tester_ip']
+                tmp_slot = flow['send_port'].split('/')[0]
+                tmp_port = flow['send_port'].split('/')[1]
+                self.baseAPI.stc_config_port_location(flow['port_handle'],tmp_test_ip,tmp_slot,tmp_port)
+                #create streamblock
+                flow['streamblock'] = self.baseAPI.stc_create_streamblock(port_name  = flow['port_handle'],
+                                                                     ExpectedRxPort = '',
+                                                                     vlan_tag = flow['vlan'],
+                                                                     srcMac = flow['src_mac'],
+                                                                     dstMac = flow['dst_mac'],
+                                                                     sourceAddr = flow['src_ip'],
+                                                                     destAddr =flow['dst_ip'])
+                #create port and stream block list
+                port_list.append(flow['port_handle'])
+                streamblock_list.append(flow['streamblock'])
+
+            #attach port
+            self.baseAPI.stc_attach_ports(port_list)
+            #start streamblock
+            flag = self.baseAPI.stc_streamblock_start(streamblock_list)
+            # mac learning
+            time.sleep(2)
+            # stop stream block
+            self.baseAPI.stc_streamblock_stop(streamblock_list)
+            # delete streamblock and release port
+            for flow in [ flowA,flowB ]:
+                tmp_test_ip = flow['tester_ip']
+                tmp_slot = flow['send_port'].split('/')[0]
+                tmp_port = flow['send_port'].split('/')[1]
+                self.baseAPI.stc_delete(flow['streamblock'])
+                self.baseAPI.stc_release('%s/%s/%s' %(tmp_test_ip,tmp_slot,tmp_port))
+            # delete project
+            self.baseAPI.stc_delete('project1')
+            ret = self.baseAPI.stc_perform('ResetConfig -config system1')
+            return True
+        except :
+            print("[ERROR]mac learning failed")
+            return False
+
+    def stop_flow(self,streamblock_list,flow):
+        flow = eval(flow)
+        streamblock_list = streamblock_list.strip('\'').split(',')
+        #stop streamblock list
+        try :
+            ret = self.baseAPI.stc_streamblock_stop(streamblock_list)
+        except :
+            print("[ERROR]Stop the streamblock list failed.")
+            return False
+        #delete streamblock
+        try :
+            for streamblock in streamblock_list :
+                ret = self.baseAPI.stc_delete(streamblock)
+        except :
+            print("[ERROR]delete stream block.")
+            return False
+        #release port
+        try :
+            slot = flow['send_port'].split('/')[0]
+            port = flow['send_port'].split('/')[1]
+            ret = self.baseAPI.stc_release('%s/%s/%s' %(flow['tester_ip'],slot,port))
+        except :
+            print("[ERROR]Release port failed")
+            return False
+        ##delete project
+        try :
+            ret = self.baseAPI.stc_delete('project1')
+            ret = self.baseAPI.stc_perform('ResetConfig -config system1')
+            return True
+        except :
+            print("[ERROR]Delete project1 failed.")
+            return False
+        
+    def run_rfc2544_throughput(self,forward_init_flows,reverse_init_flows):
+        #import pdb
+        #pdb.set_trace()
+        #rebuild the flows 
+        forward_init_flows = eval(forward_init_flows)
+        reverse_init_flows = eval(reverse_init_flows)
+        #stc init action
+        self.baseAPI.stc_perform(' ResetConfig -config system1')
+        self.baseAPI.stc_init()
+        #create project 
+        project = self.baseAPI.stc_create_project()
+        #create sequencer
+        seq_handle = self.baseAPI.stc_create('Sequencer -under %s' %(project))
+        #create port handle
+        forward_port_handle = self.baseAPI.stc_create_port(project)
+        reverse_port_handle = self.baseAPI.stc_create_port(project)
+        #create forward flow streamblock
+        for key in forward_init_flows.keys():
+            forward_init_flows[key]['port_handle'] = forward_port_handle
+            tmp_test_ip = forward_init_flows[key]['tester_ip']
+            tmp_slot    = forward_init_flows[key]['send_port'].split('/')[0]
+            tmp_port    = forward_init_flows[key]['send_port'].split('/')[1]
+            self.baseAPI.stc_config_port_location(forward_init_flows[key]['port_handle'],tmp_test_ip,tmp_slot,tmp_port)
+            #create streamblock
+            forward_init_flows[key]['streamblock'] = self.baseAPI.stc_create_streamblock(port_name  = forward_init_flows[key]['port_handle'],
+                                                                                     vlan_tag   = forward_init_flows[key]['vlan'],
+                                                                                     ExpectedRxPort = reverse_port_handle,
+                                                                                     srcMac     = forward_init_flows[key]['src_mac'],
+                                                                                     dstMac     = forward_init_flows[key]['dst_mac'],
+                                                                                     sourceAddr = forward_init_flows[key]['src_ip'],
+                                                                                     destAddr   = forward_init_flows[key]['dst_ip'])
+        #create reverse flow streamblock
+        for key in reverse_init_flows.keys():
+            reverse_init_flows[key]['port_handle'] = reverse_port_handle
+            tmp_test_ip = reverse_init_flows[key]['tester_ip']
+            tmp_slot    = reverse_init_flows[key]['send_port'].split('/')[0]
+            tmp_port    = reverse_init_flows[key]['send_port'].split('/')[1]
+            self.baseAPI.stc_config_port_location(reverse_init_flows[key]['port_handle'],tmp_test_ip,tmp_slot,tmp_port)
+            #create streamblock
+            reverse_init_flows[key]['streamblock'] = self.baseAPI.stc_create_streamblock(port_name  = reverse_init_flows[key]['port_handle'],
+                                                                                     vlan_tag   = reverse_init_flows[key]['vlan'],
+                                                                                     ExpectedRxPort = forward_port_handle,
+                                                                                     srcMac     = reverse_init_flows[key]['src_mac'],
+                                                                                     dstMac     = reverse_init_flows[key]['dst_mac'],
+                                                                                     sourceAddr = reverse_init_flows[key]['src_ip'],
+                                                                                     destAddr   = reverse_init_flows[key]['dst_ip'])
+        #Create the RFC 2544 throughput test
+        throughput_config = self.baseAPI.stc_create('Rfc2544ThroughputConfig -under ',project,
+                                                '-AcceptableFrameLoss 0.01',
+                                                '-NumOfTrials 1',
+                                                '-DurationSeconds 60',
+                                                '-SearchMode BINARY',
+                                                '-RateLowerLimit 1',
+                                                '-RateUpperLimit 100',
+                                                '-RateInitial 10',
+                                                '-UseExistingStreamBlocks True',
+                                                '-EnableLearning False',
+                                                '-FrameSizeIterationMode CUSTOM',
+                                                '-CustomFrameSizeList "70 128 256 512 1024 1280 1518"',
+                                                '-LatencyType LIFO',
+                                                '-EnableJitterMeasurement TRUE'
+                                                )
+        #import pdb
+        #pdb.set_trace()
+        # list streamblocks
+        streamblock_list = '" '
+        for key in forward_init_flows.keys():
+            streamblock_list = streamblock_list+forward_init_flows[key]['streamblock']+' '
+        for key in reverse_init_flows.keys():
+            streamblock_list = streamblock_list+reverse_init_flows[key]['streamblock']+' '
+        streamblock_list = streamblock_list+'"'
+
+        throughput_sbProfile= self.baseAPI.stc_create('Rfc2544StreamBlockProfile -under '+throughput_config+' -Active TRUE -LocalActive TRUE')
+        self.baseAPI.stc_config(throughput_sbProfile,'-StreamBlockList '+streamblock_list)
+        self.baseAPI.stc_perform('ExpandBenchmarkConfigCommand','-config ',throughput_config)
+
+        #attach the port before testing
+        port_list = [ forward_port_handle,reverse_port_handle]
+        self.baseAPI.stc_attach_ports(port_list)
+
+        #stc apply and begin to sequence test
+        self.baseAPI.stc_apply()
+        self.baseAPI.stc_perform("SequencerStart")
+
+        #wait until complete
+        self.baseAPI.stc_waituntilcomplete()
+        
+        #get result db
+        resultsdb = self.baseAPI.stc_get("system1.project.TestResultSetting", "-CurrentResultFileName")
+        results_dict = self.baseAPI.stc_perform('QueryResult','-DatabaseConnectionString',resultsdb,'-ResultPath RFC2544ThroughputTestResultDetailedSummaryView')
+        #print results_dict
+        return True,results_dict
+
+    def run_rfc2544_frameloss(self,forward_init_flows,reverse_init_flows):
+        #import pdb
+        #pdb.set_trace()
+        #rebuild the flows
+        forward_init_flows = eval(forward_init_flows)
+        reverse_init_flows = eval(reverse_init_flows)
+        #stc init action
+        self.baseAPI.stc_perform(' ResetConfig -config system1')
+        self.baseAPI.stc_init()
+        #create project
+        project = self.baseAPI.stc_create_project()
+        #create sequencer
+        seq_handle = self.baseAPI.stc_create('Sequencer -under %s' %(project))
+        #create port handle
+        forward_port_handle = self.baseAPI.stc_create_port(project)
+        reverse_port_handle = self.baseAPI.stc_create_port(project)
+        #create forward flow streamblock
+        for key in forward_init_flows.keys():
+            forward_init_flows[key]['port_handle'] = forward_port_handle
+            tmp_test_ip = forward_init_flows[key]['tester_ip']
+            tmp_slot    = forward_init_flows[key]['send_port'].split('/')[0]
+            tmp_port    = forward_init_flows[key]['send_port'].split('/')[1]
+            self.baseAPI.stc_config_port_location(forward_init_flows[key]['port_handle'],tmp_test_ip,tmp_slot,tmp_port)
+            #create streamblock
+            forward_init_flows[key]['streamblock'] = self.baseAPI.stc_create_streamblock(port_name  = forward_init_flows[key]['port_handle'],
+                                                                                     vlan_tag   = forward_init_flows[key]['vlan'],
+                                                                                     ExpectedRxPort = reverse_port_handle,
+                                                                                     srcMac     = forward_init_flows[key]['src_mac'],
+                                                                                     dstMac     = forward_init_flows[key]['dst_mac'],
+                                                                                     sourceAddr = forward_init_flows[key]['src_ip'],
+                                                                                     destAddr   = forward_init_flows[key]['dst_ip'])
+        #create reverse flow streamblock
+        for key in reverse_init_flows.keys():
+            reverse_init_flows[key]['port_handle'] = reverse_port_handle
+            tmp_test_ip = reverse_init_flows[key]['tester_ip']
+            tmp_slot    = reverse_init_flows[key]['send_port'].split('/')[0]
+            tmp_port    = reverse_init_flows[key]['send_port'].split('/')[1]
+            self.baseAPI.stc_config_port_location(reverse_init_flows[key]['port_handle'],tmp_test_ip,tmp_slot,tmp_port)
+            #create streamblock
+            reverse_init_flows[key]['streamblock'] = self.baseAPI.stc_create_streamblock(port_name  = reverse_init_flows[key]['port_handle'],
+                                                                                     vlan_tag   = reverse_init_flows[key]['vlan'],
+                                                                                     ExpectedRxPort = forward_port_handle,
+                                                                                     srcMac     = reverse_init_flows[key]['src_mac'],
+                                                                                     dstMac     = reverse_init_flows[key]['dst_mac'],
+                                                                                     sourceAddr = reverse_init_flows[key]['src_ip'],
+                                                                                     destAddr   = reverse_init_flows[key]['dst_ip'])
+        #Create the RFC 2544 frameloss test
+        frameloss_config = self.baseAPI.stc_create('Rfc2544FrameLossConfig -under ',project,
+                                                '-NumOfTrials 1 ',
+                                                '-DurationSeconds 60 ',
+                                                '-LoadUnits PERCENT_LINE_RATE ',
+                                                '-LoadType CUSTOM '
+                                                '-CustomLoadList 100 '
+                                                '-UseExistingStreamBlocks True ',
+                                                '-EnableLearning False ',
+                                                '-FrameSizeIterationMode CUSTOM ',
+                                                '-CustomFrameSizeList "70 128 256 512 1024 1280 1518"',
+                                                '-LatencyType LIFO',
+                                                '-EnableJitterMeasurement TRUE'
+                                                )
+        #import pdb
+        #pdb.set_trace()
+        # list streamblocks
+        streamblock_list = '" '
+        for key in forward_init_flows.keys():
+            streamblock_list = streamblock_list+forward_init_flows[key]['streamblock']+' '
+        for key in reverse_init_flows.keys():
+            streamblock_list = streamblock_list+reverse_init_flows[key]['streamblock']+' '
+        streamblock_list = streamblock_list+'"'
+
+        frameloss_sbProfile= self.baseAPI.stc_create('Rfc2544StreamBlockProfile -under '+frameloss_config+' -Active TRUE -LocalActive TRUE')
+        self.baseAPI.stc_config(frameloss_sbProfile,'-StreamBlockList '+streamblock_list)
+        self.baseAPI.stc_perform('ExpandBenchmarkConfigCommand','-config ',frameloss_config)
+
+        #attach the port before testing
+        port_list = [ forward_port_handle,reverse_port_handle]
+        self.baseAPI.stc_attach_ports(port_list)
+
+        #stc apply and begin to sequence test
+        self.baseAPI.stc_apply()
+        self.baseAPI.stc_perform("SequencerStart")
+
+        #wait until complete
+        self.baseAPI.stc_waituntilcomplete()
+
+        #get result db
+        resultsdb = self.baseAPI.stc_get("system1.project.TestResultSetting", "-CurrentResultFileName")
+        results_dict = self.baseAPI.stc_perform('QueryResult','-DatabaseConnectionString',resultsdb,'-ResultPath RFC2544FrameLossTestResultDetailedSummaryView')
+        #import pdb
+        #pdb.set_trace()
+        return True,results_dict
+
+    def run_rfc2544_latency(self,forward_init_flows,reverse_init_flows):
+        pass
+
diff --git a/vstf/vstf/agent/spirentagent.py b/vstf/vstf/agent/spirentagent.py
new file mode 100755 (executable)
index 0000000..04a9dc9
--- /dev/null
@@ -0,0 +1,6 @@
+from vstf.agent.spirent.tools import Spirent_Tools as Spirent
+
+
+class agentSpirent(Spirent):
+    def __init__(self):
+        super(agentSpirent, self).__init__()
diff --git a/vstf/vstf/agent/unittest/__init__.py b/vstf/vstf/agent/unittest/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/unittest/configuration.py b/vstf/vstf/agent/unittest/configuration.py
new file mode 100755 (executable)
index 0000000..3f3b366
--- /dev/null
@@ -0,0 +1,28 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+
+eth_for_test = [
+    "eth4",
+    "p57p2"               
+]
+
+mac_of_eth = [
+    "90:e2:ba:20:1f:d8",
+    '90:e2:ba:20:1f:d9'
+]
+
+bdf_of_eth = [
+    "04:00.0",
+    "04:00.1"
+]
+
+source_repo = {
+    "vnx-bin":{
+        "install": False,
+        "url": "root@192.168.188.10:/root/src/vnx-bin",
+        "repo_type": "git"
+    }
+}
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/Readme b/vstf/vstf/agent/unittest/env/Readme
new file mode 100755 (executable)
index 0000000..eb2a78d
--- /dev/null
@@ -0,0 +1,13 @@
+To enable Env unittest, please make sure:
+
+       1. br0 exists on TargetHost and connects with 'management network'.
+       2. a vm image exists on Target Host.
+       3. change options in configuration files in the "configuration" directory.
+       4. copy public key to git server, for example:
+               ssh-copy-id root@192.168.188.10
+               note:
+                       192.168.188.10 is the server you config in "../configuration.py"
+                       this is needed for source manager which use 'git' to pull down sources.
+
+run tests:
+       python run_test.py
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/__init__.py b/vstf/vstf/agent/unittest/env/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/unittest/env/configuration/Ti.json b/vstf/vstf/agent/unittest/env/configuration/Ti.json
new file mode 100755 (executable)
index 0000000..8923661
--- /dev/null
@@ -0,0 +1,58 @@
+{
+    "scenario": "ti",
+    "env-build": [
+        {
+            "ip": "192.168.188.16", 
+            "drivers": [
+                "ixgbe",
+                "vhost_net"
+            ], 
+            "vms": [
+                {
+                       "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304, 
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23", 
+                        "ctrl_gw": "192.168.188.1"
+                    }, 
+                    "taps":[
+                       {
+                               "tap_name": "tap_in",
+                               "br_type": "ovs",
+                               "br_name": "ovs1",
+                               "tap_mac": "56:6f:44:a5:3f:a2",
+                               "vlan_mode": "access",
+                               "vlan_id": "100"
+                       }
+                    ],
+                    "ctrl_mac": "16:6f:44:a5:3f:a2", 
+                    "ctrl_br": "br0"
+                }
+            ], 
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                       {
+                               "bdf": "04:00.0",
+                               "vlan_mode": "trunk",
+                               "vlan_id": "100,200,300,400"
+                       }
+                    ],
+                    "vtep":{},
+                    "fastlink":[
+                           {
+                               "inport":"04: 00.0",
+                               "outport":"tap_in"
+                           }
+                       ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/configuration/Tn-ovs.json b/vstf/vstf/agent/unittest/env/configuration/Tn-ovs.json
new file mode 100755 (executable)
index 0000000..73be161
--- /dev/null
@@ -0,0 +1,36 @@
+{
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe"
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        },
+                        {
+                            "bdf": "04:00.1",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04: 00.0",
+                            "outport": "04: 00.1"
+                        }
+                    ]
+                }
+            ],
+            "vms": []
+        }
+    ]
+}
diff --git a/vstf/vstf/agent/unittest/env/configuration/Tn.json b/vstf/vstf/agent/unittest/env/configuration/Tn.json
new file mode 100755 (executable)
index 0000000..04c160d
--- /dev/null
@@ -0,0 +1,37 @@
+{
+    "scenario": "tn",
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe"
+            ],
+            "vms": [],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                        {
+                            "bdf": "04:00.0",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        },
+                        {
+                            "bdf": "04:00.1",
+                            "vlan_mode": "trunk",
+                            "vlan_id": "1,100,200"
+                        }
+                    ],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "04:00.0",
+                            "outport": "04:00.1"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/configuration/Tn1v.json b/vstf/vstf/agent/unittest/env/configuration/Tn1v.json
new file mode 100755 (executable)
index 0000000..5a25efd
--- /dev/null
@@ -0,0 +1,85 @@
+{
+    "scenario": "tnv",
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "ixgbe",
+                "tap_vhost"
+            ],
+            "vms": [
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23",
+                        "tap_pktloop_config": "dpdk",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap_in",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a2",
+                            "vlan_mode": "access",
+                            "vlan_id": "100"
+                        },
+                        {
+                            "tap_name": "tap_out",
+                            "br_type": "ovs",
+                            "br_name": "ovs22",
+                            "tap_mac": "56:6f:44:a5:3f:a3",
+                            "vlan_mode": "access",
+                            "vlan_id": "200"
+                        }
+                    ],
+                    "ctrl_mac": "16:6f:44:a5:3f:a2",
+                    "ctrl_br": "br0"
+                }
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [
+                               {
+                            "bdf": "04: 00.0",
+                            "vlan_mode": "trunk",
+                                       "vlan_id": "100"
+                               }
+                    ],
+                    "vtep":{},
+                    "fastlink":[
+                           {
+                               "inport":"04: 00.0",
+                               "outport":"tap_in"
+                           }
+                       ]
+                },
+                {
+                    "type": "ovs",
+                    "name": "ovs2",
+                    "uplinks": [
+                       {
+                               "bdf": "04: 00.1",
+                               "vlan_mode": "trunk",
+                               "vlan_id": "200"
+                        }
+                    ],
+                    "vtep":{},
+                    "fastlink":[
+                           {
+                               "inport":"04: 00.1",
+                               "outport":"tap_out"
+                           }
+                       ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/configuration/Tu.json b/vstf/vstf/agent/unittest/env/configuration/Tu.json
new file mode 100755 (executable)
index 0000000..15a508c
--- /dev/null
@@ -0,0 +1,71 @@
+{
+    "scenario": "tu",
+    "env-build": [
+        {
+            "ip": "192.168.188.16",
+            "drivers": [
+                "tap_vhost"
+            ],
+            "vms": [
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test1",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test1.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.200/23",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap1",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a2",
+                            "vlan_mode": "access",
+                            "vlan_id": "100"
+                        }
+                    ]
+                },
+                {
+                    "vm_cpu": 3,
+                    "vm_name": "test2",
+                    "vm_memory": 4194304,
+                    "image_path": "/mnt/sdb/test2.qcow2",
+                    "image_type": "qcow2",
+                    "init_config": {
+                        "amqp_server": "192.168.188.10",
+                        "ctrl_ip_setting": "192.168.188.201/23",
+                        "ctrl_gw": "192.168.188.1"
+                    },
+                    "taps": [
+                        {
+                            "tap_name": "tap2",
+                            "br_type": "ovs",
+                            "br_name": "ovs1",
+                            "tap_mac": "56:6f:44:a5:3f:a3",
+                            "vlan_mode": "access",
+                            "vlan_id": "100"
+                        }
+                    ]
+                }
+            ],
+            "bridges": [
+                {
+                    "type": "ovs",
+                    "name": "ovs1",
+                    "uplinks": [],
+                    "vtep": {},
+                    "fastlink": [
+                        {
+                            "inport": "tap1",
+                            "outport": "tap2"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/model.py b/vstf/vstf/agent/unittest/env/model.py
new file mode 100755 (executable)
index 0000000..e3c2bca
--- /dev/null
@@ -0,0 +1,24 @@
+"""
+Created on 2015-10-9
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest import configuration
+
+
+class Test(unittest.TestCase):
+    def setUp(self):
+        self.eth_for_test = configuration.eth_for_test
+        self.mac_of_eth = configuration.mac_of_eth
+        self.source_repo = configuration.source_repo
+        self.bdf_of_eth = configuration.bdf_of_eth
+
+    def tearDown(self):
+        pass
+
+
+if __name__ == "__main__":
+    # import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/run_test.py b/vstf/vstf/agent/unittest/env/run_test.py
new file mode 100755 (executable)
index 0000000..0f630c7
--- /dev/null
@@ -0,0 +1,33 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+import importlib
+
+test_order_list = [
+    "vstf.agent.unittest.env.test_origin_driver",
+    "vstf.agent.unittest.env.test_bridge_plugin",
+    "vstf.agent.unittest.env.test_drivermanager",
+    "vstf.agent.unittest.env.test_devicemanager",
+    "vstf.agent.unittest.env.test_vs_plugin_manager",
+    "vstf.agent.unittest.env.test_builder",
+    "vstf.agent.unittest.env.test_sourcemanager",
+]
+
+
+def main():
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level=logging.INFO)
+    suite = unittest.TestSuite()
+    for mod_name in test_order_list:
+        mod = importlib.import_module(mod_name)
+        suit = unittest.TestLoader().loadTestsFromModule(mod)
+        suite.addTest(suit)
+    unittest.TextTestRunner().run(suite)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/agent/unittest/env/test_bridge_plugin.py b/vstf/vstf/agent/unittest/env/test_bridge_plugin.py
new file mode 100755 (executable)
index 0000000..ac4eb26
--- /dev/null
@@ -0,0 +1,56 @@
+"""
+Created on 2015-10-12
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.common.utils import check_call
+from vstf.agent.unittest.env import model
+from vstf.agent.env.vswitch_plugins import bridge_plugin
+from vstf.agent.env.driver_plugins import manager
+
+
+class Test(model.Test):
+    def setUp(self):
+        super(Test, self).setUp()
+        self.plugin = bridge_plugin.BridgePlugin()
+        self.dr_mgr = manager.DriverPluginManager()
+        self.br_cfg = {
+            "name": "br1",
+            "uplinks": [
+                {
+                    "bdf": self.bdf_of_eth[0],
+                },
+                {
+                    "bdf": self.bdf_of_eth[1],
+                }
+            ]
+        }
+        self.dr_mgr.clean()
+        self.dr_mgr.load(['ixgbe'])
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def _check_br_exists(self, name):
+        try:
+            check_call('ifconfig %s' % name, shell=True)
+        except Exception, e:
+            return False
+        return True
+
+    def test_create_br(self):
+        self.plugin.clean()
+        self.plugin.create_br(self.br_cfg)
+        self.assertTrue(self._check_br_exists(self.br_cfg['name']))
+        self.plugin.clean()
+        self.assertFalse(self._check_br_exists(self.br_cfg['name']))
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_builder.py b/vstf/vstf/agent/unittest/env/test_builder.py
new file mode 100755 (executable)
index 0000000..fd2d438
--- /dev/null
@@ -0,0 +1,74 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+import os
+
+from vstf.controller.env_build.cfg_intent_parse import IntentParser
+from vstf.agent.env import builder
+
+
+class Test(unittest.TestCase):
+    def setUp(self):
+        self.mgr = builder.PluginManager()
+        self.dir = os.path.dirname(__file__)
+
+    def tearDown(self):
+        self.mgr.clean()
+
+    def __build(self, filepath, drivers=None):
+        parser = IntentParser(filepath)
+        cfg_intent = parser.parse_cfg_file()
+        host_cfg = cfg_intent['env-build'][0]
+        print filepath
+        print host_cfg
+        host_cfg["src-install"] = {}
+        if drivers:
+            host_cfg['drivers'] = drivers
+        return self.mgr.build(host_cfg)
+
+    def test_build_tn_using_origin_driver(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tn.json'), drivers=['ixgbe'])
+        self.assertTrue(ret, "test_build_tn_using_origin_driver failed, ret = %s" % ret)
+
+    def test_build_tn1v_using_origin_driver(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tn1v.json'), drivers=['ixgbe', 'vhost_net'])
+        self.assertTrue(ret, "test_build_tn1v_using_origin_driver failed, ret = %s" % ret)
+
+    def test_build_ti_using_origin_driver(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Ti.json'), drivers=['ixgbe', 'vhost_net'])
+        self.assertTrue(ret, "test_build_ti_using_origin_driver failed, ret = %s" % ret)
+
+    def test_build_tu_using_origin_driver(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tu.json'), drivers=['vhost_net'])
+        self.assertTrue(ret, "test_build_ti_using_origin_driver failed, ret = %s" % ret)
+
+    @unittest.skip('can be tested by tn1v')
+    def test_build_tn(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tn.json'))
+        self.assertTrue(ret, "test_build_tn failed, ret = %s" % ret)
+
+    @unittest.skip('can be tested by tn1v')
+    def test_build_tn1v(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tn1v.json'))
+        self.assertTrue(ret, "test_build_tn failed,ret = %s" % ret)
+
+    @unittest.skip('can be tested by tn1v')
+    def test_build_ti(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Ti.json'))
+        self.assertTrue(ret, "test_build_tn failed, ret = %s" % ret)
+
+    @unittest.skip('can be tested by tn1v')
+    def test_build_tu(self):
+        ret = self.__build(os.path.join(self.dir, 'configuration/Tu.json'))
+        self.assertTrue(ret, "test_build_tn failed, ret = %s" % ret)
+
+
+if __name__ == "__main__":
+    import logging
+
+    LOG = logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_devicemanager.py b/vstf/vstf/agent/unittest/env/test_devicemanager.py
new file mode 100755 (executable)
index 0000000..7e5bc3b
--- /dev/null
@@ -0,0 +1,38 @@
+"""
+Created on 2015-9-25
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.env import model
+from vstf.agent.env.basic.device_manager import DeviceManager
+
+
+class Test(model.Test):
+    def setUp(self):
+        super(Test, self).setUp()
+        self.dm = DeviceManager()
+        self.device_list = self.dm.list_nic_devices()
+        self.device_detail = self.device_list[0]
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def test_get_device_detail(self):
+        detail1 = self.dm.get_device_detail(self.device_detail['bdf'])
+        detail2 = self.dm.get_device_detail(self.device_detail['mac'])
+        detail3 = self.dm.get_device_detail(self.device_detail['device'])
+        self.assertTrue(detail1 == detail2 == detail3 == self.device_detail)
+
+    def test_list_nic_devices(self):
+        import json
+        print json.dumps(self.device_list, indent=4)
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_drivermanager.py b/vstf/vstf/agent/unittest/env/test_drivermanager.py
new file mode 100755 (executable)
index 0000000..df6ad5d
--- /dev/null
@@ -0,0 +1,50 @@
+"""
+Created on 2015-10-9
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.common.utils import check_output
+from vstf.agent.unittest.env import model
+from vstf.agent.env.driver_plugins import manager
+
+
+class Test(model.Test):
+    def setUp(self):
+        super(Test, self).setUp()
+        self.driver_mgr = manager.DriverPluginManager()
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def _driver_exists(self, drivers=[]):
+        all_drivers = check_output("lsmod | awk '{print $1}'", shell=True).split()
+        for mod in drivers:
+            if mod not in all_drivers:
+                return False
+        return True
+
+    def test_load(self):
+        self.driver_mgr.clean()
+        for _, drivers in self.driver_mgr.get_all_supported_drivers().items():
+            self.assertFalse(self._driver_exists(drivers))
+
+
+        self.driver_mgr.load(['ixgbe', 'vhost_net'])
+        self.assertTrue(self._driver_exists(['ixgbe', 'vhost_net']))
+
+        self.driver_mgr.clean()
+        self.assertFalse(self._driver_exists(['ixgbe', 'vhost_net']))
+
+    def test_load_unsuported_pair(self):
+        with self.assertRaises(Exception):
+            self.driver_mgr.load(['ixgbe', 'tap_vhost'])
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_origin_driver.py b/vstf/vstf/agent/unittest/env/test_origin_driver.py
new file mode 100755 (executable)
index 0000000..8daa481
--- /dev/null
@@ -0,0 +1,44 @@
+"""
+Created on 2015-10-9
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.common.utils import check_output
+from vstf.agent.unittest.env import model
+from vstf.agent.env.driver_plugins import origin_driver
+
+
+class Test(model.Test):
+    
+    def setUp(self):
+        super(Test, self).setUp()
+        self.driver_mgr = origin_driver.OriginDriverPlugin()
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def _driver_exists(self, drivers=[]):
+        all_drivers = check_output("lsmod | awk '{print $1}'",shell = True).split()
+        for mod in drivers:
+            if mod not in all_drivers:
+                return False
+        return True
+                  
+    def test_load(self):
+        self.driver_mgr.clean()
+        self.assertFalse(self._driver_exists(self.driver_mgr.get_supported_drivers()))
+        
+        self.driver_mgr.load(['ixgbe','vhost_net'])
+        self.assertTrue(self._driver_exists(['ixgbe','vhost_net']))
+        
+        self.driver_mgr.clean()
+        self.assertFalse(self._driver_exists(self.driver_mgr.get_supported_drivers()))
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/test_sourcemanager.py b/vstf/vstf/agent/unittest/env/test_sourcemanager.py
new file mode 100755 (executable)
index 0000000..fda567d
--- /dev/null
@@ -0,0 +1,53 @@
+"""
+Created on 2015-10-9
+
+@author: y00228926
+"""
+import unittest
+import shutil
+import time
+import os
+
+from vstf.agent.env.basic.source_manager import SourceCodeManager
+from vstf.agent.unittest.env import model
+
+
+class TestSourceManager(model.Test):
+    def setUp(self):
+        super(TestSourceManager, self).setUp()
+        self.sm = SourceCodeManager()
+        self.dest_path = '/tmp/test_source_manager'
+        os.mkdir(self.dest_path)
+
+    def tearDown(self):
+        shutil.rmtree(self.dest_path, ignore_errors = True)
+
+    def _time(self,func):
+        def _deco(*args):
+            start_time = time.time()
+            func(*args)
+            end_time = time.time()
+            return end_time - start_time
+        return _deco
+
+    def test_download_source_code(self):
+        for key, item in self.source_repo.items():
+            print self.source_repo
+            url = item['url']
+            target = os.path.join(self.dest_path, key)
+            install = item['install']
+            if install:
+                self.sm._git_pull(url, target)
+                self.assertTrue(os.path.isdir(target))
+                my_download = self._time(self.sm._git_pull)
+                t = my_download(url, target)
+                self.assertTrue(t < 1.0)
+            else:
+                self.assertFalse(os.path.isdir(target))
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/env/test_vm9pfs.py b/vstf/vstf/agent/unittest/env/test_vm9pfs.py
new file mode 100755 (executable)
index 0000000..4f52013
--- /dev/null
@@ -0,0 +1,71 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.env import model
+from vstf.agent.env.basic.vm9pfs import VMConfigBy9pfs
+from vstf.agent.env.basic.vm_manager import VMControlOperation
+
+
+class TestVM9pfs(model.Test):
+    def setUp(self):
+        super(TestVM9pfs, self).setUp()
+        self.vm_config = {
+            'vm_name': 'vm1',
+            'vm_cpu': 5,
+            'image_path': "/mnt/sdb/ubuntu_salt_master.img",
+            'child_dir': '/mnt/sdb/',
+            'image_type': 'qcow2',
+            'ctrl_br': 'br0',
+            'ctrl_mac': '56:6f:44:a5:3f:a4',
+            "taps": [
+                {
+                    "tap_name": "tap_in",
+                    "br_type": "bridge",
+                    "br_name": "br0",
+                    "tap_mac": "56:6f:44:a5:3f:a2",
+                },
+                {
+                    "tap_name": "tap_out",
+                    "br_type": "bridge",
+                    "br_name": "br0",
+                    "tap_mac": "56:6f:44:a5:3f:a3",
+                }
+            ],
+            'init_config': {}
+        }
+        self.init_config = {
+            'amqp_server': '192.168.188.10',
+            'ctrl_ip_setting': '192.168.188.200/23',
+            'tap_pktloop_config': 'dpdk',
+            'ctrl_gw': '192.168.188.1'
+        }
+        self.mgr = VMControlOperation()
+        self.mgr.clean_all_vms()
+        self.mgr.create_vm(self.vm_config)
+        self.mgr.wait_vm(self.vm_config["vm_name"])
+        self.vm9pctrl = self.mgr.vm_9p_controllers[self.vm_config["vm_name"]]
+
+    def tearDown(self):
+        self.mgr.clean_all_vms()
+        super(TestVM9pfs, self).tearDown()
+
+    def test_init_config(self):
+        ret = self.vm9pctrl.config_ip(self.vm_config['ctrl_mac'], self.init_config['ctrl_ip_setting'])
+        self.assertTrue(ret)
+        ret = self.vm9pctrl.config_gw(self.init_config['ctrl_gw'])
+        self.assertTrue(ret)
+        ret = self.vm9pctrl.set_pktloop_dpdk([self.vm_config['taps'][0]['tap_mac'], self.vm_config['taps'][1]['tap_mac']])
+        self.assertTrue(ret)
+        ret = self.vm9pctrl.config_amqp(self.init_config['ctrl_ip_setting'].split('/')[0], self.init_config['amqp_server'])
+        self.assertTrue(ret)
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_vm_manager.py b/vstf/vstf/agent/unittest/env/test_vm_manager.py
new file mode 100755 (executable)
index 0000000..3c3deac
--- /dev/null
@@ -0,0 +1,69 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.env import model
+from vstf.agent.env.basic.vm9pfs import VMConfigBy9pfs
+from vstf.agent.env.basic.vm_manager import VMControlOperation
+
+
+class TestVM9pfs(model.Test):
+    def setUp(self):
+        super(TestVM9pfs, self).setUp()
+        self.vm_config = {
+            'vm_name': 'vm1',
+            'vm_cpu': 5,
+            'image_path': "/mnt/sdb/ubuntu_salt_master.img",
+            'child_dir': '/mnt/sdb/',
+            'image_type': 'qcow2',
+            'ctrl_br': 'br0',
+            'ctrl_mac': '56:6f:44:a5:3f:a4',
+            "taps": [
+                {
+                    "tap_name": "tap_in",
+                    "br_type": "bridge",
+                    "br_name": "br0",
+                    "tap_mac": "56:6f:44:a5:3f:a2",
+                },
+                {
+                    "tap_name": "tap_out",
+                    "br_type": "bridge",
+                    "br_name": "br0",
+                    "tap_mac": "56:6f:44:a5:3f:a3",
+                }
+            ],
+            'init_config': {
+                "amqp_passwd": "guest",
+                "amqp_user": "guest",
+                "amqp_server": "192.168.188.10",
+                "amqp_port": 5672,
+                'ctrl_ip_setting': '192.168.188.200/23',
+                'tap_pktloop_config': 'dpdk',
+                'ctrl_gw': '192.168.188.1'
+            }
+        }
+        self.mgr = VMControlOperation()
+        self.mgr.clean_all_vms()
+
+    def tearDown(self):
+        self.mgr.clean_all_vms()
+        super(TestVM9pfs, self).tearDown()
+
+    def test_create_vm_bridge(self):
+        self.mgr.create_vm(self.vm_config)
+        self.mgr.wait_vm(self.vm_config["vm_name"])
+        self.mgr.init_config_vm(self.vm_config["vm_name"])
+
+    def _replace_opts(self, cfg, br_type):
+        for tap_cfg in cfg["taps"]:
+            tap_cfg["br_type"] = br_type
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/env/test_vs_plugin_manager.py b/vstf/vstf/agent/unittest/env/test_vs_plugin_manager.py
new file mode 100755 (executable)
index 0000000..f78c3e7
--- /dev/null
@@ -0,0 +1,68 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.env import model
+from vstf.agent.env.vswitch_plugins import manager
+from vstf.common.utils import check_call
+
+
+class TestVsPlugins(model.Test):
+    def setUp(self):
+        super(TestVsPlugins, self).setUp()
+        self.cfg = {
+            "type": "ovs",
+            "name": "ovs1",
+            "uplinks": [
+                {
+                    "bdf": self.bdf_of_eth[0],
+                    "vlan_mode": "trunk",
+                    "vlan_id": "100,200,300,400"
+                },
+                {
+                    "bdf": self.bdf_of_eth[1],
+                    "vlan_mode": "trunk",
+                    "vlan_id": "100,200,300,400"
+                }
+            ],
+            "vtep": {}
+        }
+        self.mgr = manager.VswitchPluginManager()
+
+    def tearDown(self):
+        super(TestVsPlugins, self).tearDown()
+
+    def _check_br_exists(self, name):
+        try:
+            check_call('ifconfig %s' % name, shell=True)
+        except Exception, e:
+            return False
+        return True
+
+    def test_create_bridge(self):
+        self.cfg['name'] = 'br1'
+        self.br = self.mgr.get_vs_plugin('bridge')
+        self.br.clean()
+        self.br.init()
+        self.br.create_br(self.cfg)
+        self.assertTrue(self._check_br_exists('br1'))
+        self.br.clean()
+        self.assertFalse(self._check_br_exists('br1'))
+
+    def test_clean(self):
+        self.mgr.clean()
+
+    def test_get_supported_plugins(self):
+        ret = self.mgr.get_supported_plugins()
+        self.assertEqual(set(ret), {'bridge',  'ovs'})
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.INFO)
+    LOG = logging.getLogger(__name__)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/perf/Readme b/vstf/vstf/agent/unittest/perf/Readme
new file mode 100755 (executable)
index 0000000..3ebe6c4
--- /dev/null
@@ -0,0 +1,7 @@
+To enable Perf unittest, please make sure:
+
+       1. you have setup a "Tn virtual network" on Target Host, so you can ping from one nic to the other nic.
+       2. change options in "unittest/configuration.py". 
+
+run tests:
+       python run_test.py
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/perf/__init__.py b/vstf/vstf/agent/unittest/perf/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/agent/unittest/perf/model.py b/vstf/vstf/agent/unittest/perf/model.py
new file mode 100755 (executable)
index 0000000..6383ae0
--- /dev/null
@@ -0,0 +1,46 @@
+"""
+Created on 2015-9-25
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest import configuration
+from vstf.agent.perf import netns
+
+
+class LocalModel(unittest.TestCase):
+    def _ping(self):
+        device_list, ns_list, ip_setting_list, ip_list = self.device_list, self.ns_list, self.ip_setting_list, self.ip_list
+        for ns, dev, ip_setting in zip(ns_list, device_list, ip_setting_list):
+            netdev = {
+                "namespace": ns,
+                "iface": dev,
+                'ip_setting': ip_setting
+            }
+            self.mgr.config_dev(netdev)
+        ip_list_copy = ip_list[:]
+        ip_list_copy.reverse()
+        for ns, ip in zip(ns_list, ip_list_copy):
+            self.assertTrue(sself.mgr.ping(ns, ip), True)
+        self.mgr.clean_all_namespace()
+
+    def setUp(self):
+        # make sure you have set up Tn loop on the "Target Host"
+        self.mgr = netns.NetnsManager()
+        self.mgr.clean_all_namespace()
+        self.device_list = configuration.eth_for_test
+        self.mac_list = configuration.mac_of_eth
+        self.ns_list = ['send', 'receive']
+        self.ip_setting_list = ['192.168.1.1/24', '192.168.1.2/24']
+        self.ip_list = ['192.168.1.1', '192.168.1.2']
+
+    def tearDown(self):
+        self.mgr.clean_all_namespace()
+
+
+class Model(LocalModel):
+    def setUp(self):
+        # make sure you have set up Tn loop on the "Target Host"
+        super(Model, self).setUp()
+        self._ping()
diff --git a/vstf/vstf/agent/unittest/perf/run_test.py b/vstf/vstf/agent/unittest/perf/run_test.py
new file mode 100755 (executable)
index 0000000..684ca18
--- /dev/null
@@ -0,0 +1,32 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+import importlib
+
+test_order_list = [
+    "vstf.agent.unittest.perf.test_utils",
+    "vstf.agent.unittest.perf.test_netns",
+    "vstf.agent.unittest.perf.test_netperf",
+    "vstf.agent.unittest.perf.test_qperf",
+    "vstf.agent.unittest.perf.test_pktgen",
+    "vstf.agent.unittest.perf.test_vstfperf",
+]
+
+
+def main():
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    suite = unittest.TestSuite()
+    for mod_name in test_order_list:
+        mod = importlib.import_module(mod_name)
+        suit = unittest.TestLoader().loadTestsFromModule(mod)
+        suite.addTest(suit)
+    unittest.TextTestRunner().run(suite)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/agent/unittest/perf/test_ethtool.py b/vstf/vstf/agent/unittest/perf/test_ethtool.py
new file mode 100755 (executable)
index 0000000..48e0d7f
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/13
+# see license for license details
+
+import unittest
+import logging
+from vstf.agent.perf import ethtool
+from vstf.agent.unittest import configuration
+from vstf.common.log import setup_logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Testethtool(unittest.TestCase):
+    def setUp(self):
+        LOG.info("start Testethtool unit test.")
+        self._devices = configuration.eth_for_test
+        super(Testethtool, self).setUp()
+
+    def teardown(self):
+        LOG.info("stop Testethtool unit test.")
+
+#    @unittest.skip('for now')
+    def test_autoneg_on(self):
+        for dev in self._devices:
+            self.assertTrue(ethtool.autoneg_on(dev), True)
+
+    def test_autoneg_off(self):
+        for dev in self._devices:
+            self.assertTrue(ethtool.autoneg_off(dev), True)
+
+    def test_autoneg_query(self):
+        for dev in self._devices:
+            result = ethtool.autoneg_query(dev)
+            LOG.info(result)
+
+if __name__ == "__main__":
+    setup_logging(level=logging.INFO, log_file="/var/log/vstf/vstf-unit-test.log", clevel=logging.INFO)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/perf/test_netns.py b/vstf/vstf/agent/unittest/perf/test_netns.py
new file mode 100755 (executable)
index 0000000..912358b
--- /dev/null
@@ -0,0 +1,67 @@
+"""
+Created on 2015-9-23
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.perf import model
+from vstf.agent.perf import netns
+
+
+class TestNetnsManager(model.Model):
+    def setUp(self):
+        super(TestNetnsManager, self).setUp()
+        self.ns = netns.Netns()
+                
+    def tearDown(self):
+        super(TestNetnsManager, self).tearDown()
+
+    def testNetns(self):
+        device_list,ns_list,ip_setting_list,ip_list = self.device_list,self.ns_list,self.ip_setting_list,self.ip_list
+        net = self.ns
+        for ns in ns_list:
+            self.assertTrue(net.create_namespace(ns),'create_namespace failed')
+        for ns,dev,ip_setting in zip(ns_list,device_list,ip_setting_list):
+            self.assertTrue(net.add_device(ns, dev),'add_device failed')
+            self.assertTrue(net.activate_device(ns,dev),'activate_device failed')
+            self.assertTrue(net.config_ip(ns,dev,ip_setting),'config_ip failed')
+        for ns in ns_list:
+            self.assertTrue(net.remove_namespace(ns),'remove_namespace failed')
+    
+    def testNetNsManager(self):
+        mgr = self.mgr
+        device_list,ns_list,ip_setting_list,ip_list = self.device_list,self.ns_list,self.ip_setting_list,self.ip_list
+        for ns,dev,ip_setting in zip(ns_list,device_list,ip_setting_list):
+            netdev = {
+                      "namespace":ns,
+                      "iface":dev,
+                      'ip_setting':ip_setting
+                }
+            ret = mgr.config_dev(netdev)
+            self.assertTrue(ret,"config_dev failed, netdev=%s" % netdev)
+            
+        for ns,dev,ip_setting in zip(ns_list,device_list,ip_setting_list):
+            netdev = {
+                      "namespace":ns,
+                      "iface":dev,
+                      'ip_setting':ip_setting
+                }
+            self.assertTrue(mgr.recover_dev(netdev),"recover_dev failed, netdev=%s" % netdev)
+            
+        for ns,dev,ip_setting in zip(ns_list,device_list,ip_setting_list):
+            netdev = {
+                      "namespace":ns,
+                      "iface":dev,
+                      'ip_setting':ip_setting
+                }
+            self.assertTrue(mgr.config_dev(netdev),"config_dev failed, netdev=%s" % netdev)       
+        self.assertTrue(mgr.clean_all_namespace(),'remove_namespace failed')
+
+
+if __name__ == "__main__":
+    import logging
+    LOG = logging.getLogger(__name__)
+    logging.basicConfig(level = logging.DEBUG)
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/perf/test_netperf.py b/vstf/vstf/agent/unittest/perf/test_netperf.py
new file mode 100755 (executable)
index 0000000..b5f8cb2
--- /dev/null
@@ -0,0 +1,105 @@
+'''
+Created on 2015-9-24
+
+@author: y00228926
+'''
+import unittest
+import time
+import subprocess
+
+from vstf.agent.unittest.perf import model
+from vstf.agent.perf import netperf
+from vstf.agent.perf.utils import get_pid_by_name
+
+
+class TestNetperf(model.Model):
+    '''
+    please make sure 'Tn' network on 'Target Host' is created.
+    '''
+    def setUp(self):
+        super(TestNetperf, self).setUp()
+        subprocess.call("killall netperf", shell = True)
+        subprocess.call("killall netserver",shell = True)
+        for ns, dev, ip_setting in zip(self.ns_list, self.device_list, self.ip_setting_list):
+            netdev = {
+                  "namespace":ns,
+                  "iface":dev,
+                  'ip_setting':ip_setting
+            }
+            self.mgr.config_dev(netdev)
+        self.send_cfg = {
+            "namespace": "send",
+            "protocol": "udp_bw",
+            "dst":[
+                    {"ip": "192.168.1.2"}
+                ],
+            "size": 64,
+            "threads": 1,
+            "time": 10, 
+        }
+  
+    def tearDown(self):
+        super(TestNetperf, self).tearDown()
+    
+    def test_netperf_start_success(self):
+        perf = netperf.Netperf()
+        ret = perf.receive_start(namespace='receive')
+        exp = (0, 'start netserver success')
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        ret = perf.send_start(**self.send_cfg)
+        exp = (0,"start netperf send success")
+        self.assertEqual(ret, exp, "failed to start netperf")
+        
+        time.sleep(3)
+        
+        ret = perf.send_stop()
+        exp = [(0, "process is stopped by killed")]
+        self.assertEqual(ret, exp, "send_stop failed, ret = %s" % str(ret)) 
+          
+        ret = perf.receive_stop()
+        exp = (0, "stop netserver success")
+        self.assertEqual(ret, exp, "receive_stop failedf, ret = %s" % str(ret)) 
+    
+    def test_netperf_start_success_mutil_threads(self):
+        perf = netperf.Netperf()
+        ret = perf.receive_start(namespace='receive')
+        exp = (0, 'start netserver success')
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        self.send_cfg.update({"threads":3})
+        exp = (0,"start netperf send success")
+        ret = perf.send_start(**self.send_cfg)
+        self.assertEqual(ret, exp, "failed to start netperf")
+        
+        time.sleep(3)
+        
+        rets = perf.send_stop()
+        exp = [(0, 'process is stopped by killed'), (0, 'process is stopped by killed'), (0, 'process is stopped by killed')]
+        self.assertEqual(rets, exp, "send_stop failed, rets = %s" % str(rets)) 
+          
+        rets = perf.receive_stop()
+        self.assertEqual(rets, (0, "stop netserver success"), "receive_stop failedf, rets = %s" % str(rets))
+    
+    def test_clean(self):
+        perf = netperf.Netperf()
+        ret = perf.receive_start(namespace='receive')
+        exp = (0, 'start netserver success')
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        self.send_cfg.update({"threads":3})
+        exp = (0,"start netperf send success")
+        ret = perf.send_start(**self.send_cfg)
+        self.assertEqual(ret, exp, "failed to start netperf")
+        perf.clean()
+        ret = get_pid_by_name('netperf')
+        self.assertEqual(ret, [], "failed to clean netperf")
+        ret = get_pid_by_name('netserver')
+        self.assertEqual(ret, [], "failed to clean netserver")
+            
+if __name__ == "__main__":
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level = logging.DEBUG)
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/perf/test_pktgen.py b/vstf/vstf/agent/unittest/perf/test_pktgen.py
new file mode 100755 (executable)
index 0000000..5e6cdf7
--- /dev/null
@@ -0,0 +1,90 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+import time
+
+from vstf.agent.unittest.perf import model
+from vstf.agent.perf import pktgen
+
+
+class TestPktgen(model.Model):
+    def setUp(self):
+        super(TestPktgen, self).setUp()
+
+    def tearDown(self):
+        super(TestPktgen, self).tearDown()
+
+    def test_single_thread(self):
+        perf = pktgen.Pktgen()
+        print perf.receive_start()
+        send = {
+            "src": [
+                {"iface": self.device_list[0], "mac": self.mac_list[0]}
+            ],
+            "dst": [
+                {"mac": self.mac_list[1]}
+            ],
+            "size": 64,
+            "threads": 1,
+            'ratep': 0
+        }
+        ret = perf.send_start(**send)
+        self.assertEqual((0, 'start pktgen send success'), ret, "send_start failed, ret=%s" % str(ret))
+        time.sleep(5)
+        ret = perf.send_stop()
+        self.assertEqual([(0, '')], ret, "send_start failed, ret=%s" % ret)
+        ret = perf.receive_stop()
+        self.assertEqual((0, 'pktgen neednt receive stop'), ret, "send_stop failed, ret=%s" % str(ret))
+
+    def test_single_thread_bidirectional(self):
+        perf = pktgen.Pktgen()
+        print perf.receive_start()
+        send = {
+            "src": [
+                {"iface": self.device_list[0], "mac": self.mac_list[0]},
+                {"iface": self.device_list[1], "mac": self.mac_list[1]}
+            ],
+            "dst": [
+                {"mac": self.mac_list[1]},
+                {"mac": self.mac_list[0]}
+            ],
+            "size": 64,
+            "threads": 1,
+            'ratep': 0
+        }
+        ret = perf.send_start(**send)
+        self.assertEqual((0, 'start pktgen send success'), ret, "send_start failed, ret=%s" % str(ret))
+        time.sleep(5)
+        ret = perf.send_stop()
+        self.assertEqual([(0, '')], ret, "send_start failed, ret=%s" % ret)
+        ret = perf.receive_stop()
+        self.assertEqual((0, 'pktgen neednt receive stop'), ret, "send_stop failed, ret=%s" % str(ret))
+
+    def test_clean(self):
+        perf = pktgen.Pktgen()
+        print perf.receive_start()
+        send = {
+            "src": [
+                {"iface": self.device_list[0], "mac": self.mac_list[0]}
+            ],
+            "dst": [
+                {"mac": self.mac_list[1]}
+            ],
+            "size": 64,
+            "threads": 1,
+            'ratep': 0
+        }
+        ret = perf.send_start(**send)
+        self.assertEqual((0, 'start pktgen send success'), ret, "send_start failed, ret=%s" % str(ret))
+        perf.clean()
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/perf/test_qperf.py b/vstf/vstf/agent/unittest/perf/test_qperf.py
new file mode 100755 (executable)
index 0000000..ec9a290
--- /dev/null
@@ -0,0 +1,102 @@
+'''
+Created on 2015-9-24
+
+@author: y00228926
+'''
+import unittest
+import subprocess
+import time
+
+from vstf.agent.unittest.perf import model
+from vstf.agent.perf import qperf
+from vstf.agent.perf.utils import get_pid_by_name
+
+
+class testQperf(model.Model):
+    def setUp(self):
+        super(testQperf, self).setUp()
+        subprocess.call("killall qperf", shell = True)
+        for ns, dev, ip_setting in zip(self.ns_list, self.device_list, self.ip_setting_list):
+            netdev = {
+                  "namespace":ns,
+                  "iface":dev,
+                  'ip_setting':ip_setting
+            }
+            self.mgr.config_dev(netdev)
+        self.send_cfg = {
+            "namespace": self.ns_list[0],
+            "time":1,
+            "protocol": "udp_lat",
+            "dst":[
+                    {"ip": self.ip_list[1]}
+                ],
+            "size": 64,
+        }
+        
+    def tearDown(self):
+        super(testQperf, self).tearDown()
+
+    def test_qperf_quick(self):
+        perf = qperf.Qperf()
+        ret = perf.receive_start(namespace=self.ns_list[1])
+        exp = (0, "start qperf receive success")
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        ret = perf.send_start(**self.send_cfg)
+        exp = (0,"start qperf send success")
+        self.assertEqual(ret, exp, "send_start failed")
+        
+        time.sleep(3)
+        
+        ret = perf.send_stop()
+        for r in ret:
+            self.assertEqual(r[0], 0, "send_stop failed, ret = %s" % str(ret))
+            for key in ('MaximumLatency', 'AverageLatency', 'MinimumLatency'):
+                self.assertIn(key, r[1], "send_stop failed, ret = %s" % str(ret))
+          
+        ret = perf.receive_stop()
+        exp = (0, "stop qperf receive success")
+        self.assertEqual(ret, exp, "receive_stop failed, ret = %s" % str(ret)) 
+    
+    def test_qperf_quick_3s(self):
+        perf = qperf.Qperf()
+        self.send_cfg.update({"time":3})
+        ret = perf.receive_start(namespace='receive')
+        exp = (0, 'start qperf receive success')
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        self.send_cfg.update({"threads":3})
+        exp = (0,"start qperf send success")
+        ret = perf.send_start(**self.send_cfg)
+        self.assertEqual(ret, exp, "send_start failed %s" % str(ret))
+        
+        ret = perf.send_stop()
+        for r in ret:
+            self.assertEqual(r[0], 0, "send_stop failed, ret = %s" % str(ret))
+            for key in ('MaximumLatency', 'AverageLatency', 'MinimumLatency'):
+                self.assertIn(key, r[1], "send_stop failed, ret = %s" % str(ret))
+          
+        ret = perf.receive_stop()
+        self.assertEqual(ret, (0, 'stop qperf receive success'), "receive_stop failedf, ret = %s" % str(ret))
+    
+    def test_clean(self):
+        perf = qperf.Qperf()
+        self.send_cfg.update({"time":10})
+        ret = perf.receive_start(namespace=self.ns_list[1])
+        exp = (0, "start qperf receive success")
+        self.assertEqual(ret, exp, "receive_start failed %s" % str(ret))
+        
+        ret = perf.send_start(**self.send_cfg)
+        exp = (0,"start qperf send success")
+        self.assertEqual(ret, exp, "send_start failed")  
+        
+        perf.clean()
+        ret = get_pid_by_name('qperf')
+        self.assertEqual(ret, [], "clean qperf failed")      
+
+
+if __name__ == "__main__":
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level = logging.DEBUG)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/perf/test_utils.py b/vstf/vstf/agent/unittest/perf/test_utils.py
new file mode 100755 (executable)
index 0000000..83410c5
--- /dev/null
@@ -0,0 +1,31 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+import unittest
+from vstf.common import utils
+
+
+class TestUtils(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_ns_cmd(self):
+        cmd = "ls"
+        ns = "xx"
+        exp_cmd = "ip netns exec xx ls"
+        ret = utils.ns_cmd(ns, cmd)
+        self.assertEqual(ret, exp_cmd, "ns_cmd failed to add ns header prefix:%s != %s" % (ret, exp_cmd))
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    # import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/perf/test_vnstat.py b/vstf/vstf/agent/unittest/perf/test_vnstat.py
new file mode 100755 (executable)
index 0000000..f7c31f5
--- /dev/null
@@ -0,0 +1,57 @@
+"""
+Created on 2015-9-23
+
+@author: y00228926
+"""
+import unittest
+import time
+
+from vstf.agent.perf.utils import get_pid_by_name
+from vstf.agent.unittest.perf import model
+from vstf.agent.perf import vnstat
+
+
+class TestVnstat(model.LocalModel):
+    def setUp(self):
+        super(TestVnstat, self).setUp()
+        self.vnstat = vnstat.VnStat()
+        self.namespace = self.ns_list[0]
+        self.device = self.device_list[0]
+        self.ip_setting = self.ip_setting_list[0]
+        netdev = {
+            "namespace": self.namespace,
+            "iface": self.device,
+            'ip_setting': self.ip_setting
+        }
+        self.mgr.config_dev(netdev)
+
+    def tearDown(self):
+        super(TestVnstat, self).tearDown()
+
+    def test_run_vnstat(self):
+        logging.basicConfig(level=logging.DEBUG)
+        pid = self.vnstat.run_vnstat(self.device, self.namespace)
+        time.sleep(12)
+        raw = self.vnstat.kill_vnstat(pid, self.namespace)
+        print raw['raw_data']
+        data = self.vnstat.process(raw['raw_data'])
+        self.assertTrue(type(data) is dict)
+        for key in ('rxmB/s', 'txmB', 'rxpck', 'txpck', \
+                    'rxpck_min/s', 'txmB_max/s', 'txpck_max/s', \
+                    'txmB/s', 'rxmB', 'rxmB_max/s', 'rxpck/s', 'rxmB_min/s', \
+                    'time', 'rxpck_max/s', 'txpck_min/s', 'txpck/s', 'txmB_min/s'):
+            self.assertTrue(key in data)
+
+    def test_clean(self):
+        self.vnstat.run_vnstat(self.device, self.namespace)
+        self.vnstat.clean()
+        self.assertTrue(get_pid_by_name('vnstat') == [])
+
+
+if __name__ == "__main__":
+    import logging
+
+    LOG = logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    # import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
diff --git a/vstf/vstf/agent/unittest/perf/test_vstfperf.py b/vstf/vstf/agent/unittest/perf/test_vstfperf.py
new file mode 100755 (executable)
index 0000000..30d7c7f
--- /dev/null
@@ -0,0 +1,98 @@
+"""
+Created on 2015-9-28
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.perf.vstfperf import Vstfperf
+from vstf.agent.unittest.perf import model
+
+
+class Test(model.Model):
+    def setUp(self):
+        super(Test, self).setUp()
+        
+        for ns, dev, ip_setting in zip(self.ns_list, self.device_list, self.ip_setting_list):
+            net_dev = {
+                  "namespace":ns,
+                  "iface":dev,
+                  'ip_setting':ip_setting
+            }
+            self.mgr.config_dev(net_dev)
+            
+        self.start = {
+            "operation": "start",
+            "action": "send",
+            "tool": "netperf",
+            "params":{
+                "namespace": self.ns_list[0],
+                "protocol": "tcp_lat",
+                "dst":[
+                        {"ip": self.ip_list[1]}
+                    ],
+                "size": 64,
+                "threads": 1,
+                "time": 1,  
+            },
+        }
+        self.stop = {
+            "operation": "stop",
+            "action": "send",
+            "tool": "netperf",
+            "params":{
+                "namespace": self.ns_list[1],
+            },
+        }
+        
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def testNetperf(self):
+        perf = Vstfperf()
+        self.start['tool'] = 'netperf'
+        self.stop['tool'] = 'netperf'
+        self.start['action'] = 'receive'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[1]
+        self.start['params']['protocol'] = 'udp_bw'
+        perf.run(**self.start)
+        self.start['action'] = 'send'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[0]
+        perf.run(**self.start)
+        self.stop['action'] = 'send'
+        self.stop['operation'] = 'stop'
+        self.stop['params']['namespace'] = self.ns_list[0]
+        perf.run(**self.stop)
+        self.stop['action'] = 'receive'
+        self.stop['operation'] = 'stop'
+        self.stop['params']['namespace'] = self.ns_list[1]
+        perf.run(**self.stop)
+        
+    def testQperf(self):
+        perf = Vstfperf()
+        self.start['tool'] = 'qperf'
+        self.stop['tool'] = 'qperf'
+        self.start['action'] = 'receive'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[1]
+        perf.run(**self.start)
+        self.start['action'] = 'send'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[0]
+        perf.run(**self.start)
+        self.stop['action'] = 'send'
+        self.stop['operation'] = 'stop'
+        self.stop['params']['namespace'] = self.ns_list[0]
+        perf.run(**self.stop)
+        self.stop['action'] = 'receive'
+        self.stop['operation'] = 'stop'
+        self.stop['params']['namespace'] = self.ns_list[1]
+        perf.run(**self.stop)
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.DEBUG)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/agent/unittest/test_callback.py b/vstf/vstf/agent/unittest/test_callback.py
new file mode 100755 (executable)
index 0000000..35336d9
--- /dev/null
@@ -0,0 +1,80 @@
+"""
+Created on 2015-9-28
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.agent.unittest.perf import model
+from vstf.agent import softagent
+
+
+class TestCallback(model.Model):
+    def setUp(self):
+        super(TestCallback, self).setUp()
+        self.agent = softagent.softAgent()
+        for ns, dev, ip_setting in zip(self.ns_list, self.device_list, self.ip_setting_list):
+            netdev = {
+                "namespace": ns,
+                "iface": dev,
+                'ip_setting': ip_setting
+            }
+            self.mgr.config_dev(netdev)
+
+        self.start = {
+            "operation": "start",
+            "action": "send",
+            "tool": "netperf",
+            "params": {
+                "namespace": self.ns_list[0],
+                "protocol": "tcp_lat",
+                "dst": [
+                    {"ip": self.ip_list[1]}
+                ],
+                "size": 64,
+                "threads": 1,
+                "time": 1,
+            },
+        }
+        self.stop = {
+            "operation": "stop",
+            "action": "send",
+            "tool": "netperf",
+            "params": {
+                "namespace": self.ns_list[1],
+            },
+        }
+
+    def tearDown(self):
+        super(TestCallback, self).tearDown()
+
+    def test_clean(self):
+        agent = self.agent
+        agent.perf_clean()
+        self.start['tool'] = 'netperf'
+        self.stop['tool'] = 'netperf'
+        self.start['action'] = 'receive'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[1]
+        self.start['params']['protocol'] = 'udp_bw'
+        agent.perf_run(**self.start)
+        self.start['action'] = 'send'
+        self.start['operation'] = 'start'
+        self.start['params']['namespace'] = self.ns_list[0]
+        agent.perf_run(**self.start)
+        agent.perf_clean()
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.getLogger(__name__)
+    logging.basicConfig(level=logging.DEBUG)
+    # import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.main()
diff --git a/vstf/vstf/common/__init__.py b/vstf/vstf/common/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/common/cfgparser.py b/vstf/vstf/common/cfgparser.py
new file mode 100755 (executable)
index 0000000..3d50a1b
--- /dev/null
@@ -0,0 +1,27 @@
+"""
+Created on 2015-8-5
+
+@author: c00225995
+"""
+import os
+from oslo.config import cfg
+
+
+class CfgParser(object):
+    def __init__(self, config_file):
+        super(CfgParser, self).__init__()
+        if os.path.isfile(config_file) is False:
+            raise Exception('The config file not found <%s>' % config_file)
+        self.config_file = config_file
+        self.CONF = cfg.ConfigOpts()
+
+    def register_my_opts(self, opts, name=None):
+        if name:
+            self.CONF.register_opts(opts, name)
+        else:
+            self.CONF.register_opts(opts)
+
+    def parse(self):
+        #  self.register_my_opts(opts, name=name)
+        self.CONF(args=[], default_config_files=[self.config_file])
+        return self.CONF
diff --git a/vstf/vstf/common/check.py b/vstf/vstf/common/check.py
new file mode 100755 (executable)
index 0000000..9b2f3d0
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-11-5
+# see license for license details
+
+import logging
+import vstf.common.constants as cst
+
+LOG = logging.getLogger(__name__)
+
+
+def check_case_params(protocol, typ, tool):
+    if "throughput" == typ:
+        return False, "Not support 'throughput' at this version"
+    if "tcp" == protocol:
+        if tool in ["pktgen", "netmap"]:
+            return False, "%s cant support tcp test" % tool
+    if "qperf" == tool and "latency" != typ:
+        return False, "qperf support latency test only, cant support %s" % typ
+    if "latency" == typ and tool not in ["netperf", "qperf"]:
+        return False, "%s cant support latency test" % tool
+    return True, "support successfully"
diff --git a/vstf/vstf/common/cliutil.py b/vstf/vstf/common/cliutil.py
new file mode 100755 (executable)
index 0000000..91ff7f1
--- /dev/null
@@ -0,0 +1,27 @@
+def arg(*args, **kwargs):
+    """Decorator for CLI args.
+
+    Example:
+
+    >>> @arg("name", help="Name of the new entity")
+    ... def entity_create(args):
+    ...     pass
+    """
+    def _decorator(func):
+        add_arg(func, *args, **kwargs)
+        return func
+    return _decorator
+
+
+def add_arg(func, *args, **kwargs):
+    """Bind CLI arguments to a shell.py `do_foo` function."""
+
+    if not hasattr(func, 'arguments'):
+        func.arguments = []
+
+    # NOTE(sirp): avoid dups that can occur when the module is shared across
+    # tests.
+    if (args, kwargs) not in func.arguments:
+        # Because of the semantics of decorator composition if we just append
+        # to the options list positional options will appear to be backwards.
+        func.arguments.insert(0, (args, kwargs))
\ No newline at end of file
diff --git a/vstf/vstf/common/cmds.py b/vstf/vstf/common/cmds.py
new file mode 100755 (executable)
index 0000000..c30f2be
--- /dev/null
@@ -0,0 +1,19 @@
+import commands
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def execute(cmd=None, care_result=True):
+    if not cmd:
+        LOG.error('The cmd is None')
+        return None
+    try:
+        (status, ret) = commands.getstatusoutput(cmd)
+        if care_result and 0 != status:
+            LOG.error('CMD<%(cmd)s> \nSTDOUT:\n%(ret)s.', {'cmd':cmd, 'ret':ret})
+            return None
+        else:
+            return ret
+    except Exception as e:
+        raise e
diff --git a/vstf/vstf/common/constants.py b/vstf/vstf/common/constants.py
new file mode 100755 (executable)
index 0000000..1cace39
--- /dev/null
@@ -0,0 +1,66 @@
+slave_project_path = "/opt/esp-atf"
+VSTFCPATH = "/opt/vstf"
+sockaddr = VSTFCPATH + "/vstf.socket"
+vstf_pid = VSTFCPATH + "/vstf-server.pid"
+buff_size = 1024
+
+# the message's len must be < 9999999999
+MSG_FLAG_LEN = 10
+MSG_FLAG = "%010d"
+
+# all command run timeout
+TIMEOUT = 20
+# timmer SECOND
+TICK = 3
+
+HW_INFO = "HW_INFO"
+CPU_INFO = "CPU INFO"
+MEMORY_INFO = "MEMORY INFO"
+OS_INFO = "OS INFO"
+
+TOOLS = ["pktgen", "netperf", "qperf", "netmap"]
+OPERATIONS = ["start", "stop", "restart"]
+ACTIONS = ["send", "receive"]
+PROTOCOLS = ["tcp_lat", "udp_lat", "tcp_bw", "udp_bw"]
+TPROTOCOLS = ["tcp", "udp"]
+PROFILES = ["rdp", "fastlink", "l2switch"]
+TTYPES = ["throughput", "latency", "frameloss"]
+SCENARIOS = ["Ti", "Tn", "Tnv", "Tu"]
+SOCKET_BUF = 102400
+WAIT_BALANCE = 2
+CPU_USAGE_ROUND = 2
+PKTLOSS_ROUND = 2
+RATEP_ROUND = 3
+TIME_ROUND = 3
+TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
+TIME_STR = "%Y%m%d_%H%M%S"
+REPORT_DEFAULTS = "/tmp"
+
+CASE_ACTOR_MAP = {
+    # unidirection
+    "Tn-1": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tn-2": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+    # unidirection with vxlan
+    "Tn-3": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tn-4": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+    # unidirection
+    "Tnv-1": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tnv-2": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+    # unidirection with vxlan
+    "Tnv-3": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tnv-4": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+
+    "Ti-1": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Ti-2": {"senders": [-1], "receivers": [0], "flows": 1},
+    "Ti-3": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+    "Ti-4": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Ti-5": {"senders": [-1], "receivers": [0], "flows": 1},
+    "Ti-6": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+
+    "Tu-1": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tu-2": {"senders": [-1], "receivers": [0], "flows": 1},
+    "Tu-3": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2},
+    "Tu-4": {"senders": [0], "receivers": [-1], "flows": 1},
+    "Tu-5": {"senders": [-1], "receivers": [0], "flows": 1},
+    "Tu-6": {"senders": [0, -1], "receivers": [-1, 0], "flows": 2}
+}
diff --git a/vstf/vstf/common/daemon.py b/vstf/vstf/common/daemon.py
new file mode 100755 (executable)
index 0000000..8a29861
--- /dev/null
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+import sys, os, time, atexit
+import logging
+from signal import SIGTERM
+
+LOG = logging.getLogger(__name__)
+
+
+class Daemon(object):
+    """
+    A generic daemon class.
+    
+    Usage: subclass the Daemon class and override the run() method
+    """
+    def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
+        super(Daemon, self).__init__()
+        self.stdin = stdin
+        self.stdout = stdout
+        self.stderr = stderr
+        self.pidfile = pidfile
+    
+    def daemonize(self):
+        """
+        do the UNIX double-fork magic, see Stevens' "Advanced 
+        Programming in the UNIX Environment" for details (ISBN 0201563177)
+        http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
+        """
+        try: 
+            pid = os.fork() 
+            if pid > 0:
+                sys.exit(0) 
+        except OSError, e: 
+            LOG.error("fork #1 failed: %(errno)s, %(strerror)s",
+                      {'errno':e.errno, 'strerror': e.strerror})
+            sys.exit(1)
+    
+        # decouple from parent environment
+        os.chdir("/") 
+        os.setsid() 
+        os.umask(0) 
+    
+        # do second fork
+        try: 
+            pid = os.fork() 
+            if pid > 0:
+                # exit from second parent
+                sys.exit(0) 
+        except OSError, e: 
+            LOG.error("fork #1 failed: %(errno)s, %(strerror)s",
+                      {'errno':e.errno, 'strerror': e.strerror})
+            sys.exit(1) 
+    
+        # redirect standard file descriptors
+        sys.stdout.flush()
+        sys.stderr.flush()
+        si = file(self.stdin, 'r')
+        so = file(self.stdout, 'a+')
+        se = file(self.stderr, 'a+', 0)
+        os.dup2(si.fileno(), sys.stdin.fileno())
+        os.dup2(so.fileno(), sys.stdout.fileno())
+        os.dup2(se.fileno(), sys.stderr.fileno())
+    
+        # write pidfile
+        atexit.register(self.delpid)
+        pid = str(os.getpid())
+        file(self.pidfile,'w+').write("%s\n" % pid)
+    
+    def delpid(self):
+        os.remove(self.pidfile)
+
+    def start(self):
+        """
+        Start the daemon
+        """
+        
+        # Check for a pidfile to see if the daemon already runs
+        try:
+            pf = file(self.pidfile,'r')
+            pid = int(pf.read().strip())
+            pf.close()
+        except IOError:
+            pid = None
+    
+        if pid:
+            message = "pidfile %s already exist. Daemon already running?\n"
+            sys.stderr.write(message % self.pidfile)
+            sys.exit(1)
+        LOG.info("daemon start to run daemonize")
+        # Start the daemon
+        self.daemonize()
+        self.run()
+
+    def stop(self):
+        """
+        Stop the daemon
+        """
+        # Get the pid from the pidfile
+        try:
+            pf = file(self.pidfile,'r')
+            pid = int(pf.read().strip())
+            pf.close()
+        except IOError:
+            pid = None
+    
+        if not pid:
+            message = "pidfile %s does not exist. Daemon not running?\n"
+            sys.stderr.write(message % self.pidfile)
+            return # not an error in a restart
+
+        # Try killing the daemon process    
+        try:
+            while 1:
+                os.kill(pid, SIGTERM)
+                time.sleep(0.1)
+        except OSError, err:
+            err = str(err)
+            if err.find("No such process") > 0:
+                if os.path.exists(self.pidfile):
+                    os.remove(self.pidfile)
+            else:
+                print str(err)
+                sys.exit(1)
+
+    def restart(self):
+        """
+        Restart the daemon
+        """
+        self.stop()
+        self.start()
+
+    def run(self):
+        """
+        You should override this method when you subclass Daemon. 
+        It will be called after the process has been
+        daemonized by start() or restart().
+        
+        """
+        pass
+        
+    def daemon_die(self):
+        """You should this method when you shutdown daemon
+        this func will be call by stop() before kill the process
+        
+        """
+        pass
\ No newline at end of file
diff --git a/vstf/vstf/common/decorator.py b/vstf/vstf/common/decorator.py
new file mode 100755 (executable)
index 0000000..cafbb3f
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-09
+# see license for license details
+_DEFAULTS = "vstf check key defaults".encode()
+
+
+def check(key, choices=[], defaults=_DEFAULTS):
+    def _deco(func):
+        def __deco(*args, **kwargs):
+            if key not in kwargs:
+                if defaults != _DEFAULTS:
+                    kwargs[key] = defaults
+                else:
+                    raise Exception("Error: '%s' is needed in %s" % (key, func))
+
+            if choices and kwargs[key] not in choices:
+                raise Exception("Error: %s :%s" % (key, kwargs[key]))
+            ret = func(*args, **kwargs)
+            return ret
+
+        return __deco
+
+    return _deco
+
+
+def dcheck(key, choices=[]):
+    def _deco(func):
+        def __deco(*args):
+            if len(args) == 2:
+                values = args[1]
+            elif len(args) == 1:
+                values = args[0]
+            else:
+                values = None
+            if isinstance(values, dict):
+                if key not in values:
+                    raise Exception("Error: '%s' is needed in %s" % (key, func))
+                if choices and values[key] not in choices:
+                    raise Exception("Error: %s :%s" % (key, values[key]))
+            ret = func(*args)
+            return ret
+
+        return __deco
+
+    return _deco
+
+
+def vstf_input(key, types=str, choices=[], default=None):
+    def _deco(func):
+        def __deco(*args):
+            ret = func(*args)
+            if not isinstance(ret, dict):
+                ret = {}
+            in_str = "----> %s : " % key
+            if choices:
+                in_str = "---- %s\n" % (str(choices)) + in_str
+            while True:
+                if types == list or types == dict:
+                    value = input(in_str)
+                else:
+                    value = raw_input(in_str)
+                    value = types(value)
+                if not value:
+                    value = default
+                if not choices or value in choices:
+                    break
+            ret.update({key: value})
+            return ret
+
+        return __deco
+
+    return _deco
+
+
+def namespace():
+    def _deco(func):
+        def __deco(*args, **kwargs):
+            ret = func(*args, **kwargs)
+            nspace = kwargs.get("namespace", None)
+            if nspace:
+                ret = "ip netns exec %(namespace)s " % {"namespace": nspace} + ret
+            return ret
+
+        return __deco
+
+    return _deco
diff --git a/vstf/vstf/common/excepts.py b/vstf/vstf/common/excepts.py
new file mode 100755 (executable)
index 0000000..dc781b9
--- /dev/null
@@ -0,0 +1,12 @@
+class ChannelDie(Exception):
+    """rabbitmq's channel connect failed"""
+    pass
+
+
+class UnsolvableExit(Exception):
+    """the soft maybe error , and the code can not solvable, must be exit"""
+    pass
+
+
+class AgentExit(Exception):
+    pass
diff --git a/vstf/vstf/common/input.py b/vstf/vstf/common/input.py
new file mode 100755 (executable)
index 0000000..9e2f933
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# date: 2015-09-09
+# see license for license details
+
+__author__ = 'wly'
+__version__ = '0.1'
+
+import os
+
+
+def raw_choice(title):
+    ret = {'Y': True, 'N': False}
+    while True:
+        os.system("clear")
+        in_str = "\n%s:(y|n)  " % title
+        uin = raw_input(in_str).title()
+        if uin in ['Y', 'N']:
+            break
+    return ret[uin]
diff --git a/vstf/vstf/common/log.py b/vstf/vstf/common/log.py
new file mode 100755 (executable)
index 0000000..b34a8a9
--- /dev/null
@@ -0,0 +1,41 @@
+import logging
+
+
+def _init_log_to_file(log_file, level, _format):
+    file_handler = logging.FileHandler(log_file)
+    file_handler.setLevel(level)
+    file_handler.setFormatter(logging.Formatter(_format))
+    return file_handler
+
+
+def _init_log_to_console(level, _format):
+    console = logging.StreamHandler()
+    console.setLevel(level)
+    console.setFormatter(logging.Formatter(_format))
+    return console
+
+
+def _init_log(log_file, level=logging.INFO, clevel=logging.INFO):
+    _format = '%(asctime)s <%(levelname)s> [%(funcName)s.%(lineno)d]: %(message)s'
+    # _format = '%(asctime)s [%(levelname)s] %(message)s'
+    _verbose = '%(levelname)s %(asctime)s [%(filename)s:%(lineno)d] %(funcName)s ### %(message)s'
+    _simple = '<%(levelname)s> [%(filename)s:%(lineno)d] ### %(message)s'
+    file_handler = _init_log_to_file(log_file, level, _verbose)
+    console = _init_log_to_console(clevel, _simple)
+    return file_handler, console
+
+
+def setup_logging(level=logging.INFO, log_file="/var/log/esp_test.log", clevel=logging.WARNING):
+    log = logging.getLogger()
+    log.setLevel(level)
+    file_handler, console = _init_log(log_file, level, clevel)
+    log.addHandler(file_handler)
+    log.addHandler(console)
+
+
+if __name__ == "__main__":
+    setup_logging()
+    logger = logging.getLogger("common")
+    logger.info('this is a test.')
+    logger.warning('this is a test.')
+    logger.error('this is a test.')
diff --git a/vstf/vstf/common/message.py b/vstf/vstf/common/message.py
new file mode 100755 (executable)
index 0000000..926091f
--- /dev/null
@@ -0,0 +1,141 @@
+import json
+import uuid
+import logging
+import traceback
+from vstf.common import constants
+
+LOG = logging.getLogger(__name__)
+
+
+def json_defaults(obj):
+    if isinstance(obj, set):
+        return list(obj)
+    return "unknow obj"
+
+
+def encode(msg):
+    """obj to string"""
+    if isinstance(msg, str):
+        return msg
+    else:
+        return json.dumps(msg, default=json_defaults)
+
+
+def decode(msg):
+    """string to obj"""
+    if isinstance(msg, str):
+        return json.loads(msg)
+    else:
+        return msg
+
+
+def gen_corrid():
+    return str(uuid.uuid4())
+
+
+def add_context(msg, **kwargs):
+    return {'head': kwargs, 'body': msg}
+
+
+def get_context(msg):
+    if "head" in msg.iterkeys():
+        return msg['head']
+    else:
+        return ""
+
+
+def get_body(msg):
+    if "body" in msg.iterkeys():
+        return msg['body']
+    else:
+        return None
+
+
+def get_corrid(context):
+    """
+    :param return: string of corrid or empty
+    """
+    if "corrid" in context.iterkeys():
+        return context['corrid']
+    else:
+        return ""
+
+
+def send(func, data):
+    # the message must be a string
+    if not isinstance(data, str):
+        raise ValueError("the data must be a string")
+
+    # the message's len must > 0
+    msg_len = len(data)
+    if msg_len <= 0:
+        return True
+
+    # the message's len must be less 999999999
+    if len(str(msg_len)) > constants.MSG_FLAG_LEN:
+        raise ValueError("the data's len too long")
+
+    data = (constants.MSG_FLAG % (msg_len)) + data
+    total_send = msg_len + constants.MSG_FLAG_LEN
+
+    count = 0
+    while count < total_send:
+        sent = func(data[count:])
+        if 0 == sent:
+            raise RuntimeError("socket connection broken")
+        count += sent
+
+    return msg_len
+
+
+def sendto(func, data, addr):
+    # the message must be a string
+    if not isinstance(data, str):
+        raise ValueError("the data must be a string")
+
+    # the message's len must > 0
+    msg_len = len(data)
+    if msg_len <= 0:
+        return True
+
+    # the message's len must be less 999999999
+    if len(str(msg_len)) > constants.MSG_FLAG_LEN:
+        raise ValueError("the data's len too long")
+
+    data = (constants.MSG_FLAG % (msg_len)) + data
+    total_send = msg_len + constants.MSG_FLAG_LEN
+
+    count = 0
+    while count < total_send:
+        sent = func(data[count:], addr)
+        if 0 == sent:
+            raise RuntimeError("socket connection broken")
+        count += sent
+
+    return msg_len
+
+
+def recv(func):
+    head = func(constants.MSG_FLAG_LEN)
+    # the FIN change to '' in python
+    if head == '':
+        raise RuntimeError("socket connection broken")
+
+    if not head.isdigit():
+        raise ValueError("the msg head is not a num.")
+
+    msg_len = int(head)
+    chunks = []
+    count = 0
+    while count < msg_len:
+        chunk = func(min(msg_len - count, constants.buff_size))
+        if chunk == '':
+            raise RuntimeError("socket connection broken")
+        chunks.append(chunk)
+        count += len(chunk)
+
+    return ''.join(chunks)
+
+
+def dumpstrace():
+    return traceback.format_exc()
diff --git a/vstf/vstf/common/perfmark.py b/vstf/vstf/common/perfmark.py
new file mode 100755 (executable)
index 0000000..f03ee53
--- /dev/null
@@ -0,0 +1,14 @@
+pktSize = 'AvgFrameSize'
+offLoad = 'OfferedLoad'
+percentLoss = 'PercentLoss'
+bandwidth = 'Bandwidth'
+minLatency = 'MinimumLatency'
+maxLatency = 'MaximumLatency'
+avgLatency = 'AverageLatency'
+txCount = 'TxFrameCount'
+rxCount = 'RxFrameCount'
+duration = 'Duration'
+cpu = 'CPU'
+mppsGhz = 'MppspGhz'
+rxMbps = "RxMbit"
+txMbps = 'TxMbit'
diff --git a/vstf/vstf/common/pyhtml.py b/vstf/vstf/common/pyhtml.py
new file mode 100755 (executable)
index 0000000..c2a2620
--- /dev/null
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-25
+# see license for license details
+
+from sys import stdout, modules
+
+doc_type = '<!DOCTYPE HTML>\n'
+default_title = "Html Page"
+charset = '<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />\n'
+
+html4_tags = {'a', 'abbr', 'acronym', 'address', 'area', 'b', 'base', 'bdo', 'big',
+              'blockquote', 'body', 'br', 'button', 'caption', 'cite', 'code', 'col',
+              'colgroup', 'dd', 'del', 'div', 'dfn', 'dl', 'dt', 'em', 'fieldset',
+              'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head',
+              'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd',
+              'label', 'legend', 'li', 'link', 'map', 'menu', 'menuitem', 'meta',
+              'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'p',
+              'param', 'pre', 'q', 'samp', 'script', 'select', 'small', 'span', 'strong',
+              'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th',
+              'thead', 'title', 'tr', 'tt', 'ul', 'var'}
+disused_tags = {'isindex', 'font', 'dir', 's', 'strike',
+                'u', 'center', 'basefont', 'applet', 'xmp'}
+html5_tags = {'article', 'aside', 'audio', 'bdi', 'canvas', 'command', 'datalist', 'details',
+              'dialog', 'embed', 'figcaption', 'figure', 'footer', 'header',
+              'keygen', 'mark', 'meter', 'nav', 'output', 'progress', 'rp', 'rt', 'ruby',
+              'section', 'source', 'summary', 'details', 'time', 'track', 'video', 'wbr'}
+
+nl = '\n'
+tags = html4_tags | disused_tags | html5_tags
+
+__all__ = [x.title() for x in tags] + ['PyHtml']
+
+self_close = {'input', 'img', 'link', 'br'}
+
+
+class Tag(list):
+    tag_name = ''
+
+    def __init__(self, *args, **kwargs):
+        self.attributes = kwargs
+        if self.tag_name:
+            name = self.tag_name
+            self.is_seq = False
+        else:
+            name = 'sequence'
+            self.is_seq = True
+        self._id = kwargs.get('id', name)
+        for arg in args:
+            self.add_obj(arg)
+
+    def __iadd__(self, obj):
+        if isinstance(obj, Tag) and obj.is_seq:
+            for o in obj:
+                self.add_obj(o)
+        else:
+            self.add_obj(obj)
+        return self
+
+    def add_obj(self, obj):
+        if not isinstance(obj, Tag):
+            obj = str(obj)
+        _id = self.set_id(obj)
+        setattr(self, _id, obj)
+        self.append(obj)
+
+    def set_id(self, obj):
+        if isinstance(obj, Tag):
+            _id = obj._id
+            obj_lst = filter(lambda t: isinstance(
+                t, Tag) and t._id.startswith(_id), self)
+        else:
+            _id = 'content'
+            obj_lst = filter(lambda t: not isinstance(t, Tag), self)
+        length = len(obj_lst)
+        if obj_lst:
+            _id = '%s_%03i' % (_id, length)
+        if isinstance(obj, Tag):
+            obj._id = _id
+        return _id
+
+    def __add__(self, obj):
+        if self.tag_name:
+            return Tag(self, obj)
+        self.add_obj(obj)
+        return self
+
+    def __lshift__(self, obj):
+        if isinstance(obj, Tag):
+            self += obj
+            return obj
+        print "unknown obj: %s " % obj
+        return self
+
+    def render(self):
+        result = ''
+        if self.tag_name:
+            result += '<%s%s%s>' % (self.tag_name,
+                                    self._render_attr(), self._self_close() * ' /')
+        if not self._self_close():
+            isnl = True
+            for c in self:
+                if isinstance(c, Tag):
+                    result += isnl * nl
+                    isnl = False
+                    result += c.render()
+                else:
+                    result += c
+            if self.tag_name:
+                result += '</%s>' % self.tag_name
+        result += nl
+        return result
+
+    def _render_attr(self):
+        result = ''
+        for key, value in self.attributes.iteritems():
+            if key != 'txt' and key != 'open':
+                if key == 'cl':
+                    key = 'class'
+                result += ' %s="%s"' % (key, value)
+        return result
+
+    def _self_close(self):
+        return self.tag_name in self_close
+
+"""
+def tag_factory(tag):
+    class F(Tag):
+        tag_name = tag
+
+    F.__name__ = tag.title()
+    return F
+
+
+THIS = modules[__name__]
+
+for t in tags:
+    setattr(THIS, t.title(), tag_factory(t))
+"""
+THIS = modules[__name__]
+for t in tags:
+    obj = type(t.title(), (Tag, ), {'tag_name': t})
+    setattr(THIS, t.title(), obj)
+
+
+def _render_style(style):
+    result = ''
+    for item in style:
+        result += item
+        result += '\n{\n'
+        values = style[item]
+        for key, value in values.iteritems():
+            result += "    %s: %s;\n" % (key, value)
+        result += '}\n'
+    if result:
+        result = '\n' + result
+    return result
+
+
+class PyHtml(Tag):
+    tag_name = 'html'
+
+    def __init__(self, title=default_title):
+        self._id = 'html'
+        self += Head()
+        self += Body()
+        self.attributes = dict(xmlns='http://www.w3.org/1999/xhtml', lang='en')
+        self.head += Title(title)
+
+    def __iadd__(self, obj):
+        if isinstance(obj, Head) or isinstance(obj, Body):
+            self.add_obj(obj)
+        elif isinstance(obj, Meta) or isinstance(obj, Link):
+            self.head += obj
+        else:
+            self.body += obj
+            _id = self.set_id(obj)
+            setattr(self, _id, obj)
+        return self
+
+    def add_js(self, *arg):
+        for f in arg:
+            self.head += Script(type='text/javascript', src=f)
+
+    def add_css(self, *arg):
+        for f in arg:
+            self.head += Link(rel='stylesheet', type='text/css', href=f)
+
+    def output(self, name=''):
+        if name:
+            fil = open(name, 'w')
+        else:
+            fil = stdout
+        fil.write(self.as_string())
+        fil.flush()
+        if name:
+            fil.close()
+
+    def as_string(self):
+        return doc_type + self.render()
+
+    def add_style(self, style):
+        self.head += Style(_render_style(style))
+
+    def add_table(self, data):
+        table = self << Table()
+        rows = len(data)
+        cols = len(zip(*data))
+
+        for i in range(rows):
+            tr = table << Tr()
+            tr << Th(data[i][0])
+            for j in range(1, cols):
+                tr << Td(data[i][j])
diff --git a/vstf/vstf/common/rsync.py b/vstf/vstf/common/rsync.py
new file mode 100755 (executable)
index 0000000..b566136
--- /dev/null
@@ -0,0 +1,518 @@
+#!/usr/bin/python
+
+# Python conterpart of rsync written by Vivian De Smedt
+# Send any comment or bug report to vivian@vdesmedt.com.
+# I would like to thanks William Tan for its support in tuning rsync.py to support unicode path.
+# I would like to thanks Luc Saffre for its bug reports and fixes.
+
+#from __future__ import nested_scopes
+
+import os, os.path, shutil, glob, re, sys, getopt, stat, string
+
+
+try:
+       import win32file
+except:
+       win32file = None
+
+class Cookie:
+       def __init__(self):
+               self.sink_root = ""
+               self.target_root = ""
+               self.quiet = 0
+               self.recursive = 0
+               self.relative = 0
+               self.dry_run = 0
+               self.time = 0
+               self.update = 0
+               self.cvs_ignore = 0
+               self.ignore_time = 0
+               self.delete = 0
+               self.delete_excluded = 0
+               self.delete_from_source = 0
+               self.size_only = 0
+               self.modify_window = 2
+               self.existing = 0
+               self.filters = []
+               self.case_sensitivity = 0
+               if os.name == "nt":
+                       self.case_sensitivity = re.I
+
+def visit(cookie, dirname, names):
+       """Copy files names from sink_root + (dirname - sink_root) to target_root + (dirname - sink_root)"""
+       if os.path.split(cookie.sink_root)[1]: # Should be tested with (C:\Cvs -> C:\)! (C:\Archives\MyDatas\UltraEdit -> C:\Archives\MyDatas) (Cvs -> "")! (Archives\MyDatas\UltraEdit -> Archives\MyDatas) (\Cvs -> \)! (\Archives\MyDatas\UltraEdit -> Archives\MyDatas)
+               dirname = dirname[len(cookie.sink_root) + 1:]
+       else:
+               dirname = dirname[len(cookie.sink_root):]
+       target_dir = os.path.join(cookie.target_root, dirname)
+       if not os.path.isdir(target_dir):
+               makeDir(cookie, target_dir)
+       sink_dir = os.path.join(cookie.sink_root, dirname)
+
+       filters = []
+       if cookie.cvs_ignore:
+               ignore = os.path.join(sink_dir, ".cvsignore")
+               if os.path.isfile(ignore):
+                       filters = convertPatterns(ignore, "-")
+       filters = filters + cookie.filters
+
+       names_excluded = []
+       if filters:
+               # filter sink files (names):
+               name_index = 0
+               while name_index < len(names):
+                       name = names[name_index]
+                       path = os.path.join(dirname, name)
+                       path = convertPath(path)
+                       if os.path.isdir(os.path.join(sink_dir, name)):
+                               path = path + "/"
+                       for filter in filters:
+                               if re.search(filter[1], path, cookie.case_sensitivity):
+                                       if filter[0] == '-':
+                                               sink = os.path.join(sink_dir, name)
+                                               if cookie.delete_from_source:
+                                                       if os.path.isfile(sink):
+                                                               removeFile(cookie, sink)
+                                                       elif os.path.isdir(sink):
+                                                               removeDir(cookie, sink)
+                                                       else:
+                                                               logError("Sink %s is neither a file nor a folder (skip removal)" % sink)
+                                               names_excluded += [names[name_index]]
+                                               del(names[name_index])
+                                               name_index = name_index - 1
+                                               break 
+                                       elif filter[0] == '+':
+                                               break
+                       name_index = name_index + 1
+
+       if cookie.delete and os.path.isdir(target_dir):
+               # Delete files and folder in target not present in filtered sink.
+               for name in os.listdir(target_dir):
+                       if not cookie.delete_excluded and name in names_excluded:
+                               continue
+                       if not name in names:
+                               target = os.path.join(target_dir, name)
+                               if os.path.isfile(target):
+                                       removeFile(cookie, target)
+                               elif os.path.isdir(target):
+                                       removeDir(cookie, target)
+                               else:
+                                       pass
+
+       for name in names:
+               # Copy files and folder from sink to target.
+               sink = os.path.join(sink_dir, name)
+               #print sink
+               target = os.path.join(target_dir, name)
+               if os.path.exists(target):
+                       # When target already exit:
+                       if os.path.isfile(sink):
+                               if os.path.isfile(target):
+                                       # file-file
+                                       if shouldUpdate(cookie, sink, target):
+                                               updateFile(cookie, sink, target)
+                               elif os.path.isdir(target):
+                                       # file-folder
+                                       removeDir(cookie, target)
+                                       copyFile(cookie, sink, target)
+                               else:
+                                       # file-???
+                                       logError("Target %s is neither a file nor folder (skip update)" % sink)
+
+                       elif os.path.isdir(sink):
+                               if os.path.isfile(target):
+                                       # folder-file
+                                       removeFile(cookie, target)
+                                       makeDir(cookie, target)
+                       else:
+                               # ???-xxx
+                               logError("Sink %s is neither a file nor a folder (skip update)" % sink)
+
+               elif not cookie.existing:
+                       # When target dont exist:
+                       if os.path.isfile(sink):
+                               # file
+                               copyFile(cookie, sink, target)
+                       elif os.path.isdir(sink):
+                               # folder
+                               makeDir(cookie, target)
+                       else:
+                               logError("Sink %s is neither a file nor a folder (skip update)" % sink)
+
+
+def log(cookie, message):
+       if not cookie.quiet:
+               try:
+                       print message
+               except UnicodeEncodeError:
+                       print message.encode("utf8")
+
+
+def logError(message):
+       try:
+               sys.stderr.write(message + "\n")
+       except UnicodeEncodeError:
+               sys.stderr.write(message.encode("utf8") + "\n")
+
+
+def shouldUpdate(cookie, sink, target):
+       try:
+               sink_st = os.stat(sink)
+               sink_sz = sink_st.st_size
+               sink_mt = sink_st.st_mtime
+       except:
+               logError("Fail to retrieve information about sink %s (skip update)" % sink)
+               return 0
+
+       try:
+               target_st = os.stat(target)
+               target_sz = target_st.st_size
+               target_mt = target_st.st_mtime
+       except:
+               logError("Fail to retrieve information about target %s (skip update)" % target)
+               return 0
+
+       if cookie.update:
+               return target_mt < sink_mt - cookie.modify_window
+
+       if cookie.ignore_time:
+               return 1
+
+       if target_sz != sink_sz:
+               return 1
+
+       if cookie.size_only:
+               return 0
+
+       return abs(target_mt - sink_mt) > cookie.modify_window
+
+
+def copyFile(cookie, sink, target):
+       log(cookie, "copy: %s to: %s" % (sink, target))
+       if not cookie.dry_run:
+               try:
+                       shutil.copyfile(sink, target)
+               except:
+                       logError("Fail to copy %s" % sink)
+
+               if cookie.time:
+                       try:
+                               s = os.stat(sink)
+                               os.utime(target, (s.st_atime, s.st_mtime));
+                       except:
+                               logError("Fail to copy timestamp of %s" % sink)
+
+
+def updateFile(cookie, sink, target):
+       log(cookie, "update: %s to: %s" % (sink, target))
+       if not cookie.dry_run:
+               # Read only and hidden and system files can not be overridden.
+               try:
+                       try:
+                               if win32file:
+                                       filemode = win32file.GetFileAttributesW(target)
+                                       win32file.SetFileAttributesW(target, filemode & ~win32file.FILE_ATTRIBUTE_READONLY & ~win32file.FILE_ATTRIBUTE_HIDDEN & ~win32file.FILE_ATTRIBUTE_SYSTEM)
+                               else:
+                                       os.chmod(target, stat.S_IWUSR)
+                       except:
+                               #logError("Fail to allow override of %s" % target)
+                               pass
+
+                       shutil.copyfile(sink, target)
+                       if cookie.time:
+                               try:
+                                       s = os.stat(sink)
+                                       os.utime(target, (s.st_atime, s.st_mtime));
+                               except:
+                                       logError("Fail to copy timestamp of %s" % sink) # The utime api of the 2.3 version of python is not unicode compliant.
+               except:
+                       logError("Fail to override %s" % sink)
+
+               if win32file:
+                       win32file.SetFileAttributesW(target, filemode)
+
+
+def prepareRemoveFile(path):
+       if win32file:
+               filemode = win32file.GetFileAttributesW(path)
+               win32file.SetFileAttributesW(path, filemode & ~win32file.FILE_ATTRIBUTE_READONLY & ~win32file.FILE_ATTRIBUTE_HIDDEN & ~win32file.FILE_ATTRIBUTE_SYSTEM)
+       else:
+               os.chmod(path, stat.S_IWUSR)
+
+
+def removeFile(cookie, target):
+       # Read only files could not be deleted.
+       log(cookie, "remove: %s" % target)
+       if not cookie.dry_run:
+               try:
+                       try:
+                               prepareRemoveFile(target)
+                       except:
+                               #logError("Fail to allow removal of %s" % target)
+                               pass
+
+                       os.remove(target)
+               except:
+                       logError("Fail to remove %s" % target)
+
+
+
+def makeDir(cookie, target):
+       log(cookie, "make dir: %s" % target)
+       if not cookie.dry_run:
+               try:
+                       os.makedirs(target)
+               except:
+                       logError("Fail to make dir %s" % target)
+
+
+def visitForPrepareRemoveDir(arg, dirname, names):
+       for name in names:
+               path = os.path.join(dirname, name)
+               prepareRemoveFile(path)
+
+
+def prepareRemoveDir(path):
+       prepareRemoveFile(path)
+       os.path.walk(path, visitForPrepareRemoveDir, None)
+
+
+def OnRemoveDirError(func, path, excinfo):
+       logError("Fail to remove %s" % path)
+
+
+def removeDir(cookie, target):
+       # Read only directory could not be deleted.
+       log(cookie, "remove dir: %s" % target)
+       if not cookie.dry_run:
+               prepareRemoveDir(target)
+               try:
+                       shutil.rmtree(target, False, OnRemoveDirError)
+               except:
+                       logError("Fail to remove dir %s" % target)
+
+
+def convertPath(path):
+       # Convert windows, mac path to unix version.
+       separator = os.path.normpath("/")
+       if separator != "/":
+               path = re.sub(re.escape(separator), "/", path)
+
+       # Help file, folder pattern to express that it should match the all file or folder name.
+       path = "/" + path
+       return path
+
+
+def convertPattern(pattern, sign):
+       """Convert a rsync pattern that match against a path to a filter that match against a converted path."""
+
+       # Check for include vs exclude patterns.
+       if pattern[:2] == "+ ":
+               pattern = pattern[2:]
+               sign = "+"
+       elif pattern[:2] == "- ":
+               pattern = pattern[2:]
+               sign = "-"
+
+       # Express windows, mac patterns in unix patterns (rsync.py extension).
+       separator = os.path.normpath("/")
+       if separator != "/":
+               pattern = re.sub(re.escape(separator), "/", pattern)
+
+       # If pattern contains '/' it should match from the start.
+       temp = pattern
+       if pattern[0] == "/":
+               pattern = pattern[1:]
+       if temp[-1] == "/":
+               temp = temp[:-1]
+
+       # Convert pattern rules: ** * ? to regexp rules.
+       pattern = re.escape(pattern)
+       pattern = string.replace(pattern, "\\?", ".")
+       pattern = string.replace(pattern, "\\*\\*", ".*")
+       pattern = string.replace(pattern, "\\*", "[^/]*")
+       pattern = string.replace(pattern, "\\*", ".*")
+
+       if "/" in temp:
+               # If pattern contains '/' it should match from the start.
+               pattern = "^\\/" + pattern
+       else:
+               # Else the pattern should match the all file or folder name.
+               pattern = "\\/" + pattern
+
+       if pattern[-2:] != "\\/" and pattern[-2:] != ".*":
+               # File patterns should match also folders.
+               pattern = pattern + "\\/?"
+
+       # Pattern should match till the end.
+       pattern = pattern + "$"
+       return (sign, pattern)
+
+
+def convertPatterns(path, sign):
+       """Read the files for pattern and return a vector of filters"""
+       filters = []
+       f = open(path, "r")
+       while 1:
+               pattern = f.readline()
+               if not pattern:
+                       break
+               if pattern[-1] == "\n":
+                       pattern = pattern[:-1]
+
+               if re.match("[\t ]*$", pattern):
+                       continue
+               if pattern[0] == "#":
+                       continue
+               filters = filters + [convertPattern(pattern, sign)]
+       f.close()
+       return filters
+
+
+def printUsage():
+       """Print the help string that should printed by rsync.py -h"""
+       print "usage: rsync.py [options] source target"
+       print """
+ -q, --quiet              decrease verbosity
+ -r, --recursive          recurse into directories
+ -R, --relative           use relative path names
+ -u, --update             update only (don't overwrite newer files)
+ -t, --times              preserve times
+ -n, --dry-run            show what would have been transferred
+     --existing           only update files that already exist
+     --delete             delete files that don't exist on the sending side
+     --delete-excluded    also delete excluded files on the receiving side
+     --delete-from-source delete excluded files on the receiving side
+ -I, --ignore-times       don't exclude files that match length and time
+     --size-only          only use file size when determining if a file should
+                          be transferred
+     --modify-window=NUM  timestamp window (seconds) for file match (default=2)
+     --existing           only update existing target files or folders
+ -C, --cvs-exclude        auto ignore files in the same way CVS does
+     --exclude=PATTERN    exclude files matching PATTERN
+     --exclude-from=FILE  exclude patterns listed in FILE
+     --include=PATTERN    don't exclude files matching PATTERN
+     --include-from=FILE  don't exclude patterns listed in FILE
+     --version            print version number
+ -h, --help               show this help screen
+
+See http://www.vdesmedt.com/~vds2212/rsync.html for informations and updates.
+Send an email to vivian@vdesmedt.com for comments and bug reports."""
+
+
+def printVersion():
+       print "rsync.py version 2.0.1"
+
+
+def main(args):
+       cookie = Cookie()
+
+       opts, args = getopt.getopt(args, "qrRntuCIh", ["quiet", "recursive", "relative", "dry-run", "time", "update", "cvs-ignore", "ignore-times", "help", "delete", "delete-excluded", "delete-from-source", "existing", "size-only", "modify-window=", "exclude=", "exclude-from=", "include=", "include-from=", "version"])
+       for o, v in opts:
+               if o in ["-q", "--quiet"]:
+                       cookie.quiet = 1
+               if o in ["-r", "--recursive"]:
+                       cookie.recursive = 1
+               if o in ["-R", "--relative"]:
+                       cookie.relative = 1
+               elif o in ["-n", "--dry-run"]:
+                       cookie.dry_run = 1
+               elif o in ["-t", "--times", "--time"]: # --time is there to guaranty backward compatibility with previous buggy version.
+                       cookie.time = 1
+               elif o in ["-u", "--update"]:
+                       cookie.update = 1
+               elif o in ["-C", "--cvs-ignore"]:
+                       cookie.cvs_ignore = 1
+               elif o in ["-I", "--ignore-time"]:
+                       cookie.ignore_time = 1
+               elif o == "--delete":
+                       cookie.delete = 1
+               elif o == "--delete-excluded":
+                       cookie.delete = 1
+                       cookie.delete_excluded = 1
+               elif o == "--delete-from-source":
+                       cookie.delete_from_source = 1
+               elif o == "--size-only":
+                       cookie.size_only = 1
+               elif o == "--modify-window":
+                       cookie.modify_window = int(v)
+               elif o == "--existing":
+                       cookie.existing = 1
+               elif o == "--exclude":
+                       cookie.filters = cookie.filters + [convertPattern(v, "-")]
+               elif o == "--exclude-from":
+                       cookie.filters = cookie.filters + convertPatterns(v, "-")
+               elif o == "--include":
+                       cookie.filters = cookie.filters + [convertPattern(v, "+")]
+               elif o == "--include-from":
+                       cookie.filters = cookie.filters + convertPatterns(v, "+")
+               elif o == "--version":
+                       printVersion()
+                       return 0
+               elif o in ["-h", "--help"]:
+                       printUsage()
+                       return 0
+
+       if len(args) <= 1:
+               printUsage()
+               return 1
+
+       #print cookie.filters
+
+       target_root = args[1]
+       try: # In order to allow compatibility below 2.3.
+               pass
+               if os.path.__dict__.has_key("supports_unicode_filenames") and os.path.supports_unicode_filenames:
+                       target_root = unicode(target_root, sys.getfilesystemencoding())
+       finally:
+               cookie.target_root = target_root
+
+       sinks = glob.glob(args[0])
+       if not sinks:
+               return 0
+
+       sink_families = {}
+       for sink in sinks:
+               try: # In order to allow compatibility below 2.3.
+                       if os.path.__dict__.has_key("supports_unicode_filenames") and os.path.supports_unicode_filenames:
+                               sink = unicode(sink, sys.getfilesystemencoding())
+               except:
+                       pass
+               sink_name = ""
+               sink_root = sink
+               sink_drive, sink_root = os.path.splitdrive(sink)
+               while not sink_name:
+                       if sink_root == os.path.sep:
+                               sink_name = "."
+                               break
+                       sink_root, sink_name = os.path.split(sink_root)
+               sink_root = sink_drive + sink_root
+               if not sink_families.has_key(sink_root):
+                       sink_families[sink_root] = []
+               sink_families[sink_root] = sink_families[sink_root] + [sink_name]
+
+       for sink_root in sink_families.keys():
+               if cookie.relative:
+                       cookie.sink_root = ""
+               else:
+                       cookie.sink_root = sink_root
+
+               global y # In order to allow compatibility below 2.1 (nested scope where used before).
+               y = sink_root
+               files = filter(lambda x: os.path.isfile(os.path.join(y, x)), sink_families[sink_root])
+               if files:
+                       visit(cookie, sink_root, files)
+
+               #global y # In order to allow compatibility below 2.1 (nested scope where used before).
+               y = sink_root
+               folders = filter(lambda x: os.path.isdir(os.path.join(y, x)), sink_families[sink_root])
+               for folder in folders:
+                       folder_path = os.path.join(sink_root, folder)
+                       if not cookie.recursive:
+                               visit(cookie, folder_path, os.listdir(folder_path))
+                       else:
+                               os.path.walk(folder_path, visit, cookie)
+       return 0
+
+if __name__ == "__main__":
+       sys.exit(main(sys.argv[1:]))
diff --git a/vstf/vstf/common/saltstack.py b/vstf/vstf/common/saltstack.py
new file mode 100755 (executable)
index 0000000..efc953c
--- /dev/null
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+# coding=utf-8
+import os
+import sys
+import inspect
+import logging
+import salt.client as sclient
+
+from vstf.common import cmds
+
+log = logging.getLogger(__name__)
+
+
+class Mysalt(object):
+    IS_DIR = 1
+    IS_FILE = 2
+    FAILED = -1
+
+    def __init__(self):
+        self.cur_path = os.path.abspath(os.path.dirname(inspect.stack()[1][1]))
+        self.salt_conf = "/etc/salt/master"
+        if not os.path.exists(self.salt_conf):
+            raise Exception("this python must be run on the salt master.")
+        self.pillar_path = str(
+            cmds.execute("grep '^pillar_roots' \
+                    /etc/salt/master -A 2 | sed 1,2d | awk '{print $2}'") + '/')
+        if self.pillar_path == "":
+            log.warning("pillar path not found, make sure the pillar_roots configed")
+        else:
+            os.system("mkdir -p " + self.pillar_path)
+
+        self.state_path = str(cmds.execute("grep '^file_roots' \
+            /etc/salt/master -A 2 | sed 1,2d | awk '{print $2}'") + '/')
+        if self.state_path == "":
+            log.warning("state path not found, make sure the file_roots configed")
+        else:
+            os.system("mkdir -p " + self.state_path)
+
+        self.salt = sclient.LocalClient()
+
+    def slave_exists(self, host):
+        pslave = "/etc/salt/pki/master/minions/" + host
+        if os.path.exists(pslave):
+            return True
+        else:
+            return False
+
+    def __is_dir_or_file(self, src):
+        if not os.path.exists(src):
+            return self.FAILED
+        if os.path.isdir(src):
+            return self.IS_DIR
+        elif os.path.isfile(src):
+            return self.IS_FILE
+        else:
+            return self.FAILED
+
+    def __copy_target(self, target, flag=""):
+        if not os.path.exists(target):
+            log.error("target %(d)s  not exists.", {'d': target})
+            return False
+
+        if flag == "pillar":
+            dst = self.pillar_path
+        elif flag == "state":
+            dst = self.state_path
+        else:
+            log.error("this file or dir not pillar or state, can not support now.")
+            return False
+
+        if self.IS_FILE == self.__is_dir_or_file(target):
+            os.system('cp ' + target + ' ' + dst)
+        else:
+            os.system("cp -r " + target + ' ' + dst)
+        return True
+
+    def copy(self, host, src, dst):
+        """copy file or dir to slave.
+        :src a file or a dir
+        :dst if src is a file, the dst must be like this /home/xx.py, not /home
+        """
+
+        '''check if the host exists on the master'''
+        if not self.slave_exists(host):
+            log.error("the host %(h)s is not held by master, please check.")
+            return False
+
+        '''copy file to salt's file_roots'''
+        if not self.__copy_target(src, "state"):
+            return False
+
+        if self.IS_DIR == self.__is_dir_or_file(src):
+            dir_name = os.path.basename(src)
+            self.salt.cmd(host, "cp.get_dir", ["salt://" + dir_name, dst])
+        elif self.IS_FILE == self.__is_dir_or_file(src):
+            file_name = os.path.basename(src)
+            print self.salt.cmd(host, "cp.get_file", ["salt://" + file_name, dst])
+        else:
+            log.error("not file and not dir, what is it")
+            return False
+        return True
+
+    def __luxuriant_line(self, str, color):
+        if "red" == color:
+            return "\033[22;35;40m" + str + "\033[0m"
+        elif "green" == color:
+            return "\033[22;32;40m" + str + "\033[0m"
+        else:
+            return str
+
+    def result_check(self, ret, host):
+        num_s = 0
+        num_f = 0
+        msg = ""
+        try:
+            for key in ret[host].keys():
+                if True == ret[host][key]['result']:
+                    num_s += 1
+                else:
+                    num_f += 1
+                    msg = msg + self.__luxuriant_line("Failed %d:\n" % num_f, "red")
+                    msg = msg + "\t" + key + '\n'
+                    msg = msg + self.__luxuriant_line("\t%s\n" % ret[host][key]['comment'], "red")
+                    if True == ret[host][key]['changes'].has_key('retcode'):
+                        msg = msg + "RETCODE: %s\n" % (ret[host][key]['changes']['retcode'])
+                    if True == ret[host][key]['changes'].has_key('stderr'):
+                        msg = msg + "STDERR: %s\n" % (ret[host][key]['changes']['stderr'])
+                    if True == ret[host][key]['changes'].has_key('stdout'):
+                        msg = msg + "STDOUT: %s\n" % (ret[host][key]['changes']['stdout'])
+            msg = msg + self.__luxuriant_line("total success: %d\n" % num_s, "green")
+            msg = msg + self.__luxuriant_line("failed: %d\n" % num_f, "red")
+        except Exception as e:
+            log.error("sorry, thy to check result happend error, <%(e)s>.\nret:%(ret)s",
+                      {'e': e, 'ret': ret})
+            return -1
+        log.info(':\n' + msg)
+        return num_f
+
+    def run_state(self, host, fstate, ext_pillar={}, care_result=True):
+        try:
+            log.info("salt " + host + " state.sls " +
+                     fstate + ' pillar=\'' + str(ext_pillar) + '\'')
+            ret = self.salt.cmd(host, 'state.sls', [fstate, 'pillar=' + str(ext_pillar)], 180, 'list')
+        except Exception as e:
+            log.error("try to init host %(host)s happend error: <%(e)s>.",
+                      {'host': host, 'e': e})
+            if True == care_result:
+                raise e
+
+        if 0 != self.result_check(ret, host) and care_result:
+            sys.exit(-1)
+        return True
+
+    def salt_cmd(self, host, cmd):
+        # import pdb
+        # pdb.set_trace()
+        logging.info("Begin to run cmd %s on %s" % (host, cmd))
+
+        try:
+            ret = self.salt.cmd(host, 'cmd.run', [cmd])
+        except Exception:
+            log.error("Remote salt execute failed.")
+        return ret
+
+    def copy_by_state(self, host, src, state_cmd, **kwargs):
+        '''the src must be a dir, and the state.sls 
+        must be the name of the dir name'''
+
+        if not self.slave_exists(host):
+            log.error("the host %(h)s is not held by master, please check.")
+            return False
+
+        if not self.__copy_target(src, "state"):
+            return False
+
+        return self.run_state(host, state_cmd, kwargs, care_result=True)
+
+    def get_master_ip(self, host=None):
+        if not host:
+            ret = cmds.execute("grep '^interface:' /etc/salt/master | awk '{print $2}'").strip()
+            return ret
+        try:
+            ret = self.salt.cmd(host, "grains.item", ["master"])[host]['master']
+        except Exception:
+            log.error("salt happened error when get master ip")
+            return ""
+        return ret
+
+
+mysalt = Mysalt()
diff --git a/vstf/vstf/common/ssh.py b/vstf/vstf/common/ssh.py
new file mode 100755 (executable)
index 0000000..1f7eddc
--- /dev/null
@@ -0,0 +1,230 @@
+'''
+Created on 2015-7-23
+
+@author: y00228926
+'''
+import os
+import logging
+from stat import S_ISDIR
+import Queue
+import shutil
+import paramiko
+from paramiko.ssh_exception import AuthenticationException
+
+LOG = logging.getLogger(__name__)
+
+
+class SSHClientContext(paramiko.SSHClient):
+    def __init__(self, ip, user, passwd, port=22):
+        self.host = ip
+        self.user = user
+        self.passwd = passwd
+        self.port = port
+        super(SSHClientContext, self).__init__()
+
+    def sync_exec_command(self, cmd):
+        _, stdout, stderr = self.exec_command(cmd)
+        ret = stdout.channel.recv_exit_status()
+        out = stdout.read().strip()
+        err = stderr.read().strip()
+        LOG.info("in %s,%s,return:%s,output:%s:error:%s" % (self.host, cmd, ret, out, err))
+        return ret, out, err
+
+    def connect(self):
+        super(SSHClientContext, self).connect(self.host, self.port, self.user, self.passwd, timeout=10)
+
+    def __enter__(self):
+        self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.close()
+        if exc_type == AuthenticationException:
+            return False
+
+
+class SFTPClientContext(object):
+    def __init__(self, ip, user, passwd, port=22):
+        self.host = ip
+        self.passwd = passwd
+        self.user = user
+        self.port = port
+
+    def connect(self):
+        self.t = paramiko.Transport((self.host, self.port))
+        self.t.connect(username=self.user, password=self.passwd)
+        self.sftp = paramiko.SFTPClient.from_transport(self.t)
+
+    def get(self, remote, local):
+        self.sftp.get(remote, local)
+
+    def put(self, local, remote):
+        self.sftp.put(local, remote)
+
+    def mkdir(self, path):
+        self.sftp.mkdir(path)
+
+    def rmdir(self, path):
+        self.sftp.rmdir(path)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if exc_type == TypeError:
+            return False
+        return False
+
+
+def upload_conf_file(host, user, passwd, src, dst):
+    with SFTPClientContext(host, user, passwd) as ftp:
+        ftp.connect()
+        LOG.info('putting file:%s to %s:%s' % (src, host, dst))
+        ftp.put(src, dst)
+
+
+def upload_dir(host, user, passwd, local_dir, remote_dir):
+    assert remote_dir.startswith('/')
+    assert local_dir != '/'
+    while local_dir.endswith('/'):
+        local_dir = local_dir[:-1]
+    while remote_dir.endswith('/'):
+        remote_dir = remote_dir[:-1]
+    remote_dir = os.path.join(remote_dir, os.path.basename(local_dir))
+    ret, _, _ = run_cmd(host, user, passwd, "sudo rm -rf %s" % remote_dir)
+    if ret != 0 and ret != 1:
+        LOG.error("somehow failed in rm -rf %s on host:%s,return:%s" % (remote_dir, host, ret))
+        exit(1)
+    with SFTPClientContext(host, user, passwd) as sftp:
+        sftp.connect()
+        for root, dirs, files in os.walk(local_dir):
+            for filename in files:
+                local_file = os.path.join(root, filename)
+                remote_file = local_file.replace(local_dir, remote_dir)
+                try:
+                    sftp.put(local_file, remote_file)
+                except IOError:
+                    sftp.mkdir(os.path.split(remote_file)[0])
+                    sftp.put(local_file, remote_file)
+                LOG.info("upload %s to remote %s" % (local_file, remote_file))
+            for name in dirs:
+                local_path = os.path.join(root, name)
+                remote_path = local_path.replace(local_dir, remote_dir)
+                try:
+                    sftp.mkdir(remote_path)
+                    LOG.info("mkdir path %s" % remote_path)
+                except Exception, e:
+                    raise
+    return remote_dir
+
+
+def isdir(path, sftp):
+    exists = True
+    is_dir = False
+    file_stat = None
+    try:
+        file_stat = sftp.stat(path).st_mode
+        is_dir = S_ISDIR(file_stat)
+    except IOError:
+        exists = False
+    return exists, is_dir, file_stat
+
+
+def download_file(host, user, passwd, remote_path, local_path):
+    assert not remote_path.endswith('/')
+    remote_file_name = os.path.basename(remote_path)
+    if local_path.endswith('/'):
+        if not os.path.exists(local_path):
+            raise Exception('path:%s not exist.' % local_path)
+        dest = os.path.join(local_path, remote_file_name)
+    else:
+        if os.path.isdir(local_path):
+            dest = os.path.join(local_path, remote_file_name)
+        else:
+            dir_path = os.path.dirname(local_path)
+            if not os.path.exists(dir_path):
+                raise Exception('path:%s not exist' % dir_path)
+            dest = local_path
+    transport = paramiko.Transport((host, 22))
+    transport.connect(username=user, password=passwd)
+    sftp = paramiko.SFTPClient.from_transport(transport)
+    exists, is_dir, st = isdir(remote_path, sftp)
+    if exists and not is_dir:
+        sftp.get(remote_path, dest)
+        os.chmod(dest, st)
+    else:
+        raise Exception('error:cannot find the file or file is dir')
+    return True
+
+
+def download_dir(host, user, passwd, remote_path, local_path):
+    while remote_path.endswith('/'):
+        remote_path = remote_path[:-1]
+    if local_path.endswith('/'):
+        if not os.path.exists(local_path):
+            raise Exception('path:%s not exist.' % local_path)
+        dest_path = os.path.join(local_path, os.path.basename(remote_path))
+    else:
+        if os.path.isdir(local_path):
+            dest_path = os.path.join(local_path, os.path.basename(remote_path))
+        else:
+            dir_name = os.path.dirname(local_path)
+            if os.path.exists(dir_name):
+                dest_path = local_path
+            else:
+                raise Exception('path:%s is not exists' % dir_name)
+    LOG.info("download_dir from host:%s:%s to dest:%s" % (host, remote_path, dest_path))
+    transport = paramiko.Transport((host, 22))
+    transport.connect(username=user, password=passwd)
+    sftp = paramiko.SFTPClient.from_transport(transport)
+    exists, is_dir, _ = isdir(remote_path, sftp)
+    if exists and is_dir:
+        q = Queue.Queue(0)
+        q.put(remote_path)
+        while not q.empty():
+            path = q.get()
+            st = sftp.lstat(path).st_mode
+            relative_path = path[len(remote_path):]
+            if relative_path.startswith('/'): relative_path = relative_path[1:]
+            local = os.path.join(dest_path, relative_path)
+            if os.path.exists(local):
+                shutil.rmtree(local)
+            os.mkdir(local)
+            os.chmod(local, st)
+            file_list = sftp.listdir(path)
+            for item in file_list:
+                fullpath = os.path.join(path, item)
+                _, is_dir, st = isdir(fullpath, sftp)
+                if is_dir:
+                    q.put(fullpath)
+                else:
+                    dest = os.path.join(local, item)
+                    sftp.get(fullpath, dest)
+                    os.chmod(dest, st)
+    else:
+        raise Exception('path:%s:%s not exists or is not a dir' % (host, remote_path))
+    return dest_path
+
+
+def run_cmd(host, user, passwd, cmd):
+    with SSHClientContext(host, user, passwd) as ssh:
+        ssh.connect()
+        ret, stdout, stderr = ssh.sync_exec_command(cmd)
+    return ret, stdout, stderr
+
+
+class SshFileTransfer(object):
+    def __init__(self, ip, user, passwd):
+        self.ip, self.user, self.passwd = ip, user, passwd
+
+    def upload_dir(self, src, dst):
+        return upload_dir(self.ip, self.user, self.passwd, src, dst)
+
+    def download_dir(self, src, dst):
+        download_dir(self.ip, self.user, self.passwd, src, dst)
+
+    def upload_file(self, src, dst):
+        upload_conf_file(self.ip, self.user, self.passwd, src, dst)
+
+    def download_file(self, src, dst):
+        download_file(self.ip, self.user, self.passwd, src, dst)
diff --git a/vstf/vstf/common/test_func.py b/vstf/vstf/common/test_func.py
new file mode 100755 (executable)
index 0000000..9b1d24f
--- /dev/null
@@ -0,0 +1,14 @@
+from vstf.common import cliutil as util
+
+
+@util.arg("--test",
+          dest="test",
+          default="",
+          help="a params of test-xx")
+@util.arg("--xx",
+          dest="xx",
+          default="",
+          help="a params of test-xx")
+def do_test_xx(args):
+    """this is a help doc"""
+    print "run test01 " + args.test + args.xx
\ No newline at end of file
diff --git a/vstf/vstf/common/unix.py b/vstf/vstf/common/unix.py
new file mode 100755 (executable)
index 0000000..f74944e
--- /dev/null
@@ -0,0 +1,53 @@
+import os
+import socket
+from vstf.common import constants
+from vstf.common import message
+
+
+class UdpServer(object):
+    def __init__(self):
+        super(UdpServer, self).__init__()
+        try:
+            os.unlink(constants.sockaddr)
+        except OSError:
+            if os.path.exists(constants.sockaddr):
+                raise Exception("socket not found %s" % constants.sockaddr)    
+        self.conn=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)    
+    
+    def listen(self,backlog=5):
+        self.conn.listen(backlog)
+        
+    def accept(self):
+        return self.conn.accept()
+    
+    def bind(self, addr=constants.sockaddr):
+        return self.conn.bind(addr)
+       
+#     def send(self, data, addr):
+#         return message.sendto(self.conn.sendto, data, addr)
+        
+#     def recv(self, size=constants.buff_size):
+#         return message.recv(self.conn.recvfrom)
+    
+    def close(self):
+        self.conn.close()
+
+
+class UdpClient(object):
+    def __init__(self):
+        super(UdpClient, self).__init__()
+        if not os.path.exists(constants.sockaddr):
+            raise Exception("socket not found %s" % constants.sockaddr)    
+        self.conn=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+     
+    def connect(self, addr=constants.sockaddr):
+        return self.conn.connect(addr)
+       
+    def send(self, data):
+        message.send(self.conn.send, data)
+        
+    def recv(self):
+        return message.recv(self.conn.recv)
+    
+    def close(self):
+        self.conn.close()
\ No newline at end of file
diff --git a/vstf/vstf/common/utils.py b/vstf/vstf/common/utils.py
new file mode 100755 (executable)
index 0000000..542eaa8
--- /dev/null
@@ -0,0 +1,247 @@
+import re
+import logging
+import subprocess
+import random
+import os
+import signal
+import time
+from StringIO import StringIO
+
+LOG = logging.getLogger(__name__)
+
+
+def info():
+    def _deco(func):
+        def __deco(*args, **kwargs):
+            if "shell" in kwargs and not kwargs["shell"]:
+                LOG.info(' '.join(args[0]))
+            else:
+                LOG.info(args[0])
+            return func(*args, **kwargs)
+        return __deco
+    return _deco
+
+
+@info()
+def call(cmd, shell=False):
+    ret = subprocess.call(cmd, shell=shell)
+    if ret != 0:
+        LOG.info("warning: %s not success.", cmd)
+
+
+@info()
+def check_call(cmd, shell=False):
+    subprocess.check_call(cmd, shell=shell)
+
+
+@info()
+def check_output(cmd, shell=False):
+    return subprocess.check_output(cmd, shell=shell)
+
+
+@info()
+def my_popen(cmd, shell=False, stdout=None, stderr=None):
+    return subprocess.Popen(cmd, shell=shell, stdout=stdout, stderr=stderr)
+
+
+def ping(ip):
+    cmd = "ping -w2 -c1 %s" % ip
+    p = my_popen(cmd, shell=True)
+    return 0 == p.wait()
+
+
+def get_device_name(bdf):
+    path = '/sys/bus/pci/devices/0000:%s/net/' % bdf
+    path1 = '/sys/bus/pci/devices/0000:%s/virtio*/net/' % bdf
+    if os.path.exists(path):
+        device = check_output("ls " + path, shell=True).strip()
+        return device
+    else:  # virtio driver
+        try:
+            device = check_output("ls " + path1, shell=True).strip()
+            return device
+        except Exception:
+            return None
+
+
+def my_sleep(delay):
+    LOG.info('sleep %s' % delay)
+    time.sleep(delay)
+
+
+def my_mkdir(filepath):
+    try:
+        LOG.info("mkdir -p %s" % filepath)
+        os.makedirs(filepath)
+    except OSError, e:
+        if e.errno == 17:
+            LOG.info("! %s already exists" % filepath)
+        else:
+            raise
+
+
+def get_eth_by_bdf(bdf):
+    bdf = bdf.replace(' ', '')
+    path = '/sys/bus/pci/devices/0000:%s/net/' % bdf
+    if os.path.exists(path):
+        device = check_output("ls " + path, shell=True).strip()
+    else:
+        raise Exception("cann't get device name of bdf:%s" % bdf)
+    return device
+
+
+def check_and_kill(process):
+    cmd = "ps -ef | grep -v grep | awk '{print $8}' | grep -w %s | wc -l" % process
+    out = check_output(cmd, shell=True)
+    if int(out):
+        check_call(['killall', process])
+
+
+def list_mods():
+    return check_output("lsmod | sed 1,1d | awk '{print $1}'", shell=True).split()
+
+
+def check_and_rmmod(mod):
+    if mod in list_mods():
+        check_call(['rmmod', mod])
+
+
+def kill_by_name(process):
+    out = check_output(['ps', '-A'])
+    for line in out.splitlines():
+        values = line.split()
+        pid, name = values[0], values[3]
+        if process == name:
+            pid = int(pid)
+            os.kill(pid, signal.SIGKILL)
+            LOG.info("os.kill(%s)" % pid)
+
+
+def ns_cmd(ns, cmd):
+    netns_exec_str = "ip netns exec %s "
+    if ns in (None, 'null', 'None', 'none'):
+        pass
+    else:
+        cmd = (netns_exec_str % ns) + cmd
+    return cmd
+
+
+def randomMAC():
+    mac = [0x00, 0x16, 0x3e,
+           random.randint(0x00, 0x7f),
+           random.randint(0x00, 0xff),
+           random.randint(0x00, 0xff)]
+    return ':'.join(map(lambda x: "%02x" % x, mac))
+
+
+class IPCommandHelper(object):
+    def __init__(self, ns=None):
+        self.devices = []
+        self.macs = []
+        self.device_mac_map = {}
+        self.mac_device_map = {}
+        self.bdf_device_map = {}
+        self.device_bdf_map = {}
+        self.mac_bdf_map = {}
+        self.bdf_mac_map = {}
+        cmd = "ip link"
+        if ns:
+            cmd = "ip netns exec %s " % ns + cmd
+        buf = check_output(cmd, shell=True)
+        sio = StringIO(buf)
+        for line in sio:
+            m = re.match(r'^\d+:(.*):.*', line)
+            if m and m.group(1).strip() != "lo":
+                device = m.group(1).strip()
+                self.devices.append(device)
+                mac = self._get_mac(ns, device)
+                self.macs.append(mac)
+        for device, mac in zip(self.devices, self.macs):
+            self.device_mac_map[device] = mac
+            self.mac_device_map[mac] = device
+
+        cmd = "ethtool -i %s"
+        if ns:
+            cmd = "ip netns exec %s " % ns + cmd
+        for device in self.devices:
+            buf = check_output(cmd % device, shell=True)
+            bdfs = re.findall(r'^bus-info: \d{4}:(\d{2}:\d{2}\.\d*)$', buf, re.MULTILINE)
+            if bdfs:
+                self.bdf_device_map[bdfs[0]] = device
+                self.device_bdf_map[device] = bdfs[0]
+                mac = self.device_mac_map[device]
+                self.mac_bdf_map[mac] = bdfs[0]
+                self.bdf_mac_map[bdfs[0]] = mac
+
+    @staticmethod
+    def _get_mac(ns, device):
+        cmd = "ip addr show dev %s" % device
+        if ns:
+            cmd = "ip netns exec %s " % ns + cmd
+        buf = check_output(cmd, shell=True)
+        macs = re.compile(r"[A-F0-9]{2}(?::[A-F0-9]{2}){5}", re.IGNORECASE | re.MULTILINE)
+        for mac in macs.findall(buf):
+            if mac.lower() not in ('00:00:00:00:00:00', 'ff:ff:ff:ff:ff:ff'):
+                return mac
+        return None
+
+    def get_device_verbose(self, identity):
+        if identity in self.device_mac_map:
+            device = identity
+        elif identity in self.bdf_device_map:
+            device = self.bdf_device_map[identity]
+        elif identity in self.mac_device_map:
+            device = self.mac_device_map[identity]
+        else:
+            raise Exception("cann't find the device by identity:%s" % identity)
+        detail = {
+            'bdf': self.device_bdf_map[device] if device in self.device_bdf_map else None,
+            'iface': device,
+            'mac': self.device_mac_map[device] if device in self.device_mac_map else None,
+        }
+        return detail
+
+
+class AttrDict(dict):
+    """A dictionary with attribute-style access. It maps attribute access to
+    the real dictionary.  """
+
+    def __init__(self, init={}):
+        dict.__init__(self, init)
+
+    def __getstate__(self):
+        return self.__dict__.items()
+
+    def __setstate__(self, items):
+        for key, val in items:
+            self.__dict__[key] = val
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))
+
+    def __setitem__(self, key, value):
+        return super(AttrDict, self).__setitem__(key, value)
+
+    def __getitem__(self, name):
+        return super(AttrDict, self).__getitem__(name)
+
+    def __delitem__(self, name):
+        return super(AttrDict, self).__delitem__(name)
+
+    __getattr__ = __getitem__
+    __setattr__ = __setitem__
+
+    def copy(self):
+        ch = AttrDict(self)
+        return ch
+
+
+if __name__ == "__main__":
+    ipcmd = IPCommandHelper()
+    print ipcmd.device_mac_map
+    print ipcmd.mac_device_map
+    print ipcmd.bdf_device_map
+    print ipcmd.device_bdf_map
+    print ipcmd.mac_bdf_map
+    print ipcmd.bdf_mac_map
+    print ipcmd.get_device_verbose("tap0")
diff --git a/vstf/vstf/common/vstfcli.py b/vstf/vstf/common/vstfcli.py
new file mode 100755 (executable)
index 0000000..9dc9977
--- /dev/null
@@ -0,0 +1,62 @@
+import argparse
+import sys
+
+
+class VstfHelpFormatter(argparse.HelpFormatter):
+    def start_section(self, heading):
+        # Title-case the headings
+        heading = '%s%s' % (heading[0].upper(), heading[1:])
+        super(VstfHelpFormatter, self).start_section(heading)
+
+
+class VstfParser(argparse.ArgumentParser):
+    def __init__(self,
+                 prog='vstf',
+                 description="",
+                 epilog='',
+                 add_help=True,
+                 formatter_class=VstfHelpFormatter):
+
+        super(VstfParser, self).__init__(
+            prog=prog,
+            description=description,
+            epilog=epilog,
+            add_help=add_help,
+            formatter_class=formatter_class)
+        self.subcommands = {}
+
+    def _find_actions(self, subparsers, actions_module):
+        for attr in (a for a in dir(actions_module) if a.startswith('do_')):
+            command = attr[3:].replace('_', '-')
+            callback = getattr(actions_module, attr)
+            desc = callback.__doc__ or ''
+            action_help = desc.strip()
+            arguments = getattr(callback, 'arguments', [])
+            subparser = subparsers.add_parser(command,
+                                              help=action_help,
+                                              description=desc,
+                                              add_help=False,
+                                              formatter_class=VstfHelpFormatter)
+            subparser.add_argument('-h', '--help',
+                                   action='help',
+                                   help=argparse.SUPPRESS)
+            self.subcommands[command] = subparser
+            for (args, kwargs) in arguments:
+                subparser.add_argument(*args, **kwargs)
+            subparser.set_defaults(func=callback)
+
+    def set_subcommand_parser(self, target, metavar="<subcommand>"):
+        subparsers = self.add_subparsers(metavar=metavar)
+        self._find_actions(subparsers, target)
+        return subparsers
+
+    def set_parser_to_subcommand(self, subparser, target):
+        self._find_actions(subparser, target)
+
+
+if __name__ == "__main__":
+    from vstf.common import test_func
+    parser = VstfParser(prog="vstf", description="test parser")
+    parser.set_subcommand_parser(test_func)
+    args = parser.parse_args(sys.argv[1:])
+    args.func(args)
diff --git a/vstf/vstf/controller/__init__.py b/vstf/vstf/controller/__init__.py
new file mode 100755 (executable)
index 0000000..4dc8a6a
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
+
diff --git a/vstf/vstf/controller/api_server.py b/vstf/vstf/controller/api_server.py
new file mode 100755 (executable)
index 0000000..d354701
--- /dev/null
@@ -0,0 +1,403 @@
+import uuid
+import time
+import os
+import sys
+import logging
+import signal
+import json
+
+from vstf.common import unix, message, cliutil, excepts
+from vstf.common.vstfcli import VstfParser
+from vstf.common.log import setup_logging
+from vstf.common import daemon
+from vstf.rpc_frame_work import rpc_producer
+from vstf.controller.fabricant import Fabricant
+from vstf.agent.env.basic.commandline import CommandLine
+from vstf.controller.env_build.env_build import EnvBuildApi as Builder
+from vstf.controller.env_build.env_collect import EnvCollectApi
+from vstf.controller.database.dbinterface import DbManage
+import vstf.controller.sw_perf.performance as pf
+from vstf.controller.settings.tester_settings import TesterSettings
+from vstf.controller.settings.device_settings import DeviceSettings
+from vstf.controller.settings.flows_settings import FlowsSettings
+from vstf.controller.settings.mail_settings import MailSettings
+from vstf.controller.settings.tool_settings import ToolSettings
+from vstf.controller.settings.perf_settings import PerfSettings
+from vstf.controller.sw_perf.perf_provider import PerfProvider
+from vstf.controller.sw_perf.flow_producer import FlowsProducer
+import vstf.controller.reporters.reporter as rp
+import vstf.common.constants as cst
+import vstf.common.check as chk
+
+LOG = logging.getLogger(__name__)
+cmd = CommandLine()
+
+
+class OpsChains(object):
+    def __init__(self, monitor, port):
+        """The ops chains will setup the proxy to rabbitmq
+        and setup a thread to watch the queues of rabbitmq
+        
+        """
+        super(OpsChains, self).__init__()
+        if not os.path.exists(cst.VSTFCPATH):
+            os.mkdir(cst.VSTFCPATH)
+
+        LOG.info("VSTF Manager start to listen to %s", monitor)
+        self.chanl = rpc_producer.Server(host=monitor, port=port)
+        self.dbconn = DbManage()
+        self.collection = EnvCollectApi(self.chanl)
+
+    def list_devs(self, **kwargs):
+        target = kwargs.get('host')
+        if not target:
+            respond = "the target is empty, not support now."
+        else:
+            respond = self.chanl.call(self.chanl.make_msg("list_nic_devices"), target)
+        return respond
+
+    def src_install(self, host, config_file):
+        if not os.path.exists(config_file):
+            raise Exception("Can not found the config file.")
+        cfg = json.load(open(config_file))
+        msg = self.chanl.make_msg("src_install", cfg=cfg)
+        return self.chanl.call(msg, host, timeout=1000)
+
+    def create_images(self, host, config_file):
+        if not os.path.exists(config_file):
+            raise Exception("Can not found the config file.")
+        cfg = json.load(open(config_file))
+        msg = self.chanl.make_msg("create_images", cfg=cfg)
+        return self.chanl.call(msg, host, timeout=1000)
+
+    def clean_images(self, host, config_file):
+        if not os.path.exists(config_file):
+            raise Exception("Can not found the config file.")
+        cfg = json.load(open(config_file))
+        msg = self.chanl.make_msg("clean_images", cfg=cfg)
+        return self.chanl.call(msg, host, timeout=1000)
+
+    def apply_model(self, host, model=None, config_file=None):
+        if config_file is None:
+            config_file = "/etc/vstf/env/%s.json" % model
+        if not os.path.exists(config_file):
+            raise Exception("Can not found the config file.")
+        env = Builder(self.chanl, config_file)
+        ret = env.build()
+        return ret
+
+    def disapply_model(self, host, model=None, config_file=None):
+        if config_file is None:
+            config_file = "/etc/vstf/env/%s.json" % model
+        if not os.path.exists(config_file):
+            raise Exception("Can not found the config file.")
+        env = Builder(self.chanl, config_file)
+        ret = env.clean()
+        return ret
+
+    def list_tasks(self):
+        ret = self.dbconn.query_tasks()
+        head = [["Task ID", "Task Name", "Task Date", "Task Remarks"]]
+        if ret:
+            ret = head + ret
+        return ret
+
+    def affctl_list(self, host):
+        if not host:
+            return "Need input the host"
+        return Fabricant(host, self.chanl).affctl_list()
+
+    def _create_task(self, scenario):
+        taskid = self.dbconn.create_task(str(uuid.uuid4()), time.strftime(cst.TIME_FORMAT),
+                                         desc=scenario + "Test")
+        LOG.info("new Task id:%s" % taskid)
+        if -1 == taskid:
+            raise Exception("DB create task failed.")
+
+        device = DeviceSettings().settings
+        hosts = [device["host"], device["tester"]]
+        for host in hosts:
+            LOG.info(host)
+
+            devs = host["devs"][0]
+            keys = ["bdf", "iface", "mac"]
+            key = devs.keys()[0]
+            if key in keys:
+                name = devs[key]
+            else:
+                raise Exception("error devs :%s", devs)
+
+            query = Fabricant(host["agent"], self.chanl)
+            nic_info = query.get_device_detail(identity=name)
+
+            LOG.info(nic_info)
+
+            os_info, cpu_info, mem_info, hw_info = self.collection.collect_host_info(host["agent"])
+            LOG.info(os_info)
+            LOG.info(cpu_info)
+            LOG.info(mem_info)
+            LOG.info(hw_info)
+
+            self.dbconn.add_host_2task(taskid,
+                                       host["agent"],
+                                       json.dumps(hw_info[cst.HW_INFO]),
+                                       json.dumps(cpu_info[cst.CPU_INFO]),
+                                       json.dumps(mem_info[cst.MEMORY_INFO]),
+                                       nic_info["desc"],
+                                       json.dumps(os_info[cst.OS_INFO]))
+
+        self.dbconn.add_extent_2task(taskid, "CETH", "driver", "version 2.0")
+        self.dbconn.add_extent_2task(taskid, "EVS", "switch", "version 3.0")
+        return taskid
+
+    def settings(self, mail=False, perf=False):
+        LOG.info("mail:%s, perf:%s" % (mail, perf))
+        if mail:
+            MailSettings().input()
+        if perf:
+            PerfSettings().input()
+
+    def report(self, rpath='./', mail_off=False, taskid=-1):
+        report = rp.Report(self.dbconn, rpath)
+        if taskid == -1:
+            taskid = self.dbconn.get_last_taskid()
+        report.report(taskid, mail_off)
+        info_str = "do report over"
+        return info_str
+
+    def run_perf_cmd(self, case, rpath='./', affctl=False, build_on=False, save_on=False, report_on=False, mail_on=False):
+        LOG.info(case)
+        LOG.info("build_on:%s report_on:%s mail_on:%s" % (build_on, report_on, mail_on))
+        casetag = case['case']
+        tool = case['tool']
+        protocol = case['protocol']
+        profile = case['profile']
+        ttype = case['type']
+        sizes = case['sizes']
+
+        ret, ret_str = chk.check_case_params(protocol, ttype, tool)
+        if not ret:
+            return ret_str
+
+        scenario = self.dbconn.query_scenario(casetag)
+        LOG.info(scenario)
+        if not scenario:
+            LOG.warn("not support the case:%s", casetag)
+            return
+
+        config_file = os.path.join("/etc/vstf/env", scenario + ".json")
+
+        LOG.info(config_file)
+        env = Builder(self.chanl, config_file)
+        if build_on:
+            env.build()
+        flows_settings = FlowsSettings()
+        tool_settings = ToolSettings()
+        tester_settings = TesterSettings()
+        flow_producer = FlowsProducer(self.chanl, flows_settings)
+        provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+
+        perf = pf.Performance(self.chanl, provider)
+        flow_producer.create(scenario, casetag)
+        result = perf.run(tool, protocol, ttype, sizes, affctl)
+        LOG.info(flows_settings.settings)
+        LOG.info(result)
+        if save_on:
+            taskid = self._create_task(scenario)
+            testid = self.dbconn.add_test_2task(taskid, casetag, protocol, profile, ttype, tool)
+            LOG.info(testid)
+            self.dbconn.add_data_2test(testid, result)
+            if report_on:
+                self.report(rpath, not mail_on, taskid)
+        return result
+
+    def run_perf_file(self, rpath='./', affctl=False, report_on=True, mail_on=True):
+        perf_settings = PerfSettings()
+        flows_settings = FlowsSettings()
+        tool_settings = ToolSettings()
+        tester_settings = TesterSettings()
+        flow_producer = FlowsProducer(self.chanl, flows_settings)
+        provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+        perf = pf.Performance(self.chanl, provider)
+        tests = perf_settings.settings
+
+        for scenario, cases in tests.items():
+            LOG.info(scenario)
+            if not cases:
+                continue
+
+            config_file = os.path.join("/etc/vstf/env", scenario + ".json")
+
+            LOG.info(config_file)
+            env = Builder(self.chanl, config_file)
+            env.build()
+
+            taskid = self._create_task(scenario)
+
+            for case in cases:
+                LOG.info(case)
+                casetag = case['case']
+                tool = case['tool']
+                protocol = case['protocol']
+                profile = case['profile']
+                ttype = case['type']
+                sizes = case['sizes']
+
+                ret, ret_str = chk.check_case_params(protocol, ttype, tool)
+                if not ret:
+                    LOG.warn(ret_str)
+                    continue
+
+                flow_producer.create(scenario, casetag)
+                result = perf.run(tool, protocol, ttype, sizes, affctl)
+                LOG.info(result)
+
+                testid = self.dbconn.add_test_2task(taskid, casetag, protocol, profile, ttype, tool)
+                LOG.info(testid)
+
+                self.dbconn.add_data_2test(testid, result)
+
+            if report_on:
+                self.report(rpath, not mail_on, taskid)
+
+        info_str = "do batch perf test successfully"
+        return info_str
+
+    def collect_host_info(self, target):
+        if self.collection is not None:
+            return self.collection.collect_host_info(target)
+        else:
+            return "collection is None"
+
+
+class Manager(daemon.Daemon):
+    def __init__(self):
+        """
+        The manager will create a socket for vstfadm.
+        also the manager own a ops chains
+        """
+        super(Manager, self).__init__(cst.vstf_pid)
+        # the connection of socket
+        self.conn = None
+        # the operations of manager
+        self.ops = None
+        # record the daemon run flag
+        self.run_flag = True
+
+    def deal_unknown_obj(self, obj):
+        return "unknown response %s" % obj
+
+    def run(self):
+        signal.signal(signal.SIGTERM, self.daemon_die)
+        # setup the socket server for communicating with vstfadm
+        try:
+            self.conn = unix.UdpServer()
+            self.conn.bind()
+            self.conn.listen()
+        except Exception as e:
+            raise e
+
+        # accept the connection of vstfadm and recv the command
+        # run the command from vstfadm and return the response
+        while self.run_flag:
+            conn, addr = self.conn.accept()
+            LOG.debug("accept the conn: %(conn)s", {'conn': conn})
+
+            # recv the msg until the conn break.
+
+            while True:
+                try:
+                    data = message.recv(conn.recv)
+                    LOG.debug("Manager recv the msg: %(msg)s", {'msg': data})
+                    msg = message.decode(data)
+                    body = message.get_body(msg)
+                    context = message.get_context(msg)
+                except RuntimeError:
+                    LOG.debug("manage catch the connection close!")
+                    break
+                except Exception as e:
+                    LOG.error("Manager recv message from socket failed.")
+                    self.daemon_die()
+                    raise e
+
+                try:
+                    func = getattr(self.ops, body.get('method'))
+                    LOG.info("Call function:%s, args:%s",
+                             func.__name__, body.get('args'))
+                    response = func(**body.get('args'))
+                    LOG.info("response: %s", response)
+                except excepts.UnsolvableExit as e:
+                    msg = "The manager opps, exit now"
+                    LOG.error(msg)
+                    # the manager has no need to be continue, just return
+                    # this msg and exit normal
+                    self.daemon_die()
+                    raise e
+                except Exception as e:
+                    # here just the function failed no need exit, just return the msg
+                    msg = "Run function failed. [ %s ]" % (e)
+                    response = msg
+                    LOG.error(msg)
+                try:
+                    response = message.add_context(response, **context)
+                    LOG.debug("Manager send the response: <%(r)s", {'r': response})
+                    message.send(conn.send, message.encode(response))
+                except Exception as e:
+                    self.daemon_die()
+                    raise e
+            # close the connection when conn down
+            conn.close()
+
+    def daemon_die(self, signum, frame):
+        """overwrite daemon.Daemon.daemon_die(self)"""
+        LOG.info("manage catch the signal %s to exit." % signum)
+        if self.conn:
+            # we can not close the conn direct, just tell manager to stop accept
+            self.run_flag = False
+
+        if self.ops:
+            # stop the ops's proxy
+            # maybe happen AttributeError: 'BlockingConnection' object has no attribute 'disconnect'
+            # this a know bug in pika. fix in 0.9.14 release
+            try:
+                self.ops.chanl.close()
+            except AttributeError:
+                LOG.warning("The connection close happens attribute error")
+
+    def start_manage(self, monitor="localhost", port=5672):
+        try:
+            # create manager's ops chains here will create a proxy to rabbitmq
+            self.ops = OpsChains(monitor, port)
+        except Exception as e:
+            raise e
+        self.start()
+
+    def stop_manage(self):
+        self.stop()
+
+
+@cliutil.arg("--monitor",
+             dest="monitor",
+             default="localhost",
+             action="store",
+             help="which ip to be monitored")
+@cliutil.arg("--port",
+             dest="port",
+             default="5672",
+             action="store",
+             help="rabbitmq conn server")
+def do_start(args):
+    Manager().start_manage(args.monitor, args.port)
+
+
+def do_stop(args):
+    Manager().stop_manage()
+
+
+def main():
+    """this is for vstfctl"""
+    setup_logging(level=logging.INFO, log_file="/var/log/vstf/vstf-manager.log", clevel=logging.INFO)
+    parser = VstfParser(prog="vstf-manager", description="vstf manager command line")
+    parser.set_subcommand_parser(target=sys.modules[__name__])
+    args = parser.parse_args()
+    args.func(args)
diff --git a/vstf/vstf/controller/database/__init__.py b/vstf/vstf/controller/database/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/database/constants.py b/vstf/vstf/controller/database/constants.py
new file mode 100755 (executable)
index 0000000..d1aef39
--- /dev/null
@@ -0,0 +1,53 @@
+SCENARIO_NAME_LEN = 16
+DESC_LEN = 256
+FIGURE_PATH_LEN = 128
+
+CASE_TAG_LEN = 16
+CASE_NAME_LEN = 128
+DIRECTION_LEN = 128
+CONF_LEN = 256
+
+HOST_NAME_LEN = 32
+
+CPU_INFO_LEN = 1024
+
+NORMAL_VAR_LEN = 32
+NORMAL_VAR_LEN1 = 64
+
+PROTOCOL_LEN = 16
+PROVIDER_LEN = 16
+
+TOOLS_LEN = 32
+TYPE_LEN = 16
+
+EXT_INFO_LEN = 256
+DBPATH = "/opt/vstf/vstf.db"
+# CaseTag, ScenarioName, CaseName, FigurePath, Description, Direction, Configure
+CASE_INFO_LIST = [
+    ['Ti-1', 'Ti', 'Ti_VM_RX_Tester-VM', 'res/', ' ', 'Tester->VM', 'tx', 'w/,wo VLAN'],
+    ['Ti-2', 'Ti', 'Ti_VM_TX_VM-Tester', 'res/', ' ', 'VM->Tester', 'rx', 'w/,wo VLAN'],
+    ['Ti-3', 'Ti', 'Ti_VM_RXTX_VM-Tester', 'res/', ' ', 'Tester<->VM', 'rxtx', 'w/,wo VLAN'],
+    ['Ti-4', 'Ti', 'Ti_VM_RX_Tester-VM_VXLAN', 'res/', ' ', 'Tester->VM', 'tx', 'VXLAN'],
+    ['Ti-5', 'Ti', 'Ti_VM_TX_VM-Tester_VXLAN', 'res/', ' ', 'VM->Tester', 'rx', 'VXLAN'],
+    ['Ti-6', 'Ti', 'Ti_VM_RXTX_VM-Tester_VXLAN', 'res/', ' ', 'Tester<->VM', 'rxtx', 'VXLAN'],
+    ['Tu-1', 'Tu', 'Tu_VM_RX_VM-VM', 'res/', ' ', 'Tester->VM', 'tx', 'w/,wo VLAN'],
+    ['Tu-2', 'Tu', 'Tu_VM_TX_VM-VM', 'res/', ' ', 'VM->Tester', 'rx', 'w/,wo VLAN'],
+    ['Tu-3', 'Tu', 'Tu_VM_RXTX_VM-VM', 'res/', ' ', 'Tester<->VM', 'rxtx', 'w/,wo VLAN'],
+    ['Tu-4', 'Tu', 'Tu_VM_RX_VM-VM_VXLAN', 'res/', ' ', 'Tester->VM', 'tx', 'VXLAN'],
+    ['Tu-5', 'Tu', 'Tu_VM_TX_VM-VM_VXLAN', 'res/', ' ', 'VM->Tester', 'rx', 'VXLAN'],
+    ['Tu-6', 'Tu', 'Tu_VM_RXTX_VM-VM_VXLAN', 'res/', ' ', 'VM<->Tester', 'rxtx', 'VXLAN'],
+    ['Tn-1', 'Tn', 'Tn_VSW_FWD_Tester-Tester', 'res/', ' ', 'Tester->Tester', 'tx', 'w/,wo VLAN'],
+    ['Tn-2', 'Tn', 'Tn_VSW_FWD-BI_Tester-Tester', 'res/', ' ', 'Tester<->Tester', 'rxtx', 'w/,wo VLAN'],
+    ['Tn-3', 'Tn', 'Tn_VSW_FWD_Tester-Tester_VXLAN', 'res/', ' ', 'Tester->Tester', 'tx', 'VXLAN'],
+    ['Tn-4', 'Tn', 'Tn_VSW_FWD-BI_Tester-Tester_VXLAN', 'res/', ' ', 'Tester<->Tester', 'rxtx', 'VXLAN'],
+    ['Tnv-1', 'Tnv', 'TnV_VSW_FWD_Tester-Tester', 'res/', ' ', 'Tester->Tester', 'tx', 'w/,wo VLAN'],
+    ['Tnv-2', 'Tnv', 'TnV_VSW_FWD-BI_Tester-Tester', 'res/', ' ', 'Tester<->Tester', 'rxtx', 'w/,wo VLAN'],
+    ['Tnv-3', 'Tnv', 'TnV_VSW_FWD_Tester-Tester_VXLAN', 'res/', ' ', 'Tester->Tester', 'tx', 'VXLAN'],
+    ['Tnv-4', 'Tnv', 'TnV_VSW_FWD-BI_Tester-Tester_VXLAN', 'res/', ' ', 'Tester<->Tester', 'rxtx', 'VXLAN']
+]
+SCENARIO_INFO_LIST = [
+    ['Ti', 'res/', ' '],
+    ['Tu', 'res/', ' '],
+    ['Tn', 'res/', ' '],
+    ['Tnv', 'res/', ' '],
+]
diff --git a/vstf/vstf/controller/database/dbinterface.py b/vstf/vstf/controller/database/dbinterface.py
new file mode 100755 (executable)
index 0000000..ae34c86
--- /dev/null
@@ -0,0 +1,567 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-07-29
+# see license for license details
+__version__ = ''' '''
+import os
+import logging
+
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy import and_
+from vstf.controller.database.tables import *
+
+LOG = logging.getLogger(__name__)
+
+"""
+@event.listens_for(Engine, "before_cursor_execute")
+def before_cursor_execute(conn, cursor, statement,
+    parameters, context, executemany):
+    conn.info.setdefault('query_start_time', []).append(time.time())
+    logging.debug("Start Query: %s", statement)
+@event.listens_for(Engine, "after_cursor_execute")
+def after_cursor_execute(conn, cursor, statement,
+    parameters, context, executemany):
+    total = time.time() - conn.info['query_start_time'].pop(-1)
+    logging.debug("Query Complete!")
+    logging.debug("Total Time: %f", total)"""
+
+
+class DbManage(object):
+    def __init__(self, db_name=const.DBPATH):
+        db_exists = os.path.exists(db_name)
+        try:
+            self._engine = create_engine('sqlite:///%s' % db_name, echo=False)
+            db_session = sessionmaker(bind=self._engine)
+            self._session = db_session()
+        except Exception as e:
+            raise e
+
+        # if the db is new , cleate all tables and init static tables
+        if not db_exists:
+            self.create_tables()
+            self.init_tables()
+
+    def __delete__(self):
+        self._engine.close_all()
+
+    def create_tables(self):
+        Base.metadata.create_all(self._engine)
+        self._session.commit()
+
+    def drop_tables(self):
+        Base.metadata.drop_all(self._engine)
+        self._session.commit()
+
+    def init_tables(self):
+        self.init_casetable()
+        self.init_scenario_table()
+        self._session.commit()
+
+    def init_scenario_table(self):
+        items = []
+        for values in const.SCENARIO_INFO_LIST:
+            item = TblScenarioInfo(ScenarioName=values[0],
+                                   FigurePath=values[1],
+                                   Description=values[2])
+            items.append(item)
+        self._session.add_all(items)
+
+    # Single TblCaseInfo API
+    def init_casetable(self):
+        items = []
+        for values in const.CASE_INFO_LIST:
+            item = TblCaseInfo(CaseTag=values[0],
+                               ScenarioName=values[1],
+                               CaseName=values[2],
+                               FigurePath=values[3],
+                               Description=values[4],
+                               Direction=values[5],
+                               Directiontag=values[6],
+                               Configure=values[7])
+            items.append(item)
+        self._session.add_all(items)
+
+    def query_caseinfo(self):
+        query = self._session.query(TblCaseInfo.ScenarioName,
+                                    TblCaseInfo.CaseTag,
+                                    TblCaseInfo.CaseName,
+                                    TblCaseInfo.Direction,
+                                    TblCaseInfo.Configure)
+        return query.all()
+
+    def query_case(self, casetag):
+        query = self._session.query(TblCaseInfo.ScenarioName,
+                                    TblCaseInfo.Directiontag)
+        return query.first()
+
+    # Single TblTaskList API
+    def get_last_taskid(self):
+        query = self._session.query(TblTaskList.TaskID)
+        if query:
+            return query.all()[-1][0]
+        else:
+            return 0
+
+    def query_tasklist(self):
+        query = self._session.query(TblTaskList)
+        return query.all()
+
+    def query_taskdate(self, taskid):
+        query = self._session.query(TblTaskList.Date).filter(and_(
+            TblTaskList.TaskID == taskid))
+        result = ""
+        if query:
+            result += query.first()[0]
+        return result
+
+    def query_taskname(self, taskid):
+        query = self._session.query(TblTaskList.TaskName).filter(and_(
+            TblTaskList.TaskID == taskid))
+        result = ""
+        if query:
+            result += query.first()[0]
+        return result
+
+    def create_task(self, name, date, desc):
+        try:
+            item = TblTaskList(name, date, desc)
+            self._session.add(item)
+            self._session.commit()
+        except Exception:
+            return -1
+
+        return self.get_last_taskid()
+
+    # Single TblHostInfo API
+    def add_host_2task(self, taskid, name, machine, cpu, men, nic, os):
+        """All var except task must be string"""
+        item = TblHostInfo(taskid, name, machine, cpu, men, nic, os)
+
+        self._session.add(item)
+        self._session.commit()
+
+    def query_task_host_list(self, taskid):
+        query = self._session.query(TblHostInfo.HostName,
+                                    TblHostInfo.Server,
+                                    TblHostInfo.CPU,
+                                    TblHostInfo.MEM,
+                                    TblHostInfo.NIC,
+                                    TblHostInfo.OS).filter(
+            TblHostInfo.TaskID == taskid)
+        return query.all()
+
+    # Single TblTestList API
+    def get_last_testid(self):
+        query = self._session.query(TblTestList.TestID)
+        if query:
+            return query.all()[-1][0]
+        else:
+            return 0
+
+    def add_test_2task(self, task, case, protocol, provider, typ, tool):
+        try:
+            item = TblTestList(task, case, protocol, provider, typ, tool)
+            self._session.add(item)
+            self._session.commit()
+        except Exception:
+            return -1
+
+        return self.get_last_testid()
+
+    def get_test_type(self, testid):
+        query = self._session.query(TblTestList.Type).filter(
+            TblTestList.TestID == testid)
+        return query.first()
+
+    def add_extent_2task(self, task, name, content, description):
+        item = TblEXTInfo(task, name, content, description)
+        self._session.add(item)
+        self._session.commit()
+
+    def add_data_2test(self, testid, data):
+        """
+        :data example {'64':{
+                            'AvgFrameSize':0
+                            'OfferedLoad':0
+                            'PercentLoss':0
+                            'Bandwidth':0
+                            'MinimumLatency':0
+                            'MaximumLatency':0
+                            'AverageLatency':0
+                            'TxFrameCount':0
+                            'RxFrameCount':0
+                            'Duration':0
+                            'CPU':0
+                            'MppspGhz':0
+                            }}
+        """
+        ptype = self.get_test_type(testid)
+        instance_map = {
+            'throughput': TblThroughput,
+            'frameloss': TblFrameloss,
+            'latency': TblLatency
+        }
+
+        if ptype and ptype[0] not in instance_map:
+            print "cant find this test(id=%d)" % (testid)
+            return False
+
+        test_table_instance = instance_map[ptype[0]]
+        for pktlen in data.iterkeys():
+            args = data.get(pktlen)
+            query = self._session.query(test_table_instance).filter(and_(
+                test_table_instance.TestID == testid,
+                test_table_instance.AvgFrameSize == pktlen))
+            if query.all():
+                data_dict = {}
+                for key, value in data.items():
+                    if key in test_table_instance.__dict__:
+                        data_dict[test_table_instance.__dict__[key]] = value
+                query.update(data_dict)
+            else:
+                print args
+                tester = test_table_instance(testid, pktlen, **args)
+                self._session.add(tester)
+        self._session.commit()
+
+    def query_tasks(self):
+        result = []
+        ret = self._session.query(TblTaskList)
+        if ret:
+            for tmp in ret.all():
+                result.append([tmp.TaskID, tmp.TaskName, tmp.Date, tmp.EXTInfo])
+        return result
+
+    def query_all_task_id(self):
+        query = self._session.query(TblTaskList.TaskID)
+        if query:
+            return query.all()
+        else:
+            return []
+
+    def get_caseinfo(self):
+        query = self._session.query(TblCaseInfo.ScenarioName,
+                                    TblCaseInfo.CaseTag,
+                                    TblCaseInfo.CaseName,
+                                    TblCaseInfo.Direction,
+                                    TblCaseInfo.Configure)
+        return query.all()
+
+    def query_scenario(self, casetag):
+        query = self._session.query(TblCaseInfo.ScenarioName).filter(TblCaseInfo.CaseTag == casetag)
+        ret = ""
+        if query and query.first():
+            ret = query.first()[0]
+        return ret
+
+    def query_casefigure(self, casetag, tools):
+        query = self._session.query(TblCaseInfo.FigurePath).filter(and_(
+            TblCaseInfo.CaseTag == casetag))
+        result = ""
+        if query:
+            result += query.first()[0]
+        print tools, casetag
+        result += tools + '/' + casetag + '.jpg'
+        return result
+
+    def query_casename(self, casetag):
+        query = self._session.query(TblCaseInfo.CaseName).filter(and_(
+            TblCaseInfo.CaseTag == casetag))
+        result = ""
+        if query:
+            result += query.first()[0]
+        return result
+
+    # Single TblScenarioInfo API
+
+    def query_caselist(self, taskid, scenario):
+        query = self._session.query(TblTestList.CaseTag).filter(and_(
+            TblTestList.CaseTag == TblCaseInfo.CaseTag,
+            TblCaseInfo.ScenarioName == scenario,
+            TblTestList.TaskID == taskid)).group_by(TblCaseInfo.CaseTag)
+        return query.all()
+
+    def query_casetool(self, taskid, casetag, provider, ptype):
+        query = self._session.query(TblTestList.Tools).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider,
+            TblTestList.Type == ptype))
+        return query.all()
+
+    def query_casetools(self, taskid, casetag):
+        query = self._session.query(TblTestList.Tools).filter(and_(
+            TblTestList.CaseTag == casetag,
+            TblTestList.TaskID == taskid)).group_by(TblTestList.Tools)
+        return query.all()
+
+    def query_scenariolist(self, taskid):
+        query = self._session.query(TblCaseInfo.ScenarioName).filter(and_(
+            TblTestList.CaseTag == TblCaseInfo.CaseTag,
+            TblTestList.TaskID == taskid)).group_by(TblCaseInfo.ScenarioName)
+        return query.all()
+
+    def query_throughput_load(self, taskid, casetag, provider):
+        ptype = 'throughput'
+        query = self._session.query(TblThroughput.AvgFrameSize, TblThroughput.OfferedLoad).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_throughput_bandwidth(self, taskid, casetag, provider):
+        ptype = 'throughput'
+        query = self._session.query(TblThroughput.AvgFrameSize, TblThroughput.Bandwidth).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_throughput_table(self, taskid, casetag, provider):
+        ptype = 'throughput'
+        query = self._session.query(TblThroughput.AvgFrameSize,
+                                    TblThroughput.Bandwidth,
+                                    TblThroughput.OfferedLoad,
+                                    TblThroughput.CPU,
+                                    TblThroughput.MppspGhz,
+                                    TblThroughput.MinimumLatency,
+                                    TblThroughput.MaximumLatency,
+                                    TblThroughput.AverageLatency,
+                                    ).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_throughput_simpletable(self, taskid, casetag, provider):
+        ptype = 'throughput'
+        query = self._session.query(TblThroughput.AvgFrameSize,
+                                    TblThroughput.Bandwidth,
+                                    TblThroughput.OfferedLoad,
+                                    TblThroughput.CPU,
+                                    TblThroughput.MppspGhz,
+                                    TblThroughput.AverageLatency,
+                                    ).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_throughput_avg(self, taskid, casetag, provider):
+        ptype = 'throughput'
+        query = self._session.query(TblThroughput.AvgFrameSize, TblThroughput.AverageLatency).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_frameloss_bandwidth(self, taskid, casetag, provider):
+        ptype = 'frameloss'
+        query = self._session.query(TblFrameloss.AvgFrameSize, TblFrameloss.Bandwidth).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_frameloss_load(self, taskid, casetag, provider):
+        ptype = 'frameloss'
+        query = self._session.query(TblFrameloss.AvgFrameSize, TblFrameloss.OfferedLoad).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_frameloss_table(self, taskid, casetag, provider):
+        ptype = 'frameloss'
+        query = self._session.query(TblFrameloss.AvgFrameSize,
+                                    TblFrameloss.Bandwidth,
+                                    TblFrameloss.OfferedLoad,
+                                    TblFrameloss.CPU,
+                                    TblFrameloss.MppspGhz,
+                                    TblFrameloss.MinimumLatency,
+                                    TblFrameloss.MaximumLatency,
+                                    TblFrameloss.AverageLatency,
+                                    ).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_frameloss_simpletable(self, taskid, casetag, provider):
+        ptype = 'frameloss'
+        query = self._session.query(TblFrameloss.AvgFrameSize,
+                                    TblFrameloss.Bandwidth,
+                                    TblFrameloss.OfferedLoad,
+                                    TblFrameloss.CPU,
+                                    TblFrameloss.MppspGhz,
+                                    TblFrameloss.AverageLatency,
+                                    ).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_frameloss_avg(self, taskid, casetag, provider):
+        ptype = 'frameloss'
+        query = self._session.query(TblFrameloss.AvgFrameSize, TblFrameloss.AverageLatency).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_latency_avg(self, taskid, casetag, provider):
+        ptype = 'latency'
+        query = self._session.query(TblLatency.AvgFrameSize, TblLatency.AverageLatency).filter(and_(
+            TblTestList.TaskID == taskid,
+            TblTestList.CaseTag == casetag,
+            TblTestList.Provider == provider, TblTestList.Type == ptype,
+            TblTestList.TestID == TblLatency.TestID))
+        return query.all()
+
+    def query_summary_table(self, taskid, casetag, provider, ptype):
+        if ptype in ['throughput', 'frameloss']:
+            qfunc = getattr(self, "query_%s_table" % (ptype))
+            return qfunc(taskid, casetag, provider)
+        return []
+
+    def query_summary_simpletable(self, taskid, casetag, provider, ptype):
+        if ptype in ['throughput', 'frameloss']:
+            qfunc = getattr(self, "query_%s_simpletable" % (ptype))
+            return qfunc(taskid, casetag, provider)
+        return []
+
+    def query_bandwidth(self, taskid, casetag, provider, ptype):
+        if ptype in ['throughput', 'frameloss']:
+            qfunc = getattr(self, "query_%s_bandwidth" % (ptype))
+            return qfunc(taskid, casetag, provider)
+        return []
+
+    def query_load(self, taskid, casetag, provider, ptype):
+        if ptype in ['throughput', 'frameloss']:
+            qfunc = getattr(self, "query_%s_load" % (ptype))
+            return qfunc(taskid, casetag, provider)
+        return []
+
+    def query_avglatency(self, taskid, casetag, provider, ptype):
+        if ptype in ['throughput', 'frameloss', 'latency']:
+            qfunc = getattr(self, "query_%s_avg" % (ptype))
+            return qfunc(taskid, casetag, provider)
+        return []
+
+    def query_throughput_provider(self, taskid, casetag, provider):
+        query = self._session.query(TblThroughput).filter(and_(TblTestList.CaseTag == casetag,
+                                                               TblTestList.Provider == provider,
+                                                               TblTestList.TaskID == taskid,
+                                                               TblTestList.TestID == TblThroughput.TestID))
+        return query.all()
+
+    def query_frameloss_provider(self, taskid, casetag, provider):
+        query = self._session.query(TblFrameloss).filter(and_(TblTestList.CaseTag == casetag,
+                                                              TblTestList.Provider == provider,
+                                                              TblTestList.TaskID == taskid,
+                                                              TblTestList.TestID == TblFrameloss.TestID))
+        return query.all()
+
+    def query_latency_provider(self, taskid, casetag, provider):
+        query = self._session.query(TblLatency).filter(and_(TblTestList.CaseTag == casetag,
+                                                            TblTestList.Provider == provider,
+                                                            TblTestList.TaskID == taskid,
+                                                            TblTestList.TestID == TblLatency.TestID))
+        return query.all()
+
+    def query_case_type_count(self, taskid, casetag, ptype):
+        query = self._session.query(TblTestList).filter(and_(TblTestList.CaseTag == casetag,
+                                                             TblTestList.Type == ptype, TblTestList.TaskID == taskid))
+
+        return query.count()
+
+    def query_case_provider_count(self, taskid, casetag, provider):
+        query = self._session.query(TblTestList).filter(and_(TblTestList.CaseTag == casetag,
+                                                             TblTestList.Provider == provider,
+                                                             TblTestList.TaskID == taskid))
+        return query.count()
+
+    def query_case_type_provider_count(self, taskid, casetag, provider, ptype):
+        query = self._session.query(TblTestList).filter(and_(TblTestList.CaseTag == casetag,
+                                                             TblTestList.Type == ptype,
+                                                             TblTestList.Provider == provider,
+                                                             TblTestList.TaskID == taskid))
+
+        return query.count()
+
+    def query_exten_info(self, taskid):
+        query = self._session.query(TblEXTInfo.EXTName,
+                                    TblEXTInfo.EXTContent,
+                                    TblEXTInfo.Description).filter(TblEXTInfo.TaskID == taskid)
+        return query.all()
+
+
+def unit_test():
+    import time
+    dbase = DbManage()
+
+    taskid = dbase.create_task("test", str(time.ctime()), "this is a unit test")
+    dbase.add_host_2task(taskid, "hosta", "hw82576", "xxx", "x", "82599", "ubuntu")
+    dbase.add_extent_2task(taskid, "CETH", "driver", "version 2.0")
+    dbase.add_extent_2task(taskid, "EVS", "switch", "version 3.0")
+
+    testid = dbase.add_test_2task(taskid, "Tn-1", 'udp', "rdp", "throughput", "netperf")
+    data = {
+        '64': {
+            'OfferedLoad': 2,
+            'PercentLoss': 3,
+            'Bandwidth': 4,
+            'MinimumLatency': 5,
+            'MaximumLatency': 6,
+            'AverageLatency': 7,
+            'TxFrameCount': 8,
+            'RxFrameCount': 9,
+            'Duration': 10,
+            'CPU': 11,
+            'MppspGhz': 12,
+        }
+    }
+    dbase.add_data_2test(testid, data)
+
+    testid = dbase.add_test_2task(taskid, "Tn-1", 'udp', "rdp", "frameloss", "netperf")
+    data = {
+        '64': {
+            'OfferedLoad': 2,
+            'PercentLoss': 3,
+            'Bandwidth': 4,
+            'MinimumLatency': 5,
+            'MaximumLatency': 6,
+            'AverageLatency': 7,
+            'TxFrameCount': 8,
+            'RxFrameCount': 9,
+            'Duration': 10,
+            'CPU': 11,
+            'MppspGhz': 12,
+        }
+    }
+    dbase.add_data_2test(testid, data)
+
+    testid = dbase.add_test_2task(taskid, "Tn-1", 'udp', "rdp", "latency", "netperf")
+    data = {
+        64: {'MaximumLatency': 0.0, 'AverageLatency': 0.0, 'MinimumLatency': 0.0, 'OfferedLoad': 0.0},
+        128: {'MaximumLatency': 0.0, 'AverageLatency': 0.0, 'MinimumLatency': 0.0, 'OfferedLoad': 0.0},
+        512: {'MaximumLatency': 0.0, 'AverageLatency': 0.0, 'MinimumLatency': 0.0, 'OfferedLoad': 0.0},
+        1024: {'MaximumLatency': 0.0, 'AverageLatency': 0.0, 'MinimumLatency': 0.0, 'OfferedLoad': 0.0}
+    }
+    dbase.add_data_2test(testid, data)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/database/tables.py b/vstf/vstf/controller/database/tables.py
new file mode 100755 (executable)
index 0000000..a7658f4
--- /dev/null
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-07-29
+# see license for license details
+__version__ = ''' '''
+from sqlalchemy import Column, Integer, String, Float, ForeignKey
+from sqlalchemy.ext.declarative import declarative_base
+from vstf.controller.database import constants as const
+
+Base = declarative_base()
+
+
+class TblScenarioInfo(Base):
+    __tablename__ = "TblScenarioInfo"
+    ScenarioID = Column(Integer, primary_key=True)
+    ScenarioName = Column(String(const.SCENARIO_NAME_LEN), unique=True)
+    FigurePath = Column(String(const.FIGURE_PATH_LEN))
+    Description = Column(String(const.DESC_LEN))
+
+    def __init__(self, ScenarioName, FigurePath, Description, **kwargs):
+        """
+        :param ScenarioName: name of the scenario, like Tn
+        :param FigurePath: ??
+        :param Description: desc of scenario table
+        """
+        self.ScenarioName = ScenarioName
+        self.FigurePath = FigurePath
+        self.Description = Description
+
+    def __repr__(self):
+        return "<User(ScenarioName='%s', FigurePath='%s', Description='%s')>" % (
+            self.ScenarioName, self.FigurePath, self.Description)
+
+
+class TblCaseInfo(Base):
+    __tablename__ = "TblCaseInfo"
+    CaseID = Column(Integer, primary_key=True)
+    CaseTag = Column(String(const.CASE_TAG_LEN), unique=True)
+    CaseName = Column(String(const.CASE_NAME_LEN), unique=True)
+    ScenarioName = Column(String(const.SCENARIO_NAME_LEN))
+    FigurePath = Column(String(const.FIGURE_PATH_LEN))
+    Direction = Column(String(const.DIRECTION_LEN))
+    Directiontag = Column(String(const.DIRECTION_LEN))
+    Configure = Column(String(const.CONF_LEN))
+    Description = Column(String(const.DESC_LEN))
+
+    def __init__(self, CaseTag, CaseName,
+                 ScenarioName, FigurePath, Direction, Directiontag,
+                 Configure, Description, **kwargs):
+        """
+        :param CaseID: 
+        :param CaseTag: ??
+        :param CaseName: name of case, like tester-vm
+        :param ScenarioName: name of scenario, like Tn
+        :param FigurePath:
+        :param Direction: the test direction, Tx or Rx
+        :param Configure:
+        :param Description: desc of table case info
+        """
+        # CaseID will auto builded by db
+        self.CaseTag = CaseTag
+        self.CaseName = CaseName
+        self.ScenarioName = ScenarioName
+        self.FigurePath = FigurePath
+        self.Direction = Direction
+        self.Directiontag = Directiontag
+        self.Configure = Configure
+        self.Description = Description
+
+    def __repr__(self):
+        return "<User(CaseTag='%s', CaseName='%s',ScenarioName='%s',FigurePath='%s', Direction='%s', \
+            Directiontag='%s', Configure='%s', Description='%s')>" % (self.CaseTag, self.CaseName,
+                                                                      self.ScenarioName, self.FigurePath,
+                                                                      self.Direction, self.Directiontag, self.Configure,
+                                                                      self.Description)
+
+
+class TblHostInfo(Base):
+    __tablename__ = "TblHostInfo"
+    Index = Column(Integer, primary_key=True)
+    TaskID = Column(Integer, ForeignKey('TblTaskList.TaskID'))
+    HostName = Column(String(const.HOST_NAME_LEN))
+    Server = Column(String(const.NORMAL_VAR_LEN1))
+    CPU = Column(String(const.CPU_INFO_LEN))
+    MEM = Column(String(const.NORMAL_VAR_LEN))
+    NIC = Column(String(const.NORMAL_VAR_LEN))
+    OS = Column(String(const.NORMAL_VAR_LEN))
+
+    def __init__(self, TaskID, HostName, Server, CPU, MEM, NIC, OS, **kwargs):
+        """table of host info
+        """
+        self.TaskID = TaskID
+        self.HostName = HostName
+        self.Server = Server
+        self.CPU = CPU
+        self.MEM = MEM
+        self.NIC = NIC
+        self.OS = OS
+
+    def __repr__(self):
+        return "<User(HostName='%s',  Server='%s', CPU='%s', MEM='%s', NIC='%s',\
+         OS='%s')>" % (self.HostName, self.Server, self.CPU, self.MEM, self.NIC, self.OS)
+
+
+class TblTaskList(Base):
+    __tablename__ = "TblTaskList"
+    TaskID = Column(Integer, primary_key=True)
+    TaskName = Column(String(const.NORMAL_VAR_LEN1))
+    Date = Column(String(const.NORMAL_VAR_LEN1))
+    EXTInfo = Column(String(const.EXT_INFO_LEN))
+
+    def __init__(self, TaskName, Date, EXTInfo="", **kwargs):
+        """Table of task"""
+        self.TaskName = TaskName
+        self.Date = Date
+        self.EXTInfo = EXTInfo
+
+    def __repr__(self):
+        return "<User(TaskID='%s', TaskName='%s', Date='%s', EXTInfo='%s')>" % (
+            self.TaskID, self.TaskName, self.Date, self.EXTInfo)
+
+
+class TblTestList(Base):
+    __tablename__ = "TblTestList"
+    TestID = Column(Integer, primary_key=True)
+    TaskID = Column(Integer, ForeignKey('TblTaskList.TaskID'))
+    CaseTag = Column(String(const.CASE_TAG_LEN))
+    Protocol = Column(String(const.PROTOCOL_LEN))
+    Provider = Column(String(const.PROVIDER_LEN))
+    Type = Column(String(const.TYPE_LEN))
+    Tools = Column(String(const.TOOLS_LEN))
+
+    def __init__(self, taskid, casetag, protocol, provider, typ, tools, **kwargs):
+        """Table of test"""
+        self.TaskID = taskid
+        self.CaseTag = casetag
+        self.Protocol = protocol
+        self.Provider = provider
+        self.Type = typ
+        self.Tools = tools
+
+    def __repr__(self):
+        return "<User(TaskID='%d', CaseTag='%s', Protocol='%s', Provider=%s, Type='%s', Tools='%s')>" % (
+            self.TaskID, self.CaseTag, self.Protocol, self.Provider, self.Type, self.Tools)
+
+
+class TblThroughput(Base):
+    __tablename__ = "TblThroughput"
+    Index = Column(Integer, primary_key=True)
+    TestID = Column(Integer, ForeignKey('TblTestList.TestID'))
+    AvgFrameSize = Column(Integer)
+    OfferedLoad = Column(Float)
+    PercentLoss = Column(Float)
+    Bandwidth = Column(Float)
+    MinimumLatency = Column(Float)
+    MaximumLatency = Column(Float)
+    AverageLatency = Column(Float)
+    TxFrameCount = Column(Float)
+    RxFrameCount = Column(Float)
+    Duration = Column(Float)
+    CPU = Column(Float)
+    MppspGhz = Column(Float)
+
+    def __init__(self, TestID, AvgFrameSize,
+                 OfferedLoad, PercentLoss, Bandwidth,
+                 MinimumLatency, MaximumLatency, AverageLatency,
+                 TxFrameCount, RxFrameCount, Duration,
+                 CPU, MppspGhz, **kwargs):
+        """table of throughput"""
+        self.TestID = TestID
+        self.AvgFrameSize = AvgFrameSize
+        self.OfferedLoad = OfferedLoad
+        self.PercentLoss = PercentLoss
+        self.Bandwidth = Bandwidth
+        self.MinimumLatency = MinimumLatency
+        self.MaximumLatency = MaximumLatency
+        self.AverageLatency = AverageLatency
+        self.TxFrameCount = TxFrameCount
+        self.RxFrameCount = RxFrameCount
+        self.Duration = Duration
+        self.CPU = CPU
+        self.MppspGhz = MppspGhz
+
+    def __repr__(self):
+        return "<User(TestID='%d', AvgFrameSize='%d', OfferedLoad='%f', \
+                      PercentLoss='%f', MinimumLatency='%f', AverageLatency='%f', MaximumLatency='%f',\
+                      TxFrameCount='%f', RxFrameCount='%f', Duration='%f', CPU='%f', MppspGhz='%f', \
+                      Bandwidth='%f')>" % (self.TestID,
+                                           self.AvgFrameSize, self.OfferedLoad, self.PercentLoss,
+                                           self.MinimumLatency, self.AverageLatency, self.MaximumLatency,
+                                           self.TxFrameCount,
+                                           self.RxFrameCount, self.Duration, self.CPU, self.MppspGhz, self.Bandwidth)
+
+
+class TblFrameloss(Base):
+    __tablename__ = "TblFrameloss"
+    Index = Column(Integer, primary_key=True)
+    TestID = Column(Integer, ForeignKey('TblTestList.TestID'))
+    AvgFrameSize = Column(Integer)
+    OfferedLoad = Column(Float)
+    PercentLoss = Column(Float)
+    Bandwidth = Column(Float)
+    MinimumLatency = Column(Float)
+    MaximumLatency = Column(Float)
+    AverageLatency = Column(Float)
+    TxFrameCount = Column(Float)
+    RxFrameCount = Column(Float)
+    Duration = Column(Float)
+    CPU = Column(Float)
+    MppspGhz = Column(Float)
+
+    def __init__(self, TestID, AvgFrameSize,
+                 OfferedLoad, PercentLoss, Bandwidth,
+                 MinimumLatency, MaximumLatency, AverageLatency,
+                 TxFrameCount, RxFrameCount, Duration,
+                 CPU, MppspGhz, **kwargs):
+        """table of frameloss"""
+        self.TestID = TestID
+        self.AvgFrameSize = AvgFrameSize
+        self.OfferedLoad = OfferedLoad
+        self.PercentLoss = PercentLoss
+        self.Bandwidth = Bandwidth
+        self.MinimumLatency = MinimumLatency
+        self.MaximumLatency = MaximumLatency
+        self.AverageLatency = AverageLatency
+        self.TxFrameCount = TxFrameCount
+        self.RxFrameCount = RxFrameCount
+        self.Duration = Duration
+        self.CPU = CPU
+        self.MppspGhz = MppspGhz
+
+    def __repr__(self):
+        return "<User(TestID='%d', AvgFrameSize='%d', OfferedLoad='%f', \
+                      PercentLoss='%f', MinimumLatency='%f', AverageLatency='%f', MaximumLatency='%f',\
+                      TxFrameCount='%f', RxFrameCount='%f', Duration='%f', CPU='%f', MppspGhz='%f', \
+                      Bandwidth='%f')>" % (self.TestID,
+                                           self.AvgFrameSize, self.OfferedLoad, self.PercentLoss,
+                                           self.MinimumLatency, self.AverageLatency, self.MaximumLatency,
+                                           self.TxFrameCount,
+                                           self.RxFrameCount, self.Duration, self.CPU, self.MppspGhz, self.Bandwidth)
+
+
+class TblLatency(Base):
+    __tablename__ = "TblLatency"
+    Index = Column(Integer, primary_key=True)
+    TestID = Column(Integer, ForeignKey('TblTestList.TestID'))
+    AvgFrameSize = Column(Integer)
+    OfferedLoad = Column(Float)
+    MinimumLatency = Column(Float)
+    MaximumLatency = Column(Float)
+    AverageLatency = Column(Float)
+
+    def __init__(self, TestID, AvgFrameSize, OfferedLoad,
+                 MinimumLatency, MaximumLatency, AverageLatency, **kwargs):
+        """table of latency"""
+        self.TestID = TestID
+        self.AvgFrameSize = AvgFrameSize
+        self.OfferedLoad = OfferedLoad
+        self.MinimumLatency = MinimumLatency
+        self.MaximumLatency = MaximumLatency
+        self.AverageLatency = AverageLatency
+
+    def __repr__(self):
+        return "<User(TestID='%d', AvgFrameSize='%d', OfferedLoad='%f', \
+                      MinimumLatency='%f', AverageLatency='%f', MaximumLatency='%f')>" % (self.TestID,
+                                                                                          self.AvgFrameSize,
+                                                                                          self.OfferedLoad,
+                                                                                          self.MinimumLatency,
+                                                                                          self.AverageLatency,
+                                                                                          self.MaximumLatency)
+
+
+class TblEXTInfo(Base):
+    __tablename__ = "TblEXTInfo"
+    Index = Column(Integer, primary_key=True)
+    TaskID = Column(Integer)
+    EXTName = Column(String(const.NORMAL_VAR_LEN))
+    EXTContent = Column(String(const.DESC_LEN))
+    Description = Column(String(const.NORMAL_VAR_LEN1))
+
+    def __init__(self, TaskID, EXTName, EXTContent, Description, **kwargs):
+        """table extern info"""
+        self.TaskID = TaskID
+        self.EXTName = EXTName
+        self.EXTContent = EXTContent
+        self.Description = Description
+
+    def __repr__(self):
+        return "<User(TaskID='%d', CodeType='%s', EXTContent='%s',Version='%s')>" % (
+            self.TaskID, self.EXTName, self.EXTContent, self.Version)
diff --git a/vstf/vstf/controller/env_build/README b/vstf/vstf/controller/env_build/README
new file mode 100755 (executable)
index 0000000..ecb4e11
--- /dev/null
@@ -0,0 +1,15 @@
+ env_build.py contains a quick test code for create virtual network in a remote host.
+
+ usage:
+    
+   python env_build.py --rpc_server 192.168.188.10 --config /etc/vstf/env/Tn.json
+
+   --rpc_server RPC_SERVER
+                        the rabbitmq server for deliver messages.
+   --config CONFIG
+                                               env-build config file to parse
+
+the above command will build a 'Tn-type' network according to config file: /etc/vstf/env/Tn.json.
+       
+        
+
diff --git a/vstf/vstf/controller/env_build/__init__.py b/vstf/vstf/controller/env_build/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/env_build/cfg_intent_parse.py b/vstf/vstf/controller/env_build/cfg_intent_parse.py
new file mode 100755 (executable)
index 0000000..8c7c10b
--- /dev/null
@@ -0,0 +1,130 @@
+"""
+Created on 2015-10-13
+
+@author: y00228926
+"""
+import json
+import logging
+from vstf.common.utils import randomMAC
+
+LOG = logging.getLogger(__name__)
+
+
+class IntentParser(object):
+    def __init__(self, cfg_file):
+        self.cfg_file = cfg_file
+        with file(cfg_file) as fp:
+            self.cfg_intent = json.load(fp)
+
+    def parse_cfg_file(self):
+        self.set_default()
+        self.parse_br_type()
+        self.parse_vms_cfg()
+        return self.cfg_intent
+
+    def set_default(self):
+        for host_cfg in self.cfg_intent['env-build']:
+            host_cfg.setdefault("scheme", 'libvirt')
+            host_cfg.setdefault("drivers", [])
+            host_cfg.setdefault("vms", [])
+            host_cfg.setdefault("bridges", [])
+            for vm_cfg in host_cfg["vms"]:
+                vm_cfg.setdefault("init_config", {})
+                vm_cfg["init_config"].setdefault('amqp_port', 5672)
+                vm_cfg["init_config"].setdefault('amqp_user', "guest")
+                vm_cfg["init_config"].setdefault('amqp_passwd', "guest")
+                vm_cfg["init_config"].setdefault('amqp_id', "")
+
+    def _nomornize_boolean(self, flag):
+        if isinstance(flag, bool):
+            return flag
+        lflag = flag.lower()
+        if lflag == 'true':
+            return True
+        if lflag == 'false':
+            return False
+        raise Exception("flag %s cannot be nomonized to bool value" % flag)
+
+    def parse_br_type(self):
+        for host_cfg in self.cfg_intent['env-build']:
+            br_cfgs = host_cfg['bridges']
+            br_type_set = set()
+            for br_cfg in br_cfgs:
+                br_type_set.add(br_cfg["type"])
+            for vm_cfg in host_cfg['vms']:
+                for tap_cfg in vm_cfg['taps']:
+                    br_type_set.add(tap_cfg["br_type"])
+            if len(br_type_set) > 1:
+                raise Exception("specified more than one type of vswitchfor host:%s" % host_cfg['ip'])
+            if len(br_type_set) > 0:
+                br_type = br_type_set.pop()
+                host_cfg['br_type'] = br_type
+
+    def parse_vms_cfg(self):
+        for host_cfg in self.cfg_intent['env-build']:
+            vm_cfgs = host_cfg["vms"]
+            self._parse_vm_init_cfg(vm_cfgs)
+            self._parse_vm_ctrl_cfg(vm_cfgs)
+            for vm_cfg in vm_cfgs:
+                self._parse_taps_cfg(vm_cfg['taps'])
+
+    def _parse_taps_cfg(self, tap_cfgs):
+        tap_name_set = set()
+        tap_mac_set = set()
+        count = 0
+        for tap_cfg in tap_cfgs:
+            count += 1
+            tap_name_set.add(tap_cfg["tap_mac"])
+            tap_mac_set.add(tap_cfg["tap_name"])
+        if len(tap_mac_set) != len(tap_name_set) != count:
+            raise Exception('config same tap_mac/tap_name for different taps')
+        LOG.info("tap_name_set: %s", tap_name_set)
+        LOG.info("tap_mac_set: %s", tap_mac_set)
+
+    def _parse_vm_init_cfg(self, vm_cfgs):
+        count = 0
+        ip_set = set()
+        gw_set = set()
+        required_options = {"ctrl_ip_setting", "ctrl_gw", "amqp_server"}
+        for vm_cfg in vm_cfgs:
+            init_cfg = vm_cfg["init_config"]
+            sub = required_options - set(init_cfg.keys())
+            if sub:
+                raise Exception("unset required options:%s" % sub)
+            count += 1
+            ip_set.add(init_cfg["ctrl_ip_setting"])
+            gw_set.add(init_cfg["ctrl_gw"])
+        if len(gw_set) > 1:
+            raise Exception("cannot config more than one gw for vm")
+        if len(ip_set) < count:
+            raise Exception("config same ip for different vm")
+        LOG.info("ip_set: %s", ip_set)
+        LOG.info("gw_set: %s", gw_set)
+
+    def _parse_vm_ctrl_cfg(self, vm_cfgs):
+        count = 0
+        ctrl_mac_set = set()
+        ctrl_br_set = set()
+        for vm_cfg in vm_cfgs:
+            count += 1
+            vm_cfg.setdefault("ctrl_mac", randomMAC())
+            vm_cfg.setdefault("ctrl_br", 'br0')
+            ctrl_mac_set.add(vm_cfg['ctrl_mac'])
+            ctrl_br_set.add(vm_cfg['ctrl_br'])
+        if len(ctrl_br_set) > 1:
+            raise Exception("cannot config more than one ctrl_br_set.")
+        if len(ctrl_mac_set) < count:
+            raise Exception("config same ctrl_mac_set for different vm.")
+        LOG.info("ctrl_mac_set: %s", ctrl_mac_set)
+        LOG.info("ctrl_br_set: %s", ctrl_br_set)
+
+
+if __name__ == '__main__':
+    import argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--config', help='config file to parse')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    p = IntentParser(args.config)
+    LOG.info(json.dumps(p.parse_cfg_file(), indent=4))
diff --git a/vstf/vstf/controller/env_build/env_build.py b/vstf/vstf/controller/env_build/env_build.py
new file mode 100755 (executable)
index 0000000..85ad5d2
--- /dev/null
@@ -0,0 +1,77 @@
+"""
+Created on 2015-8-27
+
+@author: y00228926
+"""
+import logging
+
+from vstf.controller.fabricant import Fabricant
+from vstf.rpc_frame_work.rpc_producer import Server
+from vstf.controller.env_build.cfg_intent_parse import IntentParser
+
+LOG = logging.getLogger(__name__)
+
+
+class EnvBuildApi(object):
+    def __init__(self, conn, config_file):
+        LOG.info("welcome to EnvBuilder")
+        self.conn = conn
+        intent_parser = IntentParser(config_file)
+        self.cfg_intent = intent_parser.parse_cfg_file()
+
+    def build(self):
+        LOG.info("start build")
+        for host_cfg in self.cfg_intent['env-build']:
+            rpc = Fabricant(host_cfg['ip'], self.conn)
+            rpc.build_env(timeout=1800, cfg_intent=host_cfg)
+        return True
+
+    def clean(self):
+        for host_cfg in self.cfg_intent['env-build']:
+            rpc = Fabricant(host_cfg['ip'], self.conn)
+            rpc.clean_env(timeout=120)
+        return True
+
+    def get_hosts(self):
+        result = []
+        for host_cfg in self.cfg_intent['env-build']:
+            host = {
+                'name': host_cfg['ip'],
+                'nic': "82599ES 10-Gigabit"
+            }
+            result.append(host)
+        return result
+
+
+class TransmitterBuild(object):
+    def __init__(self, conn, config_file):
+        LOG.info("welcome to TransmitterBuild")
+        self.conn = conn
+        self._cfg_intent = config_file["transmitter-build"]
+
+    def build(self):
+        LOG.info("start build")
+        for cfg in self.cfg_intent:
+            rpc = Fabricant(cfg['ip'], self.conn)
+            cfg.setdefault("scheme", 'transmitter')
+            rpc.build_env(timeout=1800, cfg_intent=cfg)
+        return True
+
+    def clean(self):
+        for cfg in self.cfg_intent:
+            rpc = Fabricant(cfg['ip'], self.conn)
+            rpc.clean_env(timeout=10)
+        return True
+
+
+if __name__ == "__main__":
+    import argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--rpc_server', help='rabbitmq server for deliver messages.')
+    parser.add_argument('--config', help='config file to parse')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    conn = Server(args.rpc_server)
+    tn = EnvBuildApi(conn, args.config)
+    tn.build()
diff --git a/vstf/vstf/controller/env_build/env_collect.py b/vstf/vstf/controller/env_build/env_collect.py
new file mode 100755 (executable)
index 0000000..888f71c
--- /dev/null
@@ -0,0 +1,30 @@
+from vstf.rpc_frame_work import rpc_producer
+
+
+class EnvCollectApi(object):
+    def __init__(self, rb_mq_server):
+        """
+        When use collect, a connection of rabbitmq is needed.
+        """
+        super(EnvCollectApi, self).__init__()
+        if rb_mq_server is None:
+            raise Exception("The connection of rabbitmq is None.")
+        self.conn = rb_mq_server
+
+    def collect_host_info(self, host):
+        msg = self.conn.make_msg("collect_host_info")
+        return self.conn.call(msg, host, timeout=2)
+
+    def get_device_detail(self, host, nic_identity):
+        msg = self.conn.make_msg("get_device_detail", identity=nic_identity)
+        return self.conn.call(msg, host, timeout=2)
+
+    def list_nic_devices(self, host):
+        msg = self.conn.make_msg("list_nic_devices")
+        return self.conn.call(msg, host, timeout=2)
+
+
+if __name__ == "__main__":
+    conn = rpc_producer.Server("192.168.188.10")
+    c = EnvCollectApi(conn)
+    print c.collect_host_info("local")
diff --git a/vstf/vstf/controller/fabricant.py b/vstf/vstf/controller/fabricant.py
new file mode 100755 (executable)
index 0000000..c67bfa1
--- /dev/null
@@ -0,0 +1,49 @@
+from vstf.rpc_frame_work import constant as const
+import vstf.common.constants as cst
+
+
+class Fabricant(object):
+    def __init__(self, target, conn):
+        self.conn = conn
+        self.target = target
+
+        self.all_commands = self.declare_commands
+        self.instance_commands()
+
+    @property
+    def declare_commands(self):
+        driver = {"install_drivers", "clean_drivers", "autoneg_on", "autoneg_off", "autoneg_query"}
+
+        builder = {"build_env", "clean_env"}
+
+        cpu = {"affctl_load", "affctl_list", "run_cpuwatch", "kill_cpuwatch"}
+
+        perf = {"perf_run", "run_vnstat", "kill_vnstat", "force_clean"}
+
+        device_mgr = {"get_device_detail", "list_nic_devices", "get_device_verbose"}
+
+        netns = {"clean_all_namespace", "config_dev", "recover_dev", "ping"}
+
+        collect = {"collect_host_info"}
+
+        cmdline = {"execute"}
+
+        spirent = {"send_packet", "stop_flow", "mac_learning", "run_rfc2544suite", "run_rfc2544_throughput",
+                   "run_rfc2544_frameloss", "run_rfc2544_latency"}
+
+        equalizer = {"get_numa_core", "get_nic_numa", "get_nic_interrupt_proc", "get_vm_info", "bind_cpu",
+                     "catch_thread_info"}
+
+        return driver | cpu | builder | perf | device_mgr | netns | cmdline | collect | spirent | equalizer
+
+    def instance_commands(self):
+        for command in self.all_commands:
+            setattr(self, command, self.__transfer_msg(command))
+
+    def __transfer_msg(self, command):
+        def infunc(timeout=cst.TIMEOUT, **kwargs):
+            msg = self.conn.make_msg(command, **kwargs)
+            return self.conn.call(msg, self.target, timeout)
+
+        infunc.__name__ = command
+        return infunc
diff --git a/vstf/vstf/controller/reporters/README b/vstf/vstf/controller/reporters/README
new file mode 100755 (executable)
index 0000000..1ed6536
--- /dev/null
@@ -0,0 +1,109 @@
+Tree
+
+├── __init__.py
+├── mail
+│   â”œâ”€â”€ __init__.py
+│   â”œâ”€â”€ mail.py
+│   â””── sendmail.py
+├── report
+│   â”œâ”€â”€ data_factory.py
+│   â”œâ”€â”€ html
+│   â”‚   â”œâ”€â”€ html_base.py
+│   â”‚   â”œâ”€â”€ htmlcreater.py
+│   â”‚   â”œâ”€â”€ html_text.py
+│   â”‚   â””── __init__.py
+│   â”œâ”€â”€ __init__.py
+│   â”œâ”€â”€ pdf
+│   â”‚   â”œâ”€â”€ element.py
+│   â”‚   â”œâ”€â”€ __init__.py
+│   â”‚   â”œâ”€â”€ pdfcreater.py
+│   â”‚   â”œâ”€â”€ pdftemplate.py
+│   â”‚   â”œâ”€â”€ story.py
+│   â”‚   â””── styles.py
+│   â””── provider
+│       â”œâ”€â”€ html_provider.py
+│       â””── __init__.py
+└── reporter.py
+
+
+Entry:
+    reporter.py
+
+    usage: reporter.py [-h] [-rpath RPATH] [-mail_off] [--taskid TASKID]
+
+    optional arguments:
+        -h, --help       show this help message and exit
+        -rpath RPATH     the path name of test results
+        -mail_off        is need send mail the for the report
+        --taskid TASKID  report depand of a history task id.
+
+Settings:
+    mail_settings
+
+        {
+            "server":
+            {
+                "host": "localhost",
+                "username": null,
+                "password": null
+            },
+            "body":
+            {
+                "from": ["vstf_from@vstf.com"],
+                "to": ["vstf_to@vstf.com"],
+                "cc": ["vstf_cc@vstf.com"],
+                "bcc": ["vstf_bcc@vstf.com"],
+                "subject": "Elastic Virtual Switching Performance Test Report"
+            }
+        }
+
+    html_settings
+
+        {
+            "style":{
+                "table":{
+                    "font-family":"\"Trebuchet MS\", Arial, Helvetica, sans-serif",
+                    "border":"1px solid green",
+                    "border-collapse":"collapse",
+                    "padding":"8px",
+                    "text-align":"center"
+                },
+                "td":{
+                    "border":"1px solid green",
+                    "padding":"8px",
+                    "word-wrap":"break-all"
+                },
+                "th":{
+                    "background-color":"#EAF2D3",
+                    "border":"1px solid green",
+                    "padding":"8px"
+                }
+            }
+        }
+
+    data_settings
+
+        {
+            "ovs":{
+                "content":{
+                    "version":3.0
+                },
+                "title":"Ovs info"
+            },
+            "result":{
+                "content":{},
+                "title":"Performance Result"
+            },
+            "subject":"ATF Performance Test Tnv Model"
+        }
+Module:
+    mail
+    html
+    pdf
+
+
+Others:
+     pip processes the package "reportlab"
+
+     pip install reportlab
+
diff --git a/vstf/vstf/controller/reporters/__init__.py b/vstf/vstf/controller/reporters/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/mail/__init__.py b/vstf/vstf/controller/reporters/mail/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/mail/mail.py b/vstf/vstf/controller/reporters/mail/mail.py
new file mode 100755 (executable)
index 0000000..42d60b1
--- /dev/null
@@ -0,0 +1,117 @@
+import smtplib
+import logging
+import os
+from email.mime.application import MIMEApplication
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+
+LOG = logging.getLogger(__name__)
+SRV = 'localhost'
+USER = None
+PASSWD = None
+
+
+class Mail(object):
+    def __init__(self, srv=SRV, user=USER, passwd=PASSWD):
+        self.srv = srv
+        self.user = USER
+        self.passwd = PASSWD
+        self._msg = MIMEMultipart('mixed')
+
+        # addr type
+        self.TO = "To"
+        self.FROM = "From"
+        self.CC = "Cc"
+        self.BCC = "Bcc"
+        self.__addr_choice = [self.TO, self.FROM, self.CC, self.BCC]
+
+        # text mode
+        self.HTML = "html"
+        self.PLAIN = "plain"
+        self.__mode = [self.HTML, self.PLAIN]
+        # self._charset = 'gb2312'
+
+        # timeout
+        self.timeout = 10
+
+    def attach_addr(self, addr, addr_type):
+        """
+        :param addr: a list of email address.
+        :param addr_type: must be one of [to, from, cc, bcc]
+        """
+        if not addr or not isinstance(addr, list):
+            LOG.error("The addr must be a list")
+            return False
+
+        if addr_type not in self.__addr_choice:
+            LOG.error("Not support addr type")
+            return False
+
+        if not self._msg[addr_type]:
+            self._msg[addr_type] = ','.join(addr)
+        self._msg[addr_type].join(addr)
+
+    def attach_title(self, title):
+        """Notice:
+        each time attach title, the old title will be covered.
+        """
+        if title:
+            self._msg["Subject"] = str(title)
+
+    def attach_text(self, text, mode):
+        if mode not in self.__mode:
+            LOG.error("The text mode not support.")
+            return False
+
+        msg_alternative = MIMEMultipart('alternative')
+        msg_text = MIMEText(text, mode)
+        msg_alternative.attach(msg_text)
+
+        return self._msg.attach(msg_alternative)
+
+    def attach_files(self, files):
+        for _file in files:
+            part = MIMEApplication(open(_file, "rb").read())
+            part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(_file))
+            self._msg.attach(part)
+
+    def send(self):
+        server = smtplib.SMTP(self.srv, timeout=self.timeout)
+        if self.user:
+            server.ehlo()
+            server.starttls()
+            server.ehlo()
+            server.login(self.user, self.passwd)
+        maillist = []
+        if self._msg[self.TO]:
+            maillist += self._msg[self.TO].split(',')
+        if self._msg[self.CC]:
+            maillist += self._msg[self.CC].split(',')
+        if self._msg[self.BCC]:
+            maillist += self._msg[self.BCC].split(',')
+        ret = server.sendmail(self._msg[self.FROM].split(','),
+                              maillist, self._msg.as_string())
+        LOG.info("send mail ret:%s", ret)
+        server.close()
+
+
+if __name__ == "__main__":
+    m = Mail()
+    m.attach_addr(["vstf_server@vstf.com"], m.FROM)
+    m.attach_addr(["wangli11@huawei.com"], m.TO)
+    context = """
+        <!DOCTYPE html>
+        <html>
+        <head>
+        <title>vstf</title>
+        </head>
+        
+        <body>
+            hello vstf
+        </body>
+        
+        </html>
+    """
+    m.attach_text(context, m.HTML)
+    m.attach_title("Email from xeson Check")
+    m.send()
diff --git a/vstf/vstf/controller/reporters/mail/sendmail.py b/vstf/vstf/controller/reporters/mail/sendmail.py
new file mode 100755 (executable)
index 0000000..ecc6fe9
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-07
+# see license for license details
+__version__ = ''' '''
+
+import logging
+from vstf.controller.reporters.mail.mail import Mail
+from vstf.controller.settings.mail_settings import MailSettings
+LOG = logging.getLogger(__name__)
+
+
+class SendMail(object):
+    def __init__(self, mail_info):
+        self._mail_info = mail_info
+
+    def send(self):
+        send = Mail(self._mail_info['server']['host'],
+                    self._mail_info['server']['username'],
+                    self._mail_info['server']['password']
+                    )
+        send.attach_addr(self._mail_info['body']['from'], send.FROM)
+        send.attach_addr(self._mail_info['body']['to'], send.TO)
+        send.attach_addr(self._mail_info['body']['cc'], send.CC)
+        send.attach_addr(self._mail_info['body']['bcc'], send.CC)
+
+        LOG.info(self._mail_info['body'])
+
+        if 'attach' in self._mail_info['body']:
+            send.attach_files(self._mail_info['body']['attach'])
+        send.attach_text(self._mail_info['body']['content'], self._mail_info['body']['subtype'])
+        send.attach_title(self._mail_info['body']['subject'])
+        send.send()
+
+
+def unit_test():
+    mail_settings = MailSettings()
+    mail = SendMail(mail_settings.settings)
+
+    attach_list = ['1', '2']
+    mail_settings.set_attach(attach_list)
+
+    context = """
+        <!DOCTYPE html>
+        <html>
+        <head>
+        <title>vstf</title>
+        </head>
+        
+        <body>
+            hello vstf
+        </body>
+        
+        </html>
+    """
+    mail_settings.set_subtype('html')
+    mail_settings.set_content(context)
+
+    mail.send()
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/reporters/report/__init__.py b/vstf/vstf/controller/reporters/report/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/report/data_factory.py b/vstf/vstf/controller/reporters/report/data_factory.py
new file mode 100755 (executable)
index 0000000..39c534b
--- /dev/null
@@ -0,0 +1,494 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-07-29
+# see license for license details
+__version__ = ''' '''
+
+from vstf.controller.database.dbinterface import DbManage
+
+
+class DataProvider(object):
+    def __init__(self, taskid, dbase):
+        self._dbase = dbase
+        self._taskid = taskid
+
+
+class CommonData(DataProvider):
+    def get_components(self):
+        result = []
+        query = self._dbase.query_exten_info(self._taskid)
+        print "CommonData", query
+        for item in query:
+            if item[2]:
+                context = "%s:%s(%s)" % (item[0], item[1], item[2])
+            else:
+                context = "%s:%s" % (item[0], item[1])
+            result.append(context)
+        return result
+
+    def get_software(self):
+        result = [
+            "  Host OS:  ubuntu 14.04.2",
+            "  Guest OS: ubuntu 12.04.4"
+        ]
+        return result
+
+    def get_hardware(self):
+        result = [
+            "  Server: Dell R920",
+            "  CPU: E7-8893/2P/3.4GHz/10-Cores/37.5M-L3C",
+            "  MEM: 128G",
+            "  NIC: Intel 82599"
+        ]
+        return result
+
+    def get_taskname(self):
+        return self._dbase.query_taskname(self._taskid)
+
+    def get_gitinfo_tabledata(self):
+        result = []
+        return result
+
+    def get_profileparameters_tabledData(self):
+        result = [
+        ]
+        return result
+
+    def get_testingoptions_tabledata(self):
+        result = [
+        ]
+        return result
+
+    def get_systeminfo_tabledata(self):
+        result = [
+        ]
+        return result
+
+    def get_systeminfo(self):
+        systable = [
+            ['host', 'Server', 'CPU', 'MEM', 'NIC', 'OS'],
+        ]
+        query = self._dbase.query_task_host_list(self._taskid)
+        query = map(lambda x: list(x), query)
+        #    rows = len(query)
+        #    cols = len(zip(*query))
+        #    for i in range(rows):
+        #        for j in range(cols):
+        #            query[i][j] = query[i][j].replace('\",','\"\n')
+        systable += query
+        systable = map(lambda x: list(x), zip(*systable))
+        return systable
+
+    def get_introduct_tabledata(self):
+        result = [
+            ["Type", "Case", "Name", "Direction", "Configure"]
+        ]
+        query = self._dbase.query_caseinfo()
+        result += map(lambda x: list(x), query)
+        return result
+
+    def get_scenariolist(self):
+        query = self._dbase.query_scenariolist(self._taskid)
+        result = map(lambda x: list(x), zip(*query))
+        if result:
+            return result[0]
+        else:
+            return result
+
+    def is_scenario_start(self):
+        scenarioList = self.get_scenariolist()
+        print "scenarioList: ", scenarioList
+        if scenarioList:
+            return True
+        return False
+
+    def get_contact(self):
+        result = [
+            "Name: xxx",
+            "ID: xxxxxxxx",
+            "Email: xxxx@xxx.com"
+        ]
+        return result
+
+    def get_casename(self, case):
+        return self._dbase.query_casename(case)
+
+    def get_casefigure(self, case, tools):
+        return self._dbase.query_casefigure(case, tools)
+
+
+class ScenarioData(DataProvider):
+    def __init__(self, taskid, dbase, scenario):
+        print "ScenarioData in"
+        DataProvider.__init__(self, taskid, dbase)
+        self._scenario = scenario
+
+    def get_covertitle(self):
+        result = [
+            "",
+            "",
+            "Elastic Virtual Switching Performance "
+            "Test Report",
+            "Scenario %s" % (self._scenario)
+        ]
+        return result
+
+    def get_test(self):
+        result = [
+            "Scenario: %s" % (self._scenario),
+            "Configuration: without VLAN",
+        ]
+        return result
+
+    def get_test_tools(self, case):
+        query = self._dbase.query_casetools(self._taskid, case)
+        result = map(lambda x: list(x), query)
+        if result:
+            return result[0][0]
+        else:
+            return result
+
+    def get_caselist(self):
+        query = self._dbase.query_caselist(self._taskid, self._scenario)
+        result = map(lambda x: list(x), zip(*query))
+        if result:
+            return result[0]
+        else:
+            return result
+
+    def is_provider_start(self, case, provider):
+        count = self._dbase.query_case_provider_count(self._taskid, case, provider)
+        if count:
+            return True
+        return False
+
+    def is_type_provider_start(self, case, provider, ptype):
+        count = self._dbase.query_case_type_provider_count(self._taskid, case, provider, ptype)
+        if count:
+            return True
+        return False
+
+    def is_type_start(self, case, ptype):
+        count = self._dbase.query_case_type_count(self._taskid, case, ptype)
+        if count:
+            return True
+        return False
+
+    def is_throughput_start(self, case):
+        test_type = "throughput"
+        return self.is_type_start(case, test_type)
+
+    def is_frameloss_start(self, case):
+        test_type = "frameloss"
+        return self.is_type_start(case, test_type)
+
+    def is_latency_start(self, case):
+        test_type = "latency"
+        return self.is_type_start(case, test_type)
+
+    def get_summary_throughput_data(self, case, provider):
+        test_type = "throughput"
+        return self.get_summary_tabledata(case, provider, test_type)
+
+    def get_summary_frameLoss_data(self, case, provider):
+        test_type = "frameloss"
+        return self.get_summary_tabledata(case, provider, test_type)
+
+    def get_summary_tabledata(self, case, provider, test_type, table_type='pdf'):
+        table_head = []
+        table_body = []
+        type_title = {
+            "frameloss": "Load",
+            "throughput": "Load"
+        }
+        tools = self.get_test_tools(case)
+        if "spirent" in tools:
+            table_body = self._dbase.query_summary_table(self._taskid, case, provider, test_type)
+            if 'pdf' == table_type:
+                table_head = [
+                    ["FrameSize (byte)", test_type, "", "", "", "Latency(uSec)", "", ""],
+                    ["", "    Mpps    ", "   " + type_title[test_type] + " (%)   ", "CPU Used (%)", " Mpps/Ghz ",
+                     " Min ", " Max ", " Avg "]
+                ]
+            else:
+                table_head = [
+                    ["FrameSize (byte)", "    Mpps    ", "   " + type_title[test_type] + " (%)   ", "CPU Used (%)",
+                     " Mpps/Ghz ", "MinLatency(uSec)", "MaxLatency(uSec)", "AvgLatency(uSec)"],
+                ]
+        else:
+            table_body = self._dbase.query_summary_simpletable(self._taskid, case, provider, test_type)
+            if 'pdf' == table_type:
+                table_head = [
+                    ["FrameSize (byte)", test_type, "", "", "", "Latency(uSec)"],
+                    ["", "    Mpps    ", "   " + type_title[test_type] + " (%)", "CPU Used (%)", " Mpps/Ghz ",
+                     "  Avg  "]
+                ]
+            else:
+                table_head = [
+                    ["FrameSize (byte)", "    Mpps    ", "   " + type_title[test_type] + " (%)   ", "CPU Used (%)",
+                     " Mpps/Ghz ", "AvgLatency(uSec)"],
+                ]
+        return table_head + table_body
+
+    def get_tabledata(self, case, test_type, item):
+        type_dict = {
+            "FrameSize": "FrameSize (byte)",
+            "fastlink": "fastlink",
+            "l2switch": "l2switch",
+            "rdp": "kernel rdp",
+            "line": "line speed"
+        }
+        item_dict = {
+            "Percent": "  ",
+            "Mpps": "   ",
+            "Avg": "   ",
+        }
+        provider_list = ["fastlink", "rdp", "l2switch"]
+        table = []
+        line_speed = 20.0 if case in ["Tn-2v", "Tn-2"] else 10.0
+
+        for provider in provider_list:
+            if self.is_provider_start(case, provider):
+                if item == 'Percent':
+                    query = self._dbase.query_load(self._taskid, case, provider, test_type)
+                elif item == 'Mpps':
+                    query = self._dbase.query_bandwidth(self._taskid, case, provider, test_type)
+                else:
+                    query = self._dbase.query_avglatency(self._taskid, case, provider, test_type)
+                query = map(lambda x: list(x), zip(*query))
+                if query:
+                    table_head = [[type_dict["FrameSize"]] + map(lambda x: "  %4d  " % (x), query[0])]
+                    if item == "Avg":
+                        data = map(lambda x: item_dict[item] + "%.1f" % (x) + item_dict[item], query[1])
+                    else:
+                        data = map(lambda x: item_dict[item] + "%.2f" % (x) + item_dict[item], query[1])
+                    if item == "Mpps":
+                        line_table = map(lambda x: "%.2f" % (line_speed * 1000 / (8 * (x + 20))), query[0])
+                    table.append([type_dict[provider]] + data)
+        if table:
+            if item == "Mpps":
+                table.append([type_dict["line"]] + line_table)
+            table = table_head + table
+        return table
+
+    def get_frameloss_tabledata(self, case, test_type):
+        item = "Percent"
+        table = self.get_tabledata(case, test_type, item)
+        return table
+
+    def get_frameloss_chartdata(self, case, test_type):
+        result = self.get_frameloss_tabledata(case, test_type)
+        result = map(list, zip(*result))
+        return result
+
+    def get_framerate_tabledata(self, case, test_type):
+        item = "Mpps"
+        table = self.get_tabledata(case, test_type, item)
+        return table
+
+    def get_framerate_chartdata(self, case, test_type):
+        result = self.get_framerate_tabledata(case, test_type)
+        result = map(list, zip(*result))
+        return result
+
+    def get_latency_tabledata(self, case):
+        test_type = "latency"
+        item = "Avg"
+        table = self.get_tabledata(case, test_type, item)
+        return table
+
+    def get_latency_chartdata(self, case):
+        result = self.get_latency_tabledata(case)
+        result = map(list, zip(*result))
+        return result
+
+    def get_latency_bardata(self, case):
+        table_data = self.get_latency_tabledata(case)
+        result = []
+        if table_data:
+            ytitle = "Average Latency (uSec)"
+            category_names = map(lambda x: "FS:%4d" % int(float(x)) + "LOAD:50", table_data[0][1:])
+            bar_ = map(lambda x: x[0], table_data[1:])
+            data = map(lambda x: x[1:], table_data[1:])
+            result = [ytitle, category_names, bar_, data]
+        return result
+
+    def get_bardata(self, case, provider, test_type):
+        if test_type == "latency":
+            query = self._dbase.query_avglatency(self._taskid, case, provider, test_type)
+            item = "Avg"
+        else:
+            query = self._dbase.query_load(self._taskid, case, provider, test_type)
+            item = "Percent"
+
+        title_dict = {
+            "Avg": "Latency (uSec)",
+            "Percent": test_type + " (%)"
+        }
+        name_dict = {
+            "Avg": " LOAD:50",
+            "Percent": " OF:100 "
+        }
+        color_dict = {
+            "Avg": "latency",
+            "Percent": "loss"
+        }
+        ytitle = title_dict[item]
+        query = map(lambda x: list(x), zip(*query))
+        result = []
+        if query:
+            category_names = map(lambda x: "FS:%4d" % x + name_dict[item], query[0])
+            data = query[1:]
+            bar_ = [color_dict[item]]
+            result = [ytitle, category_names, bar_, data]
+        return result
+
+
+class TaskData(object):
+    def __init__(self, taskid, dbase):
+        self.__common = CommonData(taskid, dbase)
+        scenario_list = self.__common.get_scenariolist()
+        scenario_dic = {}
+        for scenario in scenario_list:
+            scenario_dic[scenario] = ScenarioData(taskid, dbase, scenario)
+        self.__dict__.update(scenario_dic)
+
+    @property
+    def common(self):
+        return self.__common
+
+
+class HistoryData(DataProvider):
+    def get_data(self, task_list, case, provider, ttype, item):
+        """
+        @provider  in ["fastlink", "rdp", "l2switch"]
+        @ttype in ["throughput", "frameloss", "latency"]
+        @item in ["avg", "ratep", "load"]
+        """
+        table = []
+        table_head = []
+        datas = []
+        sizes = []
+        for taskid in task_list:
+            if item == 'ratep':
+                query = self._dbase.query_bandwidth(taskid, case, provider, ttype)
+            else:
+                query = self._dbase.query_avglatency(taskid, case, provider, ttype)
+
+            if query:
+                data = {}
+                for size, value in query:
+                    data[size] = value
+                sizes.extend(data.keys())
+                sizes = {}.fromkeys(sizes).keys()
+                sizes.sort()
+                datas.append({taskid: data})
+
+        result = []
+        for data in datas:
+            print data
+            taskid = data.keys()[0]
+            data_th = self._dbase.query_taskdate(taskid)
+            testdata = data[taskid]
+            item = [data_th]
+            for size in sizes:
+                item.append(str(testdata.get(size, '')))
+            result.append(item)
+
+        if result:
+            head_th = "FrameSize (byte)"
+            table_head = [[head_th] + map(lambda x: "  %4d  " % (x), sizes)]
+            table = table_head + result
+
+        return table
+
+    def get_tasklist(self, count=5):
+        task_list = []
+        query = self._dbase.query_tasklist()
+        if query:
+            for item in query:
+                if item.TaskID <= self._taskid:
+                    task_list.append(item.TaskID)
+
+        task_list = task_list[-count:]
+        return task_list
+
+    def get_history_info(self, case):
+        providers = ["fastlink", "rdp", "l2switch"]
+        provider_dict = {"fastlink": "Fast Link ", "l2switch": "L2Switch ", "rdp": "Kernel RDP "}
+        ttype_dict = {
+            "throughput": "Throughput Testing ",
+            "frameloss": "Frame Loss Testing ",
+            "latency": "Latency Testing "
+        }
+
+        items_dict = {
+            "ratep": "RX Frame Rate(Mpps) ",
+            "avg": "Average Latency (uSec) "
+        }
+
+        task_list = self.get_tasklist()
+        result = []
+
+        ttypes = ["throughput", "frameloss", "latency"]
+        for ttype in ttypes:
+            content = {}
+            if ttype == "latency":
+                item = "avg"
+            else:
+                item = "ratep"
+
+            for provider in providers:
+                table_data = self.get_data(task_list, case, provider, ttype, item)
+                if table_data:
+                    data = {
+                        "title": provider_dict[provider] + items_dict[item],
+                        "data": table_data
+                    }
+                    content["title"] = ttype_dict[ttype]
+                    content.setdefault("data", [])
+                    content["data"].append(data)
+            if content:
+                result.append(content)
+        print "xxxxxxxxxxxxxx"
+        print result
+        print "xxxxxxxxxxxxxx"
+        return result
+
+
+def unit_test():
+    dbase = DbManage()
+    taskid = dbase.get_last_taskid()
+    hdata = HistoryData(taskid, dbase)
+    task_list = hdata.get_tasklist()
+
+    cdata = CommonData(taskid, dbase)
+    scenario_list = cdata.get_scenariolist()
+    print scenario_list
+
+    scenario = "Tn"
+    sdata = ScenarioData(taskid, dbase, scenario)
+
+    case_list = sdata.get_caselist()
+    print case_list
+
+    case = "Tn-1"
+
+    providers = ["fastlink", "rdp", "l2switch"]
+    ttypes = ["throughput", "frameloss"]
+    items = ["ratep", "load"]
+
+    for provider in providers:
+        for ttype in ttypes:
+            for item in items:
+                print provider
+                print ttype
+                print item
+                print hdata.get_data(task_list, case, provider, ttype, item)
+
+    hdata.get_history_info(case)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/reporters/report/html/__init__.py b/vstf/vstf/controller/reporters/report/html/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/report/html/html_base.py b/vstf/vstf/controller/reporters/report/html/html_base.py
new file mode 100755 (executable)
index 0000000..270ef39
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09.25
+# see license for license details
+__version__ = ''' '''
+
+import os
+from vstf.common.pyhtml import *
+
+
+class HtmlBase(object):
+    def __init__(self, provider, ofile='text.html'):
+        self._page = PyHtml('HtmlBase Text')
+        self._ofile = ofile
+        self._provider = provider
+        self._chapter = 1
+
+    def save(self):
+        if self._ofile:
+            os.system('rm -rf %s' % self._ofile)
+            self._page.output(self._ofile)
+
+    def as_string(self):
+        return self._page.as_string()
+
+    def add_table(self, data):
+        self._page.add_table(data)
+
+    def add_style(self):
+        style = self._provider.get_style()
+        self._page.add_style(style)
+
+    def create(self, is_save=True):
+        self.add_style()
+        self.create_story()
+        if is_save:
+            self.save()
+        return self.as_string()
+
+    def create_story(self):
+        raise NotImplementedError("abstract HtmlBase")
diff --git a/vstf/vstf/controller/reporters/report/html/html_text.py b/vstf/vstf/controller/reporters/report/html/html_text.py
new file mode 100755 (executable)
index 0000000..86505b8
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-24
+# see license for license details
+__version__ = ''' '''
+
+import logging
+
+LOG = logging.getLogger(__name__)
+import vstf.common.constants as cst
+from vstf.controller.reporters.report.html.html_base import *
+
+
+class HtmlCreator(HtmlBase):
+    def add_subject(self):
+        title = self._provider.get_subject()
+        self._page << H1(title)
+
+    def add_ovs(self):
+        title = "%s %s" % (self._chapter, self._provider.get_ovs_title())
+        self._page << H2(title)
+        data = self._provider.get_ovs_table()
+        self.add_table(data)
+        self._chapter += 1
+
+    def add_result(self):
+        title = "%s %s" % (self._chapter, self._provider.get_result_title())
+        self._page << H2(title)
+
+        section = 1
+        for ttype in cst.TTYPES:
+            data = self._provider.get_result_table(ttype)
+            if data:
+                title = "%s.%s %s" % (self._chapter, section, ttype.title())
+                self._page << H3(title)
+                self.add_table(data)
+                section += 1
+        self._chapter += 1
+
+    def create_story(self):
+        self.add_subject()
+        self.add_ovs()
+        self.add_result()
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/html-test.log", clevel=logging.INFO)
+
+    from vstf.controller.settings.html_settings import HtmlSettings
+    from vstf.controller.settings.data_settings import DataSettings
+
+    html_settings = HtmlSettings()
+    LOG.info(html_settings.settings)
+    data_settings = DataSettings()
+    LOG.info(data_settings.settings)
+
+    from vstf.controller.reporters.report.provider.html_provider import HtmlProvider
+    provider = HtmlProvider(data_settings.settings, html_settings.settings)
+    html = HtmlCreator(provider)
+
+    result = html.create()
+    print result
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/reporters/report/html/htmlcreator.py b/vstf/vstf/controller/reporters/report/html/htmlcreator.py
new file mode 100755 (executable)
index 0000000..e6c75ca
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-08-04
+# see license for license details
+__version__ = ''' '''
+
+import logging
+
+from vstf.controller.reporters.report.data_factory import TaskData
+from vstf.controller.database.dbinterface import DbManage
+from vstf.controller.reporters.report.html.html_base import *
+
+LOG = logging.getLogger(__name__)
+
+
+class HtmlvSwitchCreator(HtmlBase):
+    def __init__(self, task_data, provider, ofile='creator.html'):
+        HtmlBase.__init__(self, provider, ofile)
+        self._task = task_data
+        self._table_type = 'html'
+
+    def create_story(self):
+        self.add_subject()
+        self.add_gitinfo()
+        self.add_envinfo()
+        self.add_scenarios()
+
+    def add_subject(self):
+        job_name = "JOB_NAME: " + self._task.common.get_taskname()
+        self._page << H2(job_name)
+
+    def add_gitinfo(self):
+        self._page << H2("Trigger and Repository Info")
+
+        git_table = self._task.common.get_gitinfo_tabledata()
+        if git_table:
+            self.add_table(git_table)
+
+    def add_envinfo(self):
+        self._page << H2("System Environment Information")
+        env_table = self._task.common.get_systeminfo()
+        LOG.info(env_table)
+        if env_table:
+            self.add_table(env_table)
+
+    def add_scenarios(self):
+        scenario_list = self._task.common.get_scenariolist()
+        self._page << H2("Scenario List: " + ', '.join(scenario_list))
+        for scenario in scenario_list:
+            self._page << H2("Scenario: " + scenario)
+            data = getattr(self._task, scenario)
+            self.add_scenario(data)
+
+    def add_scenario(self, scenario_data):
+        case_list = scenario_data.get_caselist()
+        for case in case_list:
+            self.add_case(scenario_data, case)
+
+    def add_case(self, scenario_data, case):
+        case_name = self._task.common.get_casename(case)
+        title = "Case : %s (%s)" % (case, case_name)
+        self._page << H2(title)
+
+        provider_list = ["fastlink", "rdp", "l2switch"]
+        provider_dict = {"fastlink": "Fast Link", "l2switch": "L2Switch", "rdp": "Kernel RDP"}
+
+        for provider in provider_list:
+            if scenario_data.is_provider_start(case, provider):
+                title = " %s (%s_%s)" % (provider_dict[provider], case_name, provider)
+                self._page << H3(title)
+                test_types = ["throughput", "frameloss"]
+                for test_type in test_types:
+                    if scenario_data.is_type_provider_start(case, provider, test_type):
+                        self.add_casedata(scenario_data, case, provider, test_type)
+
+        if scenario_data.is_latency_start(case):
+            self.add_latency_result(scenario_data, case)
+
+    def add_casedata(self, scenario_data, case, provider, test_type):
+        table_content = scenario_data.get_summary_tabledata(case, provider, test_type, self._table_type)
+        if table_content:
+            title = "Test type:%s" % (test_type)
+            self._page << H4(title)
+            self.add_table(table_content)
+
+    def add_latency_result(self, scenario_data, case):
+        title = "Average Latency Summary"
+        table_content = scenario_data.get_latency_tabledata(case)
+        if table_content:
+            self._page << H2(title)
+            self.add_table(table_content)
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/html-creator.log", clevel=logging.INFO)
+
+    dbase = DbManage()
+    taskid = dbase.get_last_taskid()
+    task_data = TaskData(taskid, dbase)
+
+    from vstf.controller.settings.html_settings import HtmlSettings
+    from vstf.controller.reporters.report.provider.html_provider import StyleProvider
+
+    html_settings = HtmlSettings()
+    LOG.info(html_settings.settings)
+
+    provider = StyleProvider(html_settings.settings)
+    html = HtmlvSwitchCreator(task_data, provider)
+
+    result = html.create(True)
+    print result
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/reporters/report/pdf/__init__.py b/vstf/vstf/controller/reporters/report/pdf/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/report/pdf/element.py b/vstf/vstf/controller/reporters/report/pdf/element.py
new file mode 100755 (executable)
index 0000000..2528f2c
--- /dev/null
@@ -0,0 +1,781 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-05-04
+# see license for license details
+__version__ = ''' '''
+__doc__ = """
+it contains the base element for pdf
+eImage is used to draw picture on the pdf document
+eDataTable is used to draw table on the pdf document
+eGraphicsTable is used to draw plot on the pdf document
+eParagraph is used to draw text on the pdf document
+"""
+from reportlab.platypus import Image, Table
+from reportlab.graphics.shapes import Drawing
+from reportlab.graphics.charts.lineplots import LinePlot
+from reportlab.graphics.charts.linecharts import HorizontalLineChart
+from reportlab.platypus.paragraph import Paragraph
+from reportlab.graphics.widgets.markers import makeMarker
+from reportlab.graphics.charts.legends import Legend
+from reportlab.graphics.charts.textlabels import Label
+from reportlab.graphics.charts.axes import XValueAxis
+from reportlab.graphics.shapes import Group
+from reportlab.graphics.charts.barcharts import VerticalBarChart
+from vstf.controller.reporters.report.pdf.styles import *
+
+
+class eImage(Image):
+    """ an image(digital picture)which contains the function of auto zoom picture """
+
+    def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1, hAlign='CENTRE',
+                 vAlign='BOTTOM'):
+        Image.__init__(self, filename, None, None, kind, mask, lazy)
+        print height, width
+        print self.drawHeight, self.drawWidth
+        if self.drawWidth * height > self.drawHeight * width:
+            self.drawHeight = width * self.drawHeight / self.drawWidth
+            self.drawWidth = width
+        else:
+            self.drawWidth = height * self.drawWidth / self.drawHeight
+            self.drawHeight = height
+        self.hAlign = hAlign
+        self.vAlign = vAlign
+        print self.drawHeight, self.drawWidth
+
+
+class eTable(object):
+    """ an abstract table class, which is contains the base functions to create table """
+
+    def __init__(self, data, style=TableStyle(name="default")):
+        self._tablestyle = style
+        self._table = []
+        self._spin = False
+        self._colWidths = None
+        self._data = self.analysisData(data)
+        if self._data:
+            self.create()
+
+    def analysisData(self, data):
+        raise NotImplementedError("abstract eTable")
+
+    def create(self):
+        self._table = Table(self._data, style=self._style, splitByRow=1)
+        self._table.hAlign = self._tablestyle.table_hAlign
+        self._table.vAlign = self._tablestyle.table_vAlign
+        self._table.colWidths = self._tablestyle.table_colWidths
+        if self._spin or self._colWidths:
+            self._table.colWidths = self._colWidths
+        self._table.rowHeights = self._tablestyle.table_rowHeights
+
+    @property
+    def table(self):
+        return self._table
+
+
+class eCommonTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1.2, colors.black)
+        ]
+        return data
+
+
+class eConfigTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('SPAN', (2, 0), (3, 0)),
+            ('SPAN', (2, 1), (3, 1)),
+            ('SPAN', (2, 8), (3, 8)),
+            ('SPAN', (2, 9), (3, 9)),
+            ('SPAN', (2, 10), (3, 10)),
+            ('SPAN', (0, 0), (0, 7)),
+            ('SPAN', (0, 8), (0, 10)),
+            ('SPAN', (0, 11), (0, 19)),
+            ('SPAN', (1, 2), (1, 6)),
+            ('SPAN', (1, 12), (1, 13)),
+            ('SPAN', (1, 14), (1, 16)),
+            ('SPAN', (1, 17), (1, 19)),
+            ('SPAN', (2, 3), (2, 6))
+        ]
+        return data
+
+
+class eSummaryTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('SPAN', (0, 0), (0, 1)),
+            ('SPAN', (1, 0), (4, 0)),
+            ('SPAN', (5, 0), (-1, 0))
+        ]
+        return data
+
+
+class eGitInfoTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('SPAN', (0, 0), (0, 2)),
+            ('SPAN', (0, 3), (0, 5)),
+            ('SPAN', (0, 6), (0, 8))
+        ]
+        return data
+
+
+class eScenarioTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('ALIGN', (2, 1), (-1, -1), 'LEFT'),
+            ('SPAN', (0, 1), (0, 6)),
+            ('SPAN', (0, 7), (0, 12)),
+            ('SPAN', (0, 13), (0, 16)),
+            ('SPAN', (0, 17), (0, 20))
+        ]
+        return data
+
+
+class eOptionsTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('SPAN', (2, 0), (4, 0)),
+            ('SPAN', (2, 1), (4, 1)),
+            ('SPAN', (0, 0), (0, -1)),
+            ('SPAN', (1, 2), (1, 16)),
+            ('SPAN', (1, 17), (1, 19)),
+            ('SPAN', (1, 20), (1, 22)),
+            ('SPAN', (1, 23), (1, 24)),
+            ('SPAN', (2, 2), (2, 4)),
+            ('SPAN', (2, 5), (2, 12)),
+            ('SPAN', (2, 13), (2, 16)),
+            ('SPAN', (2, 17), (2, 19)),
+            ('SPAN', (2, 20), (2, 22)),
+            ('SPAN', (2, 23), (2, 24))
+        ]
+        return data
+
+
+class eProfileTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('SPAN', (0, 1), (0, -1)),
+            ('SPAN', (1, 0), (2, 0)),
+        ]
+        return data
+
+
+class eDataTable(eTable):
+    def analysisData(self, data):
+        result = data
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+            ('LEADING', (0, 0), (-1, -1), 18),
+            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
+            ('BOX', (0, 0), (-1, -1), 1, colors.black),
+            ('LINEBEFORE', (1, 0), (1, -1), 0.8, colors.black),
+            # ('LINEBEFORE', (3, 0), (3, -1), 1, colors.black),
+            # ('LINEBEFORE', (5, 0), (5, -1), 1, colors.black),
+            ('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.black),
+            # ('SPAN', (0, 0), (0, 1)),
+            # ('SPAN', (1, 0), (2, 0)),
+            # ('SPAN', (3, 0), (4, 0))
+        ]
+        if self._spin is True:
+            print "start spin"
+            result = map(list, zip(*result))
+            style = []
+            for value in self._style:
+                value = list(value)
+                value[1] = (value[1][1], value[1][0])
+                value[2] = (value[2][1], value[2][0])
+                if value[0] == 'LINEBELOW':
+                    value[0] = 'LINEAFTER'
+                elif value[0] == 'LINEBEFORE':
+                    value[0] = 'LINEABOVE'
+                value = tuple(value)
+                style.append(value)
+            self._style = style
+        return result
+
+
+class eGraphicsTable(eTable):
+    def analysisData(self, data):
+        self._style = [
+            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
+            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
+        ]
+        return data
+
+
+class noScaleXValueAxis(XValueAxis):
+    def __init__(self):
+        XValueAxis.__init__(self)
+
+    def makeTickLabels(self):
+        g = Group()
+        if not self.visibleLabels: return g
+
+        f = self._labelTextFormat  # perhaps someone already set it
+        if f is None:
+            f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str)
+        elif f is str and self._allIntTicks():
+            f = '%.0f'
+        elif hasattr(f, 'calcPlaces'):
+            f.calcPlaces(self._tickValues)
+        post = self.labelTextPostFormat
+        scl = self.labelTextScale
+        pos = [self._x, self._y]
+        d = self._dataIndex
+        pos[1 - d] = self._labelAxisPos()
+        labels = self.labels
+        if self.skipEndL != 'none':
+            if self.isXAxis:
+                sk = self._x
+            else:
+                sk = self._y
+            if self.skipEndL == 'start':
+                sk = [sk]
+            else:
+                sk = [sk, sk + self._length]
+                if self.skipEndL == 'end':
+                    del sk[0]
+        else:
+            sk = []
+
+        nticks = len(self._tickValues)
+        nticks1 = nticks - 1
+        for i, tick in enumerate(self._tickValues):
+            label = i - nticks
+            if label in labels:
+                label = labels[label]
+            else:
+                label = labels[i]
+            if f and label.visible:
+                v = self.scale(i)
+                if sk:
+                    for skv in sk:
+                        if abs(skv - v) < 1e-6:
+                            v = None
+                            break
+                if v is not None:
+                    if scl is not None:
+                        t = tick * scl
+                    else:
+                        t = tick
+                    if isinstance(f, str):
+                        txt = f % t
+                    elif isSeq(f):
+                        # it's a list, use as many items as we get
+                        if i < len(f):
+                            txt = f[i]
+                        else:
+                            txt = ''
+                    elif hasattr(f, '__call__'):
+                        if isinstance(f, TickLabeller):
+                            txt = f(self, t)
+                        else:
+                            txt = f(t)
+                    else:
+                        raise ValueError('Invalid labelTextFormat %s' % f)
+                    if post: txt = post % txt
+                    pos[d] = v
+                    label.setOrigin(*pos)
+                    label.setText(txt)
+
+                    # special property to ensure a label doesn't project beyond the bounds of an x-axis
+                    if self.keepTickLabelsInside:
+                        if isinstance(self, XValueAxis):  # not done yet for y axes
+                            a_x = self._x
+                            if not i:  # first one
+                                x0, y0, x1, y1 = label.getBounds()
+                                if x0 < a_x:
+                                    label = label.clone(dx=label.dx + a_x - x0)
+                            if i == nticks1:  # final one
+                                a_x1 = a_x + self._length
+                                x0, y0, x1, y1 = label.getBounds()
+                                if x1 > a_x1:
+                                    label = label.clone(dx=label.dx - x1 + a_x1)
+                    g.add(label)
+
+        return g
+
+    def ___calcScaleFactor(self):
+        """Calculate the axis' scale factor.
+        This should be called only *after* the axis' range is set.
+        Returns a number.
+        """
+        self._scaleFactor = self._length / (len(self._tickValues) + 1)
+        return self._scaleFactor
+
+    def scale(self, value):
+        """Converts a numeric value to a plotarea position.
+        The chart first configures the axis, then asks it to
+        """
+        assert self._configured, "Axis cannot scale numbers before it is configured"
+        if value is None: value = 0
+        # this could be made more efficient by moving the definition of org and sf into the configuration
+        org = (self._x, self._y)[self._dataIndex]
+        sf = self._length / (len(self._tickValues) + 1)
+        if self.reverseDirection:
+            sf = -sf
+            org += self._length
+        return org + sf * (value + 1)
+
+
+class noScaleLinePlot(LinePlot):
+    def __init__(self):
+        LinePlot.__init__(self)
+        self.xValueAxis = noScaleXValueAxis()
+
+    def calcPositions(self):
+        """Works out where they go.
+
+        Sets an attribute _positions which is a list of
+        lists of (x, y) matching the data.
+        """
+        self._seriesCount = len(self.data)
+        self._rowLength = max(map(len, self.data))
+
+        self._positions = []
+        for rowNo in range(len(self.data)):
+            line = []
+            len_row = len(self.data[rowNo])
+            for colNo in range(len_row):
+                datum = self.data[rowNo][colNo]  # x, y value
+                x = self.x + self.width / (len_row + 1) * (colNo + 1)
+                self.xValueAxis.labels[colNo].x = self.x + self.width / (len_row + 1) * (colNo + 1)
+                y = self.yValueAxis.scale(datum[1])
+                #               print self.width, " ", x
+                line.append((x, y))
+            self._positions.append(line)
+
+
+# def _innerDrawLabel(self, rowNo, colNo, x, y):
+#        return None
+class eLinePlot(object):
+    def __init__(self, data, style):
+        self._lpstyle = style
+        self._linename = data[0]
+        self._data = self.analysisData(data[1:])
+        if self._data:
+            self.create()
+
+    @property
+    def draw(self):
+        return self._draw
+
+    def analysisData(self, data):
+        columns = len(data)
+        # print data
+        data = map(list, zip(*data))
+        rows = len(data)
+
+        for i in range(rows):
+            for j in range(columns):
+                data[i][j] = float(data[i][j])
+        self._linename = self._linename[1:]
+        """
+        delcnt = 0
+        delrows = []
+        for i in range(columns):
+            delrows.append(0.0)
+        del_line = [self._linename[0]]
+        for i in range(rows):
+           for j in range(columns):
+              data[i][j] = float(data[i][j])
+           if data[i] == delrows:
+               delcnt += 1
+               del_line.append(self._linename[i])
+        for i in range(delcnt):
+            data.remove(delrows)
+        for name in del_line:
+            self._linename.remove(name)
+
+        rows = len(data)
+        """
+        # print rows
+        # print data
+        xvalueSteps = data[0]
+        xvalueMin = data[0][0]
+        xvalueMax = data[0][0]
+        yvalueMin = data[1][0]
+        yvalueMax = data[1][0]
+        yvalueSteps = []
+        result = []
+        for j in range(columns):
+            if xvalueMin > data[0][j]:
+                xvalueMin = data[0][j]
+            if xvalueMax < data[0][j]:
+                xvalueMax = data[0][j]
+
+        for i in range(rows - 1):
+            lst = []
+            for j in range(columns):
+                lst.append((data[0][j], data[i + 1][j]))
+                if yvalueMin > data[i + 1][j]:
+                    yvalueMin = data[i + 1][j]
+                if yvalueMax < data[i + 1][j]:
+                    yvalueMax = data[i + 1][j]
+                yvalueSteps.append(int(data[i + 1][j] * 2.5) / 2.5)
+            result.append(tuple(lst))
+        xvalueMin = int(xvalueMin) / 100 * 100
+        xvalueMax = int(xvalueMax) / 100 * 100 + 200
+        yvalueMin = int(yvalueMin) * 1.0 - 1
+        if yvalueMin < 0:
+            yvalueMin = 0.0
+        yvalueMax = int(yvalueMax) + 2.0
+        yvalueSteps.append(yvalueMin)
+        yvalueSteps.append(yvalueMax)
+        yvalueSteps = {}.fromkeys(yvalueSteps).keys()
+
+        self._xvalue = (xvalueMin, xvalueMax, xvalueSteps)
+        self._yvalue = (yvalueMin, yvalueMax, yvalueSteps)
+        print result
+        return result
+
+    def create(self):
+        lpw = self._lpstyle.width
+        lph = self._lpstyle.height
+        draw = Drawing(lpw, lph)
+        line_cnts = len(self._linename)
+        #        lp = noScaleLinePlot()
+        lp = LinePlot()
+        lg_line = (line_cnts + 3) / 4
+        lp.x = self._lpstyle.left
+        lp.y = self._lpstyle.bottom
+
+        lp.height = lph - self._lpstyle.bottom * (lg_line + 1.5)
+        lp.width = lpw - lp.x * 2
+        lp.data = self._data
+        lp.joinedLines = 1
+        lp.strokeWidth = self._lpstyle.strokeWidth
+        line_cnts = len(self._data)
+        sytle_cnts = len(self._lpstyle.linestyle)
+        color_paris = []
+        for i in range(line_cnts):
+            styleIndex = i % sytle_cnts
+            lp.lines[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
+            lp.lines[i].symbol = makeMarker(self._lpstyle.linestyle[styleIndex][1])
+            lp.lines[i].strokeWidth = self._lpstyle.linestyle[styleIndex][2]
+            color_paris.append((self._lpstyle.linestyle[styleIndex][0], self._linename[i]))
+        #            lp.lineLabels[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
+
+        lp.lineLabelFormat = self._lpstyle.format[0]
+
+        lp.strokeColor = self._lpstyle.strokeColor
+
+        lp.xValueAxis.valueMin, lp.xValueAxis.valueMax, lp.xValueAxis.valueSteps = self._xvalue
+        #       valueMin, valueMax, xvalueSteps = self._xvalue
+        #       lp.xValueAxis.valueStep = (lp.xValueAxis.valueMax - lp.xValueAxis.valueMin)/len(xvalueSteps)
+        #       lp.xValueAxis.valueSteps = map(lambda x: str(x), xvalueSteps)
+
+        lp.yValueAxis.valueMin, lp.yValueAxis.valueMax, lp.yValueAxis.valueSteps = self._yvalue
+
+
+
+        #       lp.xValueAxis.forceZero = 0
+        #       lp.xValueAxis.avoidBoundFrac = 1
+        #       lp.xValueAxis.tickDown = 3
+        #       lp.xValueAxis.visibleGrid = 1
+        #       lp.xValueAxis.categoryNames = '64 256 512 1400 1500 4096'.split(' ')
+
+        lp.xValueAxis.labelTextFormat = self._lpstyle.format[1]
+        lp.yValueAxis.labelTextFormat = self._lpstyle.format[2]
+
+        delsize = int(lp.xValueAxis.valueMax / 2000)
+        lp.xValueAxis.labels.fontSize = self._lpstyle.labelsfont
+        lp.xValueAxis.labels.angle = 25
+
+        lp.yValueAxis.labels.fontSize = self._lpstyle.labelsfont
+        lp.lineLabels.fontSize = self._lpstyle.labelsfont - delsize
+        draw.add(lp)
+
+        lg = Legend()
+        lg.colorNamePairs = color_paris
+        lg.fontName = 'Helvetica'
+        lg.fontSize = 7
+
+        lg.x = self._lpstyle.left * 3
+        lg.y = self._lpstyle.bottom * (1 + lg_line) + lp.height
+
+        lg.dxTextSpace = 5
+        lg.dy = 5
+        lg.dx = 20
+        lg.deltax = 60
+        lg.deltay = 0
+        lg.columnMaximum = 1
+        lg.alignment = 'right'
+        draw.add(lg)
+        self._draw = draw
+
+
+class eHorizontalLineChart(object):
+    def __init__(self, data, style):
+        self._lcstyle = style
+        if len(data) < 1:
+            return
+        self._linename = data[0]
+        self._data = self.analysisData(data[1:])
+        if self._data:
+            self.create()
+
+    @property
+    def draw(self):
+        return self._draw
+
+    def analysisData(self, data):
+        columns = len(data)
+        data = map(list, zip(*data))
+        self._catNames = data[0]
+        self._linename = self._linename[1:]
+        data = data[1:]
+        rows = len(data)
+
+        yvalueMin = float(data[0][0])
+        yvalueMax = float(data[0][0])
+        yvalueSteps = []
+        result = []
+
+        for rowNo in range(rows):
+            for columnNo in range(columns):
+                data[rowNo][columnNo] = float(data[rowNo][columnNo])
+                if yvalueMin > data[rowNo][columnNo]:
+                    yvalueMin = data[rowNo][columnNo]
+                if yvalueMax < data[rowNo][columnNo]:
+                    yvalueMax = data[rowNo][columnNo]
+                yvalueSteps.append(int(data[rowNo][columnNo] * 1.0) / 1.0)
+            result.append(tuple(data[rowNo]))
+
+        yvalueMin = int(yvalueMin) * 1.0 - 1
+        if yvalueMin < 0:
+            yvalueMin = 0.0
+        yvalueMax = int(yvalueMax) + 2.0
+        yvalueSteps.append(yvalueMin)
+        yvalueSteps.append(yvalueMax)
+        yvalueSteps = {}.fromkeys(yvalueSteps).keys()
+
+        self._value = (yvalueMin, yvalueMax, yvalueSteps)
+        print result
+        return result
+
+    def create(self):
+        dw = self._lcstyle.width
+        dh = self._lcstyle.height
+        draw = Drawing(dw, dh)
+
+        lc = HorizontalLineChart()
+        line_cnts = len(self._linename)
+
+        lg_line = (line_cnts + 3) / 4
+        lc.height = dh - self._lcstyle.bottom * (lg_line + 1.5)
+        lc.width = dw - lc.x * 2
+        lc.x = self._lcstyle.left
+        lc.y = self._lcstyle.bottom
+
+        lc.data = self._data
+
+        lc.strokeColor = self._lcstyle.strokeColor
+        lc.strokeWidth = self._lcstyle.strokeWidth
+        lc.useAbsolute = 1
+        lc.groupSpacing = lc.width * 2.0 / len(self._catNames)
+        lc.joinedLines = 1
+        lc.lineLabelFormat = self._lcstyle.format[0]
+
+        lc.valueAxis.valueMin, lc.valueAxis.valueMax, lc.valueAxis.valueSteps = self._value
+        lc.valueAxis.labelTextFormat = self._lcstyle.format[1]
+        lc.valueAxis.labels.fontSize = self._lcstyle.labelsfont
+
+        lc.categoryAxis.categoryNames = self._catNames
+        lc.categoryAxis.labels.boxAnchor = 'ne'
+        lc.categoryAxis.labels.dx = lc.width / 2.0 / len(self._catNames)
+        lc.categoryAxis.labels.dy = -6
+        lc.categoryAxis.labels.angle = 10
+        lc.categoryAxis.labels.fontSize = self._lcstyle.labelsfont
+        #        lc.categoryAxis.visibleGrid = 1
+        #        lc.categoryAxis.tickUp = 100
+        #        lc.categoryAxis.tickDown = 50
+        #        lc.categoryAxis.gridEnd = dh
+        sytle_cnts = len(self._lcstyle.linestyle)
+        color_paris = []
+        for i in range(line_cnts):
+            styleIndex = i % sytle_cnts
+            lc.lines[i].strokeColor = self._lcstyle.linestyle[styleIndex][0]
+            lc.lines[i].symbol = makeMarker(self._lcstyle.linestyle[styleIndex][1])
+            lc.lines[i].strokeWidth = self._lcstyle.linestyle[styleIndex][2]
+            color_paris.append((self._lcstyle.linestyle[styleIndex][0], self._linename[i]))
+
+        lc.lineLabels.fontSize = self._lcstyle.labelsfont - 2
+
+        draw.add(lc)
+
+        lg = Legend()
+        lg.colorNamePairs = color_paris
+        lg.fontName = 'Helvetica'
+        lg.fontSize = 7
+        #        lg.x = dw /2
+        #        lg.y = self._lcstyle.bottom *(1.5 + lg_line)
+
+        lg.x = self._lcstyle.left * 3
+        lg.y = self._lcstyle.bottom * (1 + lg_line) + lc.height
+
+        lg.dxTextSpace = 5
+        lg.dy = 5
+        lg.dx = 20
+        lg.deltax = 60
+        lg.deltay = 0
+        lg.columnMaximum = 1
+        lg.alignment = 'right'
+        draw.add(lg)
+        self._draw = draw
+
+
+class eBarChartColumn(object):
+    def __init__(self, data, style):
+        self._bcstyle = style
+        if len(data) < 4:
+            return
+        self._data = self.analysisData(data)
+        if self._data:
+            self.create()
+
+    @property
+    def draw(self):
+        return self._draw
+
+    def analysisData(self, data):
+        self._ytitle = data[0]
+        self._name = data[1]
+        self._bar = data[2]
+        bar_data = data[3]
+        result = []
+        for bar in bar_data:
+            bar = map(lambda x: float(x), bar)
+            result.append(tuple(bar))
+        return result
+
+    def create(self):
+        dw = self._bcstyle.width
+        dh = self._bcstyle.height
+        draw = Drawing(dw, dh)
+
+        bc = VerticalBarChart()
+        bar_cnt = len(self._bar)
+        lg_line = (bar_cnt + 3) / 4
+
+        bc.width = dw - self._bcstyle.left - self._bcstyle.right
+        bc.height = dh - self._bcstyle.top - self._bcstyle.bottom
+        if bar_cnt > 1:
+            bc.height -= lg_line * 15
+
+        bc.x = self._bcstyle.left
+        bc.y = self._bcstyle.bottom
+        color_paris = []
+        for i in range(bar_cnt):
+            bc.bars[i].fillColor = self._bcstyle.pillarstyle[self._bar[i]][0]
+            color_paris.append((self._bcstyle.pillarstyle[self._bar[i]][0], self._bar[i]))
+
+        bc.fillColor = self._bcstyle.background
+        bc.barLabels.fontName = 'Helvetica'
+        bc.barLabelFormat = self._bcstyle.pillarstyle[self._bar[0]][1]
+        bc.barLabels.fontSize = self._bcstyle.labelsfont
+        bc.barLabels.dy = self._bcstyle.labelsfont
+        bc.valueAxis.labels.fontName = 'Helvetica'
+        bc.valueAxis.labels.fontSize = self._bcstyle.labelsfont
+        bc.valueAxis.forceZero = 1
+        bc.valueAxis.valueMin = 0
+
+        bc.data = self._data
+        bc.barSpacing = self._bcstyle.barSpacing
+        bc.groupSpacing = self._bcstyle.groupSpacing / bar_cnt
+        bc.valueAxis.avoidBoundFrac = 1
+        bc.valueAxis.gridEnd = dw - self._bcstyle.right
+        bc.valueAxis.tickLeft = self._bcstyle.tick
+        bc.valueAxis.visibleGrid = 1
+        bc.categoryAxis.categoryNames = self._name
+        bc.categoryAxis.tickDown = self._bcstyle.tick
+        bc.categoryAxis.labels.fontName = 'Helvetica'
+        bc.categoryAxis.labels.fontSize = self._bcstyle.labelsfont
+        bc.categoryAxis.labels.dy = -27
+        bc.categoryAxis.labels.angle = -90
+        draw.add(bc)
+        lb = Label()
+        lb.fontName = 'Helvetica'
+        lb.fontSize = 7
+        lb.x = 12
+        lb.y = 80
+        lb.angle = 90
+        lb.textAnchor = 'middle'
+        lb.maxWidth = 100
+        lb.height = 20
+        lb._text = self._ytitle
+        draw.add(lb)
+        if bar_cnt > 1:
+            lg = Legend()
+            lg.colorNamePairs = color_paris
+            lg.fontName = 'Helvetica'
+            lg.fontSize = 7
+
+            lg.x = self._bcstyle.left + bc.width / (bar_cnt + 1)
+            lg.y = dh - self._bcstyle.top - lg_line * 5
+
+            lg.dxTextSpace = 5
+            lg.dy = 5
+            lg.dx = 25
+            lg.deltax = 80
+            lg.deltay = 0
+            lg.columnMaximum = 1
+            lg.alignment = 'right'
+            draw.add(lg)
+
+        self._draw = draw
+
+
+class eParagraph(object):
+    def __init__(self, data, style):
+        self._pstyle = style
+        self._data = self.analysisData(data)
+        self.create()
+
+    def analysisData(self, data):
+        result = ""
+        for dstr in data:
+            if self._pstyle.name == 'ps_body':
+                #               dstr = "<i>" + dstr + "</i><br/>"
+                dstr = dstr + "<br/>"
+            else:
+                dstr = dstr + "<br/>"
+            result += dstr
+        return result
+
+    def create(self):
+        self._para = Paragraph(self._data, self._pstyle)
+
+    @property
+    def para(self):
+        return self._para
diff --git a/vstf/vstf/controller/reporters/report/pdf/pdfcreator.py b/vstf/vstf/controller/reporters/report/pdf/pdfcreator.py
new file mode 100755 (executable)
index 0000000..50b3bc6
--- /dev/null
@@ -0,0 +1,446 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-05-29
+# see license for license details
+__version__ = ''' '''
+
+import os
+
+from vstf.controller.reporters.report.pdf.styles import TemplateStyle
+from vstf.controller.reporters.report.pdf.pdftemplate import PdfVswitch
+from vstf.controller.reporters.report.pdf.story import TitleStory, SpaceStory, ImageStory, LineChartStory, \
+    LinePlotStory, uTableStory, Story, TableOfContentsStory, PageBreakStory, ParagraphStory, BarChartStory, cTableStory
+from vstf.controller.reporters.report.data_factory import CommonData, ScenarioData, HistoryData
+from vstf.controller.database.dbinterface import DbManage
+import vstf.controller
+
+
+class LetterOrder(object):
+    def __init__(self):
+        self.lettertable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        self._cur = 0
+        self._len = len(self.lettertable)
+
+    def get(self):
+        return self.lettertable[self._cur]
+
+    def pre(self):
+        self._cur = (self._cur + self._len - 1) % self._len
+
+    def next(self):
+        self._cur = (self._cur + 1) % self._len
+
+
+class PdfBase(object):
+    def __init__(self):
+        self._case = ''
+        self._ofile = ''
+        self._title = []
+        self._story = []
+        self._rootdir = os.path.dirname(vstf.controller.__file__) + '/'
+        self._pdf = None
+
+    def create_pdf(self):
+        style = TemplateStyle(name='default')
+        title = self._title
+        logo = [self._rootdir + "res/logo.jpg"]
+        header = ['']
+        footer = [""]
+        note = ['', '']
+        output = [self._ofile]
+        self._pdf = PdfFrameLoss(style, title, logo, header, footer, output, note)
+
+    def save_pdf(self):
+        self._pdf.generate(self._story)
+
+    def add_coverpage(self):
+        story = Story()
+        story = PageBreakStory(story)
+        self._story += story.storylist
+
+    def create_story(self):
+        raise NotImplementedError("abstract PdfBase")
+
+    def create(self):
+        self.create_pdf()
+        self.create_story()
+        self.save_pdf()
+
+
+class PdfvSwitchCreator(PdfBase):
+    def __init__(self, ofile, common_data, scenario_data, history_data):
+        PdfBase.__init__(self)
+        self._common = common_data
+        self._result = scenario_data
+        self._history = history_data
+        self._ofile = ofile
+        self._chapterid = 0
+        self._appendixid = LetterOrder()
+
+    def create_pdf(self):
+        style = TemplateStyle(name='default')
+        title = self._result.get_covertitle()
+        logo = [self._rootdir + "res/logo.jpg"]
+        header = ['']
+        footer = [""]
+        note = ['', '']
+        output = [self._ofile]
+        self._pdf = PdfVswitch(style, title, logo, header, footer, output, note)
+
+    def get_chapterid(self):
+        self._chapterid = self._chapterid + 1
+        return self._chapterid
+
+    def create_story(self):
+        self.add_coverpage()
+        self.add_table_of_contents()
+        # self.add_contact()
+        # self.add_overview()
+        self.add_scenario()
+        # self.add_info()
+        # self.add_appendix()
+        self.add_historys()
+
+    def add_info(self):
+        self.add_systeminfo()
+        self.add_gitinfo()
+        self.add_profile_parameters()
+        self.add_testing_options()
+
+    def add_contact(self):
+        story = Story()
+        story = SpaceStory(story)
+        title = ["", "", "", "Reporter"]
+        body = self._common.get_contact()
+        story = TitleStory(story, data=title, style=7)
+        story = ParagraphStory(story, data=body)
+        self._story += story.storylist
+
+    def add_table_of_contents(self):
+        story = Story()
+        story = TableOfContentsStory(story)
+        self._story += story.storylist
+
+    def add_overview(self):
+        story = Story()
+        story = PageBreakStory(story)
+
+        chapterid = self.get_chapterid()
+        title = ["%d.Overview" % (chapterid)]
+        body = [""]
+        story = TitleStory(story, data=title, style=1)
+        story = ParagraphStory(story, data=body)
+
+        sectionid = 1
+        title = ["%d.%d Components under Test" % (chapterid, sectionid)]
+        body = self._common.get_components()
+        story = TitleStory(story, data=title, style=2)
+        story = ParagraphStory(story, data=body)
+
+        sectionid = sectionid + 1
+        title = ["%d.%d Test" % (chapterid, sectionid)]
+        body = self._result.get_test()
+        story = TitleStory(story, data=title, style=2)
+        story = ParagraphStory(story, data=body)
+
+        sectionid = sectionid + 1
+        title = ["%d.%d Configuration" % (chapterid, sectionid)]
+        story = TitleStory(story, data=title, style=2)
+
+        title = ["Software"]
+        body = self._common.get_software()
+        story = TitleStory(story, data=title, style=6)
+        story = ParagraphStory(story, data=body)
+
+        title = ["Hardware"]
+        body = self._common.get_hardware()
+        story = TitleStory(story, data=title, style=6)
+        story = ParagraphStory(story, data=body)
+        self._story += story.storylist
+
+    def add_scenario(self):
+        case_list = self._result.get_caselist()
+        for case in case_list:
+            self.add_case(case)
+
+    def add_case(self, case):
+        story = Story()
+        chapterid = self.get_chapterid()
+
+        title = ["%d. Case : %s (%s)" % (chapterid, case, self._common.get_casename(case))]
+
+        tools = self._result.get_test_tools(case)
+        pic = self._common.get_casefigure(case, tools)
+        print pic
+
+        story = TitleStory(story, data=title, style=1)
+        story = SpaceStory(story)
+        story = ImageStory(story, data=[self._rootdir + pic])
+        story = SpaceStory(story)
+
+        sectionid = 1
+        story = self.add_summary(story, chapterid, sectionid, case)
+        story = SpaceStory(story)
+
+        if self._result.is_throughput_start(case):
+            sectionid = sectionid + 1
+            story = self.add_throughput_result(story, chapterid, sectionid, case)
+
+        if self._result.is_frameloss_start(case):
+            sectionid = sectionid + 1
+            story = self.add_frameloss_result(story, chapterid, sectionid, case)
+
+        if self._result.is_latency_start(case):
+            sectionid = sectionid + 1
+            story = self.add_latency_result(story, chapterid, sectionid, case)
+
+        story = SpaceStory(story)
+        story = SpaceStory(story)
+        self._story += story.storylist
+
+    def add_summary(self, story, chapterid, sectionid, case):
+        title = ["%d.%d Summary" % (chapterid, sectionid)]
+        story = TitleStory(story, data=title, style=2)
+        provider_list = ["fastlink", "rdp", "l2switch"]
+        provider_dict = {"fastlink": "Fast Link", "l2switch": "L2Switch", "rdp": "Kernel RDP"}
+        unitid = 1
+        case_name = self._common.get_casename(case)
+        for provider in provider_list:
+            if self._result.is_provider_start(case, provider):
+                title = ["%d.%d.%d %s (%s_%s)" % (
+                chapterid, sectionid, unitid, provider_dict[provider], case_name, provider)]
+                unitid = unitid + 1
+                story = TitleStory(story, data=title, style=6)
+                test_types = ["throughput", "frameloss"]
+                for test_type in test_types:
+                    if self._result.is_type_provider_start(case, provider, test_type):
+                        story = self.add_summary_type(story, case, provider, test_type)
+        return story
+
+    def add_summary_type(self, story, case, provider, test_type):
+        bar_list = [test_type, "latency"]
+        for item in bar_list:
+            bar_data = self._result.get_bardata(case, provider, item)
+            story = SpaceStory(story)
+            story = BarChartStory(story, data=bar_data)
+
+        table_content = self._result.get_summary_tabledata(case, provider, test_type)
+        story = SpaceStory(story)
+        story = cTableStory(story, data=table_content, style=3)
+        story = SpaceStory(story)
+        return story
+
+    def add_throughput_result(self, story, chapterid, sectionid, case):
+        title = ["%d.%d Throughput " % (chapterid, sectionid)]
+        story = TitleStory(story, data=title, style=2)
+        unitid = 1
+        title = ["%d.%d.%d Summary" % (chapterid, sectionid, unitid)]
+        story = TitleStory(story, data=title, style=6)
+
+        test_type = "throughput"
+        unit = 'RX Frame Rate'
+        chart_data = self._result.get_frameloss_chartdata(case, test_type)
+        table_data = self._result.get_frameloss_tabledata(case, test_type)
+        title = [unit + ' (%)']
+        story = TitleStory(story, data=title, style=6)
+        #       story = SpaceStory(story)
+        #       story = LinePlotStory(story, data=chart_data)
+        story = SpaceStory(story)
+        story = uTableStory(story, data=table_data)
+        story = SpaceStory(story)
+
+        unit = 'Frame Loss Rate'
+        title = [unit + ' (Mpps)']
+
+        chart_data = self._result.get_framerate_chartdata(case, test_type)
+        table_data = self._result.get_framerate_tabledata(case, test_type)
+        story = TitleStory(story, data=title, style=6)
+        story = SpaceStory(story)
+        story = LinePlotStory(story, data=chart_data)
+        story = SpaceStory(story)
+        story = uTableStory(story, data=table_data)
+        story = SpaceStory(story)
+        return story
+
+    def add_frameloss_result(self, story, chapterid, sectionid, case):
+        title = ["%d.%d Frame Loss Rate " % (chapterid, sectionid)]
+        story = TitleStory(story, data=title, style=2)
+        unitid = 1
+        title = ["%d.%d.%d Summary" % (chapterid, sectionid, unitid)]
+        story = TitleStory(story, data=title, style=6)
+
+        test_type = "frameloss"
+        unit = 'RX Frame Rate'
+        chart_data = self._result.get_frameloss_chartdata(case, test_type)
+        table_data = self._result.get_frameloss_tabledata(case, test_type)
+        title = [unit + ' (%)']
+        story = TitleStory(story, data=title, style=6)
+        #       story = SpaceStory(story)
+        #       story = LineChartStory(story, data=chart_data)
+        story = SpaceStory(story)
+        story = uTableStory(story, data=table_data)
+        story = SpaceStory(story)
+
+        unit = 'Frame Loss Rate'
+        title = [unit + ' (Mpps)']
+
+        chart_data = self._result.get_framerate_chartdata(case, test_type)
+        table_data = self._result.get_framerate_tabledata(case, test_type)
+        story = TitleStory(story, data=title, style=6)
+        story = SpaceStory(story)
+        story = LineChartStory(story, data=chart_data)
+        story = SpaceStory(story)
+        story = uTableStory(story, data=table_data)
+        story = SpaceStory(story)
+        return story
+
+    def add_latency_result(self, story, chapterid, sectionid, case):
+        title = ["%d.%d Latency " % (chapterid, sectionid)]
+        story = TitleStory(story, data=title, style=2)
+        unitid = 1
+        title = ["%d.%d.%d Summary" % (chapterid, sectionid, unitid)]
+        story = TitleStory(story, data=title, style=6)
+
+        unit = 'Average Latency'
+        title = [unit + ' (uSec)']
+        #       chart_data = self._result.get_latency_chartdata(case)
+        bar_data = self._result.get_latency_bardata(case)
+        table_data = self._result.get_latency_tabledata(case)
+        story = TitleStory(story, data=title, style=6)
+        story = SpaceStory(story)
+        #       story = LineChartStory(story, data=chart_data)
+        story = BarChartStory(story, data=bar_data)
+
+        story = SpaceStory(story)
+        story = uTableStory(story, data=table_data)
+        story = SpaceStory(story)
+        return story
+
+    def add_systeminfo(self):
+        story = Story()
+        chapterid = self.get_chapterid()
+        story = SpaceStory(story)
+        title = ["%d. System Information " % (chapterid)]
+        story = PageBreakStory(story)
+        story = TitleStory(story, data=title, style=1)
+        table_content = self._common.get_systeminfo_tabledata()
+        story = SpaceStory(story)
+        story = cTableStory(story, data=table_content, style=0)
+        story = SpaceStory(story)
+        self._story += story.storylist
+
+    def add_gitinfo(self):
+        story = Story()
+        chapterid = self.get_chapterid()
+        title = ["%d. Git Repository Information " % (chapterid)]
+        story = TitleStory(story, data=title, style=1)
+
+        table_content = self._common.get_gitinfo_tabledata()
+        if table_content:
+            story = SpaceStory(story)
+            story = cTableStory(story, data=table_content, style=5)
+            story = SpaceStory(story)
+        self._story += story.storylist
+
+    def add_testing_options(self):
+        story = Story()
+        chapterid = self.get_chapterid()
+        story = SpaceStory(story)
+        title = ["%d. Testing Options" % (chapterid)]
+
+        story = TitleStory(story, data=title, style=1)
+        table_content = self._common.get_testingoptions_tabledata()
+        story = SpaceStory(story)
+        story = cTableStory(story, data=table_content, style=1)
+        story = SpaceStory(story)
+        self._story += story.storylist
+
+    def add_profile_parameters(self):
+        story = Story()
+        chapterid = self.get_chapterid()
+        story = PageBreakStory(story)
+        title = ["%d. " % (chapterid)]
+        story = TitleStory(story, data=title, style=1)
+        table_content = self._common.get_profileparameters_tabledData()
+        story = SpaceStory(story)
+        story = cTableStory(story, data=table_content, style=2)
+        story = SpaceStory(story)
+        self._story += story.storylist
+
+    def add_appendix(self):
+        story = Story()
+        story = PageBreakStory(story)
+
+        title = ["<b>Appendix %s: vSwitching Testing Methodology</b>" % (self._appendixid.get())]
+        self._appendixid.next()
+        story = TitleStory(story, data=title, style=1)
+        filename = "res/Traffic-types.jpg"
+        story = SpaceStory(story)
+        story = ImageStory(story, data=[self._rootdir + filename])
+        #       story = SpaceStory(story)
+
+        title = ["Traffic Patterns: "]
+        story = TitleStory(story, data=title, style=6)
+
+        body = [
+            "<b>Ti</b>  - South North Traffic",
+            "<b>Tu</b>  - East Eest Traffic",
+            "<b>Tn</b>  - Physical host or VM loop back",
+            "<b>Tnv</b>  - Virtual Machine loop back",
+        ]
+        story = ParagraphStory(story, data=body)
+
+        title = ["<b>Performance Testing Coverage </b> (version 0.1):"]
+        story = TitleStory(story, data=title, style=6)
+
+        table_content = self._common.get_introduct_tabledata()
+        story = SpaceStory(story)
+        story = cTableStory(story, data=table_content, style=4)
+        self._story += story.storylist
+
+    def add_historys(self):
+        case_list = self._result.get_caselist()
+        for case in case_list:
+            history = self._history.get_history_info(case)
+            if history:
+                self.add_history(case, history)
+
+    def add_history(self, case, history):
+        story = Story()
+        story = PageBreakStory(story)
+
+        title = ["<b>Appendix %s : %s History Records</b>" % (self._appendixid.get(), case)]
+        story = TitleStory(story, data=title, style=1)
+
+        for i in range(len(history)):
+            title = ["%s.%s %s" % (self._appendixid.get(), i, history[i]["title"])]
+            story = TitleStory(story, data=title, style=2)
+
+            section = history[i]["data"]
+            for unit in section:
+                title = [unit['title']]
+                story = TitleStory(story, data=title, style=6)
+                content = unit['data']
+                story = uTableStory(story, data=content)
+
+        self._appendixid.next()
+        self._story += story.storylist
+
+
+def main():
+    dbase = DbManage()
+    taskid = dbase.get_last_taskid()
+    common_data = CommonData(taskid, dbase)
+    scenario_list = common_data.get_scenariolist()
+    history_data = HistoryData(taskid, dbase)
+    for scenario in scenario_list:
+        out_file = "vstf_report_%s.pdf" % (scenario)
+        scenario_data = ScenarioData(taskid, dbase, scenario)
+        reporter = PdfvSwitchCreator(out_file, common_data, scenario_data, history_data)
+        if reporter:
+            reporter.create()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/controller/reporters/report/pdf/pdftemplate.py b/vstf/vstf/controller/reporters/report/pdf/pdftemplate.py
new file mode 100755 (executable)
index 0000000..819a5c5
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+import time
+
+from reportlab.platypus.doctemplate import SimpleDocTemplate
+from reportlab.platypus import PageBreak
+from vstf.controller.reporters.report.pdf.styles import TemplateStyle, ps_head_lv1, ps_head_lv2, ps_head_lv3
+
+
+class MyDocTemplate(SimpleDocTemplate):
+    def __init__(self, filename, **kw):
+        self.allowSplitting = 0
+        SimpleDocTemplate.__init__(self, filename, **kw)
+
+    def afterFlowable(self, flowable):
+        """Registers TOC entries."""
+        if flowable.__class__.__name__ == 'Paragraph':
+            text = flowable.getPlainText()
+            style = flowable.style.name
+            if style == ps_head_lv1.name:
+                self.notify('TOCEntry', (0, text, self.page - 1))
+            elif style == ps_head_lv2.name:
+                self.notify('TOCEntry', (1, text, self.page - 1))
+            elif style == ps_head_lv3.name:
+                self.notify('TOCEntry', (2, text, self.page - 1))
+
+
+class PdfTemplate:
+    def __init__(self, style, title, logo, header, footer, output, note=None):
+        self._style = style
+        self._title = title
+        self._logo = logo[0]
+        self._header = header[0]
+        self._footer = footer
+        self._output = output[0]
+        self._note = note
+        info = " Generated on %s " % time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())
+        self._note[0] += info
+
+    def myFirstPage(self, canvas, doc):
+        raise NotImplementedError("abstract StoryDecorator")
+
+    def myLaterPages(self, canvas, doc):
+        raise NotImplementedError("abstract StoryDecorator")
+
+    def generate(self, story):
+        sizes = (self._style.page_wight, self._style.page_height)
+        doc = MyDocTemplate(self._output, pagesize=sizes)
+        #    doc.build(story, onFirstPage=self.myFirstPage, onLaterPages=self.myLaterPages)
+        doc.multiBuild(story, onFirstPage=self.myFirstPage, onLaterPages=self.myLaterPages)
+
+
+class PdfVswitch(PdfTemplate):
+    def myFirstPage(self, canvas, doc):
+        canvas.saveState()
+        title_lines = len(self._title)
+        line_size = [self._style.title_size] * title_lines
+        line_size.append(0)
+
+        canvas.drawImage(self._logo,
+                         (self._style.page_wight - self._style.logo_width) / 2.0,
+                         self._style.page_height / 2.0 + (1 + self._style.title_leading) * reduce(lambda x, y: x + y,
+                                                                                                  line_size),
+                         self._style.logo_width,
+                         self._style.logo_height
+                         )
+        for i in range(title_lines):
+            canvas.setFont(self._style.title_font, line_size[i])
+            canvas.drawCentredString(self._style.page_wight / 2.0,
+                                     self._style.page_height / 2.0 + (1 + self._style.title_leading) * reduce(
+                                         lambda x, y: x + y, line_size[i + 1:]),
+                                     self._title[i]
+                                     )
+        size = self._style.body_size
+        canvas.setFont(self._style.body_font, size)
+        note_line = len(self._note)
+
+        for i in range(note_line):
+            print self._note[i]
+            canvas.drawCentredString(self._style.page_wight / 2.0,
+                                     self._style.page_height / 5.0 + (1 + self._style.body_leading) * size * (
+                                     note_line - i - 1),
+                                     self._note[i]
+                                     )
+        size = self._style.body_size - 2
+        canvas.setFont(self._style.body_font, size)
+        canvas.drawCentredString(self._style.page_wight / 2.0,
+                                 self._style.page_bottom / 2.0 + (1 + self._style.body_leading) * size,
+                                 self._footer[0])
+        canvas.restoreState()
+
+    def myLaterPages(self, canvas, doc):
+        canvas.saveState()
+        canvas.setLineWidth(self._style.line_width)
+        canvas.line(self._style.page_left,
+                    self._style.page_height - self._style.page_top,
+                    self._style.page_wight - self._style.page_right,
+                    self._style.page_height - self._style.page_top
+                    )
+        size = self._style.body_size - 2
+        canvas.setFont(self._style.body_font, size)
+        canvas.drawCentredString(self._style.page_wight / 2.0,
+                                 self._style.page_bottom - 24,
+                                 "%s%s Page %2d " % (self._footer[0], " " * 8, doc.page - 1)
+                                 )
+        canvas.restoreState()
+
diff --git a/vstf/vstf/controller/reporters/report/pdf/story.py b/vstf/vstf/controller/reporters/report/pdf/story.py
new file mode 100755 (executable)
index 0000000..3e56e18
--- /dev/null
@@ -0,0 +1,191 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+__doc__ = """
+Story Decorator contains ImageStory, HeaderStory, PageBreakStory,
+TableStory, LinePlotStory, TitleStory, ParagraphStory
+"""
+import sys
+import os
+from reportlab.platypus import PageBreak
+from reportlab.lib import colors
+from reportlab.platypus.tableofcontents import TableOfContents
+from styles import *
+from element import *
+
+
+class Story(object):
+    def __init__(self):
+        self._storylist = []
+
+    @property
+    def storylist(self):
+        return self._storylist
+
+
+class StoryDecorator(Story):
+    def __init__(self, story, data=None, style=None):
+        self._story = story
+        self._data = data
+        self._style = style
+        print self._data
+        self.new_story()
+
+    #       print self._story.storylist
+    @property
+    def storylist(self):
+        return self._story.storylist
+
+    def new_story(self):
+        raise NotImplementedError("abstract StoryDecorator")
+
+
+class ImageStory(StoryDecorator):
+    def new_story(self):
+        print "Image Story"
+        for filename in self._data:
+            if os.path.exists(filename) == False:
+                print "not find %s" % filename
+                continue
+            if 'Traffic-types' in filename:
+                style = is_traffic
+                image_height = style.image_height
+                image_width = style.image_width
+                image_hAlign = style.image_hAlign
+                image_vAlign = style.image_vAlign
+                self._story.storylist.append(
+                    eImage(filename, image_width, image_height, hAlign=image_hAlign, vAlign=image_vAlign))
+            else:
+                style = is_default
+                image_height = style.image_height
+                image_width = style.image_width
+                image_hAlign = style.image_hAlign
+                image_vAlign = style.image_vAlign
+                #    self._story.storylist.append(eGraphicsTable([[' ' * 5, eImage(filename, image_width, image_height, hAlign=image_hAlign, vAlign=image_vAlign)]], ts_left).table)
+                self._story.storylist.append(
+                    eImage(filename, image_width, image_height, hAlign=image_hAlign, vAlign=image_vAlign))
+
+
+class HeaderStory(StoryDecorator):
+    def new_story(self):
+        print "header story"
+        self._story.storylist.append(PageBreak())
+
+
+class PageBreakStory(StoryDecorator):
+    def new_story(self):
+        print "PageBreak story"
+        self._story.storylist.append(PageBreak())
+
+
+class TableOfContentsStory(StoryDecorator):
+    def new_story(self):
+        print "TableOfContents story"
+        self._data = [" ", " ", "Table Of Contents", ""]
+        style = ps_head_lv4
+        self._story.storylist.append(eParagraph(self._data, style).para)
+        toc = TableOfContents()
+        toc.levelStyles = [ps_head_lv7, ps_head_lv8, ps_head_lv9]
+        self._story.storylist.append(toc)
+
+
+class uTableStory(StoryDecorator):
+    def new_story(self):
+        print "utable story"
+        style = ts_left
+        if not self._data:
+            print "data error "
+            return
+        self._story.storylist.append(eCommonTable(self._data, style).table)
+
+
+class TableStory(StoryDecorator):
+    def new_story(self):
+        print "table story"
+        style = ts_default
+        self._story.storylist.append(eDataTable(self._data, style).table)
+
+
+class SpaceStory(StoryDecorator):
+    def new_story(self):
+        style = ps_space
+        self._story.storylist.append(eParagraph([" ", " "], style).para)
+
+
+class cTableStory(StoryDecorator):
+    def new_story(self):
+        print "table story"
+        style = ts_default
+        if self._style == 0:
+            self._story.storylist.append(eConfigTable(self._data, style).table)
+        elif self._style == 1:
+            self._story.storylist.append(eOptionsTable(self._data, style).table)
+        elif self._style == 2:
+            self._story.storylist.append(eProfileTable(self._data, style).table)
+        elif self._style == 3:
+            self._story.storylist.append(eSummaryTable(self._data, style).table)
+        elif self._style == 4:
+            self._story.storylist.append(eScenarioTable(self._data, style).table)
+        elif self._style == 5:
+            self._story.storylist.append(eGitInfoTable(self._data, style).table)
+
+
+class LinePlotStory(StoryDecorator):
+    def new_story(self):
+        print "LinePlot"
+        style = lps_default
+        if not self._data:
+            print "data error "
+            return
+        data = eGraphicsTable([[eLinePlot(self._data, style).draw]]).table
+        if data:
+            self._story.storylist.append(data)
+
+
+class LineChartStory(StoryDecorator):
+    def new_story(self):
+        print "LineChartStory: "
+        style = lcs_default
+        if not self._data:
+            print "data error "
+            return
+        data = eGraphicsTable([[eHorizontalLineChart(self._data, style).draw]]).table
+        if data:
+            self._story.storylist.append(data)
+
+
+class BarChartStory(StoryDecorator):
+    def new_story(self):
+        print "BarChartStory: "
+        style = bcs_default
+        if not self._data:
+            print "data error "
+            return
+
+        data = eGraphicsTable([[eBarChartColumn(self._data, style).draw]]).table
+        if data:
+            self._story.storylist.append(data)
+
+
+class ParagraphStory(StoryDecorator):
+    def new_story(self):
+        print "Paragraph Story"
+        style = ps_body
+        if not self._data:
+            print "data error "
+            return
+        data = eParagraph(self._data, style).para
+        if data:
+            self._story.storylist.append(data)
+
+
+class TitleStory(StoryDecorator):
+    def new_story(self):
+        print "Paragraph Story"
+        if self._style - 1 in range(9):
+            style = eval("ps_head_lv" + "%d" % self._style)
+        else:
+            style = ps_body
+        # print style
+        # print self._data
+
+        self._story.storylist.append(eParagraph(self._data, style).para)
diff --git a/vstf/vstf/controller/reporters/report/pdf/styles.py b/vstf/vstf/controller/reporters/report/pdf/styles.py
new file mode 100755 (executable)
index 0000000..d54ee8a
--- /dev/null
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+from reportlab.lib.styles import PropertySet
+from reportlab.lib.pagesizes import A4
+from reportlab.lib import colors
+from reportlab.lib.styles import ParagraphStyle
+from reportlab.lib.enums import TA_LEFT
+
+
+class TemplateStyle(PropertySet):
+    defaults = dict(
+        page_height=A4[1],
+        page_wight=A4[0],
+        page_left=78,
+        page_top=60,
+        page_bottom=70,
+        page_right=78,
+        title_size=16,
+        title_leading=1.25,
+        title_font='Courier-Bold',
+        body_size=10,
+        body_leading=0.8,
+        body_font='Courier',
+        line_width=1,
+        logo_width=131.2,
+        logo_height=127.7
+    )
+
+
+class ImageStyle(PropertySet):
+    defaults = dict(
+        image_height=165,
+        image_width=175,
+        image_hAlign='CENTRE',  # LEFT,CENTRE or RIGHT
+        image_vAlign='MIDDLE'  # BOTTOM,MIDDLE or TOP
+    )
+
+
+class TableStyle(PropertySet):
+    defaults = dict(
+        table_hAlign='CENTRE',  # LEFT,CENTRE or RIGHT
+        table_vAlign='MIDDLE',  # BOTTOM,MIDDLE or TOP
+        table_colWidths=None,
+        table_rowHeights=None
+    )
+
+
+class LinePlotStyle(PropertySet):
+    defaults = dict(
+        width=430,
+        height=400,
+        left=30,
+        bottom=20,
+        strokeColor=colors.black,
+        strokeWidth=1,
+        format=('%4.2f', '%4.0f', '%3.1f'),
+        labelsfont=7,
+        linestyle=[
+            (colors.red, 'Circle', 1.5),
+            (colors.blue, 'Diamond', 1.5),
+            (colors.gold, 'Square', 1.5),
+            (colors.green, 'Triangle', 1.5),
+            (colors.pink, 'FilledCircle', 1.5),
+            (colors.lightblue, 'FilledDiamond', 1.5),
+            (colors.lightgreen, 'FilledTriangle', 1.5)
+        ]
+    )
+
+
+class LineChartStyle(PropertySet):
+    defaults = dict(
+        width=430,
+        height=400,
+        left=30,
+        bottom=20,
+        strokeColor=colors.lightgrey,
+        strokeWidth=1,
+        format=('%4.2f', '%3.1f'),
+        labelsfont=8,
+        linestyle=[
+            (colors.red, 'Circle', 1.5),
+            (colors.blue, 'Diamond', 1.5),
+            (colors.gold, 'Square', 1.5),
+            (colors.green, 'Triangle', 1.5),
+            (colors.pink, 'FilledCircle', 1.5),
+            (colors.lightblue, 'FilledDiamond', 1.5),
+            (colors.lightgreen, 'FilledTriangle', 1.5)
+        ]
+    )
+
+
+class BarChartStyle(PropertySet):
+    defaults = dict(
+        width=430,
+        height=135,
+        left=30,
+        bottom=50,
+        top=0,
+        right=30,
+        groupSpacing=32,
+        barSpacing=4,
+        tick=3,
+        strokeColor=colors.lightgrey,
+        strokeWidth=1,
+        pillarstyle={
+            "loss": (colors.lightgreen, '%4.2f'),
+            "latency": (colors.indianred, '%4.1f'),
+            "fastlink": (colors.pink, '%4.1f'),
+            "l2switch": (colors.lightblue, '%4.1f'),
+            "kernel rdp": (colors.lightgreen, '%4.1f'),
+        },
+        background=colors.lightgrey,
+        labelsfont=6,
+    )
+
+
+ts_left = TableStyle(
+    name='left',
+    table_hAlign='LEFT',  # LEFT,CENTRE or RIGHT
+    table_vAlign='BOTTOM',  # BOTTOM,MIDDLE or TOP
+    table_colWidths=None,
+    table_rowHeights=None
+)
+
+is_default = ImageStyle(name='default')
+is_traffic = ImageStyle(name='traffic',
+                        image_height=150,
+                        image_width=360,
+                        image_hAlign='CENTRE')
+
+ts_default = TableStyle(name='default')
+lps_default = LinePlotStyle(name='default')
+lcs_default = LineChartStyle(name='default')
+bcs_default = BarChartStyle(name='default')
+ps_head_lv1 = ParagraphStyle(name='ps_head_lv1',
+                             fontName='Courier-Bold',
+                             alignment=TA_LEFT,  # TA_CENTRE,
+                             fontSize=13,
+                             leading=22,
+                             leftIndent=0)
+
+ps_head_lv2 = ParagraphStyle(name='ps_head_lv2',
+                             fontName='Courier',
+                             fontSize=12,
+                             leading=20,
+                             leftIndent=16)
+
+ps_head_lv3 = ParagraphStyle(name='ps_head_lv3',
+                             fontSize=11,
+                             fontName='Courier',
+                             leading=20,
+                             leftIndent=16)
+
+ps_head_lv4 = ParagraphStyle(name='ps_head_lv4',
+                             fontSize=13,
+                             fontName='Courier-Bold',
+                             leading=22,
+                             leftIndent=0)
+
+ps_head_lv5 = ParagraphStyle(name='ps_head_lv5',
+                             fontSize=12,
+                             fontName='Courier',
+                             leading=20,
+                             leftIndent=16)
+
+ps_head_lv6 = ParagraphStyle(name='ps_head_lv6',
+                             fontSize=11,
+                             fontName='Courier',
+                             leading=20,
+                             leftIndent=16)
+
+ps_head_lv7 = ParagraphStyle(name='ps_head_lv7',
+                             fontSize=11,
+                             fontName='Courier',
+                             leading=18,
+                             leftIndent=0)
+
+ps_head_lv8 = ParagraphStyle(name='ps_head_lv8',
+                             fontSize=11,
+                             fontName='Courier',
+                             leading=18,
+                             leftIndent=16)
+
+ps_head_lv9 = ParagraphStyle(name='ps_head_lv9',
+                             fontSize=11,
+                             fontName='Courier',
+                             leading=18,
+                             leftIndent=32)
+
+ps_body = ParagraphStyle(name='ps_body',
+                         fontSize=11,
+                         fontName='Courier',
+                         leading=18,
+                         leftIndent=32)
+
+ps_space = ParagraphStyle(name='ps_space',
+                          fontSize=5,
+                          leading=5)
diff --git a/vstf/vstf/controller/reporters/report/provider/__init__.py b/vstf/vstf/controller/reporters/report/provider/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/reporters/report/provider/html_provider.py b/vstf/vstf/controller/reporters/report/provider/html_provider.py
new file mode 100755 (executable)
index 0000000..b0b0743
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-25
+# see license for license details
+__version__ = ''' '''
+import logging
+
+LOG = logging.getLogger(__name__)
+from vstf.controller.settings.html_settings import HtmlSettings
+from vstf.controller.settings.data_settings import DataSettings
+
+
+class HtmlProvider(object):
+    def __init__(self, content, style):
+        self._content = content
+        self._style = style
+
+    def get_style(self):
+        return self._style["style"]
+
+    def get_subject(self):
+        return self._content["subject"]
+
+    def get_ovs_title(self):
+        return self._content["ovs"]["title"]
+
+    def get_ovs_table(self):
+        return map(lambda x: list(x), self._content["ovs"]["content"].items())
+
+    def get_result_title(self):
+        return self._content["result"]["title"]
+
+    def get_result_table(self, ttype):
+        result = []
+        content = self._content["result"]["content"]
+        if ttype in content:
+            result.append(content[ttype]["columns"])
+            result.extend(content[ttype]["data"])
+
+            result = map(lambda x: list(x), zip(*result))
+        return result
+
+
+class StyleProvider(object):
+    def __init__(self, style):
+        self._style = style
+
+    def get_style(self):
+        return self._style["style"]
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/html-provder.log", clevel=logging.INFO)
+
+    html_settings = HtmlSettings()
+    LOG.info(html_settings.settings)
+    data_settings = DataSettings()
+    LOG.info(data_settings.settings)
+
+    hprovider = HtmlProvider(data_settings.settings, html_settings.settings)
+    sprovider = StyleProvider(html_settings.settings)
diff --git a/vstf/vstf/controller/reporters/reporter.py b/vstf/vstf/controller/reporters/reporter.py
new file mode 100755 (executable)
index 0000000..1c256c6
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-05-29
+# see license for license details
+import os
+import argparse
+import logging
+import time
+
+from vstf.controller.reporters.report.pdf.pdfcreator import PdfvSwitchCreator
+from vstf.controller.reporters.report.html.htmlcreator import HtmlvSwitchCreator
+from vstf.controller.reporters.report.data_factory import CommonData, TaskData, ScenarioData, HistoryData
+from vstf.controller.database.dbinterface import DbManage
+from vstf.controller.settings.mail_settings import MailSettings
+from vstf.controller.reporters.mail.sendmail import SendMail
+from vstf.controller.settings.html_settings import HtmlSettings
+from vstf.controller.reporters.report.provider.html_provider import StyleProvider
+import vstf.common.constants as cst
+
+
+__version__ = ''' '''
+LOG = logging.getLogger(__name__)
+
+
+class Report(object):
+    def __init__(self, dbase, rpath):
+        """
+
+        :type dbase: object DbManage
+        """
+        self._dbase = dbase
+        self._rpath = "."
+        if os.path.exists(rpath):
+            self._rpath = rpath
+
+    def create_pdf(self, taskid):
+        common_data = CommonData(taskid, self._dbase)
+        scenario_list = common_data.get_scenariolist()
+        history_data = HistoryData(taskid, self._dbase)
+        attach_list = []
+        for scenario in scenario_list:
+            out_file = os.path.join(self._rpath, "vstf_report_%s_%s.pdf" % (scenario, time.strftime(cst.TIME_STR)))
+            LOG.info(out_file)
+            scenario_data = ScenarioData(taskid, self._dbase, scenario)
+            pdf = PdfvSwitchCreator(out_file, common_data, scenario_data, history_data)
+            if pdf:
+                pdf.create()
+                attach_list.append(out_file)
+        if attach_list:
+            self._mail_settings.mset_attach(attach_list)
+
+    def create_html(self, taskid):
+        task_data = TaskData(taskid, self._dbase)
+
+        html_settings = HtmlSettings()
+        LOG.info(html_settings.settings)
+
+        provider = StyleProvider(html_settings.settings)
+        out_file = os.path.join(self._rpath, "mail.html")
+        LOG.info(out_file)
+
+        html = HtmlvSwitchCreator(task_data, provider, out_file)
+        content = html.create()
+
+        self._mail_settings.mset_subtype('html')
+        self._mail_settings.mset_content(content)
+
+    def report(self, taskid, mail_off):
+        self._mail_settings = MailSettings()
+        mail = SendMail(self._mail_settings.settings)
+        self.create_pdf(taskid)
+        self.create_html(taskid)
+        if not mail_off:
+            mail.send()
+
+
+def main():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-reporter.log", clevel=logging.INFO)
+
+    parser = argparse.ArgumentParser(add_help=True)
+    parser.add_argument('-rpath',
+                        action='store',
+                        default='./',
+                        type=str,
+                        help=" the path name of test results  "
+                        )
+    parser.add_argument('-mail_off',
+                        action='store_true',
+                        help="is need send mail the for the report"
+                        )
+    parser.add_argument('--taskid',
+                        action='store',
+                        default=-1,
+                        help="report depand of a history task id."
+                        )
+    args = parser.parse_args()
+    dbase = DbManage()
+
+    report = Report(dbase, args.rpath)
+    if args.taskid == -1:
+        taskid = dbase.get_last_taskid()
+    else:
+        taskid = args.taskid
+    report.report(taskid, args.mail_off)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/controller/res/Traffic-types.gif b/vstf/vstf/controller/res/Traffic-types.gif
new file mode 100755 (executable)
index 0000000..4b1fc60
Binary files /dev/null and b/vstf/vstf/controller/res/Traffic-types.gif differ
diff --git a/vstf/vstf/controller/res/Traffic-types.jpg b/vstf/vstf/controller/res/Traffic-types.jpg
new file mode 100755 (executable)
index 0000000..07f2330
Binary files /dev/null and b/vstf/vstf/controller/res/Traffic-types.jpg differ
diff --git a/vstf/vstf/controller/res/__init__.py b/vstf/vstf/controller/res/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/res/deployment/Ti-direct.gif b/vstf/vstf/controller/res/deployment/Ti-direct.gif
new file mode 100755 (executable)
index 0000000..c06a222
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Ti-direct.gif differ
diff --git a/vstf/vstf/controller/res/deployment/Ti-direct.jpg b/vstf/vstf/controller/res/deployment/Ti-direct.jpg
new file mode 100755 (executable)
index 0000000..edb25ca
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Ti-direct.jpg differ
diff --git a/vstf/vstf/controller/res/deployment/Ti.gif b/vstf/vstf/controller/res/deployment/Ti.gif
new file mode 100755 (executable)
index 0000000..56ada3f
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Ti.gif differ
diff --git a/vstf/vstf/controller/res/deployment/Ti.jpg b/vstf/vstf/controller/res/deployment/Ti.jpg
new file mode 100755 (executable)
index 0000000..951b1c0
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Ti.jpg differ
diff --git a/vstf/vstf/controller/res/deployment/Tn.gif b/vstf/vstf/controller/res/deployment/Tn.gif
new file mode 100755 (executable)
index 0000000..4367ded
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tn.gif differ
diff --git a/vstf/vstf/controller/res/deployment/Tn.jpg b/vstf/vstf/controller/res/deployment/Tn.jpg
new file mode 100755 (executable)
index 0000000..45a1704
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tn.jpg differ
diff --git a/vstf/vstf/controller/res/deployment/Tnv.gif b/vstf/vstf/controller/res/deployment/Tnv.gif
new file mode 100755 (executable)
index 0000000..dfc0bd5
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tnv.gif differ
diff --git a/vstf/vstf/controller/res/deployment/Tnv.jpg b/vstf/vstf/controller/res/deployment/Tnv.jpg
new file mode 100755 (executable)
index 0000000..fb47d8a
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tnv.jpg differ
diff --git a/vstf/vstf/controller/res/deployment/Tu.gif b/vstf/vstf/controller/res/deployment/Tu.gif
new file mode 100755 (executable)
index 0000000..426667e
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tu.gif differ
diff --git a/vstf/vstf/controller/res/deployment/Tu.jpg b/vstf/vstf/controller/res/deployment/Tu.jpg
new file mode 100755 (executable)
index 0000000..be62df7
Binary files /dev/null and b/vstf/vstf/controller/res/deployment/Tu.jpg differ
diff --git a/vstf/vstf/controller/res/iperf/Ti-3.gif b/vstf/vstf/controller/res/iperf/Ti-3.gif
new file mode 100755 (executable)
index 0000000..e09094a
Binary files /dev/null and b/vstf/vstf/controller/res/iperf/Ti-3.gif differ
diff --git a/vstf/vstf/controller/res/iperf/Ti-3.jpg b/vstf/vstf/controller/res/iperf/Ti-3.jpg
new file mode 100755 (executable)
index 0000000..cdf7527
Binary files /dev/null and b/vstf/vstf/controller/res/iperf/Ti-3.jpg differ
diff --git a/vstf/vstf/controller/res/logo.jpg b/vstf/vstf/controller/res/logo.jpg
new file mode 100755 (executable)
index 0000000..683acfe
Binary files /dev/null and b/vstf/vstf/controller/res/logo.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-1.gif b/vstf/vstf/controller/res/pktgen/Ti-1.gif
new file mode 100755 (executable)
index 0000000..ed9e44a
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-1.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-1.jpg b/vstf/vstf/controller/res/pktgen/Ti-1.jpg
new file mode 100755 (executable)
index 0000000..5898769
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-1.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-2.gif b/vstf/vstf/controller/res/pktgen/Ti-2.gif
new file mode 100755 (executable)
index 0000000..59359b1
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-2.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-2.jpg b/vstf/vstf/controller/res/pktgen/Ti-2.jpg
new file mode 100755 (executable)
index 0000000..ea4b262
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-2.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-direct-1.gif b/vstf/vstf/controller/res/pktgen/Ti-direct-1.gif
new file mode 100755 (executable)
index 0000000..57b148c
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-direct-1.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-direct-1.jpg b/vstf/vstf/controller/res/pktgen/Ti-direct-1.jpg
new file mode 100755 (executable)
index 0000000..1255dc8
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-direct-1.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-direct-2.gif b/vstf/vstf/controller/res/pktgen/Ti-direct-2.gif
new file mode 100755 (executable)
index 0000000..43e58fb
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-direct-2.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Ti-direct-2.jpg b/vstf/vstf/controller/res/pktgen/Ti-direct-2.jpg
new file mode 100755 (executable)
index 0000000..898d68e
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Ti-direct-2.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-1.gif b/vstf/vstf/controller/res/pktgen/Tn-1.gif
new file mode 100755 (executable)
index 0000000..74407a9
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-1.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-1.jpg b/vstf/vstf/controller/res/pktgen/Tn-1.jpg
new file mode 100755 (executable)
index 0000000..f3ea6e5
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-1.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-1v.gif b/vstf/vstf/controller/res/pktgen/Tn-1v.gif
new file mode 100755 (executable)
index 0000000..18681bb
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-1v.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-1v.jpg b/vstf/vstf/controller/res/pktgen/Tn-1v.jpg
new file mode 100755 (executable)
index 0000000..59d4ed5
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-1v.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-2.gif b/vstf/vstf/controller/res/pktgen/Tn-2.gif
new file mode 100755 (executable)
index 0000000..e26db79
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-2.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-2.jpg b/vstf/vstf/controller/res/pktgen/Tn-2.jpg
new file mode 100755 (executable)
index 0000000..15ed91e
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-2.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-2v.gif b/vstf/vstf/controller/res/pktgen/Tn-2v.gif
new file mode 100755 (executable)
index 0000000..9ec5457
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-2v.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tn-2v.jpg b/vstf/vstf/controller/res/pktgen/Tn-2v.jpg
new file mode 100755 (executable)
index 0000000..2ff06ea
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tn-2v.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-1.gif b/vstf/vstf/controller/res/pktgen/Tu-1.gif
new file mode 100755 (executable)
index 0000000..9f2357a
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-1.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-1.jpg b/vstf/vstf/controller/res/pktgen/Tu-1.jpg
new file mode 100755 (executable)
index 0000000..ad2724a
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-1.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-2.gif b/vstf/vstf/controller/res/pktgen/Tu-2.gif
new file mode 100755 (executable)
index 0000000..b0a2ced
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-2.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-2.jpg b/vstf/vstf/controller/res/pktgen/Tu-2.jpg
new file mode 100755 (executable)
index 0000000..d47089b
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-2.jpg differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-3.gif b/vstf/vstf/controller/res/pktgen/Tu-3.gif
new file mode 100755 (executable)
index 0000000..e0b1521
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-3.gif differ
diff --git a/vstf/vstf/controller/res/pktgen/Tu-3.jpg b/vstf/vstf/controller/res/pktgen/Tu-3.jpg
new file mode 100755 (executable)
index 0000000..579808c
Binary files /dev/null and b/vstf/vstf/controller/res/pktgen/Tu-3.jpg differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-1.gif b/vstf/vstf/controller/res/spirent/Tn-1.gif
new file mode 100755 (executable)
index 0000000..97510da
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-1.gif differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-1.jpg b/vstf/vstf/controller/res/spirent/Tn-1.jpg
new file mode 100755 (executable)
index 0000000..6886d31
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-1.jpg differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-1v.gif b/vstf/vstf/controller/res/spirent/Tn-1v.gif
new file mode 100755 (executable)
index 0000000..cb7b668
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-1v.gif differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-1v.jpg b/vstf/vstf/controller/res/spirent/Tn-1v.jpg
new file mode 100755 (executable)
index 0000000..3dec438
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-1v.jpg differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-2.gif b/vstf/vstf/controller/res/spirent/Tn-2.gif
new file mode 100755 (executable)
index 0000000..4eb6780
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-2.gif differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-2.jpg b/vstf/vstf/controller/res/spirent/Tn-2.jpg
new file mode 100755 (executable)
index 0000000..9d425af
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-2.jpg differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-2v.gif b/vstf/vstf/controller/res/spirent/Tn-2v.gif
new file mode 100755 (executable)
index 0000000..21e3147
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-2v.gif differ
diff --git a/vstf/vstf/controller/res/spirent/Tn-2v.jpg b/vstf/vstf/controller/res/spirent/Tn-2v.jpg
new file mode 100755 (executable)
index 0000000..40a3829
Binary files /dev/null and b/vstf/vstf/controller/res/spirent/Tn-2v.jpg differ
diff --git a/vstf/vstf/controller/settings/README b/vstf/vstf/controller/settings/README
new file mode 100755 (executable)
index 0000000..febac1c
--- /dev/null
@@ -0,0 +1,61 @@
+This module providers a set of profile management solution
+File:
+    settings.py
+Interface:
+    Settings
+
+       1. it is a base class and supports two modes "Default" and "Single"
+          if the mode is "Default", the program will load the 'json' file from 'default'
+          and 'user' , merge the input, save only the 'json' file from 'user'
+          if the mode is "Single", the program will only load and save the 'json' file
+       2. it saves a file two, one is only in memory and the other is in file
+       3. it provides two types of functions, one is like "set_" and "add_" and the
+          other is like "mset" and "madd". the functions are automatically registered.
+
+       4. You can overload the function _register_func to achieve the functions what you desire
+          by function "_setting_file","_adding_file","_setting_memory" and "_addting_memory"
+
+       5. it provides "settings" to show the result in memory
+       6. it provides "reset" to reload the file
+
+Example:
+
+    1. create your-settings file and paste the contents
+
+               {
+                       "items1": "value1",
+                       "items2": "value2"
+               }
+
+
+       2. create your_settings file and paste the codes
+
+        import vstf.controller.settings.settings as sets
+               class YourSettings(sets.Settings):
+                       def __init__(self, path="./", filename="your-settings", mode=sets.SETS_SINGLE):
+                               super(MailSettings, self).__init__(path, filename, mode)
+
+               def unit_test():
+                       setting = YourSettings()
+                       print setting.settings()
+                       value1 = "test_set_items1"
+                       setting.set_items1(value1)
+                       print setting.settings()
+                       value2 = "test_set_items2"
+            setting.mset_items2(value2)
+            print setting.settings()
+            settings.reset()
+            print setting.settings()
+
+               if __name__ == '__main__':
+                       unit_test()
+
+Tree:
+
+    data_settings.py
+    flows_settings.py
+    perf_settings.py
+
+    mail_settings.py
+    tool_settings.py
+    html_settings.py
\ No newline at end of file
diff --git a/vstf/vstf/controller/settings/__init__.py b/vstf/vstf/controller/settings/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/settings/cpu_settings.py b/vstf/vstf/controller/settings/cpu_settings.py
new file mode 100755 (executable)
index 0000000..c69742a
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/19
+# see license for license details
+
+import logging
+import pprint
+
+import vstf.controller.settings.settings as sets
+import vstf.common.decorator as deco
+from vstf.common.input import raw_choice
+
+LOG = logging.getLogger(__name__)
+
+
+class CpuSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/perf/",
+                 filename="sw_perf.cpu-settings",
+                 mode=sets.SETS_SINGLE):
+        super(CpuSettings, self).__init__(path, filename, mode)
+
+    def _register_func(self):
+        super(CpuSettings, self)._register_func()
+        body = set(
+            self._fset['affctl'].keys()
+        )
+        LOG.debug(body)
+        for item in body:
+            item = item.encode()
+            func_name = "set_%s" % item
+            setattr(self, func_name, self._setting_file(func_name, self._mset['affctl'], self._fset['affctl'], item))
+            func_name = "mset_%s" % item
+            setattr(self, func_name, self._setting_memory(func_name, self._mset['affctl'], item))
+
+        LOG.debug(self.__dict__)
+
+    def sinput(self, info=None):
+        if raw_choice("if set cpu affability by affctl"):
+            affctl = self.raw_affctl(info)
+            self.set_affctl(affctl)
+
+        print "%s set finish: " % self._filename
+        print "+++++++++++++++++++++++++++++++++++++++++"
+        pprint.pprint(self.settings, indent=4)
+        print "+++++++++++++++++++++++++++++++++++++++++"
+
+    @deco.vstf_input('policy', types=int)
+    def raw_affctl(self, info):
+        print info
+        print "---------------------------------------"
+        print "Please vstf set cpu affctl params like:"
+        print "    'policy': 2,"
+        print "---------------------------------------"
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-cpu-settings.log", clevel=logging.INFO)
+
+if __name__ == '__main__':
+    unit_test()
+
diff --git a/vstf/vstf/controller/settings/data_settings.py b/vstf/vstf/controller/settings/data_settings.py
new file mode 100755 (executable)
index 0000000..d9878bf
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-25
+# see license for license details
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class DataSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/reporter/",
+                 filename="reporters.html.data-settings",
+                 mode=sets.SETS_SINGLE):
+        super(DataSettings, self).__init__(path, filename, mode)
+
+    def _register_func(self):
+        super(DataSettings, self)._register_func()
+        items = {"ovs", "result"}
+        fkeys = {"title", "content"}
+        for item in items:
+            item = item.encode()
+            for key in fkeys:
+                key = key.encode()
+                func_name = "set_%s_%s" % (item, key)
+                setattr(self, func_name, self._setting_file(func_name, self._mset[item], self._fset[item], key))
+                func_name = "mset_%s_%s" % (item, key)
+                setattr(self, func_name, self._setting_memory(func_name, self._mset[item], key))
\ No newline at end of file
diff --git a/vstf/vstf/controller/settings/device_settings.py b/vstf/vstf/controller/settings/device_settings.py
new file mode 100755 (executable)
index 0000000..45bc9eb
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/19
+# see license for license details
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class DeviceSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/perf/",
+                 filename="sw_perf.device-settings",
+                 mode=sets.SETS_SINGLE):
+        super(DeviceSettings, self).__init__(path, filename, mode)
diff --git a/vstf/vstf/controller/settings/flows_settings.py b/vstf/vstf/controller/settings/flows_settings.py
new file mode 100755 (executable)
index 0000000..b2bec62
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-18
+# see license for license details
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class FlowsSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/perf/",
+                 filename="sw_perf.flownodes-settings",
+                 mode=sets.SETS_SINGLE):
+        self._check_actors = {'namespaces', 'senders', 'receivers', 'watchers'}
+        self._nocheck_actors = {"cpu_listens"}
+        super(FlowsSettings, self).__init__(path, filename, mode)
+
+    def _register_func(self):
+        super(FlowsSettings, self)._register_func()
+        for actor in self._check_actors:
+            actor = actor.encode()
+            func_name = "add_%s" % actor
+            setattr(self, func_name, self._adding_file(func_name, self._mset, self._fset, actor, self._check_add))
+            func_name = "madd_%s" % actor
+            setattr(self, func_name, self._adding_memory(func_name, self._mset, actor, self._check_add))
+
+        for actor in self._nocheck_actors:
+            actor = actor.encode()
+            func_name = "add_%s" % actor
+            setattr(self, func_name, self._adding_file(func_name, self._mset, self._fset, actor))
+            func_name = "madd_%s" % actor
+            setattr(self, func_name, self._adding_memory(func_name, self._mset, actor))
+
+        LOG.debug(self.__dict__.keys())
+
+    def clear_all(self):
+        actors = self._check_actors | self._nocheck_actors
+        for actor in actors:
+            func_name = "set_%s" % actor
+            func = getattr(self, func_name)
+            func([])
+
+    def mclear_all(self):
+        actors = self._check_actors | self._nocheck_actors
+        for actor in actors:
+            func_name = "mset_%s" % actor
+            func = getattr(self, func_name)
+            func([])
+
+    def _check_add(self, value):
+        flows = ['agent', 'dev']
+        if not isinstance(value, dict):
+            raise Exception("type is error: %s" % (str(value)))
+        for flow in flows:
+            if flow not in value.keys():
+                raise Exception("keys[%s] is missing: %s" % (flow, str(value)))
+
+        items = ["ip", "namespace", "mac", "iface", "bdf"]
+        for item in items:
+            if item not in value['dev'].keys():
+                raise Exception("keys[%s] is error: %s" % (item, str(value)))
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-flows-settings.log", clevel=logging.INFO)
+
+    flows_settings = FlowsSettings()
+    LOG.info(flows_settings.settings)
+
+    flows_settings.clear_all()
+    flows_settings.set_flows(2)
+    LOG.info(flows_settings.settings)
+
+    flow_1 = {
+        "agent": "192.168.188.14",
+        "dev": {
+            "ip": "192.168.1.100",
+            "namespace": "vstf-space-1",
+            "mac": "90:e2:ba:20:1f:d8",
+            "iface": "eth4",
+            "bdf": "04:00.0"
+        }
+    }
+    flow_2 = {
+        "agent": "192.168.188.14",
+        "dev": {
+            "ip": "192.168.1.101",
+            "namespace": "vstf-space-2",
+            "mac": "90:e2:ba:20:1f:d9",
+            "iface": "p57p2",
+            "bdf": "04:00.1"
+        }
+    }
+
+    flows_settings.add_senders(flow_1)
+    flows_settings.add_senders(flow_2)
+    flows_settings.add_receivers(flow_2)
+    flows_settings.add_receivers(flow_1)
+
+    flows_settings.add_watchers(flow_1)
+    flows_settings.add_watchers(flow_2)
+
+    flows_settings.add_namespaces(flow_1)
+    flows_settings.add_namespaces(flow_2)
+
+    cpu = {
+        "agent": "192.168.188.16",
+        "affctl":{
+            "policy": 2
+        }
+    }
+    flows_settings.add_cpu_listens(cpu)
+    LOG.info(flows_settings.settings)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/settings/forwarding_settings.py b/vstf/vstf/controller/settings/forwarding_settings.py
new file mode 100755 (executable)
index 0000000..67ec3f8
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/19
+# see license for license details
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class ForwardingSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/perf/",
+                 filename="sw_perf.forwarding-settings",
+                 mode=sets.SETS_SINGLE):
+        super(ForwardingSettings, self).__init__(path, filename, mode)
diff --git a/vstf/vstf/controller/settings/html_settings.py b/vstf/vstf/controller/settings/html_settings.py
new file mode 100755 (executable)
index 0000000..7e71510
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-25
+# see license for license details
+__version__ = ''' '''
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class HtmlSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/", filename="reporters.html-settings", mode=sets.SETS_DEFAULT):
+        super(HtmlSettings, self).__init__(path, filename, mode)
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/html-settings.log", clevel=logging.DEBUG)
+    html_settings = HtmlSettings()
+    style = {
+        'table': {
+            'font-family': '"Trebuchet MS", Arial, Helvetica, sans-serif',
+            'border-collapse': 'collapse',
+            'border': '1px solid green',
+            'padding': '8px',
+            'text-align': 'center'
+        },
+        'td':
+            {
+                'border': '1px solid green',
+                'padding': '8px',
+                'word-wrap': 'break-all'
+            },
+        'th':
+            {
+                'background-color': '#EAF2D3',
+                'border': '1px solid green',
+                'padding': '8px'
+            }
+    }
+
+    html_settings.set_style(style)
+    LOG.info(html_settings.settings)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/settings/mail_settings.py b/vstf/vstf/controller/settings/mail_settings.py
new file mode 100755 (executable)
index 0000000..fd66b5c
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-06
+# see license for license details
+
+import logging
+import pprint
+
+import vstf.controller.settings.settings as sets
+import vstf.common.decorator as deco
+from vstf.common.input import raw_choice
+
+LOG = logging.getLogger(__name__)
+
+
+class MailSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf", filename="reporters.mail.mail-settings", mode=sets.SETS_DEFAULT):
+        super(MailSettings, self).__init__(path, filename, mode)
+
+    def _register_func(self):
+        super(MailSettings, self)._register_func()
+        body = set(
+            self._fset['body'].keys()
+        )
+        LOG.debug(body)
+        for item in body:
+            item = item.encode()
+            func_name = "set_%s" % item
+            setattr(self, func_name, self._setting_file(func_name, self._mset['body'], self._fset['body'], item))
+        other = {"attach", "content", "subtype"}
+        for item in other:
+            func_name = "mset_%s" % item
+            setattr(self, func_name, self._setting_memory(func_name, self._mset['body'], item))
+
+        LOG.debug(self.__dict__)
+
+    def sinput(self):
+        if raw_choice("if set mail server"):
+            server = self.raw_server()
+            self.set_server(server)
+
+        if raw_choice("if set mail body"):
+            body = self.raw_body()
+            self.set_body(body)
+        print "%s set finish: " % (self._filename)
+        print "+++++++++++++++++++++++++++++++++++++++++"
+        pprint.pprint(self.settings, indent=4)
+        print "+++++++++++++++++++++++++++++++++++++++++"
+
+    @deco.vstf_input("password", types=str)
+    @deco.vstf_input("username", types=str)
+    @deco.vstf_input('host', types=str)
+    def raw_server(self):
+        print "---------------------------------------"
+        print "Please vstf set mail server info like:"
+        print "    'host': 'localhost',"
+        print "    'username': 'user',['\\n' = None]"
+        print "    'password': '******',['\\n' = None]"
+        print "---------------------------------------"
+
+    @deco.vstf_input("subject", types=str, default='vstf mail')
+    @deco.vstf_input("bcc", types=list, default=[])
+    @deco.vstf_input("cc", types=list, default=[])
+    @deco.vstf_input("to", types=list, default=[])
+    @deco.vstf_input('from', types=list, default=['vstf_from@vstf.com'])
+    def raw_body(self):
+        print "----------------------------------------------------"
+        print "Please vstf set mail server info like:"
+        print "    'from': ['vstf_from@vstf.com'],"
+        print "    'to': ['vstf_to@vstf.com'],"
+        print "    'cc': ['vstf_cc@vstf.com']"
+        print "    'bcc': ['vstf_bcc@vstf.com']"
+        print "    'subject': Vstf Performance Test Report"
+        print "----------------------------------------------------"
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-mail-settings.log", clevel=logging.INFO)
+
+    mail_settings = MailSettings()
+    mail_settings.sinput()
+
+    return
+
+    mail_server = {
+        "host": "localhost",
+        "username": None,
+        "password": None
+    }
+    mail_settings.set_server(mail_server)
+
+    from_list = ['vstf_from@vstf.com']
+    mail_settings.set_from(from_list)
+    to_list = ['wangli11@huawei.com']
+    mail_settings.set_to(to_list)
+    cc_list = ['wangli11@huawei.com']
+    mail_settings.set_cc(cc_list)
+    bcc_list = ['wangli11@huawei.com']
+    mail_settings.set_bcc(bcc_list)
+    bcc_list = ['wangli11@huawei.com']
+    mail_settings.set_bcc(bcc_list)
+
+    subject = "Virtual Switching Performance Test Report"
+    mail_settings.set_subject(subject)
+
+    subtype = "plain"
+    mail_settings.mset_subtype(subtype)
+
+    attach_list = []
+    mail_settings.mset_attach(attach_list)
+
+    content = "this is a test"
+    mail_settings.mset_content(content)
+
+    LOG.info(mail_settings.settings)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/settings/perf_settings.py b/vstf/vstf/controller/settings/perf_settings.py
new file mode 100755 (executable)
index 0000000..c0c8123
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-28
+# see license for license details
+
+import pprint
+import logging
+
+import vstf.common.decorator as deco
+import vstf.common.constants as cst
+import vstf.controller.settings.settings as sets
+from vstf.common.input import raw_choice
+from vstf.controller.database.dbinterface import DbManage
+
+LOG = logging.getLogger(__name__)
+
+
+class PerfSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/perf/",
+                 filename="sw_perf.batch-settings",
+                 mode=sets.SETS_SINGLE):
+        self.dbconn = DbManage()
+        super(PerfSettings, self).__init__(path, filename, mode)
+
+    def clear(self):
+        for item in cst.SCENARIOS:
+            func = getattr(self, "set_" + item)
+            func([])
+
+    def mclear(self):
+        for item in cst.SCENARIOS:
+            func = getattr(self, "mset_" + item)
+            func([])
+
+    def add_case(self, value):
+        scenario = self.dbconn.query_scenario(value["case"])
+        LOG.info(scenario)
+        if not scenario:
+            LOG.warn("not support the case:%s", value["case"])
+            return
+        self._adding_file("add", self._mset, self._fset, scenario, check=self._check_add)(value)
+
+    def madd_case(self, case):
+        scenario = self.dbconn.query_scenario(case)
+        if not scenario:
+            LOG.warn("not support the case:%s", case)
+            return
+        self._adding_memory("madd", self._mset, scenario, check=self._check_add)(case)
+
+    @deco.dcheck('sizes')
+    @deco.dcheck("type", choices=cst.TTYPES)
+    @deco.dcheck("profile", choices=cst.PROFILES)
+    @deco.dcheck("protocol", choices=cst.TPROTOCOLS)
+    @deco.dcheck("tool", choices=cst.TOOLS)
+    @deco.dcheck('case')
+    def _check_add(self, value):
+        LOG.info("check successfully")
+
+    def sinput(self):
+        if raw_choice("if clean all Test case"):
+            self.clear()
+        while True:
+            if raw_choice("if add a new Test case"):
+                case = self.raw_addcase()
+                self.add_case(case)
+            else:
+                break
+        print "%s set finish: " % (self._filename)
+        print "+++++++++++++++++++++++++++++++++++"
+        pprint.pprint(self.settings)
+        print "+++++++++++++++++++++++++++++++++++"
+        return True
+    
+    @deco.vstf_input('sizes', types=list)
+    @deco.vstf_input("type", types=str, choices=cst.TTYPES)
+    @deco.vstf_input("profile", types=str, choices=cst.PROFILES)
+    @deco.vstf_input("protocol", types=str, choices=cst.TPROTOCOLS)
+    @deco.vstf_input("tool", types=str, choices=cst.TOOLS)
+    @deco.vstf_input('case')
+    def raw_addcase(self):
+        print "---------------------------------------"
+        print "Please vstf add case info like:"
+        print "    'case': 'Ti-1',"
+        print "    'tool': 'netperf',"
+        print "    'protocol': 'udp',"
+        print "    'profile': 'rdp',"
+        print "    'type': 'latency',"
+        print "    'sizes': [64, 128, 512, 1024]"
+        print "---------------------------------------"
+
+
+def unit_test():
+    perf_settings = PerfSettings()
+    perf_settings.sinput()
+
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-perf-settings.log", clevel=logging.DEBUG)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/settings/settings.py b/vstf/vstf/controller/settings/settings.py
new file mode 100755 (executable)
index 0000000..4730c8d
--- /dev/null
@@ -0,0 +1,286 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-06
+# see license for license details
+
+import json
+import re
+import os
+import copy
+import logging
+import sys
+
+LOG = logging.getLogger(__name__)
+
+
+def object2dict(obj):
+    # convert object to a dict
+    dic = {'__class__': obj.__class__.__name__, '__module__': obj.__module__}
+    dic.update(obj.__dict__)
+    return dic
+
+
+def dict2object(dic):
+    # convert dict to object
+    if '__class__' in dic:
+        class_name = dic.pop('__class__')
+        module_name = dic.pop('__module__')
+        module = __import__(module_name)
+        class_ = getattr(module, class_name)
+        args = dict((key.encode('ascii'), value) for key, value in dic.items())  # get args
+        inst = class_(**args)  # create new instance
+    else:
+        inst = dic
+    return inst
+
+
+def filter_comments(filename, flags="//"):
+    result = []
+    with open(filename, "r") as ifile:
+        lines = ifile.readlines()
+        for data in lines:
+            data = re.sub("%s.*$" % (flags), '', data)
+            data = re.sub("^\s*$", '', data)
+            if data:
+                result.append(data)
+    LOG.debug(result)
+    return ''.join(result)
+
+
+class BaseSettings(object):
+    def _load(self, fullname):
+        data = filter_comments(fullname)
+        LOG.debug(fullname)
+        LOG.debug(data)
+        jparams = None
+        if data:
+            jparams = json.loads(data)
+        return jparams
+
+    def _sub(self, ldata, rdata):
+        if isinstance(ldata, list) and isinstance(rdata, list):
+            data = []
+            if ldata:
+                for litem in ldata:
+                    if rdata:
+                        for ritem in rdata:
+                            if isinstance(litem, dict) or isinstance(litem, list):
+                                tmp = self._sub(litem, ritem)
+                            else:
+                                tmp = ritem
+                            if tmp and tmp not in data:
+                                data.append(tmp)
+                    else:
+                        data.append(litem)
+
+            else:
+                data = rdata
+
+        elif isinstance(ldata, dict) and isinstance(rdata, dict):
+            data = {}
+            rdata_bak = copy.deepcopy(rdata)
+            for rkey, rvalue in rdata_bak.items():
+                if rkey not in ldata:
+                    rdata_bak.pop(rkey)
+            for lkey, lvalue in ldata.items():
+                if lkey in rdata:
+                    if isinstance(lvalue, dict) or isinstance(lvalue, list):
+                        data[lkey] = self._sub(lvalue, rdata[lkey])
+                    else:
+                        data[lkey] = rdata[lkey]
+                else:
+                    if rdata_bak:
+                        data[lkey] = lvalue
+        else:
+            data = rdata
+
+        return data
+
+    def _save(self, data, filename):
+        if os.path.exists(filename):
+            os.remove(filename)
+        with open(filename, 'w') as ofile:
+            content = json.dumps(data, sort_keys=True, indent=4, separators=(',', ':'))
+            ofile.write(content)
+
+
+class DefaultSettings(BaseSettings):
+    def __init__(self, path):
+        self._default = os.path.join(path, 'default')
+        self._user = os.path.join(path, 'user')
+    
+    def load(self, filename):
+        dfile = os.path.join(self._default, filename)
+        if os.path.exists(dfile):
+            ddata = self._load(dfile)
+            data = ddata
+        else:
+            err = "default file is missing : %s" % (dfile)
+            LOG.error(err)
+            raise Exception(err)
+        ufile = os.path.join(self._user, filename)
+        if os.path.exists(ufile):
+            udata = self._load(ufile)
+            if udata:
+                data = self._sub(ddata, udata)
+        else:
+            LOG.info("no user file :%s" % (ufile))
+        return data
+
+    def save(self, data, filename):
+        ufile = os.path.join(self._user, filename)
+        self._save(data, ufile)
+
+
+class SingleSettings(BaseSettings):
+    def __init__(self, path):
+        self._path = path
+
+    def load(self, filename):
+        pfile = os.path.join(self._path, filename)
+        if os.path.exists(pfile):
+            ddata = self._load(pfile)
+            data = ddata
+        else:
+            err = "settings file is missing : %s" % (pfile)
+            LOG.error(err)
+            raise Exception(err)
+        return data
+
+    def save(self, data, filename):
+        pfile = os.path.join(self._path, filename)
+        self._save(data, pfile)
+
+SETS_DEFAULT = "Default"
+SETS_SINGLE = "Single"
+SETTINGS = [SETS_SINGLE, SETS_DEFAULT]
+
+
+class Settings(object):
+    def __init__(self, path, filename, mode=SETS_SINGLE):
+        if mode not in SETTINGS:
+            raise Exception("error Settings mode : %s" % (mode))
+        cls_name = mode + "Settings"
+        thismodule = sys.modules[__name__]
+        cls = getattr(thismodule, cls_name)
+        self._settings = cls(path)
+        self._filename = filename
+        self._fset = self._settings.load(filename)
+        self._mset = copy.deepcopy(self._fset)
+        self._register_func()
+
+    def reset(self):
+        self._fset = self._settings.load(self._filename)
+        self._mset = copy.deepcopy(self._fset)
+
+    @property
+    def settings(self):
+        return self._mset
+
+    def _setting_file(self, func_name, mset, fset, key, check=None):
+        def infunc(value):
+            if hasattr(check, '__call__'):
+                check(value)
+            if isinstance(fset, dict):
+                mset[key] = copy.deepcopy(value)
+                fset[key] = copy.deepcopy(value)
+            elif isinstance(fset, list):
+                del (mset[:])
+                del (fset[:])
+                mset.extend(copy.deepcopy(value))
+                fset.extend(copy.deepcopy(value))
+            self._settings.save(self._fset, self._filename)
+            infunc.__name__ = func_name
+            LOG.debug(self._mset)
+            LOG.debug(self._fset)
+
+        return infunc
+
+    def _setting_memory(self, func_name, mset, key, check=None):
+        def infunc(value):
+            if hasattr(check, '__call__'):
+                check(value)
+            if isinstance(mset, dict):
+                mset[key] = copy.deepcopy(value)
+            elif isinstance(mset, list):
+                for i in range(len(mset)):
+                    mset.pop()
+                mset.extend(copy.deepcopy(value))
+
+            infunc.__name__ = func_name
+            LOG.debug(self._mset)
+            LOG.debug(self._fset)
+
+        return infunc
+
+    def _adding_file(self, func_name, mset, fset, key, check=None):
+        def infunc(value):
+            if hasattr(check, '__call__'):
+                check(value)
+            if key:
+                mset[key].append(copy.deepcopy(value))
+                fset[key].append(copy.deepcopy(value))
+            else:
+                mset.append(copy.deepcopy(value))
+                fset.append(copy.deepcopy(value))
+
+            self._settings.save(self._fset, self._filename)
+            infunc.__name__ = func_name
+            LOG.debug(self._mset)
+            LOG.debug(self._fset)
+
+        return infunc
+
+    def _adding_memory(self, func_name, mset, key, check=None):
+        def infunc(value):
+            if hasattr(check, '__call__'):
+                check(value)
+            if key:
+                mset[key].append(copy.deepcopy(value))
+            else:
+                mset.append(copy.deepcopy(value))
+            infunc.__name__ = func_name
+            LOG.debug(self._mset)
+            LOG.debug(self._fset)
+
+        return infunc
+
+    def _register_func(self):
+        if isinstance(self._fset, dict):
+            items = set(
+                self._fset.keys()
+            )
+            for item in items:
+                item = item.encode()
+                func_name = "set_%s" % item
+                setattr(self, func_name, self._setting_file(func_name, self._mset, self._fset, item))
+                func_name = "mset_%s" % item
+                setattr(self, func_name, self._setting_memory(func_name, self._mset, item))
+        elif isinstance(self._fset, list):
+            func_name = "set"
+            setattr(self, func_name, self._setting_file(func_name, self._mset, self._fset, None))
+            func_name = "mset"
+            setattr(self, func_name, self._setting_memory(func_name, self._mset, None))
+            func_name = "add"
+            setattr(self, func_name, self._adding_file(func_name, self._mset, self._fset, None))
+            func_name = "madd"
+            setattr(self, func_name, self._adding_memory(func_name, self._mset, None))
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf-settings.log", clevel=logging.INFO)
+
+    path = '/etc/vstf'
+    setting = DefaultSettings(path)
+    filename = 'reporters.mail.mail-settings'
+    data = setting.load(filename)
+
+    setting.save(data, filename)
+    LOG.info(type(data))
+    LOG.info(data)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/settings/settings_input.py b/vstf/vstf/controller/settings/settings_input.py
new file mode 100755 (executable)
index 0000000..2c26284
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-10-16
+# see license for license details
+
+
+import clize
+from sigtools.modifiers import autokwoargs
+from vstf.controller.settings.mail_settings import MailSettings
+from vstf.controller.settings.perf_settings import PerfSettings
+from vstf.controller.settings.cpu_settings import CpuSettings
+from vstf.controller.settings.tool_settings import ToolSettings
+
+
+@autokwoargs
+def sinput(mail=False, perf=False, affctl=False, tool=False):
+    """Settings command line input
+
+    mail:  if start mail settings
+
+    perf:  if start perf settings
+
+    affctl:  if start set cpu affability
+
+    tool:  if start set tool properties
+
+    """
+
+    if mail:
+        MailSettings().sinput()
+    if perf:
+        PerfSettings().sinput()
+    if affctl:
+        CpuSettings().sinput()
+    if tool:
+        ToolSettings().sinput()
+
+
+def main():
+    clize.run(sinput)
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/controller/settings/tester_settings.py b/vstf/vstf/controller/settings/tester_settings.py
new file mode 100755 (executable)
index 0000000..fb116a8
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015/11/17
+# see license for license details
+
+import logging
+
+import vstf.controller.settings.settings as sets
+
+LOG = logging.getLogger(__name__)
+
+
+class TesterSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf/env/",
+                 filename="tester.json",
+                 mode=sets.SETS_SINGLE):
+        super(TesterSettings, self).__init__(path, filename, mode)
diff --git a/vstf/vstf/controller/settings/tool_settings.py b/vstf/vstf/controller/settings/tool_settings.py
new file mode 100755 (executable)
index 0000000..1d543e6
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-18
+# see license for license details
+
+import logging
+import pprint
+import vstf.controller.settings.settings as sets
+import vstf.common.decorator as deco
+from vstf.common.input import raw_choice
+
+LOG = logging.getLogger(__name__)
+
+
+class ToolSettings(sets.Settings):
+    def __init__(self, path="/etc/vstf", filename="sw_perf.tool-settings", mode=sets.SETS_DEFAULT):
+        super(ToolSettings, self).__init__(path, filename, mode)
+
+    def _register_func(self):
+        body = set(
+            self._fset.keys()
+        )
+        LOG.debug(body)
+        for item in body:
+            item = item.encode()
+            func_name = "set_%s" % (item)
+            setattr(self, func_name,
+                    self._setting_file(func_name, self._mset, self._fset, item, check=self._check_keys))
+
+    def _check_keys(self, value):
+        keys = ['threads', 'wait', 'time']
+        if not isinstance(value, dict):
+            raise Exception("type is error: %s" % (str(value)))
+        for key in keys:
+            if key not in value.keys():
+                raise Exception("keys[%s] is missing: %s" % (key, str(value)))
+
+    def sinput(self):
+        body = set(
+            self._fset.keys()
+        )
+        for tool in body:
+            info = "if set %s properties" % tool
+            if raw_choice(info):
+                properties = self.raw_properties()
+                func = getattr(self, "set_%s" % tool)
+                func(properties)
+
+        print "%s set finish: " % self._filename
+        print "+++++++++++++++++++++++++++++++++++++++++"
+        pprint.pprint(self.settings, indent=4)
+        print "+++++++++++++++++++++++++++++++++++++++++"
+
+    @deco.vstf_input("time", types=int)
+    @deco.vstf_input("wait", types=int)
+    @deco.vstf_input("threads", types=int)
+    def raw_properties(self):
+        print "---------------------------------------"
+        print "Please vstf set tool properties like:"
+        print "    'threads': 2,"
+        print "    'wait': 2,"
+        print "    'time': 10,"
+        print "---------------------------------------"
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/tool-settings.log", clevel=logging.INFO)
+    tool_settings = ToolSettings()
+    value = {
+        "time": 10,
+        "wait": 4,
+        "threads": 1
+    }
+    tool_settings.set_pktgen(value)
+    tool_settings.set_netperf(value)
+    tool_settings.set_iperf(value)
+    tool_settings.set_qperf(value)
+    LOG.info(tool_settings.settings)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/spirent/__init__.py b/vstf/vstf/controller/spirent/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/spirent/appliance.py b/vstf/vstf/controller/spirent/appliance.py
new file mode 100755 (executable)
index 0000000..a06bf45
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/python
+"""
+    @author: l00190809
+    @group: Huawei Ltd 
+"""
+import os
+import logging
+
+import stevedore
+from vstf.controller.spirent.common.result_analysis import analysis_instance as analysis_instance
+LOG = logging.getLogger(__name__)
+
+
+class spirentSTC(object):
+    def __init__(self):
+        super(spirentSTC, self).__init__()
+        self.runmodel = None
+
+    def init(self, conner="", measurand="", model="", **kwargs):
+        """
+        :param str    conner: the spirent tester, the agent id of spirent vm
+        :param list   measurand: the tested host's agent id
+        :param str    model: the model used of the tested host
+        
+        """
+        mgr = stevedore.driver.DriverManager(namespace="spirent.model.plugins",
+                                             name=model,
+                                             invoke_on_load=False)
+        self.TempMod = mgr.driver(kwargs)
+        self.conner = conner
+        self.measurand = measurand
+
+    @property
+    def run(self):
+        LOG.info(vars(self.runmodel))
+        return True
+
+
+def run(config):
+    # test option parser 
+    if not os.path.exists(config['configfile']):
+        LOG.error('The config file %s does exist.', config.get("configfile"))
+        return False
+
+    runmodel = None  # Tnv_Model(config = config)
+
+    # check parameter valid
+    flag = runmodel.check_parameter_invalid()
+    if not flag:
+        LOG.error("[ERROR]Check parameter invalid.")
+        return False
+
+    # check logical parameter in the 
+    flag = runmodel.check_logic_invalid
+    if not flag:
+        LOG.error("[ERROR]Check logic parameter with host invalid.")
+        return False
+
+    init_flows_tables = runmodel.read_flow_init
+    LOG.info(init_flows_tables)
+
+    # print init_flows_tables
+    update_flows = runmodel.flow_build
+    # print update_flows
+    LOG.info(update_flows)
+
+    flag = runmodel.affinity_bind(aff_strategy=1)
+    if not flag:
+        LOG.error("runmodel affinity bind failed.")
+        return False
+
+    # Get the result
+    result = {}
+    for suite in ["frameloss", "throughput"]:
+        ret, test_result = runmodel.Test_Run(suite)
+        if not ret:
+            LOG.error("[ERROR]Run rfc2544 %s test failed.", suite)
+            return False
+        try:
+            ret, result_dict = restrucData(test_result)
+        except:
+            LOG.error("[ERROR]Restructure the test data failed.")
+        perfdata = getResult(result_dict)
+        columndata = getResultColumn(result_dict)
+        column_array, data_array = analysis_instance.analyseResult(suite, columndata, perfdata)
+        temp = {'columns': column_array, 'data': data_array}
+        result[suite] = temp
+    return result
+
+
+if __name__ == "__main__":
+    run(None)
diff --git a/vstf/vstf/controller/spirent/common/__init__.py b/vstf/vstf/controller/spirent/common/__init__.py
new file mode 100755 (executable)
index 0000000..0e98d82
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.\r
+# All Rights Reserved.\r
+#\r
+#    Licensed under the Apache License, Version 2.0 (the License); you may\r
+#    not use this file except in compliance with the License. You may obtain\r
+#    a copy of the License at\r
+#\r
+#         http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+#    Unless required by applicable law or agreed to in writing, software\r
+#    distributed under the License is distributed on an AS IS BASIS, WITHOUT\r
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+#    License for the specific language governing permissions and limitations\r
+#    under the License.
\ No newline at end of file
diff --git a/vstf/vstf/controller/spirent/common/model.py b/vstf/vstf/controller/spirent/common/model.py
new file mode 100755 (executable)
index 0000000..511eab4
--- /dev/null
@@ -0,0 +1,462 @@
+#!/usr/bin/python
+"""
+    @author: l00190809
+    @group: Huawei Ltd 
+"""
+import re
+import copy
+import time
+import ConfigParser
+
+fwd = {'single': ['forward'],
+       'double': ['forward', 'reverse']
+       }
+models = ['Tnv']
+direction = ['single', 'double']
+reverse_dict = {
+    'forward': 'reverse',
+    'reverse': 'forward'
+}
+
+
+class BaseModel(object):
+    def __init__(self, config):
+        self.config = config
+
+    def _check_model(self):
+        return self.config['model'] in models
+
+    def _check_virtenv(self):
+        try:
+            num = int(self.config['virtenv'])
+            return num in range(1, 9)
+        except:
+            print("[ERROR]The virtenv is not a inter number.")
+
+    def _check_queues(self):
+        try:
+            num = int(self.config['queues'])
+            return num in range(1, 9)
+        except:
+            print("[ERROR]The virt queues is not a inter number.")
+
+    @property
+    def _check_flows(self):
+        try:
+            num = int(self.config['flows'])
+            return num in range(1, 9)
+        except:
+            print("[ERROR]The flow is not a inter number.")
+
+    def _check_direct(self):
+        return self.config['direct'] in direction
+
+    def _check_vlans(self):
+        return self.config['vlans'] in ['True', 'False']
+
+    def _check_bind(self):
+        return True
+
+    def check_parameter_invalid(self):
+        try:
+            if self._check_model() and \
+                    self._check_virtenv() and \
+                    self._check_queues() and \
+                    self._check_flows and \
+                    self._check_direct() and \
+                    self._check_vlans() and \
+                    self._check_bind():
+                return True
+            else:
+                print("[ERROR]Paramter check invalid")
+                return False
+        except:
+            print("[ERROR]Check parameter invalid with unknown reason.")
+            return False
+
+
+def _get_array_values(irq_array):
+    proc_list = []
+    for i in range(len(irq_array)):
+        proc_list.append(irq_array[i][1])
+    return sorted(proc_list)
+
+
+def check_dict(thread_info, flow):
+    if thread_info['src_recv_irq'] != flow['src_recv_irq']:
+        print("[WARN]Flow src_irq process %s not match %s in the table."
+              % (thread_info['src_recv_irq'],
+                 flow['src_recv_irq']))
+        return False
+    if thread_info['dst_send_irq'] != flow['dst_send_irq']:
+        print("[WARN]Flow dst_irq process %s not match %s in the table."
+              % (thread_info['dst_send_irq'],
+                 flow['dst_send_irq']))
+        return False
+    return True
+
+
+def dst_ip_update(flow):
+    try:
+        src_dst_ip = flow['dst_ip']
+        ip_section = '.'.join(src_dst_ip.split('.')[0:3]) + '.'
+        number = int(src_dst_ip.split('.')[3])
+        new_number = number + 1
+        new_dst_ip = ip_section + str(new_number)
+        flow['dst_ip'] = new_dst_ip
+    except:
+        print("[ERROR]dst ip update failed.")
+
+
+def _tranfer_array_to_range(array):
+    return str(array[0]) + '-' + str(array[-1])
+
+
+class TnV(BaseModel):
+    def __init__(self, config):
+        super(TnV, self).__init__(config)
+        self.config = config
+        self.host_instance = None
+        self.send_instace = None
+        self.vms = None
+        self.init_flows = {}
+        handle = ConfigParser.ConfigParser()
+        handle.read(self.config['configfile'])
+        self.handle = handle
+
+    def _get_vms(self):
+        return self.host_instance.get_libvirt_vms()
+
+    def flow_match(self):
+        _queues = int(self.config['queues'])
+        _virtenv = int(self.config['virtenv'])
+        _flows = int(self.config['flows'])
+        return _flows == _queues * _virtenv
+
+    def match_virt_env(self):
+        try:
+            self.vms = self._get_vms()
+            return len(self.vms) == int(self.config['virtenv'])
+        except:
+            print("[ERROR]vms or containers number is equal to virtenv.")
+            return False
+
+    @property
+    def match_flows_and_nic(self):
+        # get src_nic
+        for section in ['send', 'recv']:
+            nic = self._get_nic_from_file(section, 'nic')
+            try:
+                irq_proc = self.host_instance.get_nic_interrupt_proc(nic)
+                return int(self.config['flows']) == len(irq_proc)
+            except:
+                print("[ERROR]match flow with nic interrupt failed.")
+                return False
+
+    def _get_nic_irq_proc(self, nic):
+        return self.host_instance.get_nic_interrupt_proc(nic)
+
+    def _get_nic_from_file(self, section, column):
+        return self.handle.get(section, column)
+
+    def _get_range(self, section, column):
+        try:
+            info = self.handle.get(section, column)
+            return info.split(' ')
+        except:
+            print("[ERROR]Get mac failed.")
+            return False
+
+    def check_mac_valid(self):
+        flag = True
+        try:
+            for option in ['send', 'recv']:
+                info = self.handle.get(option, 'macs')
+                macs = info.split()
+                if len(macs) != int(self.config['virtenv']) or macs == []:
+                    print("[ERROR]The macs number is not equal to vms or containers.")
+                    return False
+                for mac in macs:
+                    # check mac valid
+                    if re.match(r'..:..:..:..:..:..', mac):
+                        continue
+                    else:
+                        print("[ERROR]mac %s invalid" % mac)
+                        flag = False
+                        break
+                if not flag:
+                    break
+            return flag
+        except:
+            print("[ERROR]parse macs failed.")
+            return False
+
+    def check_vlan_valid(self):
+        flag = True
+        for direct in ['send', 'recv']:
+            vlans = self.handle.get(direct, 'vlans').split()
+            if len(vlans) != int(self.config['virtenv']):
+                print("[ERROR]vlan un config")
+                return False
+            for vlan in vlans:
+                if int(vlan) <= 1 or int(vlan) >= 4095:
+                    flag = False
+                    break
+        return flag
+
+    @property
+    def check_logic_invalid(self):
+        return self.flow_match() and self.match_virt_env() and \
+               self.match_flows_and_nic and self.check_mac_valid() and \
+               self.check_vlan_valid()
+
+    @property
+    def read_flow_init(self):
+        # The 
+        temp_flow = {}
+        src_macs = self._get_range('send', 'macs')
+        dst_macs = self._get_range('recv', 'macs')
+        src_vlan = self._get_range('send', 'vlans')
+        dst_vlan = self._get_range('recv', 'vlans')
+        src_nic = self._get_nic_from_file('send', 'nic')
+        dst_nic = self._get_nic_from_file('recv', 'nic')
+        src_nic_irq = _get_array_values(self._get_nic_irq_proc(src_nic))
+        dst_nic_irq = _get_array_values(self._get_nic_irq_proc(dst_nic))
+        src_ip_sections = self._get_range('send', 'ip_sections')
+        dst_ip_sections = self._get_range('recv', 'ip_sections')
+        send_port = self._get_nic_from_file('send', 'port')
+        recv_port = self._get_nic_from_file('recv', 'port')
+        temp_flow['tester_ip'] = self._get_nic_from_file('common', 'tester_ip')
+        vlan = src_vlan
+        avg_flow = int(self.config['flows']) / int(self.config['virtenv'])
+        # build the main dictionary 
+        for _direct in sorted(fwd[self.config['direct']]):
+            i = 0
+            j = 0
+            temp_flow['direct'] = _direct
+            temp_flow['send_port'] = send_port
+            temp_flow['recv_port'] = recv_port
+
+            for _vm in sorted(self.vms):
+                vlan_id = {
+                    'True': vlan[i],
+                    'False': None}
+                temp_flow['virt'] = _vm
+                _vm_info = self.host_instance.get_vm_info(_vm)
+                temp_flow['qemu_proc'] = _vm_info['main_pid']
+                # temp_flow['qemu_thread']  = _vm_info['qemu_thread']
+                temp_flow['mem_numa'] = _vm_info['mem_numa']
+                # temp_flow['vhost_thread'] = _vm_info['vhost_thread']
+
+                temp_flow['src_mac'] = src_macs[i]
+                temp_flow['dst_mac'] = dst_macs[i]
+                temp_flow['vlan'] = vlan_id[self.config['vlans']]
+                src_ip = src_ip_sections[i]
+                dst_ip = dst_ip_sections[i]
+                temp_flow['src_ip'] = src_ip
+                temp_flow['dst_ip'] = dst_ip
+                vm_index = sorted(self.vms).index(_vm)
+                for _queue in range(1, int(self.config['queues']) + 1):
+                    # flow info
+                    temp_flow['queue'] = _queue
+                    # fwd thread
+
+                    temp_flow['qemu_thread_list'] = _vm_info['qemu_thread']
+                    forward_core = {
+                        "forward": _vm_info['qemu_thread'][_queue + avg_flow * vm_index],
+                        "reverse": _vm_info['qemu_thread'][_queue + avg_flow * vm_index + int(self.config['flows'])]
+                    }
+                    temp_flow['fwd_thread'] = forward_core[_direct]
+
+                    temp_flow['fwd_vhost'] = None
+                    # nic interrupts info
+                    temp_flow['src_recv_irq'] = src_nic_irq[j]
+                    temp_flow['src_nic'] = src_nic
+                    temp_flow['dst_send_irq'] = dst_nic_irq[j]
+                    temp_flow['dst_nic'] = dst_nic
+                    # above all
+                    j += 1
+                    self.init_flows[_direct + '_' + _vm + '_' + str(_queue)] = copy.deepcopy(temp_flow)
+                i += 1
+            src_nic_irq, dst_nic_irq = dst_nic_irq, src_nic_irq
+            vlan = dst_vlan
+            send_port, recv_port = recv_port, send_port
+            src_nic, dst_nic = dst_nic, src_nic
+            src_macs, dst_macs = dst_macs, src_macs
+            src_ip_sections, dst_ip_sections = dst_ip_sections, src_ip_sections
+        # return sorted(self.init_flows.iteritems(), key=lambda d:d[0])
+        return self.init_flows
+
+    def mac_learning(self, flowa, flowb):
+        flowa = str(flowa)
+        flowb = str(flowb)
+        ret = self.send_instace.mac_learning(flowa, flowb)
+        return ret
+
+    def send_packet(self, flow):
+        flow = str(flow)
+        # return a stream block handle
+        return self.send_instace.send_packet(flow)
+
+    def stop_flow(self, streamblock, flow):
+        flow = str(flow)
+        return self.send_instace.stop_flow(streamblock, flow)
+
+    def catch_thread_info(self):
+        return self.host_instance.catch_thread_info()
+
+    def set_thread2flow(self, thread_info, flow):
+        flow['fwd_vhost'] = thread_info['fwd_vhost']
+        return True
+
+    @property
+    def flow_build(self):
+        for _direct in fwd[self.config['direct']]:
+            for _vm in self.vms:
+                for _queue in range(1, int(self.config['queues']) + 1):
+                    i = 0
+                    while i < 50:
+                        try:
+                            i += 1
+                            thread_info = None
+                            self.mac_learning(self.init_flows[_direct + '_' + _vm + '_' + str(_queue)],
+                                              self.init_flows[reverse_dict[_direct] + '_' + _vm + '_' + str(_queue)])
+                            streamblock = self.send_packet(self.init_flows[_direct + '_' + _vm + '_' + str(_queue)])
+                            time.sleep(1)
+                            result, thread_info = self.catch_thread_info()
+                            thread_info = eval(thread_info)
+                            self.stop_flow(streamblock, self.init_flows[_direct + '_' + _vm + '_' + str(_queue)])
+                            time.sleep(1)
+                            if not result:
+                                print("[ERROR]Catch the thread info failed.")
+                                break
+                        except:
+                            print("[ERROR]send flow failed error or get host thread info failed.")
+
+                        # compare the got thread info to
+                        if check_dict(thread_info, self.init_flows[_direct + '_' + _vm + '_' + str(_queue)]):
+                            self.set_thread2flow(thread_info, self.init_flows[_direct + '_' + _vm + '_' + str(_queue)])
+                            print("[INFO]Flow %s_%s_%s :     fwd_vhost %s    src_recv_irq %s   dst_send_irq %s"
+                                  % (_direct, _vm, _queue, thread_info['fwd_vhost'], thread_info['src_recv_irq'],
+                                     thread_info['dst_send_irq']))
+                            print("%s" % (self.init_flows[_direct + '_' + _vm + '_' + str(_queue)]))
+                            break
+                        else:
+                            dst_ip_update(self.init_flows[_direct + '_' + _vm + '_' + str(_queue)])
+        return self.init_flows
+
+    def affinity_bind(self, aff_strategy):
+        # get the forward cores
+        qemu_list = []
+        qemu_other = []
+        src_vhost = []
+        dst_vhost = []
+        src_irq = []
+        dst_irq = []
+
+        # recognize the thread id
+        for flowname in sorted(self.init_flows.keys()):
+            tmp_thread = self.init_flows[flowname]['fwd_thread']
+            qemu_other = qemu_other + copy.deepcopy(self.init_flows[flowname]['qemu_thread_list'])
+            qemu_list.append(tmp_thread)
+            if self.init_flows[flowname]['direct'] == 'forward':
+                dst_vhost.append(self.init_flows[flowname]['fwd_vhost'])
+                src_irq.append(self.init_flows[flowname]['src_recv_irq'])
+                dst_irq.append(self.init_flows[flowname]['dst_send_irq'])
+            elif self.init_flows[flowname]['direct'] == 'reverse':
+                src_vhost.append(self.init_flows[flowname]['fwd_vhost'])
+                dst_irq.append(self.init_flows[flowname]['src_recv_irq'])
+                src_irq.append(self.init_flows[flowname]['dst_send_irq'])
+
+        qemu_list = sorted({}.fromkeys(qemu_list).keys())
+        src_vhost = sorted({}.fromkeys(src_vhost).keys())
+        dst_vhost = sorted({}.fromkeys(dst_vhost).keys())
+        src_irq = sorted({}.fromkeys(src_irq).keys())
+        dst_irq = sorted({}.fromkeys(dst_irq).keys())
+
+        # get the qemu thread except the forward core
+        qemu_other = sorted({}.fromkeys(qemu_other).keys())
+        for i in qemu_list:
+            qemu_other.remove(i)
+        # get the bind strategy
+        handle = ConfigParser.ConfigParser()
+        handle.read(self.config['strategyfile'])
+        try:
+            qemu_numa = handle.get('strategy' + self.config['strategy'], 'qemu_numa')
+            src_vhost_numa = handle.get('strategy' + self.config['strategy'], 'src_vhost_numa')
+            dst_vhost_numa = handle.get('strategy' + self.config['strategy'], 'dst_vhost_numa')
+            src_irq_numa = handle.get('strategy' + self.config['strategy'], 'src_irq_numa')
+            dst_irq_numa = handle.get('strategy' + self.config['strategy'], 'dst_irq_numa')
+            loan_numa = handle.get('strategy' + self.config['strategy'], 'loan_numa')
+        except:
+            print("[ERROR]Parse the strategy file failed or get the options failed.")
+
+        for value in [qemu_numa, src_vhost_numa, dst_vhost_numa, src_irq_numa, dst_irq_numa, loan_numa]:
+            if value is not None or value == '':
+                raise ValueError('some option in the strategy file is none.')
+        # cores mapping thread
+        numa_topo = self.host_instance.get_numa_core()
+        numa_topo = eval(numa_topo)
+        # first check the cores number
+
+        # order src_irq dst_irq src_vhost dst_vhost qemu_list
+        for node in numa_topo.keys():
+            numa_topo[node]['process'] = []
+            if 'node' + src_irq_numa == node:
+                numa_topo[node]['process'] = numa_topo[node]['process'] + src_irq
+            if 'node' + dst_irq_numa == node:
+                numa_topo[node]['process'] = numa_topo[node]['process'] + dst_irq
+            if 'node' + src_vhost_numa == node:
+                numa_topo[node]['process'] = numa_topo[node]['process'] + src_vhost
+            if 'node' + dst_vhost_numa == node:
+                numa_topo[node]['process'] = numa_topo[node]['process'] + dst_vhost
+            if 'node' + qemu_numa == node:
+                numa_topo[node]['process'] = numa_topo[node]['process'] + qemu_list
+        loan_cores = ''
+        for node in numa_topo.keys():
+            if len(numa_topo[node]['process']) > len(numa_topo[node]['phy_cores']):
+                # length distance
+                diff = len(numa_topo[node]['process']) - len(numa_topo[node]['phy_cores'])
+                # first deep copy
+                numa_topo['node' + loan_numa]['process'] = numa_topo['node' + loan_numa]['process'] + copy.deepcopy(
+                    numa_topo[node]['process'][-diff:])
+                cores_str = _tranfer_array_to_range(numa_topo['node' + loan_numa]['phy_cores'][diff:])
+                loan_cores = ','.join([loan_cores, cores_str])
+                numa_topo[node]['process'] = numa_topo[node]['process'][0:-diff]
+        loan_cores = loan_cores[1:]
+        loan_bind_list = {}
+        for proc_loan in qemu_other:
+            loan_bind_list[proc_loan] = loan_cores
+
+        bind_list = {}
+        for node in numa_topo.keys():
+            for i in range(len(numa_topo[node]['process'])):
+                bind_list[numa_topo[node]['process'][i]] = str(numa_topo[node]['phy_cores'][i])
+        bind_list.update(loan_bind_list)
+        for key in bind_list.keys():
+            self.host_instance.bind_cpu(bind_list[key], key)
+        print bind_list
+        return True
+
+    def testrun(self, suite):
+        global forward_init_flows, reverse_init_flows
+        try:
+            forward_init_flows = {}
+            reverse_init_flows = {}
+            for key in self.init_flows.keys():
+                if self.init_flows[key]['direct'] == "forward":
+                    forward_init_flows[key] = self.init_flows[key]
+                elif self.init_flows[key]['direct'] == "reverse":
+                    reverse_init_flows[key] = self.init_flows[key]
+            forward_init_flows = str(forward_init_flows)
+            reverse_init_flows = str(reverse_init_flows)
+        except:
+            print("[ERROR]init the forward and reverse flow failed.")
+
+        if suite == "throughput":
+            print("[INFO]!!!!!!!!!!!!!!!Now begin to throughput test")
+            ret, result = self.send_instace.run_rfc2544_throughput(forward_init_flows, reverse_init_flows)
+        elif suite == "frameloss":
+            print("[INFO]!!!!!!!!!!!1!!!Now begin to frameloss test")
+            ret, result = self.send_instace.run_rfc2544_frameloss(forward_init_flows, reverse_init_flows)
+        return ret, result
diff --git a/vstf/vstf/controller/spirent/common/result_analysis.py b/vstf/vstf/controller/spirent/common/result_analysis.py
new file mode 100755 (executable)
index 0000000..162e388
--- /dev/null
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+
+import re
+
+
+def getResultColumn(data_dict):
+    column_string = data_dict['Columns']
+    return column_string.strip('{}').split()
+
+
+def getResult(data_dict):
+    result_string = data_dict['Output']
+    result_array = result_string.split('} {')
+    result = []
+    for line in result_array:
+        result.append(line.split())
+    return result
+
+
+def restrucData(data_string):
+    try:
+        data_dict = {}
+        p = re.compile('-Columns.*-Output')
+        data_dict['Columns'] = p.findall(data_string)[0].strip('-Columns {} -Output')
+        p = re.compile('-Output.*-State')
+        data_dict['Output'] = p.findall(data_string)[0].strip('-Output {} -State')
+        if data_dict['Columns'] is not None or data_dict['Output'] is not None:
+            return False, None
+        return True, data_dict
+    except:
+        print("[ERROR]Find the column name or the output result failed.")
+
+
+def framelossData(column, perfdata):
+    column_name_dict = {
+        'TrialNumber': 0,
+        'Id': 1,
+        'FrameSize': 3,
+        'TxFrameCount': 9,
+        'RxFrameCount': 10,
+        'PercentLoss(%s)': 12,
+        'MinimumLatency(us)': 17,
+        'MaximumLatency(us)': 18,
+        'AverageLatency(us)': 19,
+        'MinimumJitter(us)': 20,
+        'MaximumJitter(us)': 21,
+        'AverageJitter(us)': 22,
+    }
+    # get the column array
+    column_array = [
+        column[column_name_dict['FrameSize']],
+        'ForwardingRate(Mpps)',
+        column[column_name_dict['TxFrameCount']],
+        column[column_name_dict['RxFrameCount']],
+        column[column_name_dict['PercentLoss(%s)']],
+        column[column_name_dict['AverageLatency(us)']],
+        column[column_name_dict['MinimumLatency(us)']],
+        column[column_name_dict['MaximumLatency(us)']],
+        column[column_name_dict['AverageJitter(us)']],
+        column[column_name_dict['MinimumJitter(us)']],
+        column[column_name_dict['MaximumJitter(us)']]
+    ]
+    data_array = []
+    for line in perfdata:
+        line_options = [
+            #                             line[column_name_dict['TrialNumber']],
+            #                             line[column_name_dict['Id']],
+            line[column_name_dict['FrameSize']],
+            str(float(line[column_name_dict['RxFrameCount']]) / 60 / 1000000),
+            line[column_name_dict['TxFrameCount']],
+            line[column_name_dict['RxFrameCount']],
+            line[column_name_dict['PercentLoss(%s)']],
+            line[column_name_dict['AverageLatency(us)']],
+            line[column_name_dict['MinimumLatency(us)']],
+            line[column_name_dict['MaximumLatency(us)']],
+            line[column_name_dict['AverageJitter(us)']],
+            line[column_name_dict['MinimumJitter(us)']],
+            line[column_name_dict['MaximumJitter(us)']]
+        ]
+        data_array.append(line_options)
+    return [column_array, data_array]
+
+
+class analysis(object):
+    def __init__(self):
+        pass
+
+    def analyseResult(self, suite, column, perfdata):
+        """
+        :type self: object
+        """
+        global data_array, column_array
+        if suite == 'throughput':
+            [column_array, data_array] = self.throughputData(column, perfdata)
+        elif suite == 'frameloss':
+            [column_array, data_array] = self.framelossData(column, perfdata)
+        elif suite == 'latency':
+            self.latencyData(column, perfdata)
+        else:
+            return None
+        for line in data_array:
+            print line
+        return [column_array, data_array]
+
+    def throughputData(self, column, perfdata):
+        column_name_dict = {
+            'TrialNumber': 0,
+            'Id': 1,
+            'FrameSize': 3,
+            'Load(%)': 6,
+            'Result': 8,
+            'TxFrameCount': 12,
+            'RxFrameCount': 13,
+            'ForwardingRate(mpps)': 17,
+            'MinimumLatency(us)': 18,
+            'MaximumLatency(us)': 19,
+            'AverageLatency(us)': 20,
+            'MinimumJitter(us)': 21,
+            'MaximumJitter(us)': 22,
+            'AverageJitter(us)': 23
+        }
+        column_array = {column[column_name_dict['FrameSize']],
+                        column[column_name_dict['Load(%)']],
+                        column[column_name_dict['Result']],
+                        'ForwardingRate(mpps)',
+                        column[column_name_dict['TxFrameCount']],
+                        column[column_name_dict['RxFrameCount']],
+                        column[column_name_dict['AverageLatency(us)']],
+                        column[column_name_dict['MinimumLatency(us)']],
+                        column[column_name_dict['MaximumLatency(us)']],
+                        column[column_name_dict['AverageJitter(us)']],
+                        column[column_name_dict['MinimumJitter(us)']],
+                        column[column_name_dict['MaximumJitter(us)']]}
+        data_array = []
+        for line in perfdata:
+            if line[column_name_dict['Result']] == 'Passed':
+                line_options = [
+                    #                                 line[column_name_dict['TrialNumber']],
+                    #                                 line[column_name_dict['Id']],
+                    line[column_name_dict['FrameSize']],
+                    line[column_name_dict['Load(%)']],
+                    line[column_name_dict['Result']],
+                    str(float(line[column_name_dict['ForwardingRate(mpps)']]) / 1000000),
+                    line[column_name_dict['TxFrameCount']],
+                    line[column_name_dict['RxFrameCount']],
+                    line[column_name_dict['AverageLatency(us)']],
+                    line[column_name_dict['MinimumLatency(us)']],
+                    line[column_name_dict['MaximumLatency(us)']],
+                    line[column_name_dict['AverageJitter(us)']],
+                    line[column_name_dict['MinimumJitter(us)']],
+                    line[column_name_dict['MaximumJitter(us)']]]
+            else:
+                continue
+            data_array.append(line_options)
+        # delete the redundant test data
+        delete_index = []
+        new_data_array = []
+        for ele in range(len(data_array) - 1):
+            if data_array[ele][0] == data_array[ele + 1][0]:
+                delete_index.append(ele)
+
+        for num in len(data_array):
+            if num not in delete_index:
+                new_data_array.append(data_array[num])
+
+        return column_array, new_data_array
+
+    def latencyData(self, column, perfdata):
+        pass
+
+
+analysis_instance = analysis()
diff --git a/vstf/vstf/controller/sw_perf/README b/vstf/vstf/controller/sw_perf/README
new file mode 100755 (executable)
index 0000000..02844a3
--- /dev/null
@@ -0,0 +1,39 @@
+Tree
+
+|--- flow_producer.py
+|--- model.py
+|--- performance.py
+|--- perf_provider.py
+|--- raw_data.py
+
+Entry:
+    performance.py
+        usage: performance.py [-h] [-case CASE]
+                          [-tool {pktgen,netperf,qperf,iperf,netmap}]
+                          [-protocol {tcp,udp}] [-profile {rdp,fastlink,l2switch}]
+                          [-type {throughput,latency,frameloss}] [-sizes SIZES]
+                          [--monitor MONITOR]
+
+        optional arguments:
+          -h, --help            show this help message and exit
+          -case CASE            test case like Ti-1, Tn-1, Tnv-1, Tu-1...
+          -tool {pktgen,netperf,qperf,iperf,netmap}
+          -protocol {tcp,udp}
+          -profile {rdp,fastlink,l2switch}
+          -type {throughput,latency,frameloss}
+          -sizes SIZES          test size list "64 128"
+          --monitor MONITOR     which ip to be monitored
+
+Interface:
+    usage:
+        conn = Server(host=args.monitor)
+        flows_settings = FlowsSettings()
+        tool_settings = ToolSettings()
+        tester_settings = TesterSettings()
+        flow_producer = FlowsProducer(conn, flows_settings)
+        provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+        perf = Performance(conn, provider)
+        flow_producer.create(scenario, case)
+        LOG.info(flows_settings.settings())
+        result = perf.run(tool, protocol, type, sizes)
+
diff --git a/vstf/vstf/controller/sw_perf/__init__.py b/vstf/vstf/controller/sw_perf/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/sw_perf/flow_producer.py b/vstf/vstf/controller/sw_perf/flow_producer.py
new file mode 100755 (executable)
index 0000000..1de4161
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-11-19
+# see license for license details
+
+import logging
+
+from vstf.controller.settings.device_settings import DeviceSettings
+from vstf.controller.settings.forwarding_settings import ForwardingSettings
+from vstf.controller.settings.cpu_settings import CpuSettings
+from vstf.controller.fabricant import Fabricant
+from vstf.controller.settings.flows_settings import FlowsSettings
+import vstf.common.constants as cst
+
+LOG = logging.getLogger(__name__)
+
+
+class FlowsProducer(object):
+    def __init__(self, conn, flows_settings):
+        self._perf = flows_settings
+        self._forwarding = ForwardingSettings().settings
+        self._device = DeviceSettings().settings
+        self._cpu = CpuSettings().settings
+        self._conn = conn
+        self._devs_map = {}
+
+    def get_dev(self, item):
+        agent = self._device[item[0]]["agent"]
+        devs = self._device[item[0]]["devs"][item[1]]
+
+        keys = ["bdf", "iface", "mac"]
+
+        key = devs.keys()[0]
+
+        if key in keys:
+            name = devs[key]
+        else:
+            raise Exception("error devs :%s", devs)
+        LOG.info(agent)
+        LOG.info(name)
+        if not self._devs_map.has_key((agent, name)):
+            query = Fabricant(agent, self._conn)
+            query.clean_all_namespace()
+            dev_info = query.get_device_verbose(identity=name)
+            if not isinstance(dev_info, dict):
+                err = "get device detail failed, agent:%s net:%s" % (agent, name)
+                raise Exception(err)
+            dev = {
+                "agent": agent,
+                "dev": {
+                    "bdf": dev_info["bdf"],
+                    "iface": dev_info["iface"],
+                    "mac": dev_info["mac"],
+                    "ip": None,
+                    "namespace": None
+                }
+            }
+
+            self._devs_map[(agent, name)] = dev
+            LOG.info(dev)
+
+        return self._devs_map[(agent, name)]
+
+    def get_host(self):
+        result = {
+            "agent": self._device["host"]["agent"],
+            "affctl": self._cpu["affctl"]
+        }
+        return result
+
+    def create(self, scenario, case):
+        self._devs_map = {}
+        flows_indexes = self._forwarding[scenario]["flows"]
+        flows_infos = []
+        for index in flows_indexes:
+            if not index:
+                raise Exception("error flows %s" % flows_indexes)
+            dev = self.get_dev(index)
+            flows_infos.append(dev)
+
+        flows_infos[0]['dev'].update(self._forwarding["head"])
+        flows_infos[-1]['dev'].update(self._forwarding["tail"])
+
+        LOG.info(flows_infos)
+
+        actor_info = cst.CASE_ACTOR_MAP[case]
+
+        self._perf.clear_all()
+        senders = actor_info["senders"]
+        LOG.info(senders)
+        for sender in senders:
+            dev = flows_infos[sender]
+            if dev:
+                self._perf.add_senders(dev)
+
+        receivers = actor_info["receivers"]
+        for receiver in receivers:
+            dev = flows_infos[receiver]
+            if dev:
+                self._perf.add_receivers(dev)
+
+        watchers = self._forwarding[scenario]["watchers"]
+        for watcher in watchers:
+            dev = flows_infos[watcher]
+            if dev:
+                self._perf.add_watchers(dev)
+
+        namespaces = [0, -1]
+        for namespace in namespaces:
+            dev = flows_infos[namespace]
+            if dev:
+                self._perf.add_namespaces(dev)
+
+        host = self.get_host()
+        if host:
+            self._perf.add_cpu_listens(host)
+
+        self._perf.set_flows(actor_info["flows"])
+        return True
+
+
+def unit_test():
+    from vstf.rpc_frame_work.rpc_producer import Server
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.INFO, log_file="/var/log/vstf/vstf-producer.log", clevel=logging.INFO)
+
+    conn = Server("192.168.188.10")
+    flow_settings = FlowsSettings()
+    flow_producer = FlowsProducer(conn, flow_settings)
+    scenario = "Tn"
+    case = "Tn-1"
+    flow_producer.create(scenario, case)
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/sw_perf/model.py b/vstf/vstf/controller/sw_perf/model.py
new file mode 100755 (executable)
index 0000000..672daad
--- /dev/null
@@ -0,0 +1,190 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author:
+# date:
+# see license for license details
+
+import logging
+
+from vstf.controller.fabricant import Fabricant
+from vstf.controller.sw_perf.raw_data import RawDataProcess
+from vstf.common import perfmark as mark
+
+LOG = logging.getLogger(__name__)
+
+
+class NetDeviceMgr(Fabricant):
+    @classmethod
+    def add(cls, dst, conn, dev):
+        self = cls(dst, conn)
+        LOG.info(dev)
+        ret = self.config_dev(netdev=dev)
+        LOG.info(ret)
+
+    @classmethod
+    def remove(cls, dst, conn, dev):
+        self = cls(dst, conn)
+        LOG.info(dev)
+        ret = self.recover_dev(netdev=dev)
+        LOG.info(ret)
+
+    @classmethod
+    def clear(cls, dst, conn):
+        self = cls(dst, conn)
+        self.clean_all_namespace()
+
+
+class Actor(Fabricant):
+    def __init__(self, dst, conn, tool, params):
+        super(Actor, self).__init__(dst, conn)
+        self._tool = tool
+        self._params = params
+        self._data = {}
+
+    def __repr__(self):
+        repr_dict = self.__dict__
+        repr_keys = list(repr_dict.keys())
+        repr_keys.sort()
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(['%s=%r' % (k, repr_dict[k]) for k in repr_keys]))
+
+
+class Sender(Actor):
+    def start(self, pktsize, **kwargs):
+        LOG.info("Sender.start")
+        if 'ratep' in kwargs and kwargs['ratep']:
+            self._params['ratep'] = kwargs['ratep']
+        self._params['size'] = pktsize
+
+        ret, info = self.perf_run(
+            operation="start",
+            action="send",
+            tool=self._tool,
+            params=self._params
+        )
+        LOG.info(ret)
+        if ret:
+            raise Exception(info)
+        LOG.info(info)
+        print ret
+
+    def stop(self):
+        LOG.info(self._params)
+        rets = self.perf_run(
+            operation="stop",
+            action="send",
+            tool=self._tool,
+            params={}
+        )
+        LOG.info(rets)
+        minlatency, avglatency, maxlatency = 0, 0, 0
+        count = 0
+        for (ret, info) in rets:
+            if ret:
+                raise Exception(info)
+            if self.is_data() and ret == 0:
+                count += 1
+                minlatency += info[mark.minLatency]
+                avglatency += info[mark.avgLatency]
+                maxlatency += info[mark.maxLatency]
+        count = 1 if not count else count
+        self._data[mark.minLatency] = minlatency / count
+        self._data[mark.avgLatency] = avglatency / count
+        self._data[mark.maxLatency] = maxlatency / count
+
+        print rets
+
+    def is_data(self):
+        if '_lat' in self._params['protocol']:
+            return True
+        return False
+
+    def result(self):
+        return self._data
+
+
+class Receiver(Actor):
+    def start(self, **kwargs):
+        LOG.info("Receiver.start")
+        ret, info = self.perf_run(
+            operation="start",
+            action="receive",
+            tool=self._tool,
+            params=self._params
+        )
+        LOG.info(ret)
+        if ret:
+            raise Exception(info)
+        LOG.info(info)
+        return ret
+
+    def stop(self):
+        LOG.info("Receiver.stop")
+        ret, info = self.perf_run(
+            operation="stop",
+            action="receive",
+            tool=self._tool,
+            params=self._params
+        )
+        LOG.info(ret)
+        if ret:
+            raise Exception(info)
+        LOG.info(info)
+        return ret
+
+
+class NicWatcher(Fabricant):
+    def __init__(self, dst, conn, params):
+        super(NicWatcher, self).__init__(dst, conn)
+        self._params = params
+        self._pid = None
+        self._data = {}
+
+    def start(self):
+        print "NicWatcher.start"
+        self._pid = self.run_vnstat(device=self._params["iface"], namespace=self._params["namespace"])
+        print self._pid
+
+    def stop(self):
+        print "NicWatcher.stop"
+        if self._pid:
+            data = self.kill_vnstat(pid=self._pid)
+            self._data = RawDataProcess.process(data)
+            print "---------------------------------"
+            print self._data
+            print "---------------------------------"
+
+    def result(self, **kwargs):
+        return self._data
+
+
+class CpuWatcher(Fabricant):
+    def __init__(self, dst, conn):
+        super(CpuWatcher, self).__init__(dst, conn)
+        self._pid = None
+        self._data = {}
+
+    def start(self):
+        print "CpuWatcher.start"
+        self._pid = self.run_cpuwatch()
+        print self._pid
+
+    def stop(self):
+        print "CpuWatcher.stop"
+        if self._pid:
+            print self._pid
+            data = self.kill_cpuwatch(pid=self._pid)
+            self._data = RawDataProcess.process(data)
+            print "---------------------------------"
+            print self._data
+            print "---------------------------------"
+
+    def result(self, **kwargs):
+        return self._data
+
+
+def unit_test():
+    pass
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/sw_perf/perf_provider.py b/vstf/vstf/controller/sw_perf/perf_provider.py
new file mode 100755 (executable)
index 0000000..bd1027a
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-21
+# see license for license details
+
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def get_agent_dict(nodes):
+    """
+    :param:
+        nodes: list of flow info
+               and ever element must be a dict and kas key "agent"
+    :return : list for agent
+    :rtype : dict
+    """
+    agent_list = map(lambda x: x["agent"], nodes)
+    return {}.fromkeys(agent_list, False)
+
+
+class PerfProvider(object):
+    def __init__(self, flows_info, tool_info, tester_info):
+        self._flows_info = flows_info
+        self._tool_info = tool_info
+        self._tester_info = tester_info
+
+    def _islation(self):
+        flows = self._flows_info["flows"]
+        if flows == 2 and self._flows_info["senders"][0]["agent"] == self._flows_info["senders"][1]["agent"]:
+            return True
+        return False
+
+    def get_senders(self, tool, protocol):
+        result = []
+        flows = self._flows_info["flows"]
+        if self._islation() and "pktgen" == tool:
+            sender = {
+                "agent": self._flows_info["senders"][0]["agent"],
+                "params": {
+                    "protocol": protocol,
+                    "namespace": None,
+                    "src": [],
+                    "dst": [],
+                    "time": self._tool_info[tool]["time"],
+                    "threads": self._tool_info[tool]["threads"]
+                }
+            }
+            for i in range(flows):
+                sender['params']['src'].append(self._flows_info["senders"][i]['dev'])
+                sender['params']['dst'].append(self._flows_info["receivers"][i]['dev'])
+            result.append(sender)
+        else:
+            for i in range(flows):
+                sender = {
+                    "agent": self._flows_info["senders"][i]["agent"],
+                    "params": {
+                        "protocol": protocol,
+                        "namespace": None if "netmap" == tool else self._flows_info["senders"][i]['dev']['namespace'],
+                        "src": [self._flows_info["senders"][i]['dev']],
+                        "dst": [self._flows_info["receivers"][i]['dev']],
+                        "time": self._tool_info[tool]["time"],
+                        "threads": self._tool_info[tool]["threads"]
+                    }
+                }
+                result.append(sender)
+        return result
+
+    def get_receivers(self, tool, protocol):
+        result = []
+        flows = self._flows_info["flows"]
+        if self._islation() and "pktgen" == tool:
+            receiver = {
+                "agent": self._flows_info["receivers"][0]["agent"],
+                "params": {
+                    "namespace": None,
+                    "protocol": protocol,
+                }
+            }
+            result.append(receiver)
+        else:
+            for i in range(flows):
+                receiver = {
+                    "agent": self._flows_info["receivers"][i]["agent"],
+                    "params": {
+                        "namespace": None if "netmap" == tool else self._flows_info["receivers"][i]['dev']['namespace'],
+                        "protocol": protocol,
+                        "dst": [self._flows_info["receivers"][i]['dev']]
+                    }
+                }
+                result.append(receiver)
+        return result
+
+    def get_watchers(self, tool):
+        result = []
+        for watcher in self._flows_info["watchers"]:
+            node = {
+                "agent": watcher["agent"],
+                "params": {
+                    "iface": watcher['dev']["iface"],
+                    "namespace": None if tool in ["pktgen", "netmap"] else watcher['dev']["namespace"],
+                }
+            }
+            result.append(node)
+        return result
+
+    def get_namespaces(self, tool):
+        result = []
+
+        for watcher in self._flows_info["namespaces"]:
+            node = {
+                "agent": watcher["agent"],
+                "params": {
+                    "iface": watcher['dev']["iface"],
+                    "namespace": watcher['dev']["namespace"] if tool not in ["pktgen", "netmap"] else None,
+                    "ip": watcher['dev']["ip"] + '/24',
+                }
+            }
+            result.append(node)
+        return result
+
+    @property
+    def get_cpuwatcher(self):
+        LOG.info(self._flows_info["cpu_listens"])
+        result = {
+            "agent": self._flows_info["cpu_listens"][0]["agent"],
+            "params": {
+            }
+        }
+        return result
+
+    @property
+    def get_cpu_affctl(self):
+        LOG.info(self._flows_info["cpu_listens"])
+        result = {
+            "agent": self._flows_info["cpu_listens"][0]["agent"],
+            "params": {
+                "policy": self._flows_info["cpu_listens"][0]["affctl"]["policy"]
+            }
+        }
+        return result
+
+    def get_cleaners(self, tool, protocol):
+        nodes = self.get_senders(tool, protocol) + \
+            self.get_receivers(tool, protocol) + \
+            self.get_watchers(tool) + \
+            [self.get_cpuwatcher]
+        return get_agent_dict(nodes).keys()
+
+    @property
+    def get_testers(self):
+        agents = get_agent_dict(self._flows_info["namespaces"]).keys()
+        result = []
+        for agent in agents:
+            node = {
+                "agent": agent,
+                "params": {
+                    "drivers": self._tester_info["drivers"]
+                }
+            }
+            result.append(node)
+        return result
+
+    def duration(self, tool):
+        return self._tool_info[tool]["time"]
+
+    def wait_balance(self, tool):
+        return self._tool_info[tool]["wait"]
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-perf-provider.log", clevel=logging.INFO)
+
+    from vstf.controller.settings.flows_settings import FlowsSettings
+    from vstf.controller.settings.tool_settings import ToolSettings
+    from vstf.controller.settings.tester_settings import TesterSettings
+
+    flows_settings = FlowsSettings()
+    tool_settings = ToolSettings()
+    tester_settings = TesterSettings()
+
+    provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+
+    tools = ['pktgen']
+    protocols = ['udp_bw', 'udp_lat']
+
+    for tool in tools:
+        LOG.info(tool)
+        for protocol in protocols:
+            LOG.info(protocol)
+            senders = provider.get_senders(tool, protocols)
+            LOG.info(len(senders))
+            LOG.info(senders)
+
+            receivers = provider.get_receivers(tool, protocols)
+            LOG.info(len(receivers))
+            LOG.info(receivers)
+
+        LOG.info(provider.get_cpuwatcher)
+        LOG.info(provider.get_watchers(tool))
+        LOG.info(provider.get_namespaces(tool))
+        LOG.info(provider.duration(tool))
+
+
+if __name__ == '__main__':
+    unit_test()
diff --git a/vstf/vstf/controller/sw_perf/performance.py b/vstf/vstf/controller/sw_perf/performance.py
new file mode 100755 (executable)
index 0000000..6ca8160
--- /dev/null
@@ -0,0 +1,396 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-09-19
+# see license for license details
+
+import time
+import argparse
+import logging
+
+from vstf.controller.sw_perf import model
+from vstf.common import perfmark as mark
+import vstf.common.constants as cst
+from vstf.rpc_frame_work.rpc_producer import Server
+from vstf.controller.settings.flows_settings import FlowsSettings
+from vstf.controller.settings.tool_settings import ToolSettings
+from vstf.controller.settings.perf_settings import PerfSettings
+from vstf.controller.sw_perf.perf_provider import PerfProvider, get_agent_dict
+from vstf.controller.sw_perf.flow_producer import FlowsProducer
+from vstf.controller.settings.tester_settings import TesterSettings
+from vstf.controller.fabricant import Fabricant
+
+LOG = logging.getLogger(__name__)
+
+
+class Performance(object):
+    def __init__(self, conn, provider):
+        self._provider = provider
+        self._conn = conn
+        self._init()
+
+    def _init(self):
+        self._senders = []
+        self._receivers = []
+        self._watchers = []
+        self._cpuwatcher = None
+
+    def create(self, tool, tpro):
+        self._init()
+        agents = self._provider.get_cleaners(tool, tpro)
+        LOG.info(agents)
+        for agent in agents:
+            cleaner = Fabricant(agent, self._conn)
+            cleaner.clean_all_namespace()
+
+        for tester_info in self._provider.get_testers:
+            dst = tester_info["agent"]
+            params = tester_info["params"]
+            LOG.info(tester_info)
+            driver_mgr = Fabricant(dst, self._conn)
+            ret = driver_mgr.install_drivers(drivers=params["drivers"])
+            LOG.info(ret)
+
+        self.create_namespace(tool)
+        self.create_senders(tool, tpro)
+        self.create_receivers(tool, tpro)
+        self.create_watchers(tool)
+        self.create_cpuwatcher()
+
+    def destory(self, tool):
+        self.clear_namespace(tool)
+
+    def create_namespace(self, tool):
+        devices = self._provider.get_namespaces(tool)
+        agents = get_agent_dict(devices)
+        LOG.info(agents)
+        for device in devices:
+            dst = device["agent"]
+            params = device["params"]
+            if not agents[dst]:
+                model.NetDeviceMgr.clear(dst, self._conn)
+                agents[dst] = True
+
+            model.NetDeviceMgr.add(dst, self._conn, params)
+
+    def clear_namespace(self, tool):
+        devices = self._provider.get_namespaces(tool)
+        for device in devices:
+            dst = device["agent"]
+            params = device["params"]
+            model.NetDeviceMgr.remove(dst, self._conn, params)
+
+    def create_senders(self, tool, tpro):
+        sender_infos = self._provider.get_senders(tool, tpro)
+        LOG.info(sender_infos)
+        for sender_info in sender_infos:
+            dst = sender_info["agent"]
+            params = sender_info["params"]
+            send = model.Sender(dst, self._conn, tool, params)
+            self._senders.append(send)
+
+    def create_receivers(self, tool, tpro):
+        receiver_infos = self._provider.get_receivers(tool, tpro)
+        LOG.info(receiver_infos)
+        for receiver_info in receiver_infos:
+            dst = receiver_info["agent"]
+            params = receiver_info["params"]
+            receive = model.Receiver(dst, self._conn, tool, params)
+            self._receivers.append(receive)
+
+    def create_watchers(self, tool):
+        watcher_infos = self._provider.get_watchers(tool)
+        LOG.info(watcher_infos)
+        for watcher_info in watcher_infos:
+            dst = watcher_info["agent"]
+            params = watcher_info["params"]
+            watch = model.NicWatcher(dst, self._conn, params)
+            self._watchers.append(watch)
+
+    def create_cpuwatcher(self):
+        watcher_info = self._provider.get_cpuwatcher
+        LOG.info(watcher_info)
+        dst = watcher_info["agent"]
+        self._cpuwatcher = model.CpuWatcher(dst, self._conn)
+
+    def start_receivers(self, **kwargs):
+        for receiver in self._receivers:
+            receiver.start(**kwargs)
+
+    def start_senders(self, pktsize, **kwargs):
+        for sender in self._senders:
+            sender.start(pktsize, **kwargs)
+
+    def start_watchers(self):
+        for watcher in self._watchers:
+            watcher.start()
+
+    def stop_receivers(self):
+        for receiver in self._receivers:
+            receiver.stop()
+
+    def stop_senders(self):
+        for sender in self._senders:
+            sender.stop()
+
+    def stop_watchers(self):
+        for watcher in self._watchers:
+            watcher.stop()
+
+    def start_cpuwatcher(self):
+        if self._cpuwatcher:
+            self._cpuwatcher.start()
+
+    def stop_cpuwatcher(self):
+        if self._cpuwatcher:
+            self._cpuwatcher.stop()
+
+    def getlimitspeed(self, ptype, size):
+        return 0
+
+    def affctl(self):
+        ctl = self._provider.get_cpu_affctl
+        LOG.info(ctl)
+        driver_mgr = Fabricant(ctl["agent"], self._conn)
+        ret = driver_mgr.affctl_load(policy=ctl["params"]["policy"])
+        LOG.info(ret)
+
+    def run_pre_affability_settings(self, tool, tpro, pktsize, **kwargs):
+        LOG.info("run_pre_affability_settings start")
+        self.create(tool, tpro)
+        self.start_receivers()
+        self.start_senders(pktsize, **kwargs)
+        self.affctl()
+        time.sleep(2)
+        self.stop_senders()
+        self.stop_receivers()
+        self.destory(tool)
+        LOG.info("run_pre_affability_settings end")
+
+    def run_bandwidth_test(self, tool, tpro, pktsize, **kwargs):
+        LOG.info("run_bandwidth_test ")
+        self.create(tool, tpro)
+        self.start_receivers()
+        self.start_senders(pktsize, **kwargs)
+        time.sleep(self._provider.wait_balance(tool))
+        self.start_watchers()
+        self.start_cpuwatcher()
+        time.sleep(self._provider.duration(tool))
+        self.stop_watchers()
+        self.stop_cpuwatcher()
+        self.stop_senders()
+        self.stop_receivers()
+        self.destory(tool)
+        LOG.info("run_bandwidth_test end")
+
+    def run_latency_test(self, tool, tpro, pktsize, **kwargs):
+        LOG.info("run_latency_test start")
+        self.create(tool, tpro)
+        self.start_receivers()
+        self.start_senders(pktsize, **kwargs)
+        time.sleep(self._provider.duration(tool))
+        self.stop_senders()
+        self.stop_receivers()
+        self.destory(tool)
+        LOG.info("run_latency_test end")
+
+    def run(self, tool, protocol, ttype, sizes, affctl=False):
+        result = {}
+        if affctl:
+            pre_tpro = protocol + "_bw"
+            size = sizes[0]
+            self.run_pre_affability_settings(tool, pre_tpro, size, ratep=0)
+
+        for size in sizes:
+            if ttype in ['throughput', 'frameloss']:
+                realspeed = self.getlimitspeed(ttype, size)
+                bw_tpro = protocol + "_bw"
+                bw_type = ttype
+                self.run_bandwidth_test(tool, bw_tpro, size, ratep=realspeed)
+                bw_result = self.result(tool, bw_type)
+
+                lat_tool = "qperf"
+                lat_type = 'latency'
+                lat_tpro = protocol + '_lat'
+                self.run_latency_test(lat_tool, lat_tpro, size, ratep=realspeed)
+                lat_result = self.result(tool, lat_type)
+                LOG.info(bw_result)
+                LOG.info(lat_result)
+                lat_result.pop('OfferedLoad')
+                bw_result.update(lat_result)
+                result[size] = bw_result
+
+            elif ttype in ['latency']:
+                lat_tpro = protocol + '_lat'
+                lat_type = ttype
+                self.run_latency_test(tool, lat_tpro, size, ratep=None)
+                lat_result = self.result(tool, lat_type)
+                result[size] = lat_result
+            else:
+                raise Exception("error:protocol type:%s" % (ttype))
+        return result
+
+    def result(self, tool, ttype):
+        if ttype in {'throughput', 'frameloss'}:
+            record = {
+                mark.rxCount: 0,
+                mark.txCount: 0,
+                mark.bandwidth: 0,
+                mark.offLoad: 100.0,
+                mark.mppsGhz: 0,
+                mark.percentLoss: 0,
+                mark.avgLatency: 0,
+                mark.maxLatency: 0,
+                mark.minLatency: 0,
+                mark.rxMbps:0,
+                mark.txMbps:0
+            }
+
+            cpu_data = self._cpuwatcher.result()
+            print self._cpuwatcher, cpu_data
+            if cpu_data:
+                cpu_usage = cpu_data['cpu_num'] * (100 - cpu_data['idle'])
+                cpu_mhz = cpu_data['cpu_mhz']
+                record[mark.cpu] = round(cpu_usage, cst.CPU_USAGE_ROUND)
+                record[mark.duration] = self._provider.duration(tool)
+
+            for watcher in self._watchers:
+                nic_data = watcher.result()
+                record[mark.rxCount] += nic_data['rxpck']
+                record[mark.txCount] += nic_data['txpck']
+                record[mark.bandwidth] += nic_data['rxpck/s']
+                record[mark.rxMbps] += nic_data['rxmB/s']
+                record[mark.txMbps] += nic_data['txmB/s']
+
+            if record[mark.txCount]:
+                record[mark.percentLoss] = round(100 * (1 - record[mark.rxCount] / record[mark.txCount]),
+                                              cst.PKTLOSS_ROUND)
+            else:
+                record[mark.percentLoss] = 100
+
+            record[mark.bandwidth] /= 1000000.0
+            if cpu_mhz and record[mark.cpu]:
+                record[mark.mppsGhz] = round(record[mark.bandwidth] / (record[mark.cpu] * cpu_mhz / 100000),
+                                           cst.CPU_USAGE_ROUND)
+
+            record[mark.bandwidth] = round(record[mark.bandwidth], cst.RATEP_ROUND)
+
+        elif ttype in {'latency'}:
+            record = {
+                mark.offLoad: 0.0,
+                mark.avgLatency: 0,
+                mark.maxLatency: 0,
+                mark.minLatency: 0
+            }
+            minlatency, avglatency, maxlatency = 0, 0, 0
+            count = 0
+            for sender in self._senders:
+                info = sender.result()
+                LOG.info(info)
+                minlatency += info[mark.minLatency]
+                avglatency += info[mark.avgLatency]
+                maxlatency += info[mark.maxLatency]
+            count = 1 if not count else count
+            record[mark.minLatency] = round(minlatency / count, cst.TIME_ROUND)
+            record[mark.avgLatency] = round(avglatency / count, cst.TIME_ROUND)
+            record[mark.maxLatency] = round(maxlatency / count, cst.TIME_ROUND)
+
+        else:
+            raise Exception('error:protocol type:%s' % ttype)
+
+        LOG.info('record:%s' % record)
+        return record
+
+
+def unit_test():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-sw_perf.log", clevel=logging.INFO)
+
+    conn = Server("192.168.188.10")
+    perf_settings = PerfSettings()
+    flows_settings = FlowsSettings()
+    tool_settings = ToolSettings()
+    tester_settings = TesterSettings()
+    flow_producer = FlowsProducer(conn, flows_settings)
+    provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+    perf = Performance(conn, provider)
+    tests = perf_settings.settings
+    for scenario, cases in tests.items():
+        if not cases:
+            continue
+        for case in cases:
+            casetag = case['case']
+            tool = case['tool']
+            protocol = case['protocol']
+            profile = case['profile']
+            ttype = case['type']
+            sizes = case['sizes']
+
+            flow_producer.create(scenario, casetag)
+            result = perf.run(tool, protocol, ttype, sizes)
+            LOG.info(result)
+
+
+def main():
+    from vstf.common.log import setup_logging
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-performance.log", clevel=logging.INFO)
+    from vstf.controller.database.dbinterface import DbManage
+    parser = argparse.ArgumentParser(add_help=True)
+    parser.add_argument("case",
+                        action="store",
+                        help="test case like Ti-1, Tn-1, Tnv-1, Tu-1...")
+    parser.add_argument("tool",
+                        action="store",
+                        choices=cst.TOOLS,
+                        )
+    parser.add_argument("protocol",
+                        action="store",
+                        choices=cst.TPROTOCOLS,
+                        )
+    parser.add_argument("profile",
+                        action="store",
+                        choices=cst.PROFILES,
+                        )
+    parser.add_argument("type",
+                        action="store",
+                        choices=cst.TTYPES,
+                        )
+    parser.add_argument("sizes",
+                        action="store",
+                        default="64",
+                        help='test size list "64 128"')
+    parser.add_argument("--affctl",
+                        action="store_true",
+                        help="when input '--affctl', the performance will do affctl before testing")
+    parser.add_argument("--monitor",
+                        dest="monitor",
+                        default="localhost",
+                        action="store",
+                        help="which ip to be monitored")
+    args = parser.parse_args()
+
+    LOG.info(args.monitor)
+    conn = Server(host=args.monitor)
+    db_mgr = DbManage()
+
+    casetag = args.case
+    tool = args.tool
+    protocol = args.protocol
+    profile = args.profile
+    ttype = args.type
+    sizes = map(lambda x: int(x), args.sizes.strip().split())
+
+    flows_settings = FlowsSettings()
+    tool_settings = ToolSettings()
+    tester_settings = TesterSettings()
+    flow_producer = FlowsProducer(conn, flows_settings)
+    provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+    perf = Performance(conn, provider)
+    scenario = db_mgr.query_scenario(casetag)
+    flow_producer.create(scenario, casetag)
+    LOG.info(flows_settings.settings)
+    result = perf.run(tool, protocol, ttype, sizes, affctl)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/vstf/vstf/controller/sw_perf/raw_data.py b/vstf/vstf/controller/sw_perf/raw_data.py
new file mode 100755 (executable)
index 0000000..dab749e
--- /dev/null
@@ -0,0 +1,124 @@
+import subprocess
+import re
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+class RawDataProcess(object):
+    def __init__(self):
+        pass
+
+    def process_vnstat(self, data):
+        buf = data.splitlines()
+        buf = buf[9:]
+        buf = ' '.join(buf)
+        m = {}
+        digits = re.compile(r"\d{1,}\.?\d*")
+        units = re.compile(r"(?:gib|mib|kib|kbit/s|gbit/s|mbit/s|p/s)", re.IGNORECASE | re.MULTILINE)
+        units_arr = units.findall(buf)
+        LOG.debug(units_arr)
+        digits_arr = digits.findall(buf)
+
+        for i in range(len(digits_arr)):
+            digits_arr[i] = round(float(digits_arr[i]), 2)
+
+        LOG.info("-------------digit_arr------------------")
+        LOG.info(digits_arr)
+        LOG.info(units_arr)
+        LOG.info("-----------------------------------------")
+        m['rxpck'], m['txpck'] = digits_arr[8], digits_arr[9]
+        m['time'] = digits_arr[-1]
+        digits_arr = digits_arr[:8] + digits_arr[10:-1]
+        index = 0
+        for unit in units_arr:
+            unit = unit.lower()
+            if unit == 'gib':
+                digits_arr[index] *= 1024
+            elif unit == 'kib':
+                digits_arr[index] /= 1024
+            elif unit == 'gbit/s':
+                digits_arr[index] *= 1000
+            elif unit == 'kbit/s':
+                digits_arr[index] /= 1000
+            else:
+                pass
+            index += 1
+
+        for i in range(len(digits_arr)):
+            digits_arr[i] = round(digits_arr[i], 2)
+
+        m['rxmB'], m['txmB'] = digits_arr[0:2]
+        m['rxmB_max/s'], m['txmB_max/s'] = digits_arr[2:4]
+        m['rxmB/s'], m['txmB/s'] = digits_arr[4:6]
+        m['rxmB_min/s'], m['txmB_min/s'] = digits_arr[6:8]
+        m['rxpck_max/s'], m['txpck_max/s'] = digits_arr[8:10]
+        m['rxpck/s'], m['txpck/s'] = digits_arr[10:12]
+        m['rxpck_min/s'], m['txpck_min/s'] = digits_arr[12:14]
+        LOG.info("---------------vnstat data start-------------")
+        LOG.info(m)
+        LOG.info("---------------vnstat data end---------------")
+        return m
+
+    def process_sar_cpu(self, raw):
+        lines = raw.splitlines()
+        # print lines
+        head = lines[2].split()[3:]
+        average = lines[-1].split()[2:]
+        data = {}
+        for h, d in zip(head, average):
+            data[h.strip('%')] = float(d)
+        return data
+
+    def process_qperf(self, raw):
+        buf = raw.splitlines()
+        data = buf[1].strip().split()
+        key = data[0]
+        value = float(data[2])
+        unit = data[3]
+        return {key: value, 'unit': unit}
+
+    @classmethod
+    def process(cls, raw):
+        self = cls()
+        tool, data_type, data = raw['tool'], raw['type'], raw['raw_data']
+        m = {}
+        if tool == 'vnstat' and data_type == 'nic':
+            m = self.process_vnstat(data)
+        if tool == 'sar' and data_type == 'cpu':
+            m = self.process_sar_cpu(data)
+            if raw.has_key('cpu_num'):
+                m['cpu_num'] = raw['cpu_num']
+            if raw.has_key('cpu_mhz'):
+                m['cpu_mhz'] = raw['cpu_mhz']
+        if tool == 'qperf':
+            m = self.process_qperf(data)
+        return m
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    p = RawDataProcess()
+    cmd = "vnstat -i eth0 -l"
+    child = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
+    import time
+    import os
+    from signal import SIGINT
+
+    time.sleep(20)
+    os.kill(child.pid, SIGINT)
+    data = child.stdout.read()
+    print data
+    print p.process_vnstat(data)
+
+    cmd = "sar -u 2"
+    child = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    import time
+    import os
+    from signal import SIGINT
+
+    time.sleep(20)
+    os.kill(child.pid, SIGINT)
+    data = child.stdout.read()
+    print data
+    print p.process_sar_cpu(data)
diff --git a/vstf/vstf/controller/unittest/README b/vstf/vstf/controller/unittest/README
new file mode 100755 (executable)
index 0000000..f9414e9
--- /dev/null
@@ -0,0 +1,49 @@
+"""
+Created on 2015-9-28
+
+@author: y00228926
+"""
+
+the procedure to integrate a module unit testing into the unit testing framework:
+
+1.create your own unit test module, the name should start by 'test', for example, test_env.py
+
+2.create the test cases inside the module, inherit unittest.TestCase, for example:
+     class TestNetnsManager(unittest.TestCase):
+         def setUp(self): // preparing the testig
+             pass
+         def tearDown(self):// cleanup after testing
+             pass
+         def testCase1(self):// cases
+             pass
+
+3.single modules testing, appending below code at the end of the module, execute 'python test_env.py'.
+
+if __name__ == "__main__":
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level = logging.DEBUG)
+    unittest.main()
+
+4.multiple modules integration, create run_test.py,run_test.py the example code as below:
+
+import unittest
+import importlib
+
+test_order_list = [
+    "vstf.services.agent.unittest.perf.test_utils",
+    "vstf.services.agent.unittest.perf.test_netns",
+    "vstf.services.agent.unittest.perf.test_netperf",
+    "vstf.services.agent.unittest.perf.test_qperf",
+    "vstf.services.agent.unittest.perf.test_pktgen",
+]
+
+if __name__ == '__main__':
+    import logging
+    logging.getLogger(__name__)
+    logging.basicConfig(level = logging.DEBUG)
+    for mod_name in test_order_list:
+        mod = importlib.import_module(mod_name)
+        suit = unittest.TestLoader().loadTestsFromModule(mod)
+        unittest.TextTestRunner().run(suit)
+
diff --git a/vstf/vstf/controller/unittest/__init__.py b/vstf/vstf/controller/unittest/__init__.py
new file mode 100755 (executable)
index 0000000..89dcd4e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
diff --git a/vstf/vstf/controller/unittest/configuration.py b/vstf/vstf/controller/unittest/configuration.py
new file mode 100755 (executable)
index 0000000..9364bbb
--- /dev/null
@@ -0,0 +1,17 @@
+"""
+Created on 2015-9-24
+
+@author: y00228926
+"""
+
+rabbit_mq_server = '192.168.188.10'
+
+tester_host = '192.168.188.14'
+
+target_host = '192.168.188.16'
+
+source_repo = {
+    "passwd": "root",
+    "ip": "192.168.188.10",
+    "user": "root"
+}
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/model.py b/vstf/vstf/controller/unittest/model.py
new file mode 100755 (executable)
index 0000000..c4e992c
--- /dev/null
@@ -0,0 +1,27 @@
+"""
+Created on 2015-9-28
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.rpc_frame_work import rpc_producer
+from vstf.controller.unittest import configuration
+
+
+class Test(unittest.TestCase):
+
+    def setUp(self):
+        self.controller = configuration.rabbit_mq_server
+        self.tester_host = configuration.tester_host
+        self.target_host = configuration.target_host
+        self.source_repo = configuration.source_repo
+        self.conn = rpc_producer.Server(self.controller)
+
+    def tearDown(self):
+        self.conn.close()
+
+
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_cfg_intent_parse.py b/vstf/vstf/controller/unittest/test_cfg_intent_parse.py
new file mode 100755 (executable)
index 0000000..665732a
--- /dev/null
@@ -0,0 +1,32 @@
+"""
+Created on 2015-10-14
+
+@author: y00228926
+"""
+import os
+import unittest
+
+from vstf.controller.unittest import model
+from vstf.controller.env_build.cfg_intent_parse import IntentParser
+
+
+class Test(model.Test):
+    def setUp(self):
+        super(Test, self).setUp()
+        self.dir = os.path.dirname(__file__)
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+    def test_parse_cfg_file(self):
+        for m in ['Ti', 'Tu', 'Tn', 'Tnv']:
+            filepath = os.path.join(self.dir, 'configuration/env/%s.json' % m)
+            parser = IntentParser(filepath)
+            parser.parse_cfg_file()
+
+
+if __name__ == "__main__":
+    import logging
+
+    logging.basicConfig(level=logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_collect.py b/vstf/vstf/controller/unittest/test_collect.py
new file mode 100755 (executable)
index 0000000..f1d54a9
--- /dev/null
@@ -0,0 +1,41 @@
+"""
+Created on 2015-9-28
+
+@author: y00228926
+"""
+import unittest
+import json
+
+from vstf.controller.env_build import env_collect
+from vstf.controller.unittest import model
+
+
+class TestCollect(model.Test):
+    
+    def setUp(self):
+        super(TestCollect, self).setUp()
+        self.obj = env_collect.EnvCollectApi(self.conn)
+        
+    def test_collect_host_info(self):
+        ret_str = json.dumps(self.obj.collect_host_info(self.tester_host), indent = 4)
+        for key in ("CPU INFO","MEMORY INFO","HW_INFO","OS INFO"):
+            self.assertTrue(key in ret_str, "collect_host_info failed, ret_str = %s" % ret_str)
+            
+    def test_list_nic_devices(self):
+        ret_str = json.dumps(self.obj.list_nic_devices(self.tester_host), indent = 4)
+        for key in ("device","mac","bdf","desc"):
+            self.assertTrue(key in ret_str, "list_nic_devices failed, ret_str = %s" % ret_str)
+        print ret_str
+    
+    def test_get_device_detail(self):
+        identity = "01:00.0"
+        ret = self.obj.get_device_detail(self.tester_host, "01:00.0")
+        for key in ("device","mac","bdf","desc"):
+            self.assertTrue(key in ret)
+        self.assertTrue(ret['bdf'] == identity)
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_driver_function.py b/vstf/vstf/controller/unittest/test_driver_function.py
new file mode 100755 (executable)
index 0000000..0580442
--- /dev/null
@@ -0,0 +1,27 @@
+"""
+Created on 2015-10-27
+@author: l00190809
+"""
+import unittest
+import json
+
+from vstf.controller.functiontest.driver.drivertest import config_setup
+from vstf.controller.unittest import model
+
+
+class TestDriverFunction(model.Test):   
+    def setUp(self):
+        logging.info("start driver function test unit test.")
+        
+    def test_config_setup(self):
+        config ,_ = config_setup()
+        for key in ("test_scene","bond_flag","switch_module"):
+            self.assertTrue(key in config.keys(), "config_setup function failure.")
+
+    def teardown(self):
+        logging.info("stop driver function test unit test.")
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_env_build.py b/vstf/vstf/controller/unittest/test_env_build.py
new file mode 100755 (executable)
index 0000000..2d2d882
--- /dev/null
@@ -0,0 +1,55 @@
+'''
+Created on 2015-9-28
+
+@author: y00228926
+'''
+import unittest
+import os
+
+from vstf.controller.unittest import model
+from vstf.controller.env_build import env_build
+
+
+class TestEnvBuilder(model.Test):
+    def setUp(self):
+        super(TestEnvBuilder, self).setUp()
+        self.dir = os.path.dirname(__file__)
+        
+    @unittest.skip('for now')
+    def test_build_tn(self):
+        filepath = os.path.join(self.dir,'../../../etc/vstf/env/Tn.json')
+        self.mgr = env_build.EnvBuildApi(self.conn, filepath)
+        ret = self.mgr.build()
+        self.assertTrue(ret, "build_tn failed,ret = %s" % ret)
+        
+    @unittest.skip('for now')
+    def test_build_tn1v(self):
+        filepath = os.path.join(self.dir,'../../../etc/vstf/env/Tnv.json')
+        self.mgr = env_build.EnvBuildApi(self.conn, filepath)
+        ret = self.mgr.build()
+        self.assertTrue(ret, "build_tn1v failed,ret = %s" % ret)
+        
+    @unittest.skip('for now')
+    def test_build_ti(self):
+        filepath = os.path.join(self.dir,'../../../etc/vstf/env/Ti.json')
+        self.mgr = env_build.EnvBuildApi(self.conn, filepath)
+        ret = self.mgr.build()
+        self.assertTrue(ret, "build_ti failed,ret = %s" % ret)
+        
+    @unittest.skip('for now')
+    def test_build_tu(self):
+        filepath = os.path.join(self.dir,'../../../etc/vstf/env/Tu.json')
+        self.mgr = env_build.EnvBuildApi(self.conn, filepath)
+        ret = self.mgr.build()
+        self.assertTrue(ret, "build_tu failed,ret = %s" % ret)
+    
+    def test_build_tu_bridge(self):
+        filepath = os.path.join(self.dir,'../../../etc/vstf/env/Tu_br.json')
+        self.mgr = env_build.EnvBuildApi(self.conn, filepath)
+        ret = self.mgr.build()
+        self.assertTrue(ret, "build_tu failed,ret = %s" % ret)
+           
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_perf.py b/vstf/vstf/controller/unittest/test_perf.py
new file mode 100755 (executable)
index 0000000..5a54b82
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+# author: wly
+# date: 2015-10-30
+# see license for license details
+
+import unittest
+import os
+import logging
+
+from vstf.controller.unittest import model
+from vstf.controller.settings.flows_settings import FlowsSettings
+from vstf.controller.settings.tool_settings import ToolSettings
+from vstf.controller.settings.perf_settings import PerfSettings
+from vstf.controller.sw_perf.perf_provider import PerfProvider
+from vstf.controller.sw_perf.flow_producer import FlowsProducer
+from vstf.controller.settings.tester_settings import TesterSettings
+from vstf.controller.env_build.env_build import EnvBuildApi as Builder
+from vstf.common.log import setup_logging
+import vstf.controller.sw_perf.performance as pf
+
+LOG = logging.getLogger(__name__)
+
+
+class TestPerf(model.Test):
+    
+    def setUp(self):
+        LOG.info("start performance unit test.")
+        super(TestPerf, self).setUp()
+        self.dir = os.path.dirname(__file__)
+        self.perf_path = os.path.join(self.dir, '../../../etc/vstf/perf')
+        self.base_path = os.path.join(self.dir, '../../../etc/vstf/env')
+
+    def teardown(self):
+        LOG.info("stop performance unit test.")
+
+    @unittest.skip('for now')
+    def test_batch_perf(self):
+
+        LOG.info(self.perf_path)
+        LOG.info(self.base_path)
+        perf_settings = PerfSettings(path=self.perf_path)
+        flows_settings = FlowsSettings(path=self.perf_path)
+        tool_settings = ToolSettings(path=self.base_path)
+        tester_settings = TesterSettings(path=self.base_path)
+        flow_producer = FlowsProducer(self.conn, flows_settings)
+        provider = PerfProvider(flows_settings.settings, tool_settings.settings, tester_settings.settings)
+        perf = pf.Performance(self.conn, provider)
+        tests = perf_settings.settings
+        for scenario, cases in tests.items():
+            if not cases:
+                continue
+
+            config_file = os.path.join(self.base_path, scenario + '.json')
+
+            LOG.info(config_file)
+
+            env = Builder(self.conn, config_file)
+            env.build()
+
+            for case in cases:
+                casetag = case['case']
+                tool = case['tool']
+                protocol = case['protocol']
+                profile = case['profile']
+                ttype = case['type']
+                sizes = case['sizes']
+
+                flow_producer.create(scenario, casetag)
+                result = perf.run(tool, protocol, ttype, sizes)
+                self.assertEqual(True, isinstance(result, dict))
+                LOG.info(result)
+
+    @unittest.skip('for now')
+    def test_perf_settings(self):
+        perf_settings = PerfSettings()
+        self.assertEqual(True, perf_settings.input())
+
+    def test_tool_settings(self):
+        tool_settings = ToolSettings()
+        value = {
+            "time": 20,
+            "threads": 1
+        }
+        tool_settings.set_pktgen(value)
+        tool_settings.set_netperf(value)
+        tool_settings.set_iperf(value)
+        tool_settings.set_qperf(value)
+        LOG.info(tool_settings.settings)
+
+    def test_flow_settings(self):
+        tests = {
+            "Tn": ["Tn-1", "Tn-2", "Tn-3", "Tn-4"],
+            "Tnv": ["Tnv-1", "Tnv-2", "Tnv-3", "Tnv-4"],
+            "Ti": ["Ti-1", "Ti-2", "Ti-3", "Ti-4", "Ti-5", "Ti-6"],
+            "Tu": ["Tu-1", "Tu-2", "Tu-3", "Tu-4", "Tu-5", "Tu-6"]
+        }
+        flows_settings = FlowsSettings(path=self.perf_path)
+        flow_producer = FlowsProducer(self.conn, flows_settings)
+
+        for scenario, cases in tests.items():
+            if not cases:
+                continue
+
+            config_file = os.path.join(self.base_path, scenario + '.json')
+
+            LOG.info(config_file)
+
+            env = Builder(self.conn, config_file)
+            env.build()
+
+            for case in cases:
+                LOG.info(case)
+                flow_producer.create(scenario, case)
+                LOG.info(flows_settings.settings)
+
+
+if __name__ == "__main__":
+    setup_logging(level=logging.INFO, log_file="/var/log/vstf/vstf-unit-test.log", clevel=logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/unittest/test_ssh.py b/vstf/vstf/controller/unittest/test_ssh.py
new file mode 100755 (executable)
index 0000000..844f8ff
--- /dev/null
@@ -0,0 +1,32 @@
+"""
+Created on 2015-10-10
+
+@author: y00228926
+"""
+import unittest
+
+from vstf.common import ssh
+from vstf.controller.unittest import model
+
+
+class Test(model.Test):
+
+    def setUp(self):
+        super(Test, self).setUp()
+        self.host = self.source_repo["ip"]
+        self.user = self.source_repo["user"]
+        self.passwd = self.source_repo["passwd"]
+
+
+    def tearDown(self):
+        super(Test, self).tearDown()
+
+
+    def test_run_cmd(self):
+        ssh.run_cmd(self.host, self.user, self.passwd, 'ls')
+
+
+if __name__ == "__main__":
+    import logging
+    logging.basicConfig(level = logging.INFO)
+    unittest.main()
\ No newline at end of file
diff --git a/vstf/vstf/controller/vstfadm.py b/vstf/vstf/controller/vstfadm.py
new file mode 100755 (executable)
index 0000000..068ab2e
--- /dev/null
@@ -0,0 +1,270 @@
+import sys
+import logging
+import json
+from vstf.common.vstfcli import VstfParser
+from vstf.common import cliutil, constants, unix, message
+from vstf.common.log import setup_logging
+import vstf.common.constants as cst
+import pprint
+
+CONN = None
+
+
+def print_stdout(msg):
+    # out = json.dumps(message.get_body(message.decode(msg)), indent=2)
+    out = message.get_body(message.decode(msg))
+    pprint.pprint(out, indent=2)
+
+
+def call(msg):
+    """msg must be a dict"""
+    msg = message.add_context(msg, corr=message.gen_corrid())
+    CONN.send(message.encode(msg))
+    return message.decode(CONN.recv())
+
+
+def make_msg(method, **kwargs):
+    return {"method": method, "args": kwargs}
+
+
+@cliutil.arg("--host", dest="host", default="", action="store", help="list nic devices of specified host")
+def do_list_devs(args):
+    """List the host's all netdev."""
+    ret = call(make_msg("list_devs", host=args.host))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="which host to run src_install.")
+@cliutil.arg("--config_file", dest="config_file", action="store", default=None,
+             help="the git repo config.")
+def do_src_install(args):
+    """work agent to pull source code and compile.
+    use git as underlying mechanism, please make sure the host has access to git repo.
+    """
+    ret = call(make_msg("src_install", host=args.host, config_file=args.config_file))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="which host to build, must exists in your config file, use default[None] value to build all hosts.")
+@cliutil.arg("--model", dest="model", action="store", choices=('Tn', 'Ti', 'Tu', 'Tnv'),
+             help="which model to build, if specified, the according config file /etc/vstf/env/{model}.json must exist.")
+@cliutil.arg("--config_file", dest="config_file", action="store", default=None,
+             help="if specified, the config file will replace the default config file from /etc/vstf/env.")
+def do_apply_model(args):
+    """Apply model to the host."""
+    ret = call(make_msg("apply_model", host=args.host, model=args.model, config_file=args.config_file))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="to which host you wish to create images")
+@cliutil.arg("--config_file", dest="config_file", action="store", default=None,
+             help="configuration file for image creation.")
+def do_create_images(args):
+    """create images on host, images are configed by configuration file."""
+    ret = call(make_msg("create_images", host=args.host, config_file=args.config_file))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="to which host you wish to clean images")
+@cliutil.arg("--config_file", dest="config_file", action="store", default=None,
+             help="configuration file for images.")
+def do_clean_images(args):
+    """clean images on host, images are configed by configuration file."""
+    ret = call(make_msg("clean_images", host=args.host, config_file=args.config_file))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="which host to clean, must exists in your config file, use default[None] value to clean all hosts.")
+@cliutil.arg("--model", dest="model", action="store", choices=('Tn', 'Ti', 'Tu', 'Tnv'),
+             help="if specified, the according config file /etc/vstf/env/{model}.json must exist.")
+@cliutil.arg("--config_file", dest="config_file", action="store", default=None,
+             help="if specified, the config file will replace the default config file from /etc/vstf/env.")
+def do_disapply_model(args):
+    """Apply model to the host."""
+    ret = call(make_msg("disapply_model", host=args.host, model=args.model, config_file=args.config_file))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", help="collect host information about cpu/mem etc")
+def do_collect_host_info(args):
+    """Show the host's CPU/MEN info"""
+    ret = call(make_msg("collect_host_info", target=args.host))
+    print_stdout(ret)
+
+
+def do_show_tasks(args):
+    """List history performance test tasks. Can be used by report cmd to generate reports.
+    """
+    ret = call(make_msg("list_tasks"))
+    print_stdout(ret)
+
+
+@cliutil.arg("case", action="store", help="test case like Ti-1, Tn-1, Tnv-1, Tu-1, see case definition in documents")
+@cliutil.arg("tool", action="store", choices=cst.TOOLS, )
+@cliutil.arg("protocol", action="store", choices=cst.TPROTOCOLS, )
+@cliutil.arg("profile", action="store", choices=cst.PROFILES, )
+@cliutil.arg("type", action="store", choices=cst.TTYPES)
+@cliutil.arg("sizes", action="store", default="64", help='test size list "64 128"')
+@cliutil.arg("--affctl", action="store_true", help="when affctl is True, it will do affctl before testing")
+def do_perf_test(args):
+    """Runs a quick single software performance test without envbuild and generating reports.
+    Outputs the result to the stdout immediately."""
+    case_info = {
+        'case': args.case,
+        'tool': args.tool,
+        'protocol': args.protocol,
+        'profile': args.profile,
+        'type': args.type,
+        'sizes': map(lambda x: int(x), args.sizes.strip().split())
+    }
+    ret = call(make_msg("run_perf_cmd",
+                        case=case_info,
+                        rpath=cst.REPORT_DEFAULTS,
+                        affctl=args.affctl,
+                        build_on=False,
+                        save_on=False,
+                        report_on=False,
+                        mail_on=False
+                        ))
+    print_stdout(ret)
+
+
+@cliutil.arg("-rpath",
+             help="path of result",
+             default=cst.REPORT_DEFAULTS,
+             action="store")
+@cliutil.arg("--report_off",
+             help="when report_off is True, it will not generate the report",
+             action="store_true")
+@cliutil.arg("--mail_off",
+             help="when mail_off is True, it will not send mail",
+             action="store_true")
+@cliutil.arg("--affctl",
+             help="when affctl is True, it will do affctl before testing",
+             action="store_true")
+def do_batch_perf_test(args):
+    """run soft performance test cases defined in /etc/vstf/perf/sw_perf.batch-settings"""
+    ret = call(make_msg("run_perf_file",
+                        affctl=args.affctl,
+                        rpath=args.rpath,
+                        report_on=not args.report_off,
+                        mail_on=not args.mail_off
+                        ))
+    print_stdout(ret)
+
+
+@cliutil.arg('-rpath',
+             action='store',
+             default=cst.REPORT_DEFAULTS,
+             help=" the path name of test results  ")
+@cliutil.arg("--mail_off",
+             help="when mail_off is True, it will not send mail",
+             action="store_true")
+@cliutil.arg("--taskid",
+             help="report depend of a history task id",
+             default=-1,
+             action="store")
+def do_report(args):
+    """generate the report from the database"""
+    ret = call(make_msg("report",
+                        rpath=args.rpath,
+                        mail_off=args.mail_off,
+                        taskid=args.taskid
+                        ))
+    print_stdout(ret)
+
+
+@cliutil.arg("--conner",
+             dest="conner",
+             action="store",
+             help="tester")
+@cliutil.arg("--measurand",
+             dest="measurand",
+             action="store",
+             help="tested")
+@cliutil.arg("-m", "--model",
+             dest="model",
+             action="store",
+             help="Test scene name : Tnv")
+@cliutil.arg("-e", "--virtenv",
+             dest="virtenv",
+             action="store",
+             help="virt env_build number(s): [1-8]")
+@cliutil.arg("-q", "--queues",
+             dest="queues",
+             action="store",
+             help="VM nic queues.")
+@cliutil.arg("-f", "--flows",
+             dest="flows",
+             action="store",
+             help="Flow queue(s) : [1-8]")
+@cliutil.arg("-v", "--vlans",
+             dest="vlans",
+             action="store_true",
+             help="vlan setting : 100-150;200-250")
+@cliutil.arg("-d", "--direct",
+             dest="direct",
+             action="store",
+             choices=["single", "double"],
+             help="Flow Direction")
+@cliutil.arg("-b", "--bind",
+             dest="strategy",
+             action="store",
+             help="CPU bind strategy :  1 | 2 | 3 ")
+@cliutil.arg("--config_file",
+             dest="config_file",
+             default='/etc/vstf/spirent/optimize.ini',
+             action="store",
+             help="config file for optimize.")
+@cliutil.arg("--strategyfile",
+             dest="strategyfile",
+             default='/etc/vstf/spirent/strategy.ini',
+             action="store",
+             help="config file for strategy.")
+def do_spirent_test(args):
+    ret = call(make_msg("perf_test",
+                        plugin="spirent",
+                        conner=args.conner,
+                        measurand=args.measurand,
+                        virtenv=args.virtenv,
+                        queues=args.queues,
+                        direct=args.direct,
+                        flows=args.flows,
+                        strategy=args.strategy,
+                        model=args.model,
+                        vlans=args.vlans,
+                        configfile=args.config_file,
+                        strategyfile=args.strategyfile))
+    print_stdout(ret)
+
+
+@cliutil.arg("--host", dest="host", action="store", default=None,
+             help="which host to list affctl info")
+def do_affctl_list(args):
+    ret = call(make_msg("affctl_list", host=args.host))
+    print_stdout(ret)
+
+
+def main():
+    parser = VstfParser(prog="vstfadm", description="vstf administration")
+    parser.set_subcommand_parser(sys.modules[__name__], "functions")
+    args = parser.parse_args()
+    if args.func is None:
+        sys.exit(-1)
+    setup_logging(level=logging.DEBUG, log_file="/var/log/vstf/vstf-adm.log", clevel=logging.INFO)
+    # connect to manage
+    global CONN
+    try:
+        CONN = unix.UdpClient()
+        CONN.connect(constants.sockaddr)
+    except Exception as e:
+        raise e
+
+    args.func(args)
+    # call functions of manage
+    sys.exit(CONN.close())
diff --git a/vstf/vstf/rpc_frame_work/__init__.py b/vstf/vstf/rpc_frame_work/__init__.py
new file mode 100755 (executable)
index 0000000..4dc8a6a
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright Huawei Technologies Co., Ltd. 1998-2015.
+# All Rights Reserved.
+#
+#    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.
+
diff --git a/vstf/vstf/rpc_frame_work/constant.py b/vstf/vstf/rpc_frame_work/constant.py
new file mode 100755 (executable)
index 0000000..da555de
--- /dev/null
@@ -0,0 +1,13 @@
+fan_exchange = "esp_exchange"
+exchange_d = "vstf_dexch"
+queue_common = "vstf-"
+TIMEOUT = 10
+NEVERTIMEOUT = 9999
+FAN = "fanout"
+DIRECT = "direct"
+
+# these for commandline
+
+NETDEV = "netdevs"
+LISTDEVS = "lspci | grep Eth"
+LIST_DEVS_NAME = "ls /sys/class/net/"
diff --git a/vstf/vstf/rpc_frame_work/rpc_consumer.py b/vstf/vstf/rpc_frame_work/rpc_consumer.py
new file mode 100755 (executable)
index 0000000..f7aacfd
--- /dev/null
@@ -0,0 +1,421 @@
+#!/usr/bin/env python
+# coding=utf-8
+import logging
+import time
+
+import stevedore
+
+import pika
+from vstf.common import message
+from vstf.rpc_frame_work import constant
+
+LOGGER = logging.getLogger(__name__)
+
+
+class VstfConsumer(object):
+    """This is an example consumer that will handle unexpected interactions
+    with RabbitMQ such as channel and connection closures.
+
+    If RabbitMQ closes the connection, it will reopen it. You should
+    look at the output, as there are limited reasons why the connection may
+    be closed, which usually are tied to permission related issues or
+    socket timeouts.
+
+    If the channel is closed, it will indicate a problem with one of the
+    commands that were issued and that should surface in the output as well.
+
+    """
+
+    def __init__(self, agent,
+                 user='guest',
+                 passwd='guest',
+                 host='localhost',
+                 port='5672',
+                 agent_id="agent"):
+        """Create a new instance of the consumer class, passing in the AMQP
+        URL used to connect to RabbitMQ.
+
+        :param str    user: The user name of RabbitMQ server
+        :param str    passwd: The passwd of RabbitMQ server
+        :param str    host: The ip of RabbitMQ server
+        :param str    port: connection's port
+
+        """
+        self._connection = None
+        self._channel = None
+        self._closing = False
+        self._consumer_tag = None
+        self.user = user
+        self.passwd = passwd
+        self.srv = host
+        self.port = port
+        self.agent_id = agent_id
+        self.url = 'amqp://' + self.user + ':' + self.passwd + '@' + self.srv + ':' + self.port + '/%2F'
+
+        # load the agent_funcs
+        try:
+            self.agent_ops = stevedore.driver.DriverManager(
+                namespace="agent.plugins",
+                name=agent,
+                invoke_on_load=True)
+        except Exception as e:
+            LOGGER.error(message.dumpstrace())
+            raise e
+
+        super(VstfConsumer, self).__init__()
+
+    def connect(self):
+        """This method connects to RabbitMQ, returning the connection handle.
+        When the connection is established, the on_connection_open method
+        will be invoked by pika.
+
+        :rtype: pika.SelectConnection
+
+        """
+        LOGGER.info('Connecting to %s:%s', self.srv, self.port)
+        return pika.SelectConnection(pika.URLParameters(self.url),
+                                     self.on_connection_open,
+                                     stop_ioloop_on_close=False)
+
+    #         return pika.SelectConnection(pika.ConnectionParameters(host="%s:%s" %(self.srv,self.port)),
+    #                                      self.on_connection_open,
+    #                                      stop_ioloop_on_close=False)
+
+    def on_connection_open(self, unused_connection):
+        """This method is called by pika once the connection to RabbitMQ has
+        been established. It passes the handle to the connection object in
+        case we need it, but in this case, we'll just mark it unused.
+
+        :type unused_connection: pika.SelectConnection
+
+        """
+        LOGGER.info('Connection opened')
+        self.add_on_connection_close_callback()
+        self.open_channel()
+
+    def add_on_connection_close_callback(self):
+        """This method adds an on close callback that will be invoked by pika
+        when RabbitMQ closes the connection to the publisher unexpectedly.
+
+        """
+        LOGGER.info('Adding connection close callback')
+        self._connection.add_on_close_callback(self.on_connection_closed)
+
+    def on_connection_closed(self, connection, reply_code, reply_text):
+        """This method is invoked by pika when the connection to RabbitMQ is
+        closed unexpectedly. Since it is unexpected, we will reconnect to
+        RabbitMQ if it disconnects.
+
+        :param pika.connection.Connection connection: The closed connection obj
+        :param int reply_code: The server provided reply_code if given
+        :param str reply_text: The server provided reply_text if given
+
+        """
+        self._channel = None
+        if self._closing:
+            self._connection.ioloop.stop()
+        else:
+            LOGGER.warning('Connection closed, reopening in 2 seconds: (%s) %s',
+                           reply_code, reply_text)
+            self._connection.add_timeout(2, self.reconnect)
+
+    def reconnect(self):
+        """Will be invoked by the IOLoop timer if the connection is
+        closed. See the on_connection_closed method.
+
+        """
+        # This is the old connection IOLoop instance, stop its ioloop
+        # Sometimes the broken connection may be exception
+        try:
+            self._connection.ioloop.stop()
+        except Exception:
+            pass
+
+        while not self._closing:
+            # Create a new connection
+            try:
+                self._connection = self.connect()
+            except Exception:
+                LOGGER.error(message.dumpstrace())
+                time.sleep(3)
+                continue
+            break
+
+        # There is now a new connection, needs a new ioloop to run
+        self._connection.ioloop.start()
+
+    def open_channel(self):
+        """Open a new channel with RabbitMQ by issuing the Channel.Open RPC
+        command. When RabbitMQ responds that the channel is open, the
+        on_channel_open callback will be invoked by pika.
+
+        """
+        LOGGER.info('Creating a new channel')
+        self._connection.channel(on_open_callback=self.on_channel_open)
+
+    def on_channel_open(self, channel):
+        """This method is invoked by pika when the channel has been opened.
+        The channel object is passed in so we can make use of it.
+
+        Since the channel is now open, we'll declare the exchange to use.
+
+        :param pika.channel.Channel channel: The channel object
+
+        """
+        LOGGER.info('Channel opened')
+        self._channel = channel
+        self.add_on_channel_close_callback()
+        self.setup_exchanges()
+
+    def add_on_channel_close_callback(self):
+        """This method tells pika to call the on_channel_closed method if
+        RabbitMQ unexpectedly closes the channel.
+
+        """
+        LOGGER.info('Adding channel close callback')
+        self._channel.add_on_close_callback(self.on_channel_closed)
+
+    def on_channel_closed(self, channel, reply_code, reply_text):
+        """Invoked by pika when RabbitMQ unexpectedly closes the channel.
+        Channels are usually closed if you attempt to do something that
+        violates the protocol, such as re-declare an exchange or queue with
+        different parameters. In this case, we'll close the connection
+        to shutdown the object.
+
+        :param pika.channel.Channel: The closed channel
+        :param int reply_code: The numeric reason the channel was closed
+        :param str reply_text: The text reason the channel was closed
+
+        """
+        LOGGER.warning('Channel %i was closed: (%s) %s',
+                       channel, reply_code, reply_text)
+        self._connection.close()
+
+    def setup_exchanges(self):
+        """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC
+        command. When it is complete, the on_exchange_declareok method will
+        be invoked by pika.
+
+        :param str|unicode exchange_name: The name of the exchange to declare
+
+        """
+        LOGGER.info('Declaring %s exchange %s', constant.DIRECT, constant.exchange_d)
+        self._channel.exchange_declare(self.on_direct_exchange_declareok,
+                                       constant.exchange_d,
+                                       constant.DIRECT)
+
+    #         LOGGER.info('Declaring %s exchange %s', constant.FAN, constant.fan_exchange)
+    #         self._channel.exchange_declare(self.on_fan_exchange_declareok,
+    #                                        constant.fan_exchange,
+    #                                        constant.FAN)
+
+    def on_fan_exchange_declareok(self, unused_frame):
+        """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC
+        command.
+
+        :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame
+
+        """
+        LOGGER.info('Exchange declared')
+        pass
+
+    def on_direct_exchange_declareok(self, unused_frame):
+        queue_name = constant.queue_common + self.agent_id
+        self.setup_queue(queue_name, self.on_direct_queue_declareok)
+
+    def setup_queue(self, queue_name, next_ops):
+        """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC
+        command. When it is complete, the on_queue_declareok method will
+        be invoked by pika.
+
+        :param str|unicode queue_name: The name of the queue to declare.
+
+        """
+        LOGGER.info('Declaring queue %s', queue_name)
+        self._channel.queue_declare(next_ops,
+                                    queue=queue_name,
+                                    exclusive=True)
+
+    def on_direct_queue_declareok(self, method_frame):
+        """Method invoked by pika when the Queue.Declare RPC call made in
+        setup_queue has completed. In this method we will bind the queue
+        and exchange together with the routing key by issuing the Queue.Bind
+        RPC command. When this command is complete, the on_bindok method will
+        be invoked by pika.
+
+        :param pika.frame.Method method_frame: The Queue.DeclareOk frame
+
+        """
+        queue_name = constant.queue_common + self.agent_id
+        LOGGER.info('Binding %s to %s with %s',
+                    queue_name, constant.exchange_d, queue_name)
+        self._channel.queue_bind(self.on_bindok, queue_name,
+                                 constant.exchange_d, queue_name)
+
+    def on_bindok(self, unused_frame):
+        """Invoked by pika when the Queue.Bind method has completed. At this
+        point we will start consuming messages by calling start_consuming
+        which will invoke the needed RPC commands to start the process.
+
+        :param pika.frame.Method unused_frame: The Queue.BindOk response frame
+
+        """
+        LOGGER.info('Queue bound')
+        self.start_consuming()
+
+    def start_consuming(self):
+        """This method sets up the consumer by first calling
+        add_on_cancel_callback so that the object is notified if RabbitMQ
+        cancels the consumer. It then issues the Basic.Consume RPC command
+        which returns the consumer tag that is used to uniquely identify the
+        consumer with RabbitMQ. We keep the value to use it when we want to
+        cancel consuming. The on_message method is passed in as a callback pika
+        will invoke when a message is fully received.
+
+        """
+        queue_name = constant.queue_common + self.agent_id
+        LOGGER.info('Issuing consumer related RPC commands')
+        self.add_on_cancel_callback()
+        self._consumer_tag = self._channel.basic_consume(self.on_message,
+                                                         queue_name)
+
+    def add_on_cancel_callback(self):
+        """Add a callback that will be invoked if RabbitMQ cancels the consumer
+        for some reason. If RabbitMQ does cancel the consumer,
+        on_consumer_cancelled will be invoked by pika.
+
+        """
+        LOGGER.info('Adding consumer cancellation callback')
+        self._channel.add_on_cancel_callback(self.on_consumer_cancelled)
+
+    def on_consumer_cancelled(self, method_frame):
+        """Invoked by pika when RabbitMQ sends a Basic.Cancel for a consumer
+        receiving messages.
+
+        :param pika.frame.Method method_frame: The Basic.Cancel frame
+
+        """
+        LOGGER.info('Consumer was cancelled remotely, shutting down: %r',
+                    method_frame)
+        if self._channel:
+            self._channel.close()
+
+    def on_message(self, respone_chanl, basic_deliver, properties, body):
+        """Invoked by pika when a message is delivered from RabbitMQ. The
+        channel is passed for your convenience. The basic_deliver object that
+        is passed in carries the exchange, routing key, delivery tag and
+        a redelivered flag for the message. The properties passed in is an
+        instance of BasicProperties with the message properties and the body
+        is the message that was sent.
+
+        :param pika.channel.Channel unused_channel: The channel object
+        :param pika.Spec.Basic.Deliver: basic_deliver method
+        :param pika.Spec.BasicProperties: properties
+        :param str|unicode body: The message body
+
+        """
+        LOGGER.info('Received message # %s from %s: %s',
+                    basic_deliver.delivery_tag, properties.app_id, body)
+        try:
+            msg = message.decode(body)
+            head = message.get_context(msg)
+            main_body = message.get_body(msg)
+
+            LOGGER.debug("recive the msg: head:%(h)s, body:%(b)s",
+                         {'h': head,
+                          'b': main_body})
+
+            func = getattr(self.agent_ops.driver, main_body.get('method'))
+            response = func(**main_body.get('args'))
+        except Exception as e:
+            LOGGER.error(message.dumpstrace())
+            LOGGER.error("request happend error")
+            response = {'exception': {'name': e.__class__.__name__,
+                                      'message': e.message,
+                                      'args': e.args}}
+        finally:
+            response = message.add_context(response, **head)
+            LOGGER.debug("response the msg: head:%(h)s, body:%(b)s",
+                         {'h': response.get('head'), 'b': response.get('body')})
+
+            respone_chanl.basic_publish(exchange=constant.exchange_d,
+                                        routing_key=properties.reply_to,
+                                        properties=pika.BasicProperties(correlation_id=properties.correlation_id),
+                                        body=message.encode(response)
+                                        )
+            # no matter what happend, tell the mq-server to drop this msg.
+
+        self.acknowledge_message(basic_deliver.delivery_tag)
+
+    def acknowledge_message(self, delivery_tag):
+        """Acknowledge the message delivery from RabbitMQ by sending a
+        Basic.Ack RPC method for the delivery tag.
+
+        :param int delivery_tag: The delivery tag from the Basic.Deliver frame
+
+        """
+        LOGGER.info('Acknowledging message %s', delivery_tag)
+        self._channel.basic_ack(delivery_tag)
+
+    def stop_consuming(self):
+        """Tell RabbitMQ that you would like to stop consuming by sending the
+        Basic.Cancel RPC command.
+
+        """
+        if self._channel:
+            LOGGER.info('Sending a Basic.Cancel RPC command to RabbitMQ')
+            self._channel.basic_cancel(self.on_cancelok, self._consumer_tag)
+
+    def on_cancelok(self, unused_frame):
+        """This method is invoked by pika when RabbitMQ acknowledges the
+        cancellation of a consumer. At this point we will close the channel.
+        This will invoke the on_channel_closed method once the channel has been
+        closed, which will in-turn close the connection.
+
+        :param pika.frame.Method unused_frame: The Basic.CancelOk frame
+
+        """
+        LOGGER.info('RabbitMQ acknowledged the cancellation of the consumer')
+        self.close_channel()
+
+    def close_channel(self):
+        """Call to close the channel with RabbitMQ cleanly by issuing the
+        Channel.Close RPC command.
+
+        """
+        LOGGER.info('Closing the channel')
+        self._channel.close()
+
+    def run(self):
+        """Run the example consumer by connecting to RabbitMQ and then
+        starting the IOLoop to block and allow the SelectConnection to operate.
+
+        """
+        try:
+            self._connection = self.connect()
+        except Exception as e:
+            LOGGER.error(message.dumpstrace())
+        self._connection.ioloop.start()
+
+    def stop(self):
+        """Cleanly shutdown the connection to RabbitMQ by stopping the consumer
+        with RabbitMQ. When RabbitMQ confirms the cancellation, on_cancelok
+        will be invoked by pika, which will then closing the channel and
+        connection. The IOLoop is started again because this method is invoked
+        when CTRL-C is pressed raising a KeyboardInterrupt exception. This
+        exception stops the IOLoop which needs to be running for pika to
+        communicate with RabbitMQ. All of the commands issued prior to starting
+        the IOLoop will be buffered but not processed.
+
+        """
+        LOGGER.info('Stopping')
+        self._closing = True
+        self.stop_consuming()
+        self._connection.ioloop.stop()
+        self.close_connection()
+        LOGGER.info('Stopped')
+
+    def close_connection(self):
+        """This method closes the connection to RabbitMQ."""
+        LOGGER.info('Closing connection')
+        self._connection.close()
diff --git a/vstf/vstf/rpc_frame_work/rpc_producer.py b/vstf/vstf/rpc_frame_work/rpc_producer.py
new file mode 100755 (executable)
index 0000000..c56c9e5
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+# coding=utf-8
+import uuid
+import json
+import time
+import exceptions
+import logging
+
+import pika
+from vstf.common import message
+from vstf.common import excepts
+from vstf.rpc_frame_work import constant
+
+LOG = logging.getLogger(__name__)
+
+
+class RpcProxy(object):
+    def __init__(self, host,
+                 user='guest',
+                 passwd='guest',
+                 port='5672'):
+        """create a connection to rabbitmq,direct call and fan call supported.
+
+        """
+        # try to create connection of rabbitmq
+        self._channel = None
+        self._connection = None
+        self._queue = str(uuid.uuid4())
+        self._consume_tag = None
+
+        self.user = user
+        self.passwd = passwd
+        self.srv = host
+        self.port = port
+        self.url = 'amqp://' + self.user + ':' + self.passwd + '@' + self.srv + ':' + self.port + '/%2F'
+        try:
+            self.connect(host, self.setup_vstf_producer)
+        except Exception as e:
+            LOG.error("create connection failed. e:%(e)s", {'e': e})
+            raise e
+
+        self.response = None
+        self.corr_id = None
+
+    def connect(self, host, ok_callback):
+        """Create a Blocking connection to the rabbitmq-server
+        
+        :param str    host: the rabbitmq-server's host
+        :param obj    ok_callback: if connect success than do this function
+        
+        """
+        LOG.info("Connect to the server %s", host)
+        self._connection = pika.BlockingConnection(pika.URLParameters(self.url))
+        if self._connection:
+            ok_callback()
+
+    def setup_vstf_producer(self):
+        self.open_channel()
+        self.create_exchange(constant.exchange_d, constant.DIRECT)
+        self.bind_queues()
+        self.start_consumer()
+
+    def open_channel(self):
+        self._channel = self._connection.channel()
+        if self._channel:
+            self._channel.confirm_delivery()
+
+    def create_exchange(self, name, type):
+        LOG.info("Create %s exchange: %s", type, name)
+        self._channel.exchange_declare(exchange=name, type=type)
+
+    def bind_queues(self):
+        LOG.info("Declare queue %s and bind it to exchange %s",
+                 self._queue, constant.exchange_d)
+        self._channel.queue_declare(queue=self._queue, exclusive=True)
+        self._channel.queue_bind(exchange=constant.exchange_d, queue=self._queue)
+
+    def start_consumer(self):
+        LOG.info("Start response consumer")
+        self._consume_tag = self._channel.basic_consume(self.on_response,
+                                                        no_ack=True,
+                                                        queue=self._queue)
+
+    def stop_consuming(self):
+        """Tell RabbitMQ that you would like to stop consuming by sending the
+        Basic.Cancel RPC command.
+
+        """
+        if self._channel:
+            LOG.info('Sending a Basic.Cancel RPC command to RabbitMQ')
+            self._channel.basic_cancel(self._consume_tag)
+
+        self.close_channel()
+
+    def close_channel(self):
+        """Call to close the channel with RabbitMQ cleanly by issuing the
+        Channel.Close RPC command.
+
+        """
+        LOG.info('Closing the channel')
+        self._channel.close()
+
+    def close_connection(self):
+        """This method closes the connection to RabbitMQ."""
+        LOG.info('Closing connection')
+        self._connection.close()
+
+    def stop(self):
+        self.stop_consuming()
+        self.close_connection()
+
+    def on_response(self, ch, method, props, body):
+        """this func reciver the msg"""
+        self.response = None
+        if self.corr_id == props.correlation_id:
+            self.response = json.loads(body)
+            LOG.debug("Proxy producer reciver the msg: head:%(h)s, body:%(b)s",
+                      {'h': self.response.get('head'), 'b': self.response.get('body')})
+        else:
+            LOG.warn("Proxy producer Drop the msg "
+                     "because of the wrong correlation id, %s\n" % body)
+
+    def publish(self, target, corrid, body):
+        properties = pika.BasicProperties(reply_to=self._queue,
+                                          correlation_id=corrid)
+        LOG.debug("start to publish message to the exchange=%s, target=%s, msg=%s"
+                  , constant.exchange_d, target, body)
+        return self._channel.basic_publish(exchange=constant.exchange_d,
+                                           routing_key=target,
+                                           mandatory=True,
+                                           properties=properties,
+                                           body=message.encode(body))
+
+    def call(self, msg, target='agent', timeout=constant.TIMEOUT):
+        """send msg to agent by id, this func will wait ack until timeout
+        :msg the msg to be sent
+        :id agent's id
+        :timeout timeout of waiting response
+
+        """
+        self.response = None
+        queue = constant.queue_common + target
+        # the msg request and respone must be match by corr_id
+        self.corr_id = str(uuid.uuid4())
+        # same msg format 
+        msg = message.add_context(msg, corrid=self.corr_id)
+
+        # send msg to the queue
+        try:
+            ret = self.publish(queue, self.corr_id, msg)
+        except Exception as e:
+            LOG.error(message.dumpstrace())
+            raise excepts.ChannelDie
+
+        # if delivery msg failed. return error
+        # clean the msg in the queue
+        if not ret:
+            LOG.error("productor message delivery failed.")
+            return "Message can not be deliveryed, please check the connection of agent."
+
+        # wait for response
+        t_begin = time.time()
+        while self.response is None:
+            self._connection.process_data_events()
+            count = time.time() - t_begin
+            if count > timeout:
+                LOG.error("Command timeout!")
+                # flush the msg of the queue
+                self._channel.queue_purge(queue=queue)
+                # self.channel.basic_cancel()
+                return False
+
+        msg_body = message.get_body(message.decode(self.response))
+
+        # deal with exceptions
+        if msg_body \
+                and isinstance(msg_body, dict) \
+                and msg_body.has_key('exception'):
+            ename = str(msg_body['exception'].get('name'))
+            if hasattr(exceptions, ename):
+                e = getattr(exceptions, ename)()
+            else:
+                class CallError(Exception):
+                    pass
+
+                e = CallError()
+            e.message = str(msg_body['exception'].get('message'))
+            e.args = msg_body['exception'].get('args')
+            raise e
+        else:
+            return msg_body
+
+
+class Server(object):
+    def __init__(self, host=None,
+                 user='guest',
+                 passwd='guest',
+                 port='5672'):
+        super(Server, self).__init__()
+        # Default use salt's master ip as rabbit rpc server ip
+        if host is None:
+            raise Exception("Can not create rpc proxy because of the None rabbitmq server address.")
+
+        self.host = host
+        self.port = port
+        self.user = user
+        self.passwd = passwd
+        try:
+            self.proxy = RpcProxy(host=host,
+                                  port=port,
+                                  user=user,
+                                  passwd=passwd)
+        except Exception as e:
+            raise e
+
+    def call(self, msg, msg_id, timeout=constant.TIMEOUT):
+        """when you add a server listen to the rabbit
+        you must appoint which queue to be listen.
+        :@queue the queue name.
+        """
+        retry = False
+
+        try:
+            ret = self.proxy.call(msg, target=msg_id, timeout=timeout)
+        except excepts.ChannelDie:
+            # this may be the proxy die, try to reconnect to the rabbit
+            del self.proxy
+            self.proxy = RpcProxy(host=self.host,
+                                  port=self.port,
+                                  user=self.user,
+                                  passwd=self.passwd)
+            if self.proxy is None:
+                raise excepts.UnsolvableExit
+            retry = True
+
+        if retry:
+            # if retry happened except, throw to uplay
+            ret = self.proxy.call(msg, target=msg_id, timeout=timeout)
+
+        return ret
+
+    def cast(self, msg):
+        """when you want to send msg to all agent and no reply, use this func"""
+        LOG.warn("cast not support now.")
+
+    def make_msg(self, method, **kargs):
+        return {'method': method,
+                'args': kargs}
+
+    def close(self):
+        self.proxy.stop()