From 09baadba2cc75a9f3b44c109b5fe49ca63d44b59 Mon Sep 17 00:00:00 2001
From: Yichen Wang <yicwang@cisco.com>
Date: Wed, 4 Feb 2015 11:58:00 -0800
Subject: [PATCH] Initial release of VMTP to stackforge

Change-Id: I30eb092d9a70dc6b3642a84887bb4604b1a3ea54
---
 .coveragerc                 |   7 +
 .dockerignore               |   8 +
 .gitignore                  |  59 +++
 .mailmap                    |   3 +
 .testr.conf                 |   7 +
 CONTRIBUTING.rst            |  16 +
 Dockerfile                  |  22 ++
 HACKING.rst                 |   4 +
 LICENSE                     | 176 +++++++++
 MANIFEST.in                 |   6 +
 README.md                   | 314 +++++++++++++++
 README.rst                  |  29 ++
 babel.cfg                   |   2 +
 cfg.default.yaml            | 162 ++++++++
 cfg.existing.yaml           |  10 +
 cfg.nimbus.svl.yaml         |  14 +
 cfg.nimbus.yaml             |   9 +
 compute.py                  | 276 +++++++++++++
 credentials.py              | 110 ++++++
 doc/source/conf.py          |  75 ++++
 doc/source/contributing.rst |   4 +
 doc/source/index.rst        |  25 ++
 doc/source/installation.rst |  12 +
 doc/source/readme.rst       |   1 +
 doc/source/usage.rst        |   7 +
 instance.py                 | 284 +++++++++++++
 iperf_tool.py               | 206 ++++++++++
 monitor.py                  | 443 +++++++++++++++++++++
 network.py                  | 262 ++++++++++++
 nuttcp_tool.py              | 194 +++++++++
 openstack-common.conf       |   6 +
 perf_instance.py            | 105 +++++
 perf_tool.py                | 289 ++++++++++++++
 pns_mongo.py                | 142 +++++++
 pnsdb_summary.py            | 328 +++++++++++++++
 pylintrc                    |  17 +
 requirements-dev.txt        |   5 +
 requirements.txt            |  20 +
 run_tests.sh                |   2 +
 setup.cfg                   |  47 +++
 setup.py                    |  22 ++
 ssh/id_rsa                  |  27 ++
 ssh/id_rsa.pub              |   1 +
 sshutils.py                 | 475 ++++++++++++++++++++++
 test-requirements.txt       |  15 +
 tools/iperf                 | Bin 0 -> 68488 bytes
 tools/nuttcp-7.3.2          | Bin 0 -> 154592 bytes
 tox.ini                     |  42 ++
 vmtp.py                     | 768 ++++++++++++++++++++++++++++++++++++
 vmtp/__init__.py            |  19 +
 vmtp/tests/__init__.py      |   0
 vmtp/tests/base.py          |  23 ++
 vmtp/tests/test_vmtp.py     |  28 ++
 53 files changed, 5128 insertions(+)
 create mode 100644 .coveragerc
 create mode 100644 .dockerignore
 create mode 100644 .gitignore
 create mode 100644 .mailmap
 create mode 100644 .testr.conf
 create mode 100644 CONTRIBUTING.rst
 create mode 100644 Dockerfile
 create mode 100644 HACKING.rst
 create mode 100644 LICENSE
 create mode 100644 MANIFEST.in
 create mode 100644 README.md
 create mode 100644 README.rst
 create mode 100644 babel.cfg
 create mode 100644 cfg.default.yaml
 create mode 100644 cfg.existing.yaml
 create mode 100644 cfg.nimbus.svl.yaml
 create mode 100644 cfg.nimbus.yaml
 create mode 100644 compute.py
 create mode 100644 credentials.py
 create mode 100755 doc/source/conf.py
 create mode 100644 doc/source/contributing.rst
 create mode 100644 doc/source/index.rst
 create mode 100644 doc/source/installation.rst
 create mode 100644 doc/source/readme.rst
 create mode 100644 doc/source/usage.rst
 create mode 100644 instance.py
 create mode 100644 iperf_tool.py
 create mode 100755 monitor.py
 create mode 100755 network.py
 create mode 100644 nuttcp_tool.py
 create mode 100644 openstack-common.conf
 create mode 100644 perf_instance.py
 create mode 100644 perf_tool.py
 create mode 100755 pns_mongo.py
 create mode 100755 pnsdb_summary.py
 create mode 100644 pylintrc
 create mode 100644 requirements-dev.txt
 create mode 100644 requirements.txt
 create mode 100644 run_tests.sh
 create mode 100644 setup.cfg
 create mode 100755 setup.py
 create mode 100644 ssh/id_rsa
 create mode 100644 ssh/id_rsa.pub
 create mode 100644 sshutils.py
 create mode 100644 test-requirements.txt
 create mode 100755 tools/iperf
 create mode 100755 tools/nuttcp-7.3.2
 create mode 100644 tox.ini
 create mode 100755 vmtp.py
 create mode 100644 vmtp/__init__.py
 create mode 100644 vmtp/tests/__init__.py
 create mode 100644 vmtp/tests/base.py
 create mode 100644 vmtp/tests/test_vmtp.py

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..444ba6d
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,7 @@
+[run]
+branch = True
+source = vmtp
+omit = vmtp/tests/*,vmtp/openstack/*
+
+[report]
+ignore-errors = True
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..1bd415a
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+ansible
+installer
+requirements-dev.txt
+cloud_init*
+.git
+.gitignore
+.gitreview
+.pylintrc
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58d97fd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+.testrepository
+.venv
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Complexity
+output/*.html
+output/*/index.html
+
+# Sphinx
+doc/build
+
+# pbr generates these
+AUTHORS
+ChangeLog
+
+# Editors
+*~
+.*.swp
+.*sw?
+*cscope*
+.ropeproject/
+
+# vmtp
+*.local*
+*.json
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..516ae6f
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,3 @@
+# Format is:
+# <preferred e-mail> <other e-mail 1>
+# <preferred e-mail> <other e-mail 2>
diff --git a/.testr.conf b/.testr.conf
new file mode 100644
index 0000000..6d83b3c
--- /dev/null
+++ b/.testr.conf
@@ -0,0 +1,7 @@
+[DEFAULT]
+test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
+             OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
+             OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
+             ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
+test_id_option=--load-list $IDFILE
+test_list_option=--list
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..53d09d7
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,16 @@
+If you would like to contribute to the development of OpenStack,
+you must follow the steps in this page:
+
+   http://docs.openstack.org/infra/manual/developers.html
+
+Once those steps have been completed, changes to OpenStack
+should be submitted for review via the Gerrit tool, following
+the workflow documented at:
+
+   http://docs.openstack.org/infra/manual/developers.html#development-workflow
+
+Pull requests submitted through GitHub will be ignored.
+
+Bugs should be filed on Launchpad, not GitHub:
+
+   https://bugs.launchpad.net/vmtp
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c5e1591
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,22 @@
+# docker file for creating a container that has vmtp installed and ready to use
+FROM ubuntu:14.04
+MAINTAINER openstack-systems-group <openstack-systems-group@cisco.com>
+
+# Install VMTP script and dependencies
+RUN apt-get update && apt-get install -y \
+   lib32z1-dev \
+   libffi-dev \
+   libssl-dev \
+   libxml2-dev \
+   libxslt1-dev \
+   libyaml-dev \
+   openssh-client \
+   python \
+   python-dev \
+   python-lxml \
+   python-pip
+
+COPY . /vmtp/
+
+RUN pip install -r /vmtp/requirements.txt
+
diff --git a/HACKING.rst b/HACKING.rst
new file mode 100644
index 0000000..4f2031d
--- /dev/null
+++ b/HACKING.rst
@@ -0,0 +1,4 @@
+vmtp Style Commandments
+===============================================
+
+Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..68c771a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c978a52
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include AUTHORS
+include ChangeLog
+exclude .gitignore
+exclude .gitreview
+
+global-exclude *.pyc
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..aec1e39
--- /dev/null
+++ b/README.md
@@ -0,0 +1,314 @@
+
+# VMTP: An OpenStack TCP/UDP throughput measurement tool
+
+VMTP is a python application that will automatically perform ping connectivity, ping round trip time measuerment (latency) and TCP/UDP throughput measurement for the following flows on any OpenStack deployment:
+
+* VM to VM same network (private fixed IP)
+* VM to VM different network same tenant (intra-tenant L3 fixed IP)
+* VM to VM different network and tenant (floating IP inter-tenant L3)
+
+Optionally, when an external Linux host is available:
+
+* external host/VM download and upload throughput/latency (L3/floating IP)
+
+Optionally, when ssh login to any Linux host (native or virtual) is available:
+
+* host to host throughput (intra-node and inter-node)
+
+For VM-related flows, VMTP will automatically create the necessary OpenStack resources (router, networks, subnets, key pairs, security groups, test VMs), perform the throughput measurements then cleanup all related resources before exiting.
+In the case involving pre-existing native or virtual hosts, VMTP will ssh to the targeted hosts to perform measurements.
+
+All TCP/UDP throughput measurements are done using the nuttcp tool by default.
+The iperf tool can be used alternatively (--tp-tool iperf).
+
+Optionally, VMTP can extract automatically CPU usage from all native hosts in the cloud during the throughput tests, provided the Ganglia monitoring service (gmond) is installed and enabled on those hosts.
+
+Pre-requisite to run VMTP successfully:
+
+* For VM related performance measurements:
+
+    * Access to the cloud Horizon Dashboard
+    * 1 working external network pre-configured on the cloud (VMTP will pick the first one found)
+    * at least 2 floating IP if an external router is configured or 3 floating IP if there is no external router configured
+    * 1 Linux image available in OpenStack (any distribution)
+    * a configuration file that is properly set for the cloud to test (see "Configuration File" section below)
+
+* for native/external host throughput, a public key must be installed on the target hosts (see ssh password-less access below)
+* for pre-existing native host throughputs, firewalls must be configured to allow TCP/UDP ports 5001 and TCP port 5002
+* Docker if using the VMTP Docker image
+
+## VMTP results output
+
+VMTP will display the results to stdout with the following data:
+
+* session general information (date, auth_url, OpenStack encaps, VMTP version...)
+* list of results per flow, for each flow:
+
+    * flow name
+    * to and from IP addresses
+    * to and from availability zones (if VM)
+    * results:
+        * TCP
+            * throughput value
+            * number of retransmissions
+            * round trip time in ms
+            * CPU usage (if enabled), for each host in the openstack cluster:
+                * baseline (before test starts)
+                * 1 or more readings during test
+        * UDP
+            * for each packet size
+                * throughput value
+                * loss rate
+                * CPU usage (if enabled)
+        * ICMP
+            * average, min, max and stddev round trip time in ms
+
+Detailed results can also be stored in a file in JSON format using the --json command line argument.
+
+## How to run the VMTP tool
+
+### VMTP Docker image
+
+In its Docker image form, VMTP is located under the /vmtp directory in the container and can either take arguments from the host shell, or can be executed from inside the Docker image shell.
+
+To run VMTP directly from the host shell (may require "sudo" up front if not root)
+
+```
+docker run -i -t <vmtp-docker-image-name> python /vmtp/vmtp.py <args>
+
+```
+
+To run VMTP from the Docker image shell:
+
+```
+docker run -i -t <vmtp-docker-image-name> /bin/bash
+cd /vmtp.py
+python vmtp.py <args>
+
+```
+(then type exit to exit and terminate the container instance)
+
+All the examples below assume running from inside the Docker image shell.
+
+### Print VMTP usage
+
+```
+usage: vmtp.py [-h] [-c <config_file>] [-r <openrc_file>]
+               [-m <gmond_ip>[:<port>]] [-p <password>] [-t <time>]
+               [--host <user>@<host_ssh_ip>[:<server-listen-if-name>]]
+               [--external-host <user>@<ext_host_ssh_ip>]
+               [--access_info {host:<hostip>, user:<user>, password:<pass>}]
+               [--mongod_server <server ip>] [--json <file>]
+               [--tp-tool nuttcp|iperf] [--hypervisor name]
+               [--inter-node-only] [--protocols T|U|I]
+               [--bandwidth <bandwidth>] [--tcpbuf <tcp_pkt_size1,...>]
+               [--udpbuf <udp_pkt_size1,...>] [--no-env] [-d] [-v]
+               [--stop-on-error]
+
+OpenStack VM Throughput V2.0.0
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -c <config_file>, --config <config_file>
+                        override default values with a config file
+  -r <openrc_file>, --rc <openrc_file>
+                        source OpenStack credentials from rc file
+  -m <gmond_ip>[:<port>], --monitor <gmond_ip>[:<port>]
+                        Enable CPU monitoring (requires Ganglia)
+  -p <password>, --password <password>
+                        OpenStack password
+  -t <time>, --time <time>
+                        throughput test duration in seconds (default 10 sec)
+  --host <user>@<host_ssh_ip>[:<server-listen-if-name>]
+                        native host throughput (targets requires ssh key)
+  --external-host <user>@<ext_host_ssh_ip>
+                        external-VM throughput (target requires ssh key)
+  --access_info {host:<hostip>, user:<user>, password:<pass>}
+                        access info for control host
+  --mongod_server <server ip>
+                        provide mongoDB server IP to store results
+  --json <file>         store results in json format file
+  --tp-tool nuttcp|iperf
+                        transport perf tool to use (default=nuttcp)
+  --hypervisor name     hypervisor to use in the avail zone (1 per arg, up to
+                        2 args)
+  --inter-node-only     only measure inter-node
+  --protocols T|U|I     protocols T(TCP), U(UDP), I(ICMP) - default=TUI (all)
+  --bandwidth <bandwidth>
+                        the bandwidth limit for TCP/UDP flows in K/M/Gbps,
+                        e.g. 128K/32M/5G. (default=no limit)
+  --tcpbuf <tcp_pkt_size1,...>
+                        list of buffer length when transmitting over TCP in
+                        Bytes, e.g. --tcpbuf 8192,65536. (default=65536)
+  --udpbuf <udp_pkt_size1,...>
+                        list of buffer length when transmitting over UDP in
+                        Bytes, e.g. --udpbuf 128,2048. (default=128,1024,8192)
+  --no-env              do not read env variables
+  -d, --debug           debug flag (very verbose)
+  -v, --version         print version of this script and exit
+  --stop-on-error       Stop and keep everything as-is on error (must cleanup
+                        manually)
+
+```
+
+### OpenStack openrc file
+
+VMTP requires downloading an "openrc" file from the OpenStack Dashboard (Project|Acces&Security!Api Access|Download OpenStack RC File)
+This file should then be passed to VMTP using the -r option or should be sourced prior to invoking VMTP.
+
+Note: the openrc file is not needed if VMTP only runs the native host throughput option (--host)
+
+### Configuration file
+
+VMTP configuration files follow the yaml syntax and contain variables used by VMTP to run and collect performance data.
+The default configuration is stored in the cfg.default.yaml file.
+Default values should be overwritten for any cloud under test by defining new variable values in a new configuration file that follows the same format.
+Variables that are not defined in the new configuration file will retain their default values.
+
+Parameters that you are most certainly required to change are:
+
+* the VM image name to use to run the performance tools, you will need to specify any standard Linux image (Ubuntu 12.04, 14.04, Fedora, RHEL7, CentOS...) - if needed you will need to upload an image to OpenStack manually prior to running VMTP
+* VM ssh user name to use (specific to the image)
+* the flavor name to use (often specific to each cloud)
+* name of the availability zone to use for running the performance test VMs (also specific to each cloud)
+
+Check the content of cfg.default.yaml file as it contains the list of configuration variables and instructions on how to set them.
+Create one configuration file for your specific cloud and use the -c option to pass that file name to VMTP.
+
+Note: the configuratin file is not needed if the VMTP only runs the native host throughput option (--host)
+
+### Typical run on an OpenStack cloud
+
+If executing a VMTP Docker image "docker run" (or "sudo docker run") must be placed in front of these commands unless you run a shell script directly from inside the container.
+
+
+Run VMTP on an OpenStack cloud with the "cfg.nimbus.svl.yaml" configuration file, the "nimbus-openrc.sh" rc file and the "admin" password:
+
+```
+
+python vmtp.py -r nimbus-openrc.sh -c cfg.nimbus.svl.yaml -p admin
+
+```
+
+Only collect ICMP and TCP measurements:
+
+```
+
+python vmtp.py -r nimbus-openrc.sh -c cfg.nimbus.svl.yaml -p admin --protocols IT
+
+```
+
+### Collecting native host performance data
+
+Run VMTP to get native host throughput internally to 172.29.87.29 and between 172.29.87.29 and 172.29.87.30 using the localadmin ssh username and run each tcp/udp test session for 120 seconds (instead of the default 10 seconds):
+
+```
+
+python vmtp.py --host localadmin@172.29.87.29 --host localadmin@172.29.87.30 --time 120
+
+```
+
+Note that this command requires each host to have the VMTP public key (ssh/id_rsa.pub) inserted into the ssh/authorized_keys file in the username home directory.
+
+### Bandwidth limit for TCP/UDP flow measurements
+
+Specify a value in --bandwidth will limit the bandwidth when performing throughput tests.
+
+The default behavior for both TCP/UDP are unlimitted. For TCP, we are leveraging on the protocol itself to get the best performance; while for UDP, we are doing a binary search to find the optimal bandwidth. 
+
+This is useful when running vmtp on production clouds. The test tool will use up all the bandwidth that may be needed by any other live VMs if we don't set any bandwidth limit. This feature will help to prevent impacting other VMs while running the test tool.
+
+### Cloud upload/download performance data
+
+Run the tool to get external host to VM upload and download speed:
+
+```
+
+python vmtp.py --external_host 172.29.87.29 -r nimbus-openrc.sh -c cfg.nimbus.svl.yaml -p admin
+
+```
+This example requires the external host to have the VMTP public key(ssh/id_rsa.pub) inserted into the ssh/authorized_keys file in the root home directory
+
+### Public cloud
+
+Public clouds are special because they may not expose all OpenStack APIs and may not allow all types of operations.
+Some public clouds have limitations in the way virtual networks can be used or require the use of a specific external router.
+Running VMTP against a public cloud will require a specific configuration file that takes into account those specificities.
+Refer to the provided public cloud sample configuration files for more information.
+
+### ssh password-less access
+For host throughput (--host), VMTP expects the target hosts to be pre-provisioned with a public key in order to allow password-less ssh.
+Test VMs are created through OpenStack by VMTP with the appropriate public key to allow password-less ssh. By default, VMTP uses a default VMTP public key located in ssh/id_rsa.pub, simply append the content of that file into the .ssh/authorized_keys file under the host login home directory).
+This default VMTP public key should only be used for transient test VMs and MUST NOT be used to provision native hosts since the corresponding private key is open to anybody!
+To use alternate key pairs, the 'private_key_file' variable in the configuration file must be overridden to point to the file containing the private key to use to connect with SSH.
+
+### TCP throughput measurement
+The TCP throughput reported is measured using the default message size of the test tool (64KB with nuttcp). The TCP MSS (maximum segment size) used is the one suggested by the TCP-IP stack (which is dependent on the MTU).
+VMTP currently does not support specifying multiple send sizes (similar to the UDP test) but could be relatively easily extended to do so.
+
+### UDP Throughput measurement
+UDP throughput is tricky because of limitations of the performance tools used, limitations of the linux kernel used and criteria for finding the throughput to report.
+The default setting is to find the "optimal" throughput with packet loss rate within the 2%..5% range.
+This is achieved by successive iterations at different throughput values.
+In some cases, it is not possible to converge with a loss rate within that range and trying to do so may require too many iterations.
+The algorithm used is empiric and tries to achieve a result within a reasonable and bounded number of iterations. In most cases the optimal throughput is found in less than 30 seconds for any given flow.
+
+UDP measurements are only available with nuttcp (not available with iperf).
+
+### Host Selection in Availability Zone
+
+The --hypervisor argument can be used to specify explicitly where to run the test VM in the configured availability zone.
+This can be handy for example when exact VM placement can impact the data path performance (for example rack based placement when the availability zone spans across multiple racks).
+The first --hypervisor argument specifies on which host to run the test server VM. The second --hypervisor argument (in the command line) specifies on which host to run the test client VMs.
+
+The value of the argument must match the hypervisor host name as known by OpenStack (or as displayed using "nova hypervisor-list")
+
+Example of usage:
+
+```
+
+vmtp.py --inter-node-only -r admin-openrc.sh -p lab -d --json vxlan-2.json --hypervisor tme212 --hypervisor tme211
+
+```
+
+### Measuring throughput on pre-existing VMs
+
+It is possible to run VMTP between pre-existing VMs that are accessible through SSH (using floating IP).
+Prior to running, the VMTP public key must be installed on each VM.
+The first IP passed (--host) is always the one running the server side. Optionally a server side listening interface name can be passed if clients should connect using a particular server IP.
+For example to measure throughput between 2 hosts using the network attached to the server interface "eth5":
+
+```
+
+python vmtp.py --host localadmin@172.29.87.29:eth5 --host localadmin@172.29.87.30
+
+```
+
+### Docker shared volume to share files with the container
+
+VMTP can accept files as input (e.g. configuration and openrc file) and can generate json results into a file.
+It is possible to use the VMTP Docker image with files persisted on the host by using Docker shared volumes.
+For example, one can decide to mount the current host directory as /vmtp/shared in the container in read-write mode.
+
+To get a copy of the VMTP defaut configuration file from the container:
+
+```
+
+docker run -v $PWD:/vmtp/shared:rw <docker-vmtp-image-name>  cp /vmtp/cfg.default.yaml /vmtp/shared/mycfg.yaml
+
+```
+
+Assume you have edited the configuration file �mycfg.yaml� and retrieved an openrc file �admin-openrc.sh� from Horizon on the local directory and would like to get results back in the �res.json� file, you can export the current directory ($PWD), map it to /vmtp/shared in the container in read/write mode, then run the script in the container by using files from the shared directory
+
+```
+
+docker run -v $PWD:/vmtp/shared:rw -t <docker-vmtp-image-name> python /vmtp/vmtp.py -c shared/mycfg.yaml -r shared/admin-openrc.sh -p admin --json shared/res.json
+cat res.json
+
+```
+
+## Caveats and Known Issues
+
+* UDP throughput is not available if iperf is selected (the iperf UDP reported results are not reliable enough for iterating)
+* If VMTP hangs for native hosts throughputs, check firewall rules on the hosts to allow TCP/UDP ports 5001 and TCP port 5002
+
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..4ab2ef4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,29 @@
+===============================
+vmtp
+===============================
+
+A data path performance tool for OpenStack clouds.
+
+* Free software: Apache license
+* Documentation: http://docs.openstack.org/developer/vmtp
+* Source: http://git.openstack.org/cgit/stackforge/vmtp
+* Bugs: http://bugs.launchpad.net/vmtp
+
+Features
+--------
+
+VMTP is a python application that will automatically perform ping connectivity, ping round trip time measuerment (latency) and TCP/UDP throughput measurement for the following flows on any OpenStack deployment:
+
+* VM to VM same network (private fixed IP)
+* VM to VM different network same tenant (intra-tenant L3 fixed IP)
+* VM to VM different network and tenant (floating IP inter-tenant L3)
+
+Optionally, when an external Linux host is available:
+
+* external host/VM download and upload throughput/latency (L3/floating IP)
+
+Optionally, when ssh login to any Linux host (native or virtual) is available:
+
+* host to host throughput (intra-node and inter-node)
+
+Optionally, VMTP can extract automatically CPU usage from all native hosts in the cloud during the throughput tests, provided the Ganglia monitoring service (gmond) is installed and enabled on those hosts.
diff --git a/babel.cfg b/babel.cfg
new file mode 100644
index 0000000..15cd6cb
--- /dev/null
+++ b/babel.cfg
@@ -0,0 +1,2 @@
+[python: **.py]
+
diff --git a/cfg.default.yaml b/cfg.default.yaml
new file mode 100644
index 0000000..ea9a3fc
--- /dev/null
+++ b/cfg.default.yaml
@@ -0,0 +1,162 @@
+#
+# VMTP default configuration file
+#
+# This configuration file is ALWAYS loaded by VMTP and should never be modified by users.
+# To specify your own user-specific property values, always define them in a separate config file
+# and pass that file to the script using -c or --config <file>
+# Property values in that config file will override the default values in the current file
+#
+---
+
+# Name of the image to use for launching the test VMs. This name must be
+# the exact same name used in OpenStack (as shown from 'nova image-list')
+# Any image running Linux should work (Fedora, Ubuntu, CentOS...)
+image_name:  'Ubuntu Server 14.04'
+#image_name:  'Fedora 21'
+
+# User name to use to ssh to the test VMs
+# This is specific to the image being used
+ssh_vm_username: 'ubuntu'
+#ssh_vm_username: fedora
+
+# Name of the flavor to use for the test VMs
+# This name must be an exact match to a flavor name known by the target
+# OpenStack deployment (as shown from 'nova flavor-list')
+flavor_type: 'm1.small'
+
+# Name of the availability zone to use for the test VMs
+# Must be one of the zones listed by 'nova availability-zone-list'
+# If the zone selected contains more than 1 compute node, the script
+# will determine inter-node and intra-node throughput. If it contains only
+# 1 compute node, only intra-node troughput will be measured.
+availability_zone: 'nova'
+
+# DNS server IP addresses to use for the VM (list of 1 or more DNS servers)
+# This default DNS server is available on the Internet,
+# Change this to use a different DNS server if necessary,
+dns_nameservers: [ '8.8.8.8' ]
+
+# VMTP can automatically create a VM image if the image named by
+# image_name is missing, for that you need to specify a server IP address
+# from which the image can be retrieved using the wget protocol
+# These 2 properties are not used if the image is present
+server_ip_for_image: '172.29.172.152'
+image_path_in_server: 'downloads/trusty-server-cloudimg-amd64-disk1.qcow2'
+
+# -----------------------------------------------------------------------------
+# These variables are not likely to be changed
+
+# Set this variable to a network name if you want the script to reuse
+# a specific existing external network. If empty, the script will reuse the
+# first external network it can find (the cloud must have at least 1
+# external network defined and available for use)
+# When set, ignore floating ip creation and reuse existing management network for tests
+reuse_network_name :
+
+# Use of the script for special deployments
+floating_ip: True
+
+# Set this to an existing VM name if the script should not create new VM
+# and reuse existing VM
+reuse_existing_vm :
+
+# Default name for the router to use to connect the internal mgmt network
+# with the external network. If a router exists with this name it will be
+# reused, otherwise a new router will be created
+router_name: 'pns-router'
+
+# Defaul names for the internal networks used by the
+# script. If an existing network with this name exists it will be reused.
+# Otherwise a new internal network will be created with that name.
+# 2 networks are needed to test the case of network to network communication
+internal_network_name: ['pns-internal-net', 'pns-internal-net2']
+
+# Name of the subnets associated to the internal mgmt network
+internal_subnet_name: ['pns-internal-subnet', 'pns-internal-subnet2']
+
+# Default CIDRs to use for the internal mgmt subnet
+internal_cidr: ['192.168.1.0/24' , '192.168.2.0/24']
+
+# The public key to use to ssh to all targets (VMs, containers, hosts)
+# If starting with './' is relative to the location of the VMTP script
+# else can be an absolute path
+public_key_file: './ssh/id_rsa.pub'
+
+# File containing the private key to use along with the publick key
+# If starting with './' is relative to the location of the script
+# else can be an absolute path
+private_key_file: './ssh/id_rsa'
+
+# Name of the P&S public key in OpenStack
+public_key_name: 'pns_public_key'
+
+# name of the server VM
+vm_name_server:  'TestServer'
+
+# name of the client VM
+vm_name_client:   'TestClient'
+
+# name of the security group to create and use
+security_group_name: 'pns-security'
+
+# Location to the performance test tools.
+perf_tool_path: './tools'
+
+# ping variables
+ping_count: 2
+ping_pass_threshold: 80
+
+# Max retry count for ssh to a VM (5 seconds between retries)
+ssh_retry_count: 50 
+
+# General retry count
+generic_retry_count: 50 
+
+# Times to run when measuring TCP Throughput
+tcp_tp_loop_count: 3
+
+# TCP throughput list of packet sizes to measure
+# Can be overridden at the command line using --tcpbuf
+tcp_pkt_sizes: [65536]
+
+# UDP throughput list of packet sizes to measure
+# By default we measure for small, medium and large packets
+# Can be overridden at the command line using --udpbuf
+udp_pkt_sizes: [128, 1024, 8192]
+
+# UDP packet loss rate threshold in percentage beyond which bandwidth
+# iterations stop and below which iteration with a higher
+# bandwidth continues
+# The first number is the minimal loss rate (inclusive)
+# The second number is the maximum loss rate (inclusive)
+# Iteration to find the "optimal" bandwidth will stop as soon as the loss rate
+# falls within that range: min <= loss_rate <= max
+# The final throughput measurement may return a loss rate out of this range
+# as that measurement is taken on a longer time than when iterating to find
+# the optimal throughput
+#
+udp_loss_rate_range: [2, 5]
+
+# The default bandwidth limit (in Kbps) for TCP/UDP flow measurement
+# 0 means unlimited, which can be overridden at the command line using --bandwidth
+vm_bandwidth: 0
+
+#######################################
+# PNS MongoDB Connection information
+#######################################
+
+########################################
+# Default MongoDB port is 27017, to override
+#pns_mongod_port: <port no>
+
+########################################
+# MongoDB pns database.
+########################################
+pns_db: "pnsdb"
+
+########################################
+# MongoDB collection.
+# use "officialdata" for offical runs only.
+########################################
+pns_collection: "testdata"
+
diff --git a/cfg.existing.yaml b/cfg.existing.yaml
new file mode 100644
index 0000000..584f908
--- /dev/null
+++ b/cfg.existing.yaml
@@ -0,0 +1,10 @@
+#
+# Example of configuration where we froce the use of a specific external network and
+# use provider network (no floating IP)
+reuse_network_name : 'prov1'
+
+# Floating ip false is a provider network where we simply attach to it
+floating_ip : False
+
+# Floating ip is true by default:
+# attach to existing network, create a floating ip and attach instance to it
diff --git a/cfg.nimbus.svl.yaml b/cfg.nimbus.svl.yaml
new file mode 100644
index 0000000..577be26
--- /dev/null
+++ b/cfg.nimbus.svl.yaml
@@ -0,0 +1,14 @@
+# VM Throughput configuration for Nimbus (SVL6)
+---
+
+#image_name: 'ubuntu-trusty-server-cloudimg-amd64-disk1.2014-06-30'
+#image_name: 'rhel-6.5_x86_64-2014-06-23-v4'
+#image_name: 'centos-6.5_x86_64-2014-06-04-v3'
+image_name:  'Ubuntu Server 14.04'
+
+flavor_type: "Micro-Small"
+availability_zone: "svl6-csl-b"
+security_group_name: "pns-security1"
+ext_net_name: "public-floating-76"
+internal_cidr: ['192.168.10.0/24' , '192.168.20.0/24']
+
diff --git a/cfg.nimbus.yaml b/cfg.nimbus.yaml
new file mode 100644
index 0000000..e5491a0
--- /dev/null
+++ b/cfg.nimbus.yaml
@@ -0,0 +1,9 @@
+# VM Throughput configuration for Nimbus
+---
+
+image_name: 'ubuntu-trusty-server-cloudimg-amd64-disk1.2014-09-27'
+#image_name: 'rhel-6.5_x86_64-2014-06-23-v4'
+#image_name: 'centos-6.5_x86_64-2014-06-04-v3'
+flavor_type: "GP-Small"
+availability_zone: "alln01-1-csx"
+
diff --git a/compute.py b/compute.py
new file mode 100644
index 0000000..5953fdf
--- /dev/null
+++ b/compute.py
@@ -0,0 +1,276 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+
+
+'''Module for Openstack compute operations'''
+
+import os
+import subprocess
+import time
+
+import novaclient
+import novaclient.exceptions as exceptions
+
+class Compute(object):
+
+    def __init__(self, nova_client, config):
+        self.novaclient = nova_client
+        self.config = config
+
+    def find_image(self, image_name):
+        try:
+            image = self.novaclient.images.find(name=image_name)
+            return image
+        except novaclient.exceptions.NotFound:
+            print 'ERROR: Didnt find the image %s' % (image_name)
+            return None
+
+    def copy_and_upload_image(self, final_image_name, server_ip, image_path):
+        '''
+        Copies locally via wget and Uploads image in Nova, if image is
+        not present on Nova post Upload, deletes it
+        '''
+
+        wget_cmd = "wget --tries=1 http://" + str(server_ip) + "/" + str(image_path)
+        try:
+            subprocess.check_output(wget_cmd, shell=True)
+        except subprocess.CalledProcessError:
+            print 'ERROR: Failed to download, check filename %s via Wget' % (wget_cmd)
+            return 0
+
+        my_cwd = os.getcwd()
+        my_file_name = os.path.basename(image_path)
+        abs_fname_path = my_cwd + "/" + my_file_name
+        rm_file_cmd = "rm " + abs_fname_path
+        if os.path.isfile(abs_fname_path):
+            # upload in glance
+            glance_cmd = "glance image-create --name=\"" + str(final_image_name) + \
+                         "\" --disk-format=qcow2" + " --container-format=bare < " + \
+                         str(my_file_name)
+            subprocess.check_output(glance_cmd, shell=True)
+
+            # remove the image file from local dir
+            subprocess.check_output(rm_file_cmd, shell=True)
+
+            # check for the image in glance
+            glance_check_cmd = "glance image-list"
+            print "Will update image to glance via CLI: %s" % (glance_cmd)
+            result = subprocess.check_output(glance_check_cmd, shell=True)
+            if final_image_name in result:
+                print 'Image: %s successfully Uploaded in Nova' % (final_image_name)
+                return 1
+            else:
+                print 'Glance image status:\n %s' % (result)
+                print 'ERROR: Didnt find %s image in Nova' % (final_image_name)
+                return 0
+        else:
+            print 'ERROR: image %s not copied over locally via %s' % (my_file_name, wget_cmd)
+            return 0
+
+    # Remove keypair name from openstack if exists
+    def remove_public_key(self, name):
+        keypair_list = self.novaclient.keypairs.list()
+        for key in keypair_list:
+            if key.name == name:
+                self.novaclient.keypairs.delete(name)
+                print 'Removed public key %s' % (name)
+                break
+
+    # Test if keypair file is present if not create it
+    def create_keypair(self, name, private_key_pair_file):
+        self.remove_public_key(name)
+        keypair = self.novaclient.keypairs.create(name)
+        # Now write the keypair to the file
+        kpf = os.open(private_key_pair_file,
+                      os.O_WRONLY | os.O_CREAT, 0o600)
+        with os.fdopen(kpf, 'w') as kpf:
+            kpf.write(keypair.private_key)
+        return keypair
+
+    # Add an existing public key to openstack
+    def add_public_key(self, name, public_key_file):
+        self.remove_public_key(name)
+        # extract the public key from the file
+        public_key = None
+        try:
+            with open(os.path.expanduser(public_key_file)) as pkf:
+                public_key = pkf.read()
+        except IOError as exc:
+            print 'ERROR: Cannot open public key file %s: %s' % \
+                  (public_key_file, exc)
+            return None
+        print 'Adding public key %s' % (name)
+        keypair = self.novaclient.keypairs.create(name, public_key)
+        return keypair
+
+    def find_network(self, label):
+        net = self.novaclient.networks.find(label=label)
+        return net
+
+    # Create a server instance with name vmname
+    # if exists delete and recreate
+    def create_server(self, vmname, image, flavor, key_name,
+                      nic, sec_group, avail_zone=None, user_data=None,
+                      retry_count=10):
+
+        # Also attach the created security group for the test
+        instance = self.novaclient.servers.create(name=vmname,
+                                                  image=image,
+                                                  flavor=flavor,
+                                                  key_name=key_name,
+                                                  nics=nic,
+                                                  availability_zone=avail_zone,
+                                                  userdata=user_data,
+                                                  security_groups=[sec_group.id])
+        flag_exist = self.find_server(vmname, retry_count)
+        if flag_exist:
+            return instance
+        else:
+            return None
+
+    def get_server_list(self):
+        servers_list = self.novaclient.servers.list()
+        return servers_list
+
+    def find_floating_ips(self):
+        floating_ip = self.novaclient.floating_ips.list()
+        return floating_ip
+
+    # Return the server network for a server
+    def find_server_network(self, vmname):
+        servers_list = self.get_server_list()
+        for server in servers_list:
+            if server.name == vmname and server.status == "ACTIVE":
+                return server.networks
+        return None
+
+    # Returns True if server is present false if not.
+    # Retry for a few seconds since after VM creation sometimes
+    # it takes a while to show up
+    def find_server(self, vmname, retry_count):
+        for retry_attempt in range(retry_count):
+            servers_list = self.get_server_list()
+            for server in servers_list:
+                if server.name == vmname and server.status == "ACTIVE":
+                    return True
+            # Sleep between retries
+            if self.config.debug:
+                print "[%s] VM not yet found, retrying %s of %s" \
+                      % (vmname, (retry_attempt + 1), retry_count)
+            time.sleep(2)
+        print "[%s] VM not found, after %s attempts" % (vmname, retry_count)
+        return False
+
+    # Returns True if server is found and deleted/False if not,
+    # retry the delete if there is a delay
+    def delete_server_by_name(self, vmname):
+        servers_list = self.get_server_list()
+        for server in servers_list:
+            if server.name == vmname:
+                print 'deleting server %s' % (server)
+                self.novaclient.servers.delete(server)
+                return True
+        return False
+
+    def delete_server(self, server):
+        self.novaclient.servers.delete(server)
+
+    def find_flavor(self, flavor_type):
+        flavor = self.novaclient.flavors.find(name=flavor_type)
+        return flavor
+
+    #
+    #   Return a list of hosts which are in a specific availability zone
+    #   May fail per policy in that case return an empty list
+    def list_hypervisor(self, zone_info):
+        if self.config.hypervisors:
+            print 'Using hypervisors:' + ', '.join(self.config.hypervisors)
+            return self.config.hypervisors
+
+        avail_list = []
+        try:
+            host_list = self.novaclient.hosts.list()
+            for host in host_list:
+                if host.zone == zone_info:
+                    avail_list.append(host.host_name)
+        except novaclient.exceptions.Forbidden:
+            print ('Operation Forbidden: could not retrieve list of servers'
+                   ' in AZ (likely no permission)')
+        return avail_list
+
+    # Given 2 VMs test if they are running on same Host or not
+    def check_vm_placement(self, vm_instance1, vm_instance2):
+        try:
+            server_instance_1 = self.novaclient.servers.get(vm_instance1)
+            server_instance_2 = self.novaclient.servers.get(vm_instance2)
+            if server_instance_1.hostId == server_instance_2.hostId:
+                return True
+            else:
+                return False
+        except novaclient.exceptions:
+            print "Exception in retrieving the hostId of servers"
+
+    # Create a new security group with appropriate rules
+    def security_group_create(self):
+        # check first the security group exists
+        # May throw exceptions.NoUniqueMatch or NotFound
+        try:
+            group = self.novaclient.security_groups.find(name=self.config.security_group_name)
+            return group
+        except exceptions.NotFound:
+            group = self.novaclient.security_groups.create(name=self.config.security_group_name,
+                                                           description="PNS Security group")
+            # Once security group try to find it iteratively
+            # (this check may no longer be necessary)
+            for _ in range(self.config.generic_retry_count):
+                group = self.novaclient.security_groups.get(group)
+                if group:
+                    self.security_group_add_rules(group)
+                    return group
+                else:
+                    time.sleep(1)
+            return None
+        # except exceptions.NoUniqueMatch as exc:
+        #    raise exc
+
+    # Delete a security group
+    def security_group_delete(self, group):
+        if group:
+            print "Deleting security group"
+            self.novaclient.security_groups.delete(group)
+
+    # Add rules to the security group
+    def security_group_add_rules(self, group):
+        # Allow ping traffic
+        self.novaclient.security_group_rules.create(group.id,
+                                                    ip_protocol="icmp",
+                                                    from_port=-1,
+                                                    to_port=-1)
+        # Allow SSH traffic
+        self.novaclient.security_group_rules.create(group.id,
+                                                    ip_protocol="tcp",
+                                                    from_port=22,
+                                                    to_port=22)
+        # Allow TCP/UDP traffic for perf tools like iperf/nuttcp
+        # 5001: Data traffic (standard iperf data port)
+        # 5002: Control traffic (non standard)
+        # note that 5000/tcp is already picked by openstack keystone
+        self.novaclient.security_group_rules.create(group.id,
+                                                    ip_protocol="tcp",
+                                                    from_port=5001,
+                                                    to_port=5002)
+        self.novaclient.security_group_rules.create(group.id,
+                                                    ip_protocol="udp",
+                                                    from_port=5001,
+                                                    to_port=5001)
diff --git a/credentials.py b/credentials.py
new file mode 100644
index 0000000..f07287b
--- /dev/null
+++ b/credentials.py
@@ -0,0 +1,110 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+# Module for credentials in Openstack
+import getpass
+import os
+import re
+
+
+class Credentials(object):
+
+    def get_credentials(self):
+        dct = {}
+        dct['username'] = self.rc_username
+        dct['password'] = self.rc_password
+        dct['auth_url'] = self.rc_auth_url
+        dct['tenant_name'] = self.rc_tenant_name
+        return dct
+
+    def get_nova_credentials(self):
+        dct = {}
+        dct['username'] = self.rc_username
+        dct['api_key'] = self.rc_password
+        dct['auth_url'] = self.rc_auth_url
+        dct['project_id'] = self.rc_tenant_name
+        return dct
+
+    def get_nova_credentials_v2(self):
+        dct = self.get_nova_credentials()
+        dct['version'] = 2
+        return dct
+
+    #
+    # Read a openrc file and take care of the password
+    # The 2 args are passed from the command line and can be None
+    #
+    def __init__(self, openrc_file, pwd, no_env):
+        self.rc_password = None
+        self.rc_username = None
+        self.rc_tenant_name = None
+        self.rc_auth_url = None
+        success = True
+
+        if openrc_file:
+            if os.path.exists(openrc_file):
+                export_re = re.compile('export OS_([A-Z_]*)="?(.*)')
+                for line in open(openrc_file):
+                    line = line.strip()
+                    mstr = export_re.match(line)
+                    if mstr:
+                        # get rif of posible trailing double quote
+                        # the first one was removed by the re
+                        name = mstr.group(1)
+                        value = mstr.group(2)
+                        if value.endswith('"'):
+                            value = value[:-1]
+                        # get rid of password assignment
+                        # echo "Please enter your OpenStack Password: "
+                        # read -sr OS_PASSWORD_INPUT
+                        # export OS_PASSWORD=$OS_PASSWORD_INPUT
+                        if value.startswith('$'):
+                            continue
+                        # now match against wanted variable names
+                        if name == 'USERNAME':
+                            self.rc_username = value
+                        elif name == 'AUTH_URL':
+                            self.rc_auth_url = value
+                        elif name == 'TENANT_NAME':
+                            self.rc_tenant_name = value
+            else:
+                print 'Error: rc file does not exist %s' % (openrc_file)
+                success = False
+        elif not no_env:
+            # no openrc file passed - we assume the variables have been
+            # sourced by the calling shell
+            # just check that they are present
+            for varname in ['OS_USERNAME', 'OS_AUTH_URL', 'OS_TENANT_NAME']:
+                if varname not in os.environ:
+                    print 'Warning: %s is missing' % (varname)
+                    success = False
+            if success:
+                self.rc_username = os.environ['OS_USERNAME']
+                self.rc_auth_url = os.environ['OS_AUTH_URL']
+                self.rc_tenant_name = os.environ['OS_TENANT_NAME']
+
+        # always override with CLI argument if provided
+        if pwd:
+            self.rc_password = pwd
+        # if password not know, check from env variable
+        elif self.rc_auth_url and not self.rc_password and success:
+            if 'OS_PASSWORD' in os.environ and not no_env:
+                self.rc_password = os.environ['OS_PASSWORD']
+            else:
+                # interactively ask for password
+                self.rc_password = getpass.getpass(
+                    'Please enter your OpenStack Password: ')
+        if not self.rc_password:
+            self.rc_password = ""
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100755
index 0000000..de6fea3
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../..'))
+# -- General configuration ----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    #'sphinx.ext.intersphinx',
+    'oslosphinx'
+]
+
+# autodoc generation is a bit aggressive and a nuisance when doing heavy
+# text edit cycles.
+# execute "export SPHINX_DEBUG=1" in your terminal to disable
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'vmtp'
+copyright = u'2013, OpenStack Foundation'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+# html_theme_path = ["."]
+# html_theme = '_theme'
+# html_static_path = ['static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = '%sdoc' % project
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass
+# [howto/manual]).
+latex_documents = [
+    ('index',
+     '%s.tex' % project,
+     u'%s Documentation' % project,
+     u'OpenStack Foundation', 'manual'),
+]
+
+# Example configuration for intersphinx: refer to the Python standard library.
+#intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst
new file mode 100644
index 0000000..1728a61
--- /dev/null
+++ b/doc/source/contributing.rst
@@ -0,0 +1,4 @@
+============
+Contributing
+============
+.. include:: ../../CONTRIBUTING.rst
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..2041757
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,25 @@
+.. vmtp documentation master file, created by
+   sphinx-quickstart on Tue Jul  9 22:26:36 2013.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to vmtp's documentation!
+========================================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   readme
+   installation
+   usage
+   contributing
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
new file mode 100644
index 0000000..5a57705
--- /dev/null
+++ b/doc/source/installation.rst
@@ -0,0 +1,12 @@
+============
+Installation
+============
+
+At the command line::
+
+    $ pip install vmtp
+
+Or, if you have virtualenvwrapper installed::
+
+    $ mkvirtualenv vmtp
+    $ pip install vmtp
diff --git a/doc/source/readme.rst b/doc/source/readme.rst
new file mode 100644
index 0000000..a6210d3
--- /dev/null
+++ b/doc/source/readme.rst
@@ -0,0 +1 @@
+.. include:: ../../README.rst
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
new file mode 100644
index 0000000..d80eb84
--- /dev/null
+++ b/doc/source/usage.rst
@@ -0,0 +1,7 @@
+========
+Usage
+========
+
+To use vmtp in a project::
+
+    import vmtp
diff --git a/instance.py b/instance.py
new file mode 100644
index 0000000..83d9208
--- /dev/null
+++ b/instance.py
@@ -0,0 +1,284 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import os
+import re
+import stat
+import subprocess
+
+import monitor
+import sshutils
+
+# a dictionary of sequence number indexed by a name prefix
+prefix_seq = {}
+
+#
+# An openstack instance (can be a VM or a LXC)
+#
+class Instance(object):
+
+    def __init__(self, name, config, comp=None, net=None):
+        if name not in prefix_seq:
+            prefix_seq[name] = 1
+        seq = prefix_seq[name]
+        prefix_seq[name] = seq + 1
+        self.name = name + str(seq)
+        self.comp = comp
+        self.net = net
+        self.az = None
+        self.config = config
+        # internal network IP
+        self.internal_ip = None
+        self.ssh_ip = None
+        self.ssh_ip_id = None
+        self.ssh_user = config.ssh_vm_username
+        self.instance = None
+        self.ssh = None
+        if config.gmond_svr_ip:
+            self.gmond_svr = config.gmond_svr_ip
+        else:
+            self.gmond_svr = None
+        if config.gmond_svr_port:
+            self.gmond_port = int(config.gmond_svr_port)
+        else:
+            self.gmond_port = 0
+
+    # Setup the ssh connectivity
+    # Returns True if success
+    def setup_ssh(self, ssh_ip, ssh_user):
+        # used for displaying the source IP in json results
+        if not self.internal_ip:
+            self.internal_ip = ssh_ip
+        self.ssh_ip = ssh_ip
+        self.ssh_user = ssh_user
+        self.ssh = sshutils.SSH(self.ssh_user, self.ssh_ip,
+                                key_filename=self.config.private_key_file,
+                                connect_retry_count=self.config.ssh_retry_count)
+        return True
+
+    # Create a new VM instance, associate a floating IP for ssh access
+    # and extract internal network IP
+    # Retruns True if success, False otherwise
+    def create(self, image, flavor_type,
+               keypair, nics,
+               az,
+               internal_network_name,
+               sec_group,
+               init_file_name=None):
+        # if ssh is created it means this is a native host not a vm
+        if self.ssh:
+            return True
+        self.buginf('Starting on zone %s', az)
+        self.az = az
+
+        if init_file_name:
+            user_data = open(init_file_name)
+        else:
+            user_data = None
+        self.instance = self.comp.create_server(self.name,
+                                                image,
+                                                flavor_type,
+                                                keypair,
+                                                nics,
+                                                sec_group,
+                                                az,
+                                                user_data,
+                                                self.config.generic_retry_count)
+        if user_data:
+            user_data.close()
+        if not self.instance:
+            self.display('Server creation failed')
+            self.dispose()
+            return False
+
+        # If reusing existing management network skip the floating ip creation and association to VM
+        # Assume management network has direct access
+        if self.config.reuse_network_name:
+            self.ssh_ip = self.instance.networks[internal_network_name][0]
+        else:
+            fip = self.net.create_floating_ip()
+            if not fip:
+                self.display('Floating ip creation failed')
+                return False
+            self.ssh_ip = fip['floatingip']['floating_ip_address']
+            self.ssh_ip_id = fip['floatingip']['id']
+            self.buginf('Floating IP %s created', self.ssh_ip)
+            self.buginf('Started - associating floating IP %s', self.ssh_ip)
+            self.instance.add_floating_ip(self.ssh_ip)
+        # extract the IP for the data network
+        self.internal_ip = self.instance.networks[internal_network_name][0]
+        self.buginf('Internal network IP: %s', self.internal_ip)
+        self.buginf('SSH IP: %s', self.ssh_ip)
+
+        # create ssh session
+        if not self.setup_ssh(self.ssh_ip, self.config.ssh_vm_username):
+            return False
+        return True
+
+    # Send a command on the ssh session
+    # returns stdout
+    def exec_command(self, cmd, timeout=30):
+        (status, cmd_output, err) = self.ssh.execute(cmd, timeout=timeout)
+        if status:
+            self.display('ERROR cmd=%s' % (cmd))
+            if cmd_output:
+                self.display("%s", cmd_output)
+            if err:
+                self.display('error=%s' % (err))
+            return None
+        self.buginf('%s', cmd_output)
+        return cmd_output
+
+    # Display a status message with the standard header that has the instance
+    # name (e.g. [foo] some text)
+    def display(self, fmt, *args):
+        print ('[%s] ' + fmt) % ((self.name,) + args)
+
+    # Debugging message, to be printed only in debug mode
+    def buginf(self, fmt, *args):
+        if self.config.debug:
+            self.display(fmt, *args)
+
+    # Ping an IP from this instance
+    def ping_check(self, target_ip, ping_count, pass_threshold):
+        return self.ssh.ping_check(target_ip, ping_count, pass_threshold)
+
+    # Given a message size verify if ping without fragmentation works or fails
+    # Returns True if success
+    def ping_do_not_fragment(self, msg_size, ip_address):
+        cmd = "ping -M do -c 1 -s " + str(msg_size) + " " + ip_address
+        cmd_output = self.exec_command(cmd)
+        match = re.search('100% packet loss', cmd_output)
+        if match:
+            return False
+        else:
+            return True
+
+    # Set the interface IP address and mask
+    def set_interface_ip(self, if_name, ip, mask):
+        self.buginf('Setting interface %s to %s mask %s', if_name, ip, mask)
+        cmd2apply = "sudo ifconfig %s %s netmask %s" % (if_name, ip, mask)
+        (rc, _, _) = self.ssh.execute(cmd2apply)
+        return rc
+
+    # Get an interface IP address (returns None if error)
+    def get_interface_ip(self, if_name):
+        self.buginf('Getting interface %s IP and mask', if_name)
+        cmd2apply = "ifconfig %s" % (if_name)
+        (rc, res, _) = self.ssh.execute(cmd2apply)
+        if rc:
+            return None
+        # eth5      Link encap:Ethernet  HWaddr 90:e2:ba:40:74:05
+        #  inet addr:172.29.87.29  Bcast:172.29.87.31  Mask:255.255.255.240
+        #  inet6 addr: fe80::92e2:baff:fe40:7405/64 Scope:Link
+        match = re.search(r'inet addr:([\d\.]*) ', res)
+        if not match:
+            return None
+        return match.group(1)
+
+    # Set an interface MTU to passed in value
+    def set_interface_mtu(self, if_name, mtu):
+        self.buginf('Setting interface %s mtu to %d', if_name, mtu)
+        cmd2apply = "sudo ifconfig %s mtu %d" % (if_name, mtu)
+        (rc, _, _) = self.ssh.execute(cmd2apply)
+        return rc
+
+    # Get the MTU of an interface
+    def get_interface_mtu(self, if_name):
+        cmd = "cat /sys/class/net/%s/mtu" % (if_name)
+        cmd_output = self.exec_command(cmd)
+        return int(cmd_output)
+
+    # scp a file from the local host to the instance
+    # Returns True if dest file already exists or scp succeeded
+    #         False in case of scp error
+    def scp(self, tool_name, source, dest):
+
+        # check if the dest file is already present
+        if self.ssh.stat(dest):
+            self.buginf('tool %s already present - skipping install',
+                        tool_name)
+            return True
+        # scp over the tool binary
+        # first chmod the local copy since git does not keep the permission
+        os.chmod(source, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+        # scp to the target
+        scp_opts = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
+        scp_cmd = 'scp -i %s %s %s %s@%s:%s' % (self.config.private_key_file,
+                                                scp_opts,
+                                                source,
+                                                self.ssh_user,
+                                                self.ssh_ip,
+                                                dest)
+        self.buginf('Copying %s to target...', tool_name)
+        self.buginf(scp_cmd)
+        devnull = open(os.devnull, 'wb')
+        rc = subprocess.call(scp_cmd, shell=True,
+                             stdout=devnull, stderr=devnull)
+        if rc:
+            self.display('Copy to target failed rc=%d', rc)
+            self.display(scp_cmd)
+            return False
+        return True
+
+    def get_cmd_duration(self):
+        '''Get the duration of the client run
+        Will normally return the time configured in config.time
+        If cpu monitoring is enabled will make sure that this time is at least
+        30 seconds (to be adjusted based on metric collection frequency)
+        '''
+        if self.gmond_svr:
+            return max(30, self.config.time)
+        return self.config.time
+
+    def exec_with_cpu(self, cmd):
+        '''If cpu monitoring is enabled (--monitor) collect CPU in the background
+        while the test is running
+        :param duration: how long the command will run in seconds
+        :return:  a tuple (cmd_output, cpu_load)
+        '''
+        # ssh timeout should be at least set to the command duration
+        # we add 20 seconds to it as a safety
+        timeout = self.get_cmd_duration() + 20
+        if self.gmond_svr:
+            gmon = monitor.Monitor(self.gmond_svr, self.gmond_port)
+            # Adjust this frequency based on the collectors update frequency
+            # Here we assume 10 second and a max of 20 samples
+            gmon.start_monitoring_thread(freq=10, count=20)
+            cmd_output = self.exec_command(cmd, timeout)
+            gmon.stop_monitoring_thread()
+            # insert the cpu results into the results
+            cpu_load = gmon.build_cpu_metrics()
+        else:
+            cmd_output = self.exec_command(cmd, timeout)
+            cpu_load = None
+        return (cmd_output, cpu_load)
+
+    # Delete the floating IP
+    # Delete the server instance
+    # Dispose the ssh session
+    def dispose(self):
+        if self.ssh_ip_id:
+            self.net.delete_floating_ip(self.ssh_ip_id)
+            self.buginf('Floating IP %s deleted', self.ssh_ip)
+            self.ssh_ip_id = None
+        if self.instance:
+            self.comp.delete_server(self.instance)
+            self.buginf('Instance deleted')
+            self.instance = None
+        if self.ssh:
+            self.ssh.close()
+            self.ssh = None
diff --git a/iperf_tool.py b/iperf_tool.py
new file mode 100644
index 0000000..06ccdd4
--- /dev/null
+++ b/iperf_tool.py
@@ -0,0 +1,206 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import re
+
+from perf_tool import PerfTool
+
+# The resulting unit should be in K
+MULTIPLIERS = {'K': 1,
+               'M': 1.0e3,
+               'G': 1.0e6}
+
+def get_bdw_kbps(bdw, bdw_unit):
+    if not bdw_unit:
+        # bits/sec
+        return bdw / 1000
+    if bdw_unit in MULTIPLIERS:
+        return int(bdw * MULTIPLIERS[bdw_unit])
+    print('Error: unknown multiplier: ' + bdw_unit)
+    return bdw
+
+class IperfTool(PerfTool):
+
+    def __init__(self, instance, perf_tool_path):
+        PerfTool.__init__(self, 'iperf', perf_tool_path, instance)
+
+    def get_server_launch_cmd(self):
+        '''Return the command to launch the server side.'''
+        # Need 1 server for tcp (port 5001) and 1 for udp (port 5001)
+        return [self.dest_path + ' -s >/dev/null &',
+                self.dest_path + ' -s -u >/dev/null &']
+
+    def run_client(self, target_ip, target_instance,
+                   mss=None, bandwidth=0, bidirectional=False):
+        '''Run the test
+        :return:  list containing one or more dictionary results
+        '''
+        res_list = []
+
+        # Get list of protocols and packet sizes to measure
+        (proto_list, proto_pkt_sizes) = self.get_proto_profile()
+
+        for udp, pkt_size_list in zip(proto_list, proto_pkt_sizes):
+            # bidirectional is not supported for udp
+            # (need to find the right iperf options to make it work as there are
+            # issues for the server to send back results to the client in reverse
+            # direction
+            if udp:
+                bidir = False
+                loop_count = 1
+            else:
+                # For accuracy purpose, TCP throughput will be measured 3 times
+                bidir = bidirectional
+                loop_count = self.instance.config.tcp_tp_loop_count
+            for pkt_size in pkt_size_list:
+                for _ in xrange(loop_count):
+                    res = self.run_client_dir(target_ip, mss,
+                                              bandwidth_kbps=bandwidth,
+                                              bidirectional=bidir,
+                                              udp=udp,
+                                              length=pkt_size)
+                    # for bidirectional the function returns a list of 2 results
+                    res_list.extend(res)
+        return res_list
+
+    def run_client_dir(self, target_ip,
+                       mss,
+                       bidirectional=False,
+                       bandwidth_kbps=0,
+                       udp=False,
+                       length=0,
+                       no_cpu_timed=0):
+        '''Run client for given protocol and packet size
+        :param bandwidth_kbps: transmit rate limit in Kbps
+        :param udp: if true get UDP throughput, else get TCP throughput
+        :param length: length of network write|read buf (default 1K|8K/udp, 64K/tcp)
+                       for udp is the packet size
+        :param no_cpu_timed: if non zero will disable cpu collection and override
+                       the time with the provided value - used mainly for udp
+                       to find quickly the optimal throughput using short
+                       tests at various throughput values
+        :return: a list of dictionary with the 1 or 2 results (see parse_results())
+        '''
+        # run client using the default TCP window size (tcp window
+        # scaling is normally enabled by default so setting explicit window
+        # size is not going to help achieve better results)
+        opts = ''
+
+        # run iperf client using the default TCP window size (tcp window
+        # scaling is normally enabled by default so setting explicit window
+        # size is not going to help achieve better results)
+        if mss:
+            opts += " -M " + str(mss)
+
+        if bidirectional:
+            opts += " -r"
+
+        if length:
+            opts += " -l" + str(length)
+
+        if udp:
+            opts += " -u"
+            # for UDP if the bandwidth is not provided we need to calculate
+            # the optimal bandwidth
+            if not bandwidth_kbps:
+                udp_res = self.find_udp_bdw(length, target_ip)
+                if 'error' in udp_res:
+                    return [udp_res]
+                if not self.instance.gmond_svr:
+                    # if we do not collect CPU we might as well return
+                    # the results found through iteration
+                    return [udp_res]
+                bandwidth_kbps = udp_res['throughput_kbps']
+
+        if bandwidth_kbps:
+            opts += " -b%dK" % (bandwidth_kbps)
+
+        if no_cpu_timed:
+            duration_sec = no_cpu_timed
+        else:
+            duration_sec = self.instance.get_cmd_duration()
+
+        cmd = "%s -c %s -t %d %s" % (self.dest_path,
+                                     target_ip,
+                                     duration_sec,
+                                     opts)
+        self.instance.buginf(cmd)
+        if no_cpu_timed:
+            # force the timeout value with 20 second extra for the command to
+            # complete and do not collect CPU
+            cpu_load = None
+            cmd_out = self.instance.exec_command(cmd, duration_sec + 20)
+        else:
+            (cmd_out, cpu_load) = self.instance.exec_with_cpu(cmd)
+
+        if udp:
+            # Decode UDP output (unicast and multicast):
+            #
+            # [  3] local 127.0.0.1 port 54244 connected with 127.0.0.1 port 5001
+            # [ ID] Interval       Transfer     Bandwidth
+            # [  3]  0.0-10.0 sec  1.25 MBytes  1.05 Mbits/sec
+            # [  3] Sent 893 datagrams
+            # [  3] Server Report:
+            # [ ID] Interval       Transfer     Bandwidth       Jitter   Lost/Total Da
+            # [  3]  0.0-10.0 sec  1.25 MBytes  1.05 Mbits/sec  0.032 ms 1/894 (0.11%)
+            # [  3]  0.0-15.0 sec  14060 datagrams received out-of-order
+            re_udp = r'([\d\.]*)\s*([KMG]?)bits/sec\s*[\d\.]*\s*ms\s*(\d*)/\s*(\d*) '
+            match = re.search(re_udp, cmd_out)
+            if match:
+                bdw = float(match.group(1))
+                bdw_unit = match.group(2)
+                drop = float(match.group(3))
+                pkt = int(match.group(4))
+                # iperf uses multiple of 1000 for K - not 1024
+                return [self.parse_results('UDP',
+                                           get_bdw_kbps(bdw, bdw_unit),
+                                           lossrate=round(drop * 100 / pkt, 2),
+                                           msg_size=length,
+                                           cpu_load=cpu_load)]
+        else:
+            # TCP output:
+            # [  3] local 127.0.0.1 port 57936 connected with 127.0.0.1 port 5001
+            # [ ID] Interval       Transfer     Bandwidth
+            # [  3]  0.0-10.0 sec  2.09 GBytes  1.79 Gbits/sec
+            #
+            # For bi-directional option (-r), last 3 lines:
+            # [  5]  0.0-10.0 sec  36.0 GBytes  31.0 Gbits/sec
+            # [  4] local 127.0.0.1 port 5002 connected with 127.0.0.1 port 39118
+            # [  4]  0.0-10.0 sec  36.0 GBytes  30.9 Gbits/sec
+            re_tcp = r'Bytes\s*([\d\.]*)\s*([KMG])bits/sec'
+            match = re.search(re_tcp, cmd_out)
+            if match:
+                bdw = float(match.group(1))
+                bdw_unit = match.group(2)
+                res = [self.parse_results('TCP',
+                                          get_bdw_kbps(bdw, bdw_unit),
+                                          msg_size=length,
+                                          cpu_load=cpu_load)]
+                if bidirectional:
+                    # decode the last row results
+                    re_tcp = r'Bytes\s*([\d\.]*)\s*([KMG])bits/sec$'
+                    match = re.search(re_tcp, cmd_out)
+                    if match:
+                        bdw = float(match.group(1))
+                        bdw_unit = match.group(2)
+                        # use the same cpu load since the same run
+                        # does both directions
+                        res.append(self.parse_results('TCP',
+                                                      get_bdw_kbps(bdw, bdw_unit),
+                                                      reverse_dir=True,
+                                                      msg_size=length,
+                                                      cpu_load=cpu_load))
+                return res
+        return [self.parse_error('Could not parse: %s' % (cmd_out))]
diff --git a/monitor.py b/monitor.py
new file mode 100755
index 0000000..945566b
--- /dev/null
+++ b/monitor.py
@@ -0,0 +1,443 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+
+'''
+Module for parsing statistical output from Ganglia (gmond) server
+The module opens a socket connection to collect statistical data.
+It parses the raw data in xml format.
+
+The data from ganglia/gmond is in a heirarchical xml format as below:
+<CLUSTER>
+   <HOST..>
+     <METRIC ../>
+     <METRIC ../>
+     :
+   </HOST>
+   :
+   <HOST..>
+     <METRIC ../>
+     <METRIC ../>
+   </HOST>
+</CLUSTER>
+
+## Usage:
+Using the module is simple.
+
+1. instantiate the Monitor with the gmond server ip and port to poll.
+
+    gmon = Monitor("172.22.191.151", 8649)
+
+2. Start the monitoring thread
+    gmon.start_monitoring_thread(frequency, count)
+
+     < run tests/tasks>
+
+    gmon.stop_monitoring_thread()
+
+3. Collecting stats:
+    cpu_metric = gmon.build_cpu_metric()
+
+    Returns a dictionary object with all the cpu stats for each
+    node
+
+
+'''
+
+import datetime
+import re
+import socket
+import subprocess
+from threading import Thread
+import time
+
+from lxml import etree
+
+class MonitorExecutor(Thread):
+    '''
+    Thread handler class to asynchronously collect stats
+    '''
+    THREAD_STOPPED = 0
+    THREAD_RUNNING = 1
+
+    def __init__(self, gmond_svr, gmond_port, freq=5, count=5):
+        super(MonitorExecutor, self).__init__()
+        self.gmond_svr_ip = gmond_svr
+        self.gmond_port = gmond_port
+
+        self.freq = freq
+        self.count = count
+
+        self.force_stop = False
+        self.thread_status = MonitorExecutor.THREAD_STOPPED
+
+        # This dictionary always holds the latest metric.
+        self.gmond_parsed_tree_list = []
+
+
+    def run(self):
+        '''
+        The thread runnable method.
+        The function will periodically poll the gmond server and
+        collect the metrics.
+        '''
+        self.thread_status = MonitorExecutor.THREAD_RUNNING
+
+        count = self.count
+        while count > 0:
+            if self.force_stop:
+                self.thread_status = MonitorExecutor.THREAD_STOPPED
+                return
+
+            self.parse_gmond_xml_data()
+            count -= 1
+            time.sleep(self.freq)
+        self.thread_status = MonitorExecutor.THREAD_STOPPED
+
+
+    def set_force_stop(self):
+        '''
+        Setting the force stop flag to stop the thread. By default
+        the thread stops after the specific count/iterations is reached
+        '''
+        self.force_stop = True
+
+    def parse_gmond_xml_data(self):
+        '''
+        Parse gmond data (V2)
+        Retrieve the ganglia stats from the aggregation node
+        :return: None in case of error or a dictionary containing the stats
+        '''
+        gmond_parsed_tree = {}
+        raw_data = self.retrieve_stats_raw()
+
+        if raw_data is None or len(raw_data) == 0:
+            print "Failed to retrieve stats from server"
+            return
+
+        xtree = etree.XML(raw_data)
+        ############################################
+        # Populate cluster information.
+        ############################################
+        for elem in xtree.iter('CLUSTER'):
+            gmond_parsed_tree['CLUSTER-NAME'] = str(elem.get('NAME'))
+            gmond_parsed_tree['LOCALTIME'] = str(elem.get('LOCALTIME'))
+            gmond_parsed_tree['URL'] = str(elem.get('URL'))
+
+            host_list = []
+            for helem in elem.iterchildren():
+                host = {}
+                host['NAME'] = str(helem.get('NAME'))
+                host['IP'] = str(helem.get('IP'))
+                host['REPORTED'] = str(helem.get('REPORTED'))
+                host['TN'] = str(helem.get('TN'))
+                host['TMAX'] = str(helem.get('TMAX'))
+                host['DMAX'] = str(helem.get('DMAX'))
+                host['LOCATION'] = str(helem.get('LOCATION'))
+                host['GMOND_STARTED'] = str(helem.get('GMOND_STARTED'))
+
+                mlist = []
+                for metric in helem.iterchildren():
+                    mdic = {}
+                    mdic['NAME'] = str(metric.get('NAME'))
+                    mdic['VAL'] = str(metric.get('VAL'))
+                    mlist.append(mdic)
+
+                host['metrics'] = mlist
+                host_list.append(host)
+
+            gmond_parsed_tree['hosts'] = host_list
+            stat_dt = datetime.datetime.now()
+            gmond_parsed_tree['dt'] = stat_dt
+        self.gmond_parsed_tree_list.append(gmond_parsed_tree)
+
+
+    def retrieve_stats_raw(self):
+        '''
+        Retrieve stats from the gmond process.
+        '''
+        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        soc.settimeout(10)
+        try:
+            soc.connect((self.gmond_svr_ip, self.gmond_port))
+        except socket.error as exp:
+            print "Connection failure host: %s [%s]" % (self.gmond_svr_ip, exp)
+            return None
+
+        data = ""
+        while True:
+            try:
+                rbytes = soc.recv(4096)
+            except socket.error as exp:
+                print "Read failed for host: ", str(exp)
+                return None
+
+            if len(rbytes) == 0:
+                break
+            data += rbytes
+
+        soc.close()
+        return data
+
+
+class Monitor(object):
+    gmond_svr_ip = None
+    gmond_port = None
+    gmond_parsed_tree = {}
+
+    def __init__(self, gmond_svr, gmond_port=8649):
+        '''
+        The constructor simply sets the values of the gmond server and port.
+        '''
+        self.gmond_svr_ip = gmond_svr
+        self.gmond_port = gmond_port
+        # List of all stats.
+        self.gmond_parsed_tree_list = []
+        # series for all cpu loads
+        self.cpu_res = {}
+
+        self.mon_thread = None
+
+    def start_monitoring_thread(self, freq=10, count=10):
+        '''
+        Start the monitoring thread.
+        '''
+        self.mon_thread = MonitorExecutor(self.gmond_svr_ip,
+                                          self.gmond_port, freq, count)
+        self.mon_thread.start()
+
+
+    def stop_monitoring_thread(self):
+        self.mon_thread.set_force_stop()
+        self.gmond_parsed_tree_list = self.mon_thread.gmond_parsed_tree_list
+
+
+    def strip_raw_telnet_output(self, raw_data):
+        '''
+        When using the retrieve_stats_raw_telent api, the raw data
+        has some additional text along with the xml data. We need to
+        strip that before we can invoke pass it through the lxml parser.
+        '''
+        data = ""
+        xml_flag = False
+        for line in raw_data.splitlines():
+            if re.match(r".*<?xml version.*", line):
+                xml_flag = True
+            if xml_flag:
+                data += line + "\n"
+
+        return data
+
+
+    def retrieve_stats_raw_telnet(self):
+        '''
+        This way of retrieval is to create a subprocess and execute
+        the telnet command on the port to retrieve the xml raw data.
+        '''
+        cmd = "telnet " + self.gmond_svr_ip + " " + str(self.gmond_port)
+        print "cmd: ", cmd
+        port = str(self.gmond_port)
+
+        proc = subprocess.Popen(["telnet", self.gmond_svr_ip, port],
+                                stdout=subprocess.PIPE)
+        (output, _) = proc.communicate()
+
+        newout = self.strip_raw_telnet_output(output)
+        return newout
+
+
+    def get_host_list(self, gmond_parsed_tree):
+        '''
+        Function returns all the hosts {} as a list.
+        '''
+        return gmond_parsed_tree['hosts']
+
+
+    def get_metric_value(self, parsed_node, host_name, name):
+        '''
+        The function returns the value of a specific metric, given
+        the host name and the metric name to collect.
+        '''
+        for host in parsed_node['hosts']:
+            if host['NAME'] == host_name:
+                for metric in host['metrics']:
+                    if metric['NAME'] == name:
+                        return metric['VAL']
+
+        return 0
+
+    def get_aggregate_cpu_usage(self, parsed_node, host_name):
+        '''
+        The function returns the aggregate CPU usage for a specific host.
+        eqation: [user cpu + system cpu * no of cpu /100]
+        '''
+        cpu_user = float(self.get_metric_value(parsed_node, host_name, "cpu_user"))
+        cpu_system = float(self.get_metric_value(parsed_node, host_name, "cpu_system"))
+        cpu_num = int(self.get_metric_value(parsed_node, host_name, "cpu_num"))
+
+        return (cpu_user + cpu_system) * cpu_num / 100
+
+
+    def build_cpu_metrics(self):
+        '''Add a new set of cpu metrics to the results dictionary self.cpu_res
+        The result dest dictionary should look like this:
+                 key = host IP, value = list of cpu load where the
+                 the first value is the baseline value followed by 1 or more
+                 values collected during the test
+        {
+        '10.0.0.1': [ 0.03, 1.23, 1.20 ],
+        '10.0.0.2': [ 0.10, 1.98, 2.72 ]
+        }
+        After another xml is decoded:
+        {
+        '10.0.0.1': [ 0.03, 1.23, 1.20, 1.41 ],
+        '10.0.0.2': [ 0.10, 1.98, 2.72, 2.04 ]
+        }
+        Each value in the list is the cpu load calculated as
+        (cpu_user + cpu_system) * num_cpu / 100
+        The load_five metric cannot be used as it is the average for last 5'
+        '''
+        cpu_res = {}
+        for parsed_node in self.gmond_parsed_tree_list:
+            for host in self.get_host_list(parsed_node):
+                host_ip = host['IP']
+                cpu_num = 0
+                cpu_user = 0.0
+                cpu_system = 0.0
+
+                cpu_user = float(self.get_metric_value(parsed_node, host['NAME'], "cpu_user"))
+                cpu_system = float(self.get_metric_value(parsed_node, host['NAME'], "cpu_system"))
+                cpu_num = int(self.get_metric_value(parsed_node, host['NAME'], "cpu_num"))
+                cpu_load = round(((cpu_user + cpu_system) * cpu_num) / 100, 2)
+                try:
+                    cpu_res[host_ip].append(cpu_load)
+                except KeyError:
+                    cpu_res[host_ip] = [cpu_load]
+
+        return cpu_res
+
+    def get_formatted_datetime(self, parsed_node):
+        '''
+        Returns the data in formated string. This is the
+        time when the last stat was collected.
+        '''
+        now = parsed_node['dt']
+        fmt_dt = "[" + str(now.hour) + ":" + str(now.minute) + \
+                 ":" + str(now.second) + "]"
+        return fmt_dt
+
+
+    def get_formatted_host_row(self, host_list):
+        '''
+         Returns the hosts in formated order (for printing purposes)
+        '''
+        row_str = "".ljust(10)
+        for host in host_list:
+            row_str += host['NAME'].ljust(15)
+        return row_str
+
+    def get_formatted_metric_row(self, parsed_node, metric, justval):
+        '''
+        Returns a specific metric for all hosts in the same row
+        in formated string (for printing)
+        '''
+        host_list = self.get_host_list(parsed_node)
+
+        row_str = metric.ljust(len(metric) + 2)
+        for host in host_list:
+            val = self.get_metric_value(parsed_node, host['NAME'], metric)
+            row_str += str(val).ljust(justval)
+        return row_str
+
+
+    def dump_cpu_stats(self):
+        '''
+        Print the CPU stats
+        '''
+        hl_len = 80
+        print "-" * hl_len
+        print "CPU Statistics: ",
+
+        for parsed_node in self.gmond_parsed_tree_list:
+            hosts = self.get_host_list(parsed_node)
+
+            print self.get_formatted_datetime(parsed_node)
+            print self.get_formatted_host_row(hosts)
+            print "-" * hl_len
+            print self.get_formatted_metric_row(parsed_node, "cpu_user", 18)
+            print self.get_formatted_metric_row(parsed_node, "cpu_system", 18)
+
+            print "Aggregate ",
+            for host in hosts:
+                print str(self.get_aggregate_cpu_usage(parsed_node,
+                                                       host['NAME'])).ljust(16),
+            print "\n"
+
+    def dump_gmond_parsed_tree(self):
+        '''
+        Display the full tree parsed from the gmond server stats.
+        '''
+        hl_len = 60
+
+        for parsed_node in self.gmond_parsed_tree_list:
+            print "%-20s (%s) URL: %s " % \
+                  (parsed_node['CLUSTER-NAME'],
+                   parsed_node['LOCALTIME'],
+                   parsed_node['URL'])
+            print "-" * hl_len
+
+            row_str = " ".ljust(9)
+            for host in parsed_node['hosts']:
+                row_str += host['NAME'].ljust(15)
+            row_str += "\n"
+            print row_str
+            print "-" * hl_len
+            metric_count = len(parsed_node['hosts'][0]['metrics'])
+            for count in range(0, metric_count):
+                row_str = ""
+                host = parsed_node['hosts'][0]
+                row_str += parsed_node['hosts'][0]['metrics'][count]['NAME'].ljust(18)
+                for host in parsed_node['hosts']:
+                    val = str(self.get_metric_value(parsed_node, host['NAME'],
+                                                    host['metrics'][count]['NAME']))
+                    row_str += val.ljust(12)
+
+                row_str += str(parsed_node['hosts'][0]).ljust(5)
+
+                print row_str
+
+
+##################################################
+# Only invoke the module directly for test purposes. Should be
+# invoked from pns script.
+##################################################
+def main():
+    print "main: monitor"
+    gmon = Monitor("172.22.191.151", 8649)
+    gmon.start_monitoring_thread(freq=5, count=20)
+    print "wait for 15 seconds"
+    time.sleep(20)
+    print "Now force the thread to stop"
+    gmon.stop_monitoring_thread()
+    gmon.dump_cpu_stats()
+
+    cpu_metric = gmon.build_cpu_metrics()
+    print "cpu_metric: ", cpu_metric
+
+
+if __name__ == "__main__":
+    main()
diff --git a/network.py b/network.py
new file mode 100755
index 0000000..f233243
--- /dev/null
+++ b/network.py
@@ -0,0 +1,262 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import time
+
+# Module containing a helper class for operating on OpenStack networks
+import neutronclient.common.exceptions as neutron_exceptions
+
+class Network(object):
+
+    #
+    # This constructor will try to find an external network (will use the
+    # first network that is tagged as external - irrespective of its name)
+    # and a router attached to it (irrespective of the router name).
+    # ext_router_name is the name of the external router to create if not None
+    # and if no external router is found
+    #
+    def __init__(self, neutron_client, config):
+        self.neutron_client = neutron_client
+        self.networks = neutron_client.list_networks()['networks']
+        self.ext_net = None
+        self.ext_router = None
+        self.ext_router_created = False
+        self.config = config
+        # mgmt/data network:
+        # - first for same network
+        # - second for network to network communication
+        self.vm_int_net = []
+        self.ext_router_name = None
+
+        # If reusing existing management network just find this network
+        if self.config.reuse_network_name:
+            # An existing management network must be reused
+            int_net = self.lookup_network(self.config.reuse_network_name)
+            self.vm_int_net.append(int_net)
+            return
+
+        ##############################################
+        # If a user provided ext_net_name is not available,
+        # then find the first network that is external
+        ##############################################
+        for network in self.networks:
+            if network['router:external']:
+                try:
+                    if network['name'] == config.ext_net_name:
+                        self.ext_net = network
+                        break
+                    if not self.ext_net:
+                        self.ext_net = network
+                except KeyError:
+                    ###############################################
+                    # A key error indicates, no user defined
+                    # external network defined, so use the first one
+                    ###############################################
+                    self.ext_net = network
+                    break
+
+        if not self.ext_net:
+            print "No external network found."
+            return
+
+        print "Using external network: " + self.ext_net['name']
+
+        # Find or create the router to the external network
+        ext_net_id = self.ext_net['id']
+        routers = neutron_client.list_routers()['routers']
+        for router in routers:
+            external_gw_info = router['external_gateway_info']
+            if external_gw_info:
+                if external_gw_info['network_id'] == ext_net_id:
+                    self.ext_router = router
+                    print 'Found external router: %s' % \
+                          (self.ext_router['name'])
+                    break
+
+        # create a new external router if none found and a name was given
+        self.ext_router_name = config.router_name
+        if (not self.ext_router) and self.ext_router_name:
+            self.ext_router = self.create_router(self.ext_router_name,
+                                                 self.ext_net['id'])
+            print '[%s] Created ext router' % (self.ext_router_name)
+            self.ext_router_created = True
+
+        # Create the 2 internal networks
+        for (net, subnet, cidr) in zip(config.internal_network_name,
+                                       config.internal_subnet_name,
+                                       config.internal_cidr):
+            int_net = self.create_net(net, subnet, cidr,
+                                      config.dns_nameservers)
+            self.vm_int_net.append(int_net)
+
+        # Add both internal networks to router interface to enable network to network connectivity
+        self.__add_router_interface()
+
+    # Create a network with associated subnet
+    # Check first if a network with the same name exists, if it exists
+    # return that network.
+    # dns_nameservers: a list of name servers e.g. ['8.8.8.8']
+    def create_net(self, network_name, subnet_name, cidr, dns_nameservers):
+
+        for network in self.networks:
+            if network['name'] == network_name:
+                print ('Found existing internal network: %s'
+                       % (network_name))
+                return network
+
+        body = {
+            'network': {
+                'name': network_name,
+                'admin_state_up': True
+            }
+        }
+        network = self.neutron_client.create_network(body)['network']
+        body = {
+            'subnet': {
+                'name': subnet_name,
+                'cidr': cidr,
+                'network_id': network['id'],
+                'enable_dhcp': True,
+                'ip_version': 4,
+                'dns_nameservers': dns_nameservers
+            }
+        }
+        subnet = self.neutron_client.create_subnet(body)['subnet']
+        # add subnet id to the network dict since it has just been added
+        network['subnets'] = [subnet['id']]
+        print 'Created internal network: %s' % (network_name)
+        return network
+
+    # Delete a network and associated subnet
+    def delete_net(self, network):
+        if network:
+            name = network['name']
+            # it may take some time for ports to be cleared so we need to retry
+            for _ in range(1, 5):
+                try:
+                    self.neutron_client.delete_network(network['id'])
+                    print 'Network %s deleted' % (name)
+                    break
+                except neutron_exceptions.NetworkInUseClient:
+                    time.sleep(1)
+
+    # Add a network/subnet to a logical router
+    # Check that it is not already attached to the network/subnet
+    def __add_router_interface(self):
+
+        # and pick the first in the list - the list should be non empty and
+        # contain only 1 subnet since it is supposed to be a private network
+
+        # But first check that the router does not already have this subnet
+        # so retrieve the list of all ports, then check if there is one port
+        # - matches the subnet
+        # - and is attached to the router
+        # Assumed that both management networks are created together so checking for one of them
+        ports = self.neutron_client.list_ports()['ports']
+        for port in ports:
+            port_ip = port['fixed_ips'][0]
+            if (port['device_id'] == self.ext_router['id']) and \
+               (port_ip['subnet_id'] == self.vm_int_net[0]['subnets'][0]):
+                print 'Ext router already associated to the internal network'
+                return
+
+        for int_net in self.vm_int_net:
+            body = {
+                'subnet_id': int_net['subnets'][0]
+            }
+            self.neutron_client.add_interface_router(self.ext_router['id'], body)
+            if self.config.debug:
+                print 'Ext router associated to ' + int_net['name']
+
+    # Detach the ext router from the mgmt network
+    def __remove_router_interface(self):
+        for int_net in self.vm_int_net:
+            if int_net:
+                body = {
+                    'subnet_id': int_net['subnets'][0]
+                }
+                self.neutron_client.remove_interface_router(self.ext_router['id'],
+                                                            body)
+
+    # Lookup network given network name
+    def lookup_network(self, network_name):
+        networks = self.neutron_client.list_networks(name=network_name)
+        return networks['networks'][0]
+
+    # Create a router and up-date external gateway on router
+    # to external network
+    def create_router(self, router_name, net_id):
+        body = {
+            "router": {
+                "name": router_name,
+                "admin_state_up": True,
+                "external_gateway_info": {
+                    "network_id": net_id
+                }
+            }
+        }
+        router = self.neutron_client.create_router(body)
+        return router['router']
+
+    # Show a router based on name
+    def show_router(self, router_name):
+        router = self.neutron_client.show_router(router_name)
+        return router
+
+    # Update a router given router and network id
+    def update_router(self, router_id, net_id):
+        print net_id
+        body = {
+            "router": {
+                "name": "pns-router",
+                "external_gateway_info": {
+                    "network_id": net_id
+                }
+            }
+        }
+        router = self.neutron_client.update_router(router_id, body)
+        return router['router']
+
+    # Create a floating ip on the external network and return it
+    def create_floating_ip(self):
+        body = {
+            "floatingip": {
+                "floating_network_id": self.ext_net['id']
+            }
+        }
+        fip = self.neutron_client.create_floatingip(body)
+        return fip
+
+    # Delete floating ip given a floating ip ad
+    def delete_floating_ip(self, floatingip):
+        self.neutron_client.delete_floatingip(floatingip)
+
+    # Dispose all network resources, call after all VM have been deleted
+    def dispose(self):
+        # Delete the internal networks only of we did not reuse an existing
+        # network
+        if not self.config.reuse_network_name:
+            self.__remove_router_interface()
+            for int_net in self.vm_int_net:
+                self.delete_net(int_net)
+            # delete the router only if its name matches the pns router name
+            if self.ext_router_created:
+                try:
+                    if self.ext_router['name'] == self.ext_router_name:
+                        self.neutron_client.delete_router(self.ext_router['id'])
+                        print 'External router %s deleted' % \
+                              (self.ext_router['name'])
+                except TypeError:
+                    print "No external router set"
diff --git a/nuttcp_tool.py b/nuttcp_tool.py
new file mode 100644
index 0000000..7402ff9
--- /dev/null
+++ b/nuttcp_tool.py
@@ -0,0 +1,194 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import re
+
+from perf_tool import PerfTool
+import sshutils
+
+class NuttcpTool(PerfTool):
+
+    def __init__(self, instance, perf_tool_path):
+        PerfTool.__init__(self, 'nuttcp-7.3.2', perf_tool_path, instance)
+
+    def get_server_launch_cmd(self):
+        '''Return the commands to launch the server side.'''
+        return [self.dest_path + ' -P5002 -S --single-threaded &']
+
+    def run_client(self, target_ip, target_instance,
+                   mss=None, bandwidth=0, bidirectional=False):
+        '''Run the test
+        :return:  list containing one or more dictionary results
+        '''
+        res_list = []
+        if bidirectional:
+            reverse_dir_list = [False, True]
+        else:
+            reverse_dir_list = [False]
+
+        # Get list of protocols and packet sizes to measure
+        (proto_list, proto_pkt_sizes) = self.get_proto_profile()
+
+        for udp, pkt_size_list in zip(proto_list, proto_pkt_sizes):
+            for pkt_size in pkt_size_list:
+                for reverse_dir in reverse_dir_list:
+                    # nuttcp does not support reverse dir for UDP...
+                    if reverse_dir and udp:
+                        continue
+                    if udp:
+                        self.instance.display('Measuring UDP Throughput (packet size=%d)...',
+                                              pkt_size)
+                        loop_count = 1
+                    else:
+                        # For accuracy purpose, TCP throughput will be measured 3 times
+                        self.instance.display('Measuring TCP Throughput (packet size=%d)...',
+                                              pkt_size)
+                        loop_count = self.instance.config.tcp_tp_loop_count
+                    for _ in xrange(loop_count):
+                        res = self.run_client_dir(target_ip, mss,
+                                                  reverse_dir=reverse_dir,
+                                                  bandwidth_kbps=bandwidth,
+                                                  udp=udp,
+                                                  length=pkt_size)
+                        res_list.extend(res)
+
+        # For UDP reverse direction we need to start the server on self.instance
+        # and run the client on target_instance
+        if bidirectional:
+            # Start the server on the client (this tool instance)
+            self.instance.display('Start UDP server for reverse dir')
+            if self.start_server():
+                # Start the client on the target instance
+                target_instance.display('Starting UDP client for reverse dir')
+
+                for pkt_size in self.instance.config.udp_pkt_sizes:
+                    self.instance.display('Measuring UDP Throughput packet size=%d'
+                                          ' (reverse direction)...',
+                                          pkt_size)
+                    res = target_instance.tp_tool.run_client_dir(self.instance.internal_ip,
+                                                                 mss,
+                                                                 bandwidth_kbps=bandwidth,
+                                                                 udp=True,
+                                                                 length=pkt_size)
+                    res[0]['direction'] = 'reverse'
+                    res_list.extend(res)
+            else:
+                self.instance.display('Failed to start UDP server for reverse dir')
+        return res_list
+
+    def run_client_dir(self, target_ip,
+                       mss,
+                       reverse_dir=False,
+                       bandwidth_kbps=0,
+                       udp=False,
+                       length=0,
+                       no_cpu_timed=0):
+        '''Run client in one direction
+        :param reverse_dir: True if reverse the direction (tcp only for now)
+        :param bandwidth_kbps: transmit rate limit in Kbps
+        :param udp: if true get UDP throughput, else get TCP throughput
+        :param length: length of network write|read buf (default 1K|8K/udp, 64K/tcp)
+                       for udp is the packet size
+        :param no_cpu_timed: if non zero will disable cpu collection and override
+                       the time with the provided value - used mainly for udp
+                       to find quickly the optimal throughput using short
+                       tests at various throughput values
+        :return: a list of 1 dictionary with the results (see parse_results())
+        '''
+        # run client using the default TCP window size (tcp window
+        # scaling is normally enabled by default so setting explicit window
+        # size is not going to help achieve better results)
+        opts = ''
+        if mss:
+            opts += "-M" + str(mss)
+        if reverse_dir:
+            opts += " -F -r"
+        if length:
+            opts += " -l" + str(length)
+        if udp:
+            opts += " -u"
+            # for UDP if the bandwidth is not provided we need to calculate
+            # the optimal bandwidth
+            if not bandwidth_kbps:
+                udp_res = self.find_udp_bdw(length, target_ip)
+                if 'error' in udp_res:
+                    return [udp_res]
+                if not self.instance.gmond_svr:
+                    # if we do not collect CPU we miught as well return
+                    # the results found through iteration
+                    return [udp_res]
+                bandwidth_kbps = udp_res['throughput_kbps']
+        if bandwidth_kbps:
+            opts += " -R%sK" % (bandwidth_kbps)
+
+        if no_cpu_timed:
+            duration_sec = no_cpu_timed
+        else:
+            duration_sec = self.instance.get_cmd_duration()
+        # use data port 5001 and control port 5002
+        # must be enabled in the VM security group
+        cmd = "%s -T%d %s -p5001 -P5002 -fparse %s" % (self.dest_path,
+                                                       duration_sec,
+                                                       opts,
+                                                       target_ip)
+        self.instance.buginf(cmd)
+        try:
+            if no_cpu_timed:
+                # force the timeout value with 20 second extra for the command to
+                # complete and do not collect CPU
+                cpu_load = None
+                cmd_out = self.instance.exec_command(cmd, duration_sec + 20)
+            else:
+                (cmd_out, cpu_load) = self.instance.exec_with_cpu(cmd)
+        except sshutils.SSHError as exc:
+            # Timout or any SSH error
+            self.instance.display('SSH Error:' + str(exc))
+            return [self.parse_error(str(exc))]
+
+        if udp:
+            # UDP output (unicast and multicast):
+            # megabytes=1.1924 real_seconds=10.01 rate_Mbps=0.9997 tx_cpu=99 rx_cpu=0
+            #      drop=0 pkt=1221 data_loss=0.00000
+            re_udp = r'rate_Mbps=([\d\.]*) tx_cpu=\d* rx_cpu=\d* drop=(\d*) pkt=(\d*)'
+            match = re.search(re_udp, cmd_out)
+            if match:
+                rate_mbps = float(match.group(1))
+                drop = float(match.group(2))
+                pkt = int(match.group(3))
+                # nuttcp uses multiple of 1000 for Kbps - not 1024
+                return [self.parse_results('UDP',
+                                           int(rate_mbps * 1000),
+                                           lossrate=round(drop * 100 / pkt, 2),
+                                           reverse_dir=reverse_dir,
+                                           msg_size=length,
+                                           cpu_load=cpu_load)]
+        else:
+            # TCP output:
+            # megabytes=1083.4252 real_seconds=10.04 rate_Mbps=905.5953 tx_cpu=3 rx_cpu=19
+            #      retrans=0 rtt_ms=0.55
+            re_tcp = r'rate_Mbps=([\d\.]*) tx_cpu=\d* rx_cpu=\d* retrans=(\d*) rtt_ms=([\d\.]*)'
+            match = re.search(re_tcp, cmd_out)
+            if match:
+                rate_mbps = float(match.group(1))
+                retrans = int(match.group(2))
+                rtt_ms = float(match.group(3))
+                return [self.parse_results('TCP',
+                                           int(rate_mbps * 1000),
+                                           retrans=retrans,
+                                           rtt_ms=rtt_ms,
+                                           reverse_dir=reverse_dir,
+                                           msg_size=length,
+                                           cpu_load=cpu_load)]
+        return [self.parse_error('Could not parse: %s' % (cmd_out))]
diff --git a/openstack-common.conf b/openstack-common.conf
new file mode 100644
index 0000000..35665e6
--- /dev/null
+++ b/openstack-common.conf
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+# The list of modules to copy from oslo-incubator.git
+
+# The base module to hold the copy of openstack.common
+base=vmtp
diff --git a/perf_instance.py b/perf_instance.py
new file mode 100644
index 0000000..9e28d1a
--- /dev/null
+++ b/perf_instance.py
@@ -0,0 +1,105 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+from instance import Instance as Instance
+from perf_tool import PingTool
+
+class PerfInstance(Instance):
+    '''An openstack instance to run performance tools
+    '''
+    def __init__(self, name, config, comp=None, net=None, server=False):
+        Instance.__init__(self, name, config, comp, net)
+        self.is_server = server
+        if 'I' in config.protocols:
+            self.ping = PingTool(self)
+        else:
+            self.ping = None
+        if config.tp_tool:
+            self.tp_tool = config.tp_tool(self, config.perf_tool_path)
+        else:
+            self.tp_tool = None
+
+    # No args is reserved for native host server
+    def create(self, image=None, flavor_type=None,
+               keypair=None, nics=None, az=None,
+               management_network_name=None,
+               sec_group=None,
+               init_file_name=None):
+        '''Create an instance
+        :return: True on success, False on error
+        '''
+        rc = Instance.create(self, image, flavor_type, keypair,
+                             nics, az,
+                             management_network_name,
+                             sec_group,
+                             init_file_name)
+        if not rc:
+            return False
+        if self.tp_tool and not self.tp_tool.install():
+            return False
+        if not self.is_server:
+            return True
+        if self.tp_tool and not self.tp_tool.start_server():
+            return False
+        return True
+
+    def run_client(self, label, dest_ip, target_instance, mss=None,
+                   bandwidth=0,
+                   bidirectional=False,
+                   az_to=None):
+        '''test iperf client using the default TCP window size
+        (tcp window scaling is normally enabled by default so setting explicit window
+        size is not going to help achieve better results)
+        :return: a dictionary containing the results of the run
+        '''
+        # Latency (ping rtt)
+        if 'I' in self.config.protocols:
+            ping_res = self.ping.run_client(dest_ip)
+        else:
+            ping_res = None
+
+        # TCP/UDP throughput with tp_tool, returns a list of dict
+        if self.tp_tool and 'error' not in ping_res:
+            tp_tool_res = self.tp_tool.run_client(dest_ip,
+                                                  target_instance,
+                                                  mss=mss,
+                                                  bandwidth=bandwidth,
+                                                  bidirectional=bidirectional)
+        else:
+            tp_tool_res = []
+
+        res = {'ip_to': dest_ip}
+        if self.internal_ip:
+            res['ip_from'] = self.internal_ip
+        if label:
+            res['desc'] = label
+        if self.az:
+            res['az_from'] = self.az
+        if az_to:
+            res['az_to'] = az_to
+        res['distro_id'] = self.ssh.distro_id
+        res['distro_version'] = self.ssh.distro_version
+
+        # consolidate results for all tools
+        if ping_res:
+            tp_tool_res.append(ping_res)
+        res['results'] = tp_tool_res
+        return res
+
+    # Override in order to terminate the perf server
+    def dispose(self):
+        if self.tp_tool:
+            self.tp_tool.dispose()
+        Instance.dispose(self)
diff --git a/perf_tool.py b/perf_tool.py
new file mode 100644
index 0000000..9caf828
--- /dev/null
+++ b/perf_tool.py
@@ -0,0 +1,289 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import abc
+import os
+import re
+
+# where to copy the tool on the target, must end with slash
+SCP_DEST_DIR = '/tmp/'
+
+
+
+#
+# A base class for all tools that can be associated to an instance
+#
+class PerfTool(object):
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, name, perf_tool_path, instance):
+        self.name = name
+        self.instance = instance
+        self.dest_path = SCP_DEST_DIR + name
+        self.pid = None
+        self.perf_tool_path = perf_tool_path
+
+    # install the tool to the instance
+    # returns False if fail, True if success
+    def install(self):
+        if self.perf_tool_path:
+            local_path = os.path.join(self.perf_tool_path, self.name)
+            return self.instance.scp(self.name, local_path, self.dest_path)
+        # no install needed
+        return True
+
+    @abc.abstractmethod
+    def get_server_launch_cmd(self):
+        '''To be implemented by sub-classes.'''
+        return None
+
+    def start_server(self):
+        '''Launch the server side of this tool
+        :return: True if success, False if error
+        '''
+        # check if server is already started
+        if not self.pid:
+            self.pid = self.instance.ssh.pidof(self.name)
+        if not self.pid:
+            cmd_list = self.get_server_launch_cmd()
+            # Start the tool server
+            self.instance.buginf('Starting %s server...' % (self.name))
+            for launch_cmd in cmd_list:
+                launch_out = self.instance.exec_command(launch_cmd)
+            self.pid = self.instance.ssh.pidof(self.name)
+        else:
+            self.instance.buginf('%s server already started pid=%s' % (self.name, self.pid))
+        if self.pid:
+            return True
+        else:
+            self.instance.display('Cannot launch server %s: %s' % (self.name, launch_out))
+            return False
+
+    # Terminate pid if started
+    def dispose(self):
+        if self.pid:
+            # Terminate the iperf server
+            self.instance.buginf('Terminating %s', self.name)
+            self.instance.ssh.kill_proc(self.pid)
+            self.pid = None
+
+    def parse_error(self, msg):
+        return {'error': msg, 'tool': self.name}
+
+    def parse_results(self, protocol, throughput, lossrate=None, retrans=None,
+                      rtt_ms=None, reverse_dir=False,
+                      msg_size=None,
+                      cpu_load=None):
+        res = {'throughput_kbps': throughput,
+               'protocol': protocol,
+               'tool': self.name}
+        if self.instance.config.vm_bandwidth:
+            res['bandwidth_limit_kbps'] = self.instance.config.vm_bandwidth
+        if lossrate is not None:
+            res['loss_rate'] = lossrate
+        if retrans:
+            res['retrans'] = retrans
+        if rtt_ms:
+            res['rtt_ms'] = rtt_ms
+        if reverse_dir:
+            res['direction'] = 'reverse'
+        if msg_size:
+            res['pkt_size'] = msg_size
+        if cpu_load:
+            res['cpu_load'] = cpu_load
+        return res
+
+    @abc.abstractmethod
+    def run_client_dir(self, target_ip,
+                       mss,
+                       reverse_dir=False,
+                       bandwidth_kbps=0,
+                       udp=False,
+                       length=0,
+                       no_cpu_timed=0):
+        # must be implemented by sub classes
+        return None
+
+    def find_udp_bdw(self, pkt_size, target_ip):
+        '''Find highest UDP bandwidth within max loss rate for given packet size
+        :return: a dictionary describing the optimal bandwidth (see parse_results())
+        '''
+        # we use a binary search to converge to the optimal throughput
+        # start with 5Gbps - mid-range between 1 and 10Gbps
+        # Convergence can be *very* tricky because UDP throughput behavior
+        # can vary dramatically between host runs and guest runs.
+        # The packet rate limitation is going to dictate the effective
+        # send rate, meaning that small packet sizes will yield the worst
+        # throughput.
+        # The measured throughput can be vastly smaller than the requested
+        # throughput even when the loss rate is zero when the sender cannot
+        # send fast enough to fill the network, in that case increasing the
+        # requested rate will not make it any better
+        # Examples:
+        # 1. too much difference between requested/measured bw - regardless of loss rate
+        #    => retry with bw mid-way between the requested bw and the measured bw
+        # /tmp/nuttcp-7.3.2 -T2  -u -l128 -R5000000K -p5001 -P5002 -fparse 192.168.1.2
+        # megabytes=36.9785 real_seconds=2.00 rate_Mbps=154.8474 tx_cpu=23 rx_cpu=32
+        #         drop=78149 pkt=381077 data_loss=20.50746
+        # /tmp/nuttcp-7.3.2 -T2  -u -l128 -R2500001K -p5001 -P5002 -fparse 192.168.1.2
+        # megabytes=47.8063 real_seconds=2.00 rate_Mbps=200.2801 tx_cpu=24 rx_cpu=34
+        #         drop=0 pkt=391629 data_loss=0.00000
+        # 2. measured and requested bw are very close :
+        #   if loss_rate is too low
+        #     increase bw mid-way between requested and last max bw
+        #   if loss rate is too high
+        #     decrease bw mid-way between the measured bw and the last min bw
+        #   else stop iteration (converged)
+        # /tmp/nuttcp-7.3.2 -T2  -u -l8192 -R859376K -p5001 -P5002 -fparse 192.168.1.2
+        # megabytes=204.8906 real_seconds=2.00 rate_Mbps=859.2992 tx_cpu=99 rx_cpu=10
+        #           drop=0 pkt=26226 data_loss=0.00000
+
+        min_kbps = 1
+        max_kbps = 10000000
+        kbps = 5000000
+        min_loss_rate = self.instance.config.udp_loss_rate_range[0]
+        max_loss_rate = self.instance.config.udp_loss_rate_range[1]
+        # stop if the remaining range to cover is less than 5%
+        while (min_kbps * 100 / max_kbps) < 95:
+            res_list = self.run_client_dir(target_ip, 0, bandwidth_kbps=kbps,
+                                           udp=True, length=pkt_size,
+                                           no_cpu_timed=1)
+            # always pick the first element in the returned list of dict(s)
+            # should normally only have 1 element
+            res = res_list[0]
+            if 'error' in res:
+                return res
+            loss_rate = res['loss_rate']
+            measured_kbps = res['throughput_kbps']
+            self.instance.buginf('pkt-size=%d throughput=%d<%d/%d<%d Kbps loss-rate=%d' %
+                                 (pkt_size, min_kbps, measured_kbps, kbps, max_kbps, loss_rate))
+            # expected rate must be at least 80% of the requested rate
+            if (measured_kbps * 100 / kbps) < 80:
+                # the measured bw is too far away from the requested bw
+                # take half the distance or 3x the measured bw whichever is lowest
+                kbps = min(measured_kbps + (kbps - measured_kbps) / 2,
+                           measured_kbps * 3)
+                max_kbps = kbps
+                continue
+            # The measured bw is within striking distance from the requested bw
+            # increase bw if loss rate is too small
+            if loss_rate < min_loss_rate:
+                # undershot
+                if measured_kbps > min_kbps:
+                    min_kbps = measured_kbps
+                else:
+                    # to make forward progress we need to increase min_kbps
+                    # and try a higher bw since the loss rate is too low
+                    min_kbps = int((max_kbps + min_kbps) / 2)
+
+                kbps = int((max_kbps + min_kbps) / 2)
+                # print '   undershot, min=%d kbps=%d max=%d' % (min_kbps,  kbps, max_kbps)
+            elif loss_rate > max_loss_rate:
+                # overshot
+                max_kbps = kbps
+                if measured_kbps < kbps:
+                    kbps = measured_kbps
+                else:
+                    kbps = int((max_kbps + min_kbps) / 2)
+                # print '   overshot, min=%d kbps=%d max=%d' % (min_kbps,  kbps, max_kbps)
+            else:
+                # converged within loss rate bracket
+                break
+        return res
+
+    def get_proto_profile(self):
+        '''Return a tuple containing the list of protocols (tcp/udp) and
+        list of packet sizes (udp only)
+        '''
+        # start with TCP (udp=False) then UDP
+        proto_list = []
+        proto_pkt_sizes = []
+        if 'T' in self.instance.config.protocols:
+            proto_list.append(False)
+            proto_pkt_sizes.append(self.instance.config.tcp_pkt_sizes)
+        if 'U' in self.instance.config.protocols:
+            proto_list.append(True)
+            proto_pkt_sizes.append(self.instance.config.udp_pkt_sizes)
+        return (proto_list, proto_pkt_sizes)
+
+class PingTool(PerfTool):
+    '''
+    A class to run ping and get loss rate and round trip time
+    '''
+
+    def __init__(self, instance):
+        PerfTool.__init__(self, 'ping', None, instance)
+
+    def run_client(self, target_ip, ping_count=5):
+        '''Perform the ping operation
+        :return: a dict containing the results stats
+
+        Example of output:
+            10 packets transmitted, 10 packets received, 0.0% packet loss
+            round-trip min/avg/max/stddev = 55.855/66.074/103.915/13.407 ms
+        or
+            5 packets transmitted, 5 received, 0% packet loss, time 3998ms
+            rtt min/avg/max/mdev = 0.455/0.528/0.596/0.057 ms
+        '''
+        cmd = "ping -c " + str(ping_count) + " " + str(target_ip)
+        cmd_out = self.instance.exec_command(cmd)
+        if not cmd_out:
+            res = {'protocol': 'ICMP',
+                   'tool': 'ping',
+                   'error': 'failed'}
+            return res
+        match = re.search(r'(\d*) packets transmitted, (\d*) ',
+                          cmd_out)
+        if match:
+            tx_packets = match.group(1)
+            rx_packets = match.group(2)
+        else:
+            tx_packets = 0
+            rx_packets = 0
+        match = re.search(r'min/avg/max/[a-z]* = ([\d\.]*)/([\d\.]*)/([\d\.]*)/([\d\.]*)',
+                          cmd_out)
+        if match:
+            rtt_min = match.group(1)
+            rtt_avg = match.group(2)
+            rtt_max = match.group(3)
+            rtt_stddev = match.group(4)
+        else:
+            rtt_min = 0
+            rtt_max = 0
+            rtt_avg = 0
+            rtt_stddev = 0
+        res = {'protocol': 'ICMP',
+               'tool': 'ping',
+               'tx_packets': tx_packets,
+               'rx_packets': rx_packets,
+               'rtt_min_ms': rtt_min,
+               'rtt_max_ms': rtt_max,
+               'rtt_avg_ms': rtt_avg,
+               'rtt_stddev': rtt_stddev}
+        return res
+
+    def get_server_launch_cmd(self):
+        # not applicable
+        return None
+
+    def run_client_dir(self, target_ip,
+                       mss,
+                       reverse_dir=False,
+                       bandwidth_kbps=0,
+                       udp=False,
+                       length=0,
+                       no_cpu_timed=0):
+        # not applicable
+        return None
diff --git a/pns_mongo.py b/pns_mongo.py
new file mode 100755
index 0000000..ff17e69
--- /dev/null
+++ b/pns_mongo.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import pymongo
+
+def connect_to_mongod(mongod_ip, mongod_port):
+    '''
+    Create a connection to the mongo deamon.
+    '''
+    if mongod_ip is None:
+        mongod_ip = "localhost"
+
+    if mongod_port is None:
+        mongod_port = 27017
+
+    client = None
+
+    try:
+        client = pymongo.MongoClient(mongod_ip, mongod_port)
+    except pymongo.errors.ConnectionFailure:
+        print "ERROR: pymongo. Connection Failure (%s) (%d)" % \
+            (mongod_ip, mongod_port)
+        return None
+
+    return client
+
+
+def get_mongod_collection(db_client, database_name, collection_name):
+    '''
+    Given db name and collection name, get the collection object.
+    '''
+    mongo_db = db_client[database_name]
+    if mongo_db is None:
+        print "Invalid database name"
+        return None
+
+    collection = mongo_db[collection_name]
+    if collection is None:
+        return None
+
+    return collection
+
+
+def is_type_dict(var):
+    if isinstance(var, dict):
+        return True
+    return False
+
+
+def add_new_document_to_collection(collection, document):
+    if collection is None:
+        print "collection cannot be none"
+        return None
+
+    if not is_type_dict(document):
+        print "Document type should be a dictionary"
+        return None
+
+    post_id = collection.insert(document)
+
+    return post_id
+
+
+def search_documents_in_collection(collection, pattern):
+    if collection is None:
+        print "collection cannot be None"
+        return None
+
+    if pattern is None:
+        pattern = {}
+
+    if not is_type_dict(pattern):
+        print "pattern type should be a dictionary"
+        return None
+
+    try:
+        output = collection.find(pattern)
+    except TypeError:
+        print "A TypeError occured. Invalid pattern: ", pattern
+        return None
+
+    return output
+
+
+def pns_add_test_result_to_mongod(mongod_ip,
+                                  mongod_port, pns_database,
+                                  pns_collection, document):
+    '''
+    Invoked from vmtp to add a new result to the mongod database.
+    '''
+    client = connect_to_mongod(mongod_ip, mongod_port)
+    if client is None:
+        print "ERROR: Failed to connect to mongod (%s) (%d)" % \
+              (mongod_ip, mongod_port)
+        return None
+
+    collection = get_mongod_collection(client, pns_database, pns_collection)
+    if collection is None:
+        print "ERROR: Failed to get collection DB: %s, %s" % \
+              (pns_database, pns_collection)
+        return None
+
+    post_id = add_new_document_to_collection(collection, document)
+
+    return post_id
+
+
+def pns_search_results_from_mongod(mongod_ip, mongod_port,
+                                   pns_database, pns_collection,
+                                   pattern):
+    '''
+    Can be invoked from a helper script to query the mongod database
+    '''
+    client = connect_to_mongod(mongod_ip, mongod_port)
+    if client is None:
+        print "ERROR: Failed to connect to mongod (%s) (%d)" % \
+              (mongod_ip, mongod_port)
+        return
+
+    collection = get_mongod_collection(client, pns_database, pns_collection)
+    if collection is None:
+        print "ERROR: Failed to get collection DB: %s, %s" % \
+              (pns_database, pns_collection)
+        return
+
+    docs = search_documents_in_collection(collection, pattern)
+
+    return docs
diff --git a/pnsdb_summary.py b/pnsdb_summary.py
new file mode 100755
index 0000000..3535913
--- /dev/null
+++ b/pnsdb_summary.py
@@ -0,0 +1,328 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import argparse
+import re
+import sys
+
+import pns_mongo
+
+import tabulate
+
+###########################################
+# Global list of all result functions
+# that are displayed as a menu/list.
+###########################################
+pnsdb_results_list = [
+    ("Summary of all results", "show_summary_all"),
+    ("Show TCP results for vlan encap", "show_tcp_summary_encap_vlan"),
+    ("Show UDP results for vlan encap", "show_udp_summary_encap_vlan"),
+]
+
+network_type = [
+    (0, "L2 Network"),
+    (1, "L3 Network"),
+    (100, "Unknown"),
+]
+
+vm_loc = [
+    (0, "Intra-node"),
+    (1, "Inter-node"),
+]
+
+flow_re = re.compile(r".*(same|different) network.*(fixed|floating).*"
+                     "IP.*(inter|intra).*",
+                     re.IGNORECASE)
+
+
+def get_flow_type(flow_desc):
+    vm_location = None
+    nw_type = None
+    fixed_ip = None
+
+    mobj = flow_re.match(flow_desc)
+    if mobj:
+        if mobj.group(1) == "same":
+            nw_type = network_type[0][0]
+        elif mobj.group(1) == "different":
+            nw_type = network_type[1][0]
+        else:
+            nw_type = network_type[2][0]
+
+        if mobj.group(2) == "fixed":
+            fixed_ip = True
+        else:
+            fixed_ip = False
+
+        if mobj.group(3) == "inter":
+            vm_location = vm_loc[1][0]
+        else:
+            vm_location = vm_loc[0][0]
+
+    return(vm_location, nw_type, fixed_ip)
+
+
+def get_tcp_flow_data(data):
+    record_list = []
+    for record in data:
+        for flow in record['flows']:
+            results = flow['results']
+            get_flow_type(flow['desc'])
+            for result in results:
+                show_record = {}
+                if result['protocol'] == "TCP" or result['protocol'] == "tcp":
+                    show_record['throughput_kbps'] = result['throughput_kbps']
+                    show_record['rtt_ms'] = result['rtt_ms']
+                    show_record['pkt_size'] = result['pkt_size']
+                    show_record['openstack_version'] = record['openstack_version']
+                    show_record['date'] = record['date']
+                    show_record['distro'] = record['distro']
+                    # show_record['desc'] = flow['desc']
+                    record_list.append(show_record)
+
+    return record_list
+
+
+def get_udp_flow_data(data):
+    record_list = []
+    for record in data:
+        for flow in record['flows']:
+            results = flow['results']
+            get_flow_type(flow['desc'])
+            for result in results:
+                show_record = {}
+                if result['protocol'] == "UDP" or result['protocol'] == "udp":
+                    show_record['throughput_kbps'] = result['throughput_kbps']
+                    show_record['loss_rate'] = result['loss_rate']
+                    show_record['openstack_version'] = record['openstack_version']
+                    show_record['date'] = record['date']
+                    show_record['distro'] = record['distro']
+                    # show_record['desc'] = flow['desc']
+                    record_list.append(show_record)
+    return record_list
+
+
+def show_pnsdb_summary(db_server, db_port, db_name, db_collection):
+    '''
+    Show a summary of results.
+    '''
+    pattern = {}
+    data = pns_mongo.pns_search_results_from_mongod(db_server,
+                                                    db_port,
+                                                    db_name,
+                                                    db_collection,
+                                                    pattern)
+    record_list = get_tcp_flow_data(data)
+    print tabulate.tabulate(record_list, headers="keys", tablefmt="grid")
+    print data.count()
+
+    data = pns_mongo.pns_search_results_from_mongod(db_server,
+                                                    db_port,
+                                                    db_name,
+                                                    db_collection,
+                                                    pattern)
+    record_list = get_udp_flow_data(data)
+    print "UDP:"
+    print tabulate.tabulate(record_list, headers="keys", tablefmt="grid")
+
+
+def get_results_info(results, cols, protocol=None):
+    result_list = []
+
+    for result in results:
+        show_result = {}
+        if protocol is not None:
+            if result['protocol'] != protocol:
+                continue
+        for col in cols:
+            if col in result.keys():
+                show_result[col] = result[col]
+
+        result_list.append(show_result)
+
+    return result_list
+
+
+def get_flow_info(flow, cols):
+    flow_list = []
+    show_flow = {}
+    for col in cols:
+        show_flow[col] = flow[col]
+    (vmloc, nw_type, fixed_ip) = get_flow_type(flow['desc'])
+    show_flow['nw_type'] = network_type[nw_type][1]
+    show_flow['vm_loc'] = vm_loc[vmloc][1]
+    if fixed_ip:
+        show_flow['fixed_float'] = "Fixed IP"
+    else:
+        show_flow['fixed_float'] = "Floating IP"
+    flow_list.append(show_flow)
+
+    return flow_list
+
+
+def get_record_info(record, cols):
+    record_list = []
+    show_record = {}
+    for col in cols:
+        show_record[col] = record[col]
+
+    record_list.append(show_record)
+
+    return record_list
+
+
+def print_record_header(record):
+    print "#" * 60
+    print "RUN: %s" % (record['date'])
+    cols = ['date', 'distro', 'openstack_version', 'encapsulation']
+    record_list = get_record_info(record, cols)
+    print tabulate.tabulate(record_list)
+
+
+def print_flow_header(flow):
+    cols = ['desc']
+    flow_list = get_flow_info(flow, cols)
+    print tabulate.tabulate(flow_list, tablefmt="simple")
+
+
+def show_tcp_summary_encap_vlan(db_server, db_port, db_name, db_collection):
+    pattern = {"encapsulation": "vlan"}
+
+    data = pns_mongo.pns_search_results_from_mongod(db_server,
+                                                    db_port,
+                                                    db_name,
+                                                    db_collection,
+                                                    pattern)
+    for record in data:
+        print_record_header(record)
+        for flow in record['flows']:
+            print_flow_header(flow)
+            cols = ['throughput_kbps', 'protocol', 'tool', 'rtt_ms']
+            result_list = get_results_info(flow['results'], cols,
+                                           protocol="TCP")
+            print tabulate.tabulate(result_list,
+                                    headers="keys", tablefmt="grid")
+
+    print "\n"
+
+
+def show_udp_summary_encap_vlan(db_server, db_port, db_name, db_collection):
+    pattern = {"encapsulation": "vlan"}
+
+    data = pns_mongo.pns_search_results_from_mongod(db_server,
+                                                    db_port,
+                                                    db_name,
+                                                    db_collection,
+                                                    pattern)
+    for record in data:
+        print_record_header(record)
+        for flow in record['flows']:
+            print_flow_header(flow)
+            cols = ['throughput_kbps', 'protocol', 'loss_rate', 'pkt_size']
+            result_list = get_results_info(flow['results'], cols,
+                                           protocol="UDP")
+            print tabulate.tabulate(result_list,
+                                    headers="keys", tablefmt="grid")
+
+
+def show_summary_all(db_server, db_port, db_name, db_collection):
+    pattern = {}
+
+    print "-" * 60
+    print "Summary Data: "
+    print "-" * 60
+
+    data = pns_mongo.pns_search_results_from_mongod(db_server,
+                                                    db_port,
+                                                    db_name,
+                                                    db_collection,
+                                                    pattern)
+    for record in data:
+        print_record_header(record)
+        for flow in record['flows']:
+            print_flow_header(flow)
+
+            # Display the results for each flow.
+            cols = ['throughput_kbps', 'protocol', 'tool',
+                    'rtt_ms', 'loss_rate', 'pkt_size',
+                    'rtt_avg_ms']
+            result_list = get_results_info(flow['results'], cols)
+            print tabulate.tabulate(result_list,
+                                    headers="keys", tablefmt="grid")
+
+    print "\n"
+
+
+def main():
+    ####################################################################
+    # parse arguments.
+    # --server-ip [required]
+    # --server-port [optional] [default: 27017]
+    # --official [optional]
+    ####################################################################
+    parser = argparse.ArgumentParser(description="VMTP Results formatter")
+    parser.add_argument('-s', "--server-ip", dest="server_ip",
+                        action="store",
+                        help="MongoDB Server IP address")
+    parser.add_argument('-p', "--server-port", dest="server_port",
+                        action="store",
+                        help="MongoDB Server port (default 27017)")
+    parser.add_argument("-o", "--official", default=False,
+                        action="store_true",
+                        help="Access offcial results collection")
+
+    (opts, _) = parser.parse_known_args()
+
+    if not opts.server_ip:
+        print "Provide the pns db server ip address"
+        sys.exit()
+
+    db_server = opts.server_ip
+
+    if not opts.server_port:
+        db_port = 27017
+    else:
+        db_port = opts.server_port
+
+    db_name = "pnsdb"
+
+    if opts.official:
+        print "Use db collection officialdata"
+        db_collection = "officialdata"
+    else:
+        db_collection = "testdata"
+
+    print "-" * 40
+    print "Reports Menu:"
+    print "-" * 40
+    count = 0
+    for option in pnsdb_results_list:
+        print "%d: %s" % (count, option[0])
+        count += 1
+    print "\n"
+
+    try:
+        user_opt = int(raw_input("Choose a report [no] : "))
+    except ValueError:
+        print "Invalid option"
+        sys.exit()
+
+    globals()[pnsdb_results_list[user_opt][1]](db_server,
+                                               db_port, db_name, db_collection)
+
+if __name__ == '__main__':
+    main()
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 0000000..42ffb8f
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,17 @@
+[BASIC]
+# Allow constant names to be lower case
+const-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,30}$
+module-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,30}$
+max-line-length=100
+max-args=10
+max-branches=20
+max-locals=20
+good-names=az,ip,_,rc
+max-statements=100
+
+
+[MESSAGE CONTROL]
+disable=missing-docstring,too-many-public-methods,too-many-instance-attributes,star-args,pointless-string-statement,no-self-use,too-many-locals,superfluous-parens,too-few-public-methods,unused-argument
+
+[SIMILARITIES]
+min-similarity-lines=10
\ No newline at end of file
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..826ea6b
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,5 @@
+-r requirements.txt
+git-review>=1.24
+pylint>=1.3
+pep8>=1.5.7
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..d77780b
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,20 @@
+# 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
+Babel>=1.3
+
+configure>=0.5
+ecdsa>=0.11
+lxml>=3.4.0
+oslo.utils>=1.2.0
+paramiko>=1.14.0
+pycrypto>=2.6.1
+pymongo>=2.7.2
+python-neutronclient<3,>=2.3.6
+python-novaclient>=2.18.1
+python-openstackclient>=0.4.1
+python-keystoneclient>=1.0.0
+scp>=0.8.0
+tabulate>=0.7.3
diff --git a/run_tests.sh b/run_tests.sh
new file mode 100644
index 0000000..a1b333c
--- /dev/null
+++ b/run_tests.sh
@@ -0,0 +1,2 @@
+#! /bin/bash
+vmtp.py -h
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..7e60d59
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,47 @@
+[metadata]
+name = vmtp
+summary = A data path performance tool for OpenStack clouds.
+description-file =
+    README.rst
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+home-page = http://www.openstack.org/
+classifier =
+    Environment :: OpenStack
+    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
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.3
+    Programming Language :: Python :: 3.4
+
+[files]
+packages =
+    vmtp
+
+[build_sphinx]
+source-dir = doc/source
+build-dir = doc/build
+all_files = 1
+
+[upload_sphinx]
+upload-dir = doc/build/html
+
+[compile_catalog]
+directory = vmtp/locale
+domain = vmtp
+
+[update_catalog]
+domain = vmtp
+output_dir = vmtp/locale
+input_file = vmtp/locale/vmtp.pot
+
+[extract_messages]
+keywords = _ gettext ngettext l_ lazy_gettext
+mapping_file = babel.cfg
+output_file = vmtp/locale/vmtp.pot
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..70c2b3f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+#!/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
+
+setuptools.setup(
+    setup_requires=['pbr'],
+    pbr=True)
diff --git a/ssh/id_rsa b/ssh/id_rsa
new file mode 100644
index 0000000..057e6d9
--- /dev/null
+++ b/ssh/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAu1wjIM/GgPbbLPoyKfN+I1uSeqrF4PYcbsTcHaQ6mEF/Ufqe
+6uZVJFR1mT1ECfxCckUMM6aaf5ESNAfxEjE9Hrs/Yd3Qw5hwSlG3HJ4uZg79m1yg
+ifXfnkp4rGNI6sqZxgyMbeQW3KoDaQ6zOe7e/KIkxX4XzR8I4d5lRx4ofIb806AB
+QJkDrq48dHLGZnOBPvpNImpg5u6EWHGa4HI4Dl2pdyEQXTXOErupalOC1Cr8oxwu
+fimSxftnl9Nh94wQtTQADnCE2GBaMMxS/ClHtJLDfmtnVC51Y4F7Ux9F3MSTSRBP
+gNxcd9OikMKSj6RNq/PHw5+9R0h5v2lJXvalCQIDAQABAoIBAArCu/HCfTAi/WuT
+4xWtumzlcYBCFqNY/0ENZWb+a68a8+kNb9sl53Xys95dOm8oYdiWRqEgzHbPKjB6
+1EmrMkt1japdRwQ02R4rm0y1eQy7h61IoJ/L01AQDuY3vZReln5dciNNmlKKITAD
+fB+zrHLuDRaaq1tIkQYH8+ElxkWAkd/vRQC4FP1OMIGnX4TdQ8lcG2DxwMs5jqJ6
+ufTeR6QMDEymNYQwcdFhe5wNi57IEbN9B+N95yaktWsYV34HuYV2ndZtrhMLFhcq
+Psw3vgrXBrreVPZ/iX1zeWgrjJb1AVOCtsOZ+O4YfZIIBWnhjj9sJnDCpMWmioH5
+a0UmF0ECgYEA+NyIS5MmbrVJKVqWUJubSbaZXQEW3Jv4njRFAyG7NVapSbllF5t2
+lq5usUI+l1XaZ3v6IpYPG+K+U1Ggo3+E6RFEDwVrZF0NYLOPXBydhkFFB4nHpTSX
+uBo65/SiMDSassrqs/PFCDdsiUQ87sMFp+gouDePcBDC1OyHRDxR220CgYEAwLv6
+zvqi5AvzO+tZQHRHcQdvCNC436KsUZlV6jQ5j3tUlqXlLRl8bWfih7Hu2uBGNjy2
+Fao517Nd/kBdjVaechn/fvmLwgflQso0q1j63u2nZ32uYTd+zLnW1yJM4UCs/Hqb
+hebRYDeZuRfobp62lEl6rdGij5LLRzQClOArso0CgYAaHClApKO3odWXPSXgNzNH
+vJzCoUagxsyC7MEA3x0hL4J7dbQhkfITRSHf/y9J+Xv8t4k677uOFXAalcng3ZQ4
+T9NwMAVgdlLc/nngFDCC0X5ImDAWKTpx2m6rv4L0w9AnShrt3nmhrw74J+yssFF7
+mGQNT+cAvwFyDY7zndCI0QKBgEkZw0on5ApsweezHxoEQGiNcj68s7IWyBb2+pAn
+GMHj/DRbXa4aYYg5g8EF6ttXfynpIwLamq/GV1ss3I7UEKqkU7S8P5brWbhYa1um
+FxjguMLW94HmA5Dw15ynZNN2rWXhtwU1g6pjzElY2Q7D4eoiaIZu4aJlAfbSsjv3
+PnutAoGBAMBRX8BbFODtQr68c6LWWda5zQ+kNgeCv+2ejG6rsEQ+Lxwi86Oc6udG
+kTP4xuZo80MEW/t+kibFgU6gm1WTVltpbjo0XTaHE1OV4JeNC8edYFTi1DVO5r1M
+ch+pkN20FQmZ+cLLn6nOeTJ6/9KXWKAZMPZ4SH4BnmF7iEa7yc8f
+-----END RSA PRIVATE KEY-----
diff --git a/ssh/id_rsa.pub b/ssh/id_rsa.pub
new file mode 100644
index 0000000..0313858
--- /dev/null
+++ b/ssh/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7XCMgz8aA9tss+jIp834jW5J6qsXg9hxuxNwdpDqYQX9R+p7q5lUkVHWZPUQJ/EJyRQwzppp/kRI0B/ESMT0euz9h3dDDmHBKUbccni5mDv2bXKCJ9d+eSnisY0jqypnGDIxt5BbcqgNpDrM57t78oiTFfhfNHwjh3mVHHih8hvzToAFAmQOurjx0csZmc4E++k0iamDm7oRYcZrgcjgOXal3IRBdNc4Su6lqU4LUKvyjHC5+KZLF+2eX02H3jBC1NAAOcITYYFowzFL8KUe0ksN+a2dULnVjgXtTH0XcxJNJEE+A3Fx306KQwpKPpE2r88fDn71HSHm/aUle9qUJ openstack-pns
diff --git a/sshutils.py b/sshutils.py
new file mode 100644
index 0000000..39fc227
--- /dev/null
+++ b/sshutils.py
@@ -0,0 +1,475 @@
+# Copyright 2013: Mirantis Inc.
+# 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.
+
+
+"""High level ssh library.
+
+Usage examples:
+
+Execute command and get output:
+
+    ssh = sshclient.SSH('root', 'example.com', port=33)
+    status, stdout, stderr = ssh.execute('ps ax')
+    if status:
+        raise Exception('Command failed with non-zero status.')
+    print stdout.splitlines()
+
+Execute command with huge output:
+
+    class PseudoFile(object):
+        def write(chunk):
+            if 'error' in chunk:
+                email_admin(chunk)
+
+    ssh = sshclient.SSH('root', 'example.com')
+    ssh.run('tail -f /var/log/syslog', stdout=PseudoFile(), timeout=False)
+
+Execute local script on remote side:
+
+    ssh = sshclient.SSH('user', 'example.com')
+    status, out, err = ssh.execute('/bin/sh -s arg1 arg2',
+                                   stdin=open('~/myscript.sh', 'r'))
+
+Upload file:
+
+    ssh = sshclient.SSH('user', 'example.com')
+    ssh.run('cat > ~/upload/file.gz', stdin=open('/store/file.gz', 'rb'))
+
+Eventlet:
+
+    eventlet.monkey_patch(select=True, time=True)
+    or
+    eventlet.monkey_patch()
+    or
+    sshclient = eventlet.import_patched("opentstack.common.sshclient")
+
+"""
+
+import re
+import select
+import socket
+import StringIO
+import time
+
+import paramiko
+import scp
+
+# from rally.openstack.common.gettextutils import _
+
+
+class SSHError(Exception):
+    pass
+
+
+class SSHTimeout(SSHError):
+    pass
+
+
+class SSH(object):
+    """Represent ssh connection."""
+
+    def __init__(self, user, host, port=22, pkey=None,
+                 key_filename=None, password=None,
+                 connect_timeout=60,
+                 connect_retry_count=30,
+                 connect_retry_wait_sec=2):
+        """Initialize SSH client.
+
+        :param user: ssh username
+        :param host: hostname or ip address of remote ssh server
+        :param port: remote ssh port
+        :param pkey: RSA or DSS private key string or file object
+        :param key_filename: private key filename
+        :param password: password
+        :param connect_timeout: timeout when connecting ssh
+        :param connect_retry_count: how many times to retry connecting
+        :param connect_retry_wait_sec: seconds to wait between retries
+        """
+
+        self.user = user
+        self.host = host
+        self.port = port
+        self.pkey = self._get_pkey(pkey) if pkey else None
+        self.password = password
+        self.key_filename = key_filename
+        self._client = False
+        self.connect_timeout = connect_timeout
+        self.connect_retry_count = connect_retry_count
+        self.connect_retry_wait_sec = connect_retry_wait_sec
+        self.distro_id = None
+        self.distro_id_like = None
+        self.distro_version = None
+        self.__get_distro()
+
+    def _get_pkey(self, key):
+        if isinstance(key, basestring):
+            key = StringIO.StringIO(key)
+        errors = []
+        for key_class in (paramiko.rsakey.RSAKey, paramiko.dsskey.DSSKey):
+            try:
+                return key_class.from_private_key(key)
+            except paramiko.SSHException as exc:
+                errors.append(exc)
+        raise SSHError('Invalid pkey: %s' % (errors))
+
+    def _get_client(self):
+        if self._client:
+            return self._client
+        self._client = paramiko.SSHClient()
+        self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        for _ in range(self.connect_retry_count):
+            try:
+                self._client.connect(self.host, username=self.user,
+                                     port=self.port, pkey=self.pkey,
+                                     key_filename=self.key_filename,
+                                     password=self.password,
+                                     timeout=self.connect_timeout)
+                return self._client
+            except (paramiko.AuthenticationException,
+                    paramiko.BadHostKeyException,
+                    paramiko.SSHException,
+                    socket.error):
+                time.sleep(self.connect_retry_wait_sec)
+
+        self._client = None
+        msg = '[%s] SSH Connection failed after %s attempts' % (self.host,
+                                                                self.connect_retry_count)
+        raise SSHError(msg)
+
+    def close(self):
+        self._client.close()
+        self._client = False
+
+    def run(self, cmd, stdin=None, stdout=None, stderr=None,
+            raise_on_error=True, timeout=3600):
+        """Execute specified command on the server.
+
+        :param cmd:             Command to be executed.
+        :param stdin:           Open file or string to pass to stdin.
+        :param stdout:          Open file to connect to stdout.
+        :param stderr:          Open file to connect to stderr.
+        :param raise_on_error:  If False then exit code will be return. If True
+                                then exception will be raized if non-zero code.
+        :param timeout:         Timeout in seconds for command execution.
+                                Default 1 hour. No timeout if set to 0.
+        """
+
+        client = self._get_client()
+
+        if isinstance(stdin, basestring):
+            stdin = StringIO.StringIO(stdin)
+
+        return self._run(client, cmd, stdin=stdin, stdout=stdout,
+                         stderr=stderr, raise_on_error=raise_on_error,
+                         timeout=timeout)
+
+    def _run(self, client, cmd, stdin=None, stdout=None, stderr=None,
+             raise_on_error=True, timeout=3600):
+
+        transport = client.get_transport()
+        session = transport.open_session()
+        session.exec_command(cmd)
+        start_time = time.time()
+
+        data_to_send = ''
+        stderr_data = None
+
+        # If we have data to be sent to stdin then `select' should also
+        # check for stdin availability.
+        if stdin and not stdin.closed:
+            writes = [session]
+        else:
+            writes = []
+
+        while True:
+            # Block until data can be read/write.
+            select.select([session], writes, [session], 1)
+
+            if session.recv_ready():
+                data = session.recv(4096)
+                if stdout is not None:
+                    stdout.write(data)
+                continue
+
+            if session.recv_stderr_ready():
+                stderr_data = session.recv_stderr(4096)
+                if stderr is not None:
+                    stderr.write(stderr_data)
+                continue
+
+            if session.send_ready():
+                if stdin is not None and not stdin.closed:
+                    if not data_to_send:
+                        data_to_send = stdin.read(4096)
+                        if not data_to_send:
+                            stdin.close()
+                            session.shutdown_write()
+                            writes = []
+                            continue
+                    sent_bytes = session.send(data_to_send)
+                    data_to_send = data_to_send[sent_bytes:]
+
+            if session.exit_status_ready():
+                break
+
+            if timeout and (time.time() - timeout) > start_time:
+                args = {'cmd': cmd, 'host': self.host}
+                raise SSHTimeout(('Timeout executing command '
+                                  '"%(cmd)s" on host %(host)s') % args)
+            # if e:
+            #    raise SSHError('Socket error.')
+
+        exit_status = session.recv_exit_status()
+        if 0 != exit_status and raise_on_error:
+            fmt = ('Command "%(cmd)s" failed with exit_status %(status)d.')
+            details = fmt % {'cmd': cmd, 'status': exit_status}
+            if stderr_data:
+                details += (' Last stderr data: "%s".') % stderr_data
+            raise SSHError(details)
+        return exit_status
+
+    def execute(self, cmd, stdin=None, timeout=3600):
+        """Execute the specified command on the server.
+
+        :param cmd:     Command to be executed.
+        :param stdin:   Open file to be sent on process stdin.
+        :param timeout: Timeout for execution of the command.
+
+        Return tuple (exit_status, stdout, stderr)
+
+        """
+        stdout = StringIO.StringIO()
+        stderr = StringIO.StringIO()
+
+        exit_status = self.run(cmd, stderr=stderr,
+                               stdout=stdout, stdin=stdin,
+                               timeout=timeout, raise_on_error=False)
+        stdout.seek(0)
+        stderr.seek(0)
+        return (exit_status, stdout.read(), stderr.read())
+
+    def wait(self, timeout=120, interval=1):
+        """Wait for the host will be available via ssh."""
+        start_time = time.time()
+        while True:
+            try:
+                return self.execute('uname')
+            except (socket.error, SSHError):
+                time.sleep(interval)
+            if time.time() > (start_time + timeout):
+                raise SSHTimeout(('Timeout waiting for "%s"') % self.host)
+
+    def __extract_property(self, name, input_str):
+        expr = name + r'="?([\w\.]*)"?'
+        match = re.search(expr, input_str)
+        if match:
+            return match.group(1)
+        return 'Unknown'
+
+    # Get the linux distro
+    def __get_distro(self):
+        '''cat /etc/*-release | grep ID
+        Ubuntu:
+            DISTRIB_ID=Ubuntu
+            ID=ubuntu
+            ID_LIKE=debian
+            VERSION_ID="14.04"
+        RHEL:
+            ID="rhel"
+            ID_LIKE="fedora"
+            VERSION_ID="7.0"
+        '''
+        distro_cmd = "grep ID /etc/*-release"
+        (status, distro_out, _) = self.execute(distro_cmd)
+        if status:
+            distro_out = ''
+        self.distro_id = self.__extract_property('ID', distro_out)
+        self.distro_id_like = self.__extract_property('ID_LIKE', distro_out)
+        self.distro_version = self.__extract_property('VERSION_ID', distro_out)
+
+    def pidof(self, proc_name):
+        '''
+        Return a list containing the pids of all processes of a given name
+        the list is empty if there is no pid
+        '''
+        # the path update is necessary for RHEL
+        cmd = "PATH=$PATH:/usr/sbin pidof " + proc_name
+        (status, cmd_output, _) = self.execute(cmd)
+        if status:
+            return []
+        cmd_output = cmd_output.strip()
+        result = cmd_output.split()
+        return result
+
+    # kill pids in the given list of pids
+    def kill_proc(self, pid_list):
+        cmd = "kill -9 " + ' '.join(pid_list)
+        self.execute(cmd)
+
+    # check stats for a given path
+    def stat(self, path):
+        (status, cmd_output, _) = self.execute('stat ' + path)
+        if status:
+            return None
+        return cmd_output
+
+    def ping_check(self, target_ip, ping_count=2, pass_threshold=80):
+        '''helper function to ping from one host to an IP address,
+            for a given count and pass_threshold;
+           Steps:
+            ssh to the host and then ping to the target IP
+            then match the output and verify that the loss% is
+            less than the pass_threshold%
+            Return 1 if the criteria passes
+            Return 0, if it fails
+        '''
+        cmd = "ping -c " + str(ping_count) + " " + str(target_ip)
+        (_, cmd_output, _) = self.execute(cmd)
+
+        match = re.search(r'(\d*)% packet loss', cmd_output)
+        pkt_loss = match.group(1)
+        if int(pkt_loss) < int(pass_threshold):
+            return 1
+        else:
+            print 'Ping to %s failed: %s' % (target_ip, cmd_output)
+            return 0
+
+    def get_file_from_host(self, from_path, to_path):
+        '''
+        A wrapper api on top of paramiko scp module, to scp
+        a local file to the host.
+        '''
+        sshcon = self._get_client()
+        scpcon = scp.SCPClient(sshcon.get_transport())
+        try:
+            scpcon.get(from_path, to_path)
+        except scp.SCPException as exp:
+            print ("Send failed: [%s]", exp)
+            return 0
+        return 1
+
+    def read_remote_file(self, from_path):
+        '''
+        Read a remote file and save it to a buffer.
+        '''
+        cmd = "cat " + from_path
+        (status, cmd_output, _) = self.execute(cmd)
+        if status:
+            return None
+        return cmd_output
+
+    def get_host_os_version(self):
+        '''
+        Identify the host distribution/relase.
+        '''
+        os_release_file = "/etc/os-release"
+        sys_release_file = "/etc/system-release"
+        name = ""
+        version = ""
+
+        if self.stat(os_release_file):
+            data = self.read_remote_file(os_release_file)
+            if data is None:
+                print "ERROR:Failed to read file %s" % os_release_file
+                return None
+
+            for line in data.splitlines():
+                mobj = re.match(r'PRETTY_NAME=(.*)', line)
+                if mobj:
+                    name = mobj.group(1).strip("\"")
+
+                mobj = re.match(r'VERSION.*=(.*)', line)
+                if mobj:
+                    version = mobj.group(1).strip("\"")
+
+            os_name = name + " " + version
+            return os_name
+
+        if self.stat(sys_release_file):
+            data = self.read_remote_file(sys_release_file)
+            if data is None:
+                print "ERROR:Failed to read file %s" % sys_release_file
+                return None
+
+            for line in data.splitlines():
+                mobj = re.match(r'Red Hat.*', line)
+                if mobj:
+                    return mobj.group(0)
+
+        return None
+
+    def check_rpm_package_installed(self, rpm_pkg):
+        '''
+        Given a host and a package name, check if it is installed on the
+        system.
+        '''
+        check_pkg_cmd = "rpm -qa | grep " + rpm_pkg
+
+        (status, cmd_output, _) = self.execute(check_pkg_cmd)
+        if status:
+            return None
+
+        pkg_pattern = ".*" + rpm_pkg + ".*"
+        rpm_pattern = re.compile(pkg_pattern, re.IGNORECASE)
+
+        for line in cmd_output.splitlines():
+            mobj = rpm_pattern.match(line)
+            if mobj:
+                return mobj.group(0)
+
+        print "%s pkg installed " % rpm_pkg
+
+        return None
+
+    def check_openstack_version(self):
+        '''
+        Identify the openstack version running on the controller.
+        '''
+        version_file = "/tmp/version.txt"
+        nova_cmd = "nova --version >> " + version_file
+
+        (status, _, err_output) = self.execute(nova_cmd)
+        if status:
+            return None
+
+        if err_output.strip() == "2.17.0":
+            return "icehouse"
+        else:
+            return "juno"
+
+
+
+
+
+
+
+
+
+##################################################
+# Only invoke the module directly for test purposes. Should be
+# invoked from pns script.
+##################################################
+def main():
+    # ssh = SSH('localadmin', '172.29.87.29', key_filename='./ssh/id_rsa')
+    ssh = SSH('localadmin', '172.22.191.173', key_filename='./ssh/id_rsa')
+    print 'ID=' + ssh.distro_id
+    print 'ID_LIKE=' + ssh.distro_id_like
+    print 'VERSION_ID=' + ssh.distro_version
+    ssh.wait()
+    print ssh.pidof('bash')
+    print ssh.stat('/tmp')
+
+if __name__ == "__main__":
+    main()
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..101b769
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,15 @@
+# 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.
+
+hacking>=0.9.2,<0.10
+
+coverage>=3.6
+discover
+python-subunit>=0.0.18
+sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
+oslosphinx>=2.2.0  # Apache-2.0
+oslotest>=1.2.0  # Apache-2.0
+testrepository>=0.0.18 
+testscenarios>=0.4
+testtools>=0.9.36,!=1.2.0
diff --git a/tools/iperf b/tools/iperf
new file mode 100755
index 0000000000000000000000000000000000000000..acf5ea95e5569e04c1322439ef66e5d19177fd61
GIT binary patch
literal 68488
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

literal 0
HcmV?d00001

diff --git a/tools/nuttcp-7.3.2 b/tools/nuttcp-7.3.2
new file mode 100755
index 0000000000000000000000000000000000000000..7663d15b63cd915a2e91f2de3bc771c709342d86
GIT binary patch
literal 154592
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

literal 0
HcmV?d00001

diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..8899aac
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,42 @@
+[tox]
+minversion = 1.6
+envlist = py33,py34,py26,py27,pypy,pep8
+skipsdist = True
+
+[testenv]
+usedevelop = True
+install_command = pip install -U {opts} {packages}
+setenv =
+   VIRTUAL_ENV={envdir}
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
+commands = python setup.py testr --slowest --testr-args='{posargs}'
+
+[testenv:pep8]
+commands = flake8
+
+[testenv:venv]
+commands = {posargs}
+
+[testenv:cover]
+commands = python setup.py testr --coverage --testr-args='{posargs}'
+
+[testenv:docs]
+commands = python setup.py build_sphinx
+
+[flake8]
+# H803 skipped on purpose per list discussion.
+# E123, E125 skipped as they are invalid PEP-8.
+max-line-length = 100
+show-source = True
+#E302: expected 2 blank linee
+#E303: too many blank lines (2)
+#H233: Python 3.x incompatible use of print operator
+#H236: Python 3.x incompatible __metaclass__, use six.add_metaclass()
+#H302: import only modules.
+#H404: multi line docstring should start without a leading new line
+#H405: multi line docstring summary not separated with an empty line
+#H904: Wrap long lines in parentheses instead of a backslash
+ignore = E123,E125,H803,E302,E303,H233,H236,H302,H404,H405,H904
+builtins = _
+exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
diff --git a/vmtp.py b/vmtp.py
new file mode 100755
index 0000000..a839cab
--- /dev/null
+++ b/vmtp.py
@@ -0,0 +1,768 @@
+# Copyright 2014 Cisco Systems, Inc.  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.
+#
+
+import argparse
+import ast
+import datetime
+import json
+import os
+import pprint
+import re
+import socket
+import stat
+import sys
+import traceback
+
+import compute
+import credentials
+import iperf_tool
+import network
+import nuttcp_tool
+import pns_mongo
+import sshutils
+
+import configure
+from neutronclient.v2_0 import client as neutronclient
+from novaclient.client import Client
+
+__version__ = '2.0.0'
+
+from perf_instance import PerfInstance as PerfInstance
+# Global external host info
+ext_host_list = []
+
+# Check IPv4 address syntax - not completely fool proof but will catch
+# some invalid formats
+def is_ipv4(address):
+    try:
+        socket.inet_aton(address)
+    except socket.error:
+        return False
+    return True
+
+def get_absolute_path_for_file(file_name):
+    '''
+    Return the filename in absolute path for any file
+    passed as relateive path.
+    '''
+    if os.path.isabs(__file__):
+        abs_file_path = os.path.join(__file__.split("vmtp.py")[0],
+                                     file_name)
+    else:
+        abs_file = os.path.abspath(__file__)
+        abs_file_path = os.path.join(abs_file.split("vmtp.py")[0],
+                                     file_name)
+
+    return abs_file_path
+
+
+def normalize_paths(cfg):
+    '''
+    Normalize the various paths to config files, tools, ssh priv and pub key
+    files.
+    '''
+    cfg.public_key_file = get_absolute_path_for_file(cfg.public_key_file)
+    cfg.private_key_file = get_absolute_path_for_file(cfg.private_key_file)
+    cfg.perf_tool_path = get_absolute_path_for_file(cfg.perf_tool_path)
+
+class FlowPrinter(object):
+
+    def __init__(self):
+        self.flow_num = 0
+
+    def print_desc(self, desc):
+        self.flow_num += 1
+        print "=" * 60
+        print('Flow %d: %s' % (self.flow_num, desc))
+
+class ResultsCollector(object):
+
+    def __init__(self):
+        self.results = {'flows': []}
+        self.results['date'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        self.results['args'] = ' '.join(sys.argv)
+        self.results['version'] = __version__
+        self.ppr = pprint.PrettyPrinter(indent=4, width=100)
+
+    def add_property(self, name, value):
+        self.results[name] = value
+
+    def add_flow_result(self, flow_res):
+        self.results['flows'].append(flow_res)
+        self.ppr.pprint(flow_res)
+
+    def display(self):
+        self.ppr.pprint(self.results)
+
+    def pprint(self, res):
+        self.ppr.pprint(res)
+
+    def save(self, filename):
+        '''Save results in json format file.'''
+        print('Saving results in json file: ' + filename)
+        with open(filename, 'w') as jfp:
+            json.dump(self.results, jfp, indent=4, sort_keys=True)
+
+    def save_to_db(self, cfg):
+        '''Save resutls to MongoDB database.'''
+        print "Saving results to MongoDB database."
+        sshcon = sshutils.SSH(cfg.access_username,
+                              cfg.access_host,
+                              password=cfg.access_password)
+        if sshcon is not None:
+            self.results['distro'] = sshcon.get_host_os_version()
+            self.results['openstack_version'] = sshcon.check_openstack_version()
+
+        post_id = pns_mongo.\
+            pns_add_test_result_to_mongod(cfg.pns_mongod_ip,
+                                          cfg.pns_mongod_port,
+                                          cfg.pns_db,
+                                          cfg.pns_collection,
+                                          self.results)
+        if post_id is None:
+            print "ERROR: Failed to add result to DB"
+
+class VmtpException(Exception):
+    pass
+
+class VmtpTest(object):
+    def __init__(self):
+        '''
+            1. Authenticate nova and neutron with keystone
+            2. Create new client objects for neutron and nova
+            3. Find external network
+            4. Find or create router for external network
+            5. Find or create internal mgmt and data networks
+            6. Add internal mgmt network to router
+            7. Import public key for ssh
+            8. Create 2 VM instances on internal networks
+            9. Create floating ips for VMs
+            10. Associate floating ip with VMs
+        '''
+        self.server = None
+        self.client = None
+        self.net = None
+        self.comp = None
+        self.ping_status = None
+        self.client_az_list = None
+        self.sec_group = None
+        self.image_instance = None
+        self.flavor_type = None
+
+    # Create an instance on a particular availability zone
+    def create_instance(self, inst, az, int_net):
+        nics = [{'net-id': int_net['id']}]
+        self.assert_true(inst.create(self.image_instance,
+                                     self.flavor_type,
+                                     config.public_key_name,
+                                     nics,
+                                     az,
+                                     int_net['name'],
+                                     self.sec_group))
+
+    def assert_true(self, cond):
+        if not cond:
+            raise VmtpException('Assert failure')
+
+    def setup(self):
+        # If we need to reuse existing vms just return without setup
+        if not config.reuse_existing_vm:
+            creds = cred.get_credentials()
+            creds_nova = cred.get_nova_credentials_v2()
+            # Create the nova and neutron instances
+            nova_client = Client(**creds_nova)
+            neutron = neutronclient.Client(**creds)
+
+            self.comp = compute.Compute(nova_client, config)
+
+            # Add the script public key to openstack
+            self.comp.add_public_key(config.public_key_name,
+                                     config.public_key_file)
+
+            self.image_instance = self.comp.find_image(config.image_name)
+            if self.image_instance is None:
+                """
+                # Try to upload the image
+                print '%s: image not found, will try to upload it' % (config.image_name)
+                self.comp.copy_and_upload_image(config.image_name, config.server_ip_for_image,
+                                                config.image_path_in_server)
+                time.sleep(10)
+                self.image_instance = self.comp.find_image(config.image_name)
+                """
+
+                # Exit the pogram
+                print '%s: image not found.' % (config.image_name)
+                sys.exit(1)
+
+            self.assert_true(self.image_instance)
+            print 'Found image: %s' % (config.image_name)
+            self.flavor_type = self.comp.find_flavor(config.flavor_type)
+
+            self.net = network.Network(neutron, config)
+
+        # Create a new security group for the test
+        self.sec_group = self.comp.security_group_create()
+        if not self.sec_group:
+            raise VmtpException("Security group creation failed")
+        if config.reuse_existing_vm:
+            self.server.internal_ip = config.vm_server_internal_ip
+            self.client.internal_ip = config.vm_client_internal_ip
+            if config.vm_server_external_ip:
+                self.server.ssh_ip = config.vm_server_external_ip
+            else:
+                self.server.ssh_ip = config.vm_server_internal_ip
+            if config.vm_client_external_ip:
+                self.client.ssh_ip = config.vm_client_external_ip
+            else:
+                self.client.ssh_ip = config.vm_client_internal_ip
+            return
+
+        # this is the standard way of running the test
+        # NICs to be used for the VM
+        if config.reuse_network_name:
+            # VM needs to connect to existing management and new data network
+            # Reset the management network name
+            config.internal_network_name[0] = config.reuse_network_name
+        else:
+            # Make sure we have an external network and an external router
+            self.assert_true(self.net.ext_net)
+            self.assert_true(self.net.ext_router)
+            self.assert_true(self.net.vm_int_net)
+
+        # Get hosts for the availability zone to use
+        avail_list = self.comp.list_hypervisor(config.availability_zone)
+
+        # compute the list of client vm placements to run
+        if avail_list:
+            server_az = config.availability_zone + ":" + avail_list[0]
+            if len(avail_list) > 1:
+                # can do intra + inter
+                if config.inter_node_only:
+                    # inter-node only
+                    self.client_az_list = [config.availability_zone +
+                                           ":" + avail_list[1]]
+                else:
+                    self.client_az_list = [server_az, config.availability_zone +
+                                           ":" + avail_list[1]]
+            else:
+                # can only do intra
+                self.client_az_list = [server_az]
+        else:
+            # cannot get the list of hosts
+            # can do intra or inter (cannot know)
+            server_az = config.availability_zone
+            self.client_az_list = [server_az]
+
+        self.server = PerfInstance(config.vm_name_server,
+                                   config,
+                                   self.comp,
+                                   self.net,
+                                   server=True)
+        self.server.display('Creating server VM...')
+        self.create_instance(self.server, server_az,
+                             self.net.vm_int_net[0])
+
+    # Test throughput for the case of the external host
+    def ext_host_tp_test(self):
+        client = PerfInstance('Host-' + ext_host_list[1] + '-Client', config)
+        if not client.setup_ssh(ext_host_list[1], ext_host_list[0]):
+            client.display('SSH failed, check IP or make sure public key is configured')
+        else:
+            client.buginf('SSH connected')
+            client.create()
+            fpr.print_desc('External-VM (upload/download)')
+            res = client.run_client('External-VM',
+                                    self.server.ssh_ip,
+                                    self.server,
+                                    bandwidth=config.vm_bandwidth,
+                                    bidirectional=True)
+            if res:
+                rescol.add_flow_result(res)
+            client.dispose()
+
+    def add_location(self, label):
+        '''Add a note to a label to specify same node or differemt node.'''
+        # We can only tell if there is a host part in the az
+        # e.g. 'nova:GG34-7'
+        if ':' in self.client.az:
+            if self.client.az == self.server.az:
+                return label + ' (intra-node)'
+            else:
+                return label + ' (inter-node)'
+        return label
+
+    def create_flow_client(self, client_az, int_net):
+        self.client = PerfInstance(config.vm_name_client, config,
+                                   self.comp,
+                                   self.net)
+        self.create_instance(self.client, client_az, int_net)
+
+    def measure_flow(self, label, target_ip):
+        label = self.add_location(label)
+        fpr.print_desc(label)
+
+        # results for this flow as a dict
+        perf_output = self.client.run_client(label, target_ip,
+                                             self.server,
+                                             bandwidth=config.vm_bandwidth,
+                                             az_to=self.server.az)
+        if opts.stop_on_error:
+            # check if there is any error in the results
+            results_list = perf_output['results']
+            for res_dict in results_list:
+                if 'error' in res_dict:
+                    print('Stopping execution on error, cleanup all VMs/networks manually')
+                    rescol.pprint(perf_output)
+                    sys.exit(2)
+
+        rescol.add_flow_result(perf_output)
+
+    def measure_vm_flows(self):
+        network_type = 'Unknown'
+        try:
+            network_type = self.net.vm_int_net[0]['provider:network_type']
+
+            print "OpenStack network type: " + network_type
+            rescol.add_property('encapsulation', network_type)
+        except KeyError as exp:
+            network_type = 'Unknown'
+            print "Provider network type not found: ", str(exp)
+
+        # scenarios need to be tested for both inter and intra node
+        # 1. VM to VM on same data network
+        # 2. VM to VM on seperate networks fixed-fixed
+        # 3. VM to VM on seperate networks floating-floating
+
+        # we should have 1 or 2 AZ to use (intra and inter-node)
+        for client_az in self.client_az_list:
+            self.create_flow_client(client_az, self.net.vm_int_net[0])
+            self.measure_flow("VM to VM same network fixed IP",
+                              self.server.internal_ip)
+            self.client.dispose()
+            self.client = None
+            if not config.reuse_network_name:
+                # Different network
+                self.create_flow_client(client_az, self.net.vm_int_net[1])
+
+                self.measure_flow("VM to VM different network fixed IP",
+                                  self.server.internal_ip)
+                self.measure_flow("VM to VM different network floating IP",
+                                  self.server.ssh_ip)
+
+                self.client.dispose()
+                self.client = None
+
+        # If external network is specified run that case
+        if ext_host_list:
+            self.ext_host_tp_test()
+
+    def teardown(self):
+        '''
+            Clean up the floating ip and VMs
+        '''
+        print '---- Cleanup ----'
+        if self.server:
+            self.server.dispose()
+        if self.client:
+            self.client.dispose()
+        if not config.reuse_existing_vm and self.net:
+            self.net.dispose()
+        # Remove the public key
+        if self.comp:
+            self.comp.remove_public_key(config.public_key_name)
+        # Finally remove the security group
+        self.comp.security_group_delete(self.sec_group)
+
+    def run(self):
+        error_flag = False
+
+        try:
+            self.setup()
+            self.measure_vm_flows()
+        except KeyboardInterrupt:
+            traceback.format_exc()
+        except VmtpException:
+            traceback.format_exc()
+            error_flag = True
+        except sshutils.SSHError:
+            traceback.format_exc()
+            error_flag = True
+
+        if opts.stop_on_error and error_flag:
+            print('Stopping execution on error, cleanup all VMs/networks manually')
+            sys.exit(2)
+        else:
+            self.teardown()
+
+def test_native_tp(nhosts):
+    fpr.print_desc('Native Host to Host throughput')
+    server_host = nhosts[0]
+    server = PerfInstance('Host-' + server_host[1] + '-Server', config, server=True)
+
+    if not server.setup_ssh(server_host[1], server_host[0]):
+        server.display('SSH failed, check IP or make sure public key is configured')
+    else:
+        server.display('SSH connected')
+        server.create()
+        # if inter-node-only requested we avoid running the client on the
+        # same node as the server - but only if there is at least another
+        # IP provided
+        if config.inter_node_only and len(nhosts) > 1:
+            # remove the first element of the list
+            nhosts.pop(0)
+        # IP address clients should connect to, check if the user
+        # has passed a server listen interface name
+        if len(server_host) == 3:
+            # use the IP address configured on given interface
+            server_ip = server.get_interface_ip(server_host[2])
+            if not server_ip:
+                print('Error: cannot get IP address for interface ' + server_host[2])
+            else:
+                server.display('Clients will use server IP address %s (%s)' %
+                               (server_ip, server_host[2]))
+        else:
+            # use same as ssh IP
+            server_ip = server_host[1]
+
+        if server_ip:
+            # start client side, 1 per host provided
+            for client_host in nhosts:
+                client = PerfInstance('Host-' + client_host[1] + '-Client', config)
+                if not client.setup_ssh(client_host[1], client_host[0]):
+                    client.display('SSH failed, check IP or make sure public key is configured')
+                else:
+                    client.buginf('SSH connected')
+                    client.create()
+                    res = client.run_client('Native host-host',
+                                            server_ip,
+                                            server,
+                                            bandwidth=config.vm_bandwidth)
+                    rescol.add_flow_result(res)
+                client.dispose()
+    server.dispose()
+
+if __name__ == '__main__':
+
+    fpr = FlowPrinter()
+    rescol = ResultsCollector()
+
+    parser = argparse.ArgumentParser(description='OpenStack VM Throughput V' + __version__)
+
+    parser.add_argument('-c', '--config', dest='config',
+                        action='store',
+                        help='override default values with a config file',
+                        metavar='<config_file>')
+
+    parser.add_argument('-r', '--rc', dest='rc',
+                        action='store',
+                        help='source OpenStack credentials from rc file',
+                        metavar='<openrc_file>')
+
+    parser.add_argument('-m', '--monitor', dest='monitor',
+                        action='store',
+                        help='Enable CPU monitoring (requires Ganglia)',
+                        metavar='<gmond_ip>[:<port>]')
+
+    parser.add_argument('-p', '--password', dest='pwd',
+                        action='store',
+                        help='OpenStack password',
+                        metavar='<password>')
+
+    parser.add_argument('-t', '--time', dest='time',
+                        action='store',
+                        help='throughput test duration in seconds (default 10 sec)',
+                        metavar='<time>')
+
+    parser.add_argument('--host', dest='hosts',
+                        action='append',
+                        help='native host throughput (targets requires ssh key)',
+                        metavar='<user>@<host_ssh_ip>[:<server-listen-if-name>]')
+
+    parser.add_argument('--external-host', dest='ext_host',
+                        action='store',
+                        help='external-VM throughput (target requires ssh key)',
+                        metavar='<user>@<ext_host_ssh_ip>')
+
+    parser.add_argument('--access_info', dest='access_info',
+                        action='store',
+                        help='access info for control host',
+                        metavar='{host:<hostip>, user:<user>, password:<pass>}')
+
+    parser.add_argument('--mongod_server', dest='mongod_server',
+                        action='store',
+                        help='provide mongoDB server IP to store results',
+                        metavar='<server ip>')
+
+    parser.add_argument('--json', dest='json',
+                        action='store',
+                        help='store results in json format file',
+                        metavar='<file>')
+
+    parser.add_argument('--tp-tool', dest='tp_tool',
+                        action='store',
+                        default='nuttcp',
+                        help='transport perf tool to use (default=nuttcp)',
+                        metavar='nuttcp|iperf')
+
+    parser.add_argument('--hypervisor', dest='hypervisors',
+                        action='append',
+                        help='hypervisor to use in the avail zone (1 per arg, up to 2 args)',
+                        metavar='name')
+
+    parser.add_argument('--inter-node-only', dest='inter_node_only',
+                        default=False,
+                        action='store_true',
+                        help='only measure inter-node')
+
+    parser.add_argument('--protocols', dest='protocols',
+                        action='store',
+                        default='TUI',
+                        help='protocols T(TCP), U(UDP), I(ICMP) - default=TUI (all)',
+                        metavar='T|U|I')
+
+    parser.add_argument('--bandwidth', dest='vm_bandwidth',
+                        action='store',
+                        default=0,
+                        help='the bandwidth limit for TCP/UDP flows in K/M/Gbps, '
+                             'e.g. 128K/32M/5G. (default=no limit) ',
+                        metavar='<bandwidth>')
+
+    parser.add_argument('--tcpbuf', dest='tcp_pkt_sizes',
+                        action='store',
+                        default=0,
+                        help='list of buffer length when transmitting over TCP in Bytes, '
+                             'e.g. --tcpbuf 8192,65536. (default=65536)',
+                        metavar='<tcp_pkt_size1,...>')
+
+    parser.add_argument('--udpbuf', dest='udp_pkt_sizes',
+                        action='store',
+                        default=0,
+                        help='list of buffer length when transmitting over UDP in Bytes, '
+                             'e.g. --udpbuf 128,2048. (default=128,1024,8192)',
+                        metavar='<udp_pkt_size1,...>')
+
+    parser.add_argument('--no-env', dest='no_env',
+                        default=False,
+                        action='store_true',
+                        help='do not read env variables')
+
+    parser.add_argument('-d', '--debug', dest='debug',
+                        default=False,
+                        action='store_true',
+                        help='debug flag (very verbose)')
+
+    parser.add_argument('-v', '--version', dest='version',
+                        default=False,
+                        action='store_true',
+                        help='print version of this script and exit')
+
+    parser.add_argument('--stop-on-error', dest='stop_on_error',
+                        default=False,
+                        action='store_true',
+                        help='Stop and keep everything as-is on error (must cleanup manually)')
+
+    (opts, args) = parser.parse_known_args()
+
+    default_cfg_file = get_absolute_path_for_file("cfg.default.yaml")
+
+    # read the default configuration file and possibly an override config file
+    config = configure.Configuration.from_file(default_cfg_file).configure()
+    if opts.config:
+        alt_config = configure.Configuration.from_file(opts.config).configure()
+        config = config.merge(alt_config)
+
+    if opts.version:
+        print('Version ' + __version__)
+        sys.exit(0)
+
+    # debug flag
+    config.debug = opts.debug
+    config.inter_node_only = opts.inter_node_only
+
+    config.hypervisors = opts.hypervisors
+
+    # time to run each perf test in seconds
+    if opts.time:
+        config.time = int(opts.time)
+    else:
+        config.time = 10
+
+    if opts.json:
+        config.json_file = opts.json
+    else:
+        config.json_file = None
+
+    ###################################################
+    # Access info for the server to collect metadata for
+    # the run.
+    ###################################################
+    if opts.access_info:
+        access_info = ast.literal_eval(opts.access_info)
+        config.access_host = access_info['host']
+        config.access_username = access_info['user']
+        config.access_password = access_info['password']
+    else:
+        config.access_host = None
+        config.access_username = None
+        config.access_password = None
+
+    ###################################################
+    # MongoDB Server connection info.
+    ###################################################
+    if opts.mongod_server:
+        config.pns_mongod_ip = opts.mongod_server
+    else:
+        config.pns_mongod_ip = None
+
+    if 'pns_mongod_port' not in config:
+        # Set MongoDB default port if not set.
+        config.pns_mongod_port = 27017
+
+    # the bandwidth limit for VMs
+    if opts.vm_bandwidth:
+        opts.vm_bandwidth = opts.vm_bandwidth.upper().strip()
+        ex_unit = 'KMG'.find(opts.vm_bandwidth[-1])
+        try:
+            if ex_unit == -1:
+                raise ValueError
+            val = int(opts.vm_bandwidth[0:-1])
+        except ValueError:
+            print 'Invalid --bandwidth parameter. A valid input must '\
+                  'specify only one unit (K|M|G).'
+            sys.exit(1)
+        config.vm_bandwidth = int(val * (10 ** (ex_unit * 3)))
+
+    # the pkt size for TCP and UDP
+    if opts.tcp_pkt_sizes:
+        try:
+            config.tcp_pkt_sizes = opts.tcp_pkt_sizes.split(',')
+            for i in xrange(len(config.tcp_pkt_sizes)):
+                config.tcp_pkt_sizes[i] = int(config.tcp_pkt_sizes[i])
+        except ValueError:
+            print 'Invalid --tcpbuf parameter. A valid input must be '\
+                  'integers seperated by comma.'
+            sys.exit(1)
+
+    if opts.udp_pkt_sizes:
+        try:
+            config.udp_pkt_sizes = opts.udp_pkt_sizes.split(',')
+            for i in xrange(len(config.udp_pkt_sizes)):
+                config.udp_pkt_sizes[i] = int(config.udp_pkt_sizes[i])
+        except ValueError:
+            print 'Invalid --udpbuf parameter. A valid input must be '\
+                  'integers seperated by comma.'
+            sys.exit(1)
+
+    #####################################################
+    # Set Ganglia server ip and port if the monitoring (-m)
+    # option is enabled.
+    #####################################################
+    config.gmond_svr_ip = None
+    config.gmond_svr_port = None
+    if opts.monitor:
+        # Add the default gmond port if not present
+        if ':' not in opts.monitor:
+            opts.monitor += ':8649'
+
+        mobj = re.match(r'(\d+\.\d+\.\d+\.\d+):(\d+)', opts.monitor)
+        if mobj:
+            config.gmond_svr_ip = mobj.group(1)
+            config.gmond_svr_port = mobj.group(2)
+            print "Ganglia monitoring enabled (%s:%s)" % \
+                  (config.gmond_svr_ip, config.gmond_svr_port)
+            config.time = 30
+
+        else:
+            print 'Invalid --monitor syntax: ' + opts.monitor
+
+    ###################################################
+    # Once we parse the config files, normalize
+    # the paths so that all paths are absolute paths.
+    ###################################################
+    normalize_paths(config)
+
+    # first chmod the local private key since git does not keep the permission
+    # as this is required by ssh/scp
+    os.chmod(config.private_key_file, stat.S_IRUSR | stat.S_IWUSR)
+
+    # Check the tp-tool name
+    config.protocols = opts.protocols.upper()
+    if 'T' in config.protocols or 'U' in config.protocols:
+        if opts.tp_tool.lower() == 'nuttcp':
+            config.tp_tool = nuttcp_tool.NuttcpTool
+        elif opts.tp_tool.lower() == 'iperf':
+            config.tp_tool = iperf_tool.IperfTool
+        else:
+            print 'Invalid transport tool: ' + opts.tp_tool
+            sys.exit(1)
+    else:
+        config.tp_tool = None
+
+    # 3 forms are accepted:
+    # --host 1.1.1.1
+    # --host root@1.1.1.1
+    # --host root@1.1.1.1:eth0
+    # A list of 0 to 2 lists where each nested list is
+    # a list of 1 to 3 elements. e.g.:
+    # [['ubuntu','1.1.1.1'],['root', 2.2.2.2]]
+    # [['ubuntu','1.1.1.1', 'eth0'],['root', 2.2.2.2]]
+    # when not provided the default user is 'root'
+    if opts.hosts:
+        native_hosts = []
+        for host in opts.hosts:
+            # split on '@' first
+            elem_list = host.split("@")
+            if len(elem_list) == 1:
+                elem_list.insert(0, 'root')
+            # split out the if name if present
+            # ['root':'1.1.1.1:eth0'] becomes ['root':'1.1.1.1', 'eth0']
+            if ':' in elem_list[1]:
+                elem_list.extend(elem_list.pop().split(':'))
+            if not is_ipv4(elem_list[1]):
+                print 'Invalid IPv4 address ' + elem_list[1]
+                sys.exit(1)
+            native_hosts.append(elem_list)
+        test_native_tp(native_hosts)
+
+    # Add the external host info to a list
+    # if username is not given assume root as user
+    if opts.ext_host:
+        elem_list = opts.ext_host.split("@")
+        if len(elem_list) == 1:
+            elem_list.insert(0, 'root')
+        if not is_ipv4(elem_list[1]):
+            print 'Invalid IPv4 address ' + elem_list[1]
+            sys.exit(1)
+        ext_host_list = elem_list[:]
+
+    cred = credentials.Credentials(opts.rc, opts.pwd, opts.no_env)
+
+    # replace all command line arguments (after the prog name) with
+    # those args that have not been parsed by this parser so that the
+    # unit test parser is not bothered by local arguments
+    sys.argv[1:] = args
+
+    if cred.rc_auth_url:
+        if opts.debug:
+            print 'Using ' + cred.rc_auth_url
+        rescol.add_property('auth_url', cred.rc_auth_url)
+        vmtp = VmtpTest()
+        vmtp.run()
+
+    if config.json_file:
+        rescol.save(config.json_file)
+
+    if config.pns_mongod_ip:
+        rescol.save_to_db(config)
diff --git a/vmtp/__init__.py b/vmtp/__init__.py
new file mode 100644
index 0000000..9a03374
--- /dev/null
+++ b/vmtp/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+import pbr.version
+
+
+__version__ = pbr.version.VersionInfo(
+    'vmtp').version_string()
diff --git a/vmtp/tests/__init__.py b/vmtp/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/vmtp/tests/base.py b/vmtp/tests/base.py
new file mode 100644
index 0000000..1c30cdb
--- /dev/null
+++ b/vmtp/tests/base.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2010-2011 OpenStack Foundation
+# 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.
+
+from oslotest import base
+
+
+class TestCase(base.BaseTestCase):
+
+    """Test case base class for all unit tests."""
diff --git a/vmtp/tests/test_vmtp.py b/vmtp/tests/test_vmtp.py
new file mode 100644
index 0000000..e166c10
--- /dev/null
+++ b/vmtp/tests/test_vmtp.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+"""
+test_vmtp
+----------------------------------
+
+Tests for `vmtp` module.
+"""
+
+from vmtp.tests import base
+
+
+class TestVmtp(base.TestCase):
+
+    def test_something(self):
+        pass