Merge "Make tenant_network_mask_bits default setting consistent"
diff --git a/.testr.conf b/.testr.conf
index a0262d8..9a72d29 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,3 +2,4 @@
 test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
 test_id_option=--load-list $IDFILE
 test_list_option=--list
+group_regex=([^\.]*\.)*
diff --git a/HACKING.rst b/HACKING.rst
index d69f935..1eb2d4f 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,6 +1,25 @@
 Tempest Coding Guide
 ====================
 
+- Step 1: Read the OpenStack Style Commandments
+  https://github.com/openstack-dev/hacking/blob/master/HACKING.rst
+- Step 2: Read on
+
+Tempest Specific Commandments
+------------------------------
+
+[T101] If a test is broken because of a bug it is appropriate to skip the test until
+bug has been fixed. However, the skip message should be formatted so that
+Tempest's skip tracking tool can watch the bug status. The skip message should
+contain the string 'Bug' immediately followed by a space. Then the bug number
+should be included in the message '#' in front of the number.
+
+Example::
+
+  @testtools.skip("Skipped until the Bug #980688 is resolved")
+
+- [T102] Cannot import OpenStack python clients in tempest/api tests
+- [T103] tempest/tests is deprecated
 
 Test Data/Configuration
 -----------------------
@@ -10,157 +29,10 @@
 - Use configuration files for values that will vary by environment
 
 
-General
--------
-- Put two newlines between top-level code (funcs, classes, etc)
-- Put one newline between methods in classes and anywhere else
-- Long lines should be wrapped in parentheses
-  in preference to using a backslash for line continuation.
-- Do not write "except:", use "except Exception:" at the very least
-- Include your name with TODOs as in "#TODO(termie)"
-- Do not name anything the same name as a built-in or reserved word Example::
-
-    def list():
-        return [1, 2, 3]
-
-    mylist = list() # BAD, shadows `list` built-in
-
-    class Foo(object):
-        def list(self):
-            return [1, 2, 3]
-
-    mylist = Foo().list() # OKAY, does not shadow built-in
-
-Imports
--------
-- Do not import objects, only modules (*)
-- Do not import more than one module per line (*)
-- Do not make relative imports
-- Order your imports by the full module path
-- Organize your imports according to the following template
-
-Example::
-
-  # vim: tabstop=4 shiftwidth=4 softtabstop=4
-  {{stdlib imports in human alphabetical order}}
-  \n
-  {{third-party lib imports in human alphabetical order}}
-  \n
-  {{tempest imports in human alphabetical order}}
-  \n
-  \n
-  {{begin your code}}
-
-
-Human Alphabetical Order Examples
----------------------------------
-Example::
-
-  import httplib
-  import logging
-  import random
-  import StringIO
-  import testtools
-  import time
-
-  import eventlet
-  import webob.exc
-
-  import tempest.config
-  from tempest.services.compute.json.limits_client import LimitsClientJSON
-  from tempest.services.compute.xml.limits_client import LimitsClientXML
-  from tempest.services.volume.volumes_client import VolumesClientJSON
-  import tempest.test
-
-
-Docstrings
-----------
-Example::
-
-  """A one line docstring looks like this and ends in a period."""
-
-
-  """A multi line docstring has a one-line summary, less than 80 characters.
-
-  Then a new paragraph after a newline that explains in more detail any
-  general information about the function, class or method. Example usages
-  are also great to have here if it is a complex class for function.
-
-  When writing the docstring for a class, an extra line should be placed
-  after the closing quotations. For more in-depth explanations for these
-  decisions see http://www.python.org/dev/peps/pep-0257/
-
-  If you are going to describe parameters and return values, use Sphinx, the
-  appropriate syntax is as follows.
-
-  :param foo: the foo parameter
-  :param bar: the bar parameter
-  :returns: return_type -- description of the return value
-  :returns: description of the return value
-  :raises: AttributeError, KeyError
-  """
-
-
-Dictionaries/Lists
-------------------
-If a dictionary (dict) or list object is longer than 80 characters, its items
-should be split with newlines. Embedded iterables should have their items
-indented. Additionally, the last item in the dictionary should have a trailing
-comma. This increases readability and simplifies future diffs.
-
-Example::
-
-  my_dictionary = {
-      "image": {
-          "name": "Just a Snapshot",
-          "size": 2749573,
-          "properties": {
-               "user_id": 12,
-               "arch": "x86_64",
-          },
-          "things": [
-              "thing_one",
-              "thing_two",
-          ],
-          "status": "ACTIVE",
-      },
-  }
-
-
-Calling Methods
----------------
-Calls to methods 80 characters or longer should format each argument with
-newlines. This is not a requirement, but a guideline::
-
-    unnecessarily_long_function_name('string one',
-                                     'string two',
-                                     kwarg1=constants.ACTIVE,
-                                     kwarg2=['a', 'b', 'c'])
-
-
-Rather than constructing parameters inline, it is better to break things up::
-
-    list_of_strings = [
-        'what_a_long_string',
-        'not as long',
-    ]
-
-    dict_of_numbers = {
-        'one': 1,
-        'two': 2,
-        'twenty four': 24,
-    }
-
-    object_one.call_a_method('string three',
-                             'string four',
-                             kwarg1=list_of_strings,
-                             kwarg2=dict_of_numbers)
-
-
 Exception Handling
 ------------------
 According to the ``The Zen of Python`` the
- ``Errors should never pass silently.``
+``Errors should never pass silently.``
 Tempest usually runs in special environment (jenkins gate jobs), in every
 error or failure situation we should provide as much error related
 information as possible, because we usually do not have the chance to
@@ -185,6 +57,10 @@
 exception at least logged.  When the exception is logged you usually need
 to ``raise`` the same or a different exception anyway.
 
+Use of ``self.addCleanup`` is often a good way to avoid having to catch
+exceptions and still ensure resources are correctly cleaned up if the
+test fails part way through.
+
 Use the ``self.assert*`` methods provided by the unit test framework
  the signal failures early.
 
@@ -202,72 +78,10 @@
 This and the service logs are your only guide to find the root cause of flaky
 issue.
 
-
-Test Skips
+Guidelines
 ----------
-If a test is broken because of a bug it is appropriate to skip the test until
-bug has been fixed. However, the skip message should be formatted so that
-Tempest's skip tracking tool can watch the bug status. The skip message should
-contain the string 'Bug' immediately followed by a space. Then the bug number
-should be included in the message '#' in front of the number.
-
-Example::
-
-  @testtools.skip("Skipped until the Bug #980688 is resolved")
-
-
-openstack-common
-----------------
-
-A number of modules from openstack-common are imported into the project.
-
-These modules are "incubating" in openstack-common and are kept in sync
-with the help of openstack-common's update.py script. See:
-
-  http://wiki.openstack.org/CommonLibrary#Incubation
-
-The copy of the code should never be directly modified here. Please
-always update openstack-common first and then run the script to copy
-the changes across.
-
-
-OpenStack Trademark
--------------------
-
-OpenStack is a registered trademark of the OpenStack Foundation, and uses the
-following capitalization:
-
-   OpenStack
-
-
-Commit Messages
----------------
-Using a common format for commit messages will help keep our git history
-readable. Follow these guidelines:
-
-  First, provide a brief summary (it is recommended to keep the commit title
-  under 50 chars).
-
-  The first line of the commit message should provide an accurate
-  description of the change, not just a reference to a bug or
-  blueprint. It must be followed by a single blank line.
-
-  If the change relates to a specific driver (libvirt, xenapi, qpid, etc...),
-  begin the first line of the commit message with the driver name, lowercased,
-  followed by a colon.
-
-  Following your brief summary, provide a more detailed description of
-  the patch, manually wrapping the text at 72 characters. This
-  description should provide enough detail that one does not have to
-  refer to external resources to determine its high-level functionality.
-
-  Once you use 'git review', two lines will be appended to the commit
-  message: a blank line followed by a 'Change-Id'. This is important
-  to correlate this commit with a specific review in Gerrit, and it
-  should not be modified.
-
-For further information on constructing high quality commit messages,
-and how to split up commits into a series of changes, consult the
-project wiki:
-
-   http://wiki.openstack.org/GitCommitMessages
+- Do not submit changesets with only testcases which are skipped as
+  they will not be merged.
+- Consistently check the status code of responses in testcases. The
+  earlier a problem is detected the easier it is to debug, especially
+  where there is complicated setup required.
diff --git a/README.rst b/README.rst
index 992d19b..f18628a 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,3 @@
-::
-
 Tempest - The OpenStack Integration Test Suite
 ==============================================
 
@@ -37,10 +35,12 @@
 Tempest is not tied to any single test runner, but Nose been the most commonly
 used tool. After setting up your configuration file, you can execute
 the set of Tempest tests by using ``nosetests`` ::
+
     $> nosetests tempest
 
 To run one single test  ::
-    $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py:
+
+    $> nosetests -sv tempest.api.compute.servers.test_server_actions.py:
        ServerActionsTestJSON.test_rebuild_nonexistent_server
 
 Configuration
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 12aa399..a73e8a0 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -97,6 +97,9 @@
 # Number of seconds to wait for output from ssh channel
 ssh_channel_timeout = 60
 
+# Dose the SSH uses Floating IP?
+use_floatingip_for_ssh = True
+
 # The type of endpoint for a Compute API service. Unless you have a
 # custom Keystone service catalog implementation, you probably want to leave
 # this value as "compute"
@@ -206,8 +209,6 @@
 # for each tenant to have their own router.
 public_router_id = {$PUBLIC_ROUTER_ID}
 
-# Whether or not neutron is expected to be available
-neutron_available = false
 
 [volume]
 # This section contains the configuration options used when executing tests
@@ -305,9 +306,6 @@
 # tests spawn full VMs, which could be slow if the test is already in a VM.
 build_timeout = 300
 
-# Whether or not Heat is expected to be available
-heat_available = false
-
 # Instance type for tests. Needs to be big enough for a
 # full OS plus the test workload
 instance_type = m1.micro
@@ -336,8 +334,26 @@
 # ssh username for the image file
 ssh_user = cirros
 
+# specifies how many resources to request at once. Used for large operations
+# testing."
+large_ops_number = 0
+
 [cli]
 # Enable cli tests
 enabled = True
 # directory where python client binaries are located
 cli_dir = /usr/local/bin
+
+[service_available]
+# Whether or not cinder is expected to be available
+cinder = True
+# Whether or not neutron is expected to be available
+neutron = false
+# Whether or not glance is expected to be available
+glance = True
+# Whether or not swift is expected to be available
+swift = True
+# Whether or not nova is expected to be available
+nova = True
+# Whether or not Heat is expected to be available
+heat = false
diff --git a/run_tests.sh b/run_tests.sh
index 366564e..a645b22 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,6 +11,7 @@
   echo "  -u, --update             Update the virtual environment with any newer package versions"
   echo "  -s, --smoke              Only run smoke tests"
   echo "  -w, --whitebox           Only run whitebox tests"
+  echo "  -t, --with-testr         Run using testr instead of nose"
   echo "  -c, --nova-coverage      Enable Nova coverage collection"
   echo "  -C, --config             Config file location"
   echo "  -p, --pep8               Just run pep8"
@@ -26,6 +27,7 @@
 just_pep8=0
 venv=.venv
 with_venv=tools/with_venv.sh
+with_testr=0
 always_venv=0
 never_venv=0
 no_site_packages=0
@@ -37,7 +39,7 @@
 logging=0
 logging_config=etc/logging.conf
 
-if ! options=$(getopt -o VNnfuswcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
+if ! options=$(getopt -o VNnfuswtcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,with-testr,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
 then
     # parse error
     usage
@@ -60,6 +62,7 @@
     -p|--pep8) let just_pep8=1;;
     -s|--smoke) noseargs="$noseargs --attr=type=smoke";;
     -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
+    -t|--with-testr) with_testr=1;;
     -S|--stdout) noseargs="$noseargs -s";;
     -l|--logging) logging=1;;
     -L|--logging-config) logging_config=$2; shift;;
@@ -105,8 +108,20 @@
   noseargs="$noseargs tempest"
 fi
 
+function testr_init {
+  if [ ! -d .testrepository ]; then
+      ${wrapper} testr init
+  fi
+}
+
 function run_tests {
-  ${wrapper} $NOSETESTS
+  if [ $with_testr -eq 1 ]; then
+      testr_init
+      ${wrapper} find . -type f -name "*.pyc" -delete
+      ${wrapper} testr run --parallel --subunit $noseargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+  else
+      ${wrapper} $NOSETESTS
+  fi
 }
 
 function run_pep8 {
diff --git a/tempest/README.rst b/tempest/README.rst
index 8f07a07..33021c8 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -1,6 +1,6 @@
-============
+============================
 Tempest Field Guide Overview
-============
+============================
 
 Tempest is designed to be useful for a large number of different
 environments. This includes being useful for gating commits to
@@ -26,7 +26,7 @@
 
 
 api
-------------
+---
 
 API tests are validation tests for the OpenStack API. They should not
 use the existing python clients for OpenStack, but should instead use
@@ -41,7 +41,7 @@
 
 
 cli
-------------
+---
 
 CLI tests use the openstack CLI to interact with the OpenStack
 cloud. CLI testing in unit tests is somewhat difficult because unlike
@@ -51,7 +51,7 @@
 
 
 scenario
-------------
+--------
 
 Scenario tests are complex "through path" tests for OpenStack
 functionality. They are typically a series of steps where complicated
@@ -61,7 +61,7 @@
 
 
 stress
------------
+------
 
 Stress tests are designed to stress an OpenStack environment by
 running a high workload against it and seeing what breaks. Tools may
@@ -72,7 +72,7 @@
 
 
 thirdparty
-------------
+----------
 
 Many openstack components include 3rdparty API support. It is
 completely legitimate for Tempest to include tests of 3rdparty APIs,
@@ -81,7 +81,7 @@
 
 
 whitebox
-----------
+--------
 
 Whitebox tests are tests which require access to the database of the
 target OpenStack machine to verify internal state after operations
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 2eaf3b0..8b96370 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -56,7 +56,7 @@
 
     CONF = config.TempestConfig()
 
-    @testtools.skipIf(CONF.network.neutron_available, "This feature is not" +
+    @testtools.skipIf(CONF.service_available.neutron, "This feature is not" +
                       "implemented by Neutron. See bug: #1194569")
     @attr(type='gate')
     def test_list_fixed_ip_details(self):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index abc5899..8ba074e 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -22,7 +22,6 @@
 from tempest.common import log as logging
 from tempest.common.utils.data_utils import parse_image_id
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 import tempest.test
 
 
@@ -36,6 +35,9 @@
 
     @classmethod
     def setUpClass(cls):
+        if not cls.config.service_available.nova:
+            skip_msg = ("%s skipped as nova is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         cls.isolated_creds = []
 
         if cls.config.compute.allow_tenant_isolation:
@@ -79,89 +81,6 @@
         cls.servers_client_v3_auth = os.servers_client_v3_auth
 
     @classmethod
-    def _get_identity_admin_client(cls):
-        """
-        Returns an instance of the Identity Admin API client
-        """
-        os = clients.AdminManager(interface=cls._interface)
-        admin_client = os.identity_client
-        return admin_client
-
-    @classmethod
-    def _get_client_args(cls):
-
-        return (
-            cls.config,
-            cls.config.identity.admin_username,
-            cls.config.identity.admin_password,
-            cls.config.identity.uri
-        )
-
-    @classmethod
-    def _get_isolated_creds(cls):
-        """
-        Creates a new set of user/tenant/password credentials for a
-        **regular** user of the Compute API so that a test case can
-        operate in an isolated tenant container.
-        """
-        admin_client = cls._get_identity_admin_client()
-        password = "pass"
-
-        while True:
-            try:
-                rand_name_root = rand_name(cls.__name__)
-                if cls.isolated_creds:
-                # Main user already created. Create the alt one...
-                    rand_name_root += '-alt'
-                tenant_name = rand_name_root + "-tenant"
-                tenant_desc = tenant_name + "-desc"
-
-                resp, tenant = admin_client.create_tenant(
-                    name=tenant_name, description=tenant_desc)
-                break
-            except exceptions.Duplicate:
-                if cls.config.compute.allow_tenant_reuse:
-                    tenant = admin_client.get_tenant_by_name(tenant_name)
-                    LOG.info('Re-using existing tenant %s', tenant)
-                    break
-
-        while True:
-            try:
-                rand_name_root = rand_name(cls.__name__)
-                if cls.isolated_creds:
-                # Main user already created. Create the alt one...
-                    rand_name_root += '-alt'
-                username = rand_name_root + "-user"
-                email = rand_name_root + "@example.com"
-                resp, user = admin_client.create_user(username,
-                                                      password,
-                                                      tenant['id'],
-                                                      email)
-                break
-            except exceptions.Duplicate:
-                if cls.config.compute.allow_tenant_reuse:
-                    user = admin_client.get_user_by_username(tenant['id'],
-                                                             username)
-                    LOG.info('Re-using existing user %s', user)
-                    break
-        # Store the complete creds (including UUID ids...) for later
-        # but return just the username, tenant_name, password tuple
-        # that the various clients will use.
-        cls.isolated_creds.append((user, tenant))
-
-        return username, tenant_name, password
-
-    @classmethod
-    def clear_isolated_creds(cls):
-        if not cls.isolated_creds:
-            return
-        admin_client = cls._get_identity_admin_client()
-
-        for user, tenant in cls.isolated_creds:
-            admin_client.delete_user(user['id'])
-            admin_client.delete_tenant(tenant['id'])
-
-    @classmethod
     def clear_servers(cls):
         for server in cls.servers:
             try:
@@ -189,7 +108,7 @@
     def tearDownClass(cls):
         cls.clear_images()
         cls.clear_servers()
-        cls.clear_isolated_creds()
+        cls._clear_isolated_creds()
 
     @classmethod
     def create_server(cls, **kwargs):
@@ -263,10 +182,16 @@
         admin_username = cls.config.compute_admin.username
         admin_password = cls.config.compute_admin.password
         admin_tenant = cls.config.compute_admin.tenant_name
-
         if not (admin_username and admin_password and admin_tenant):
             msg = ("Missing Compute Admin API credentials "
                    "in configuration.")
             raise cls.skipException(msg)
-
-        cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+        if cls.config.compute.allow_tenant_isolation:
+            creds = cls._get_isolated_creds(admin=True)
+            admin_username, admin_tenant_name, admin_password = creds
+            cls.os_adm = clients.Manager(username=admin_username,
+                                         password=admin_password,
+                                         tenant_name=admin_tenant_name,
+                                         interface=cls._interface)
+        else:
+            cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 7b8e1cc..52239cd 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -27,6 +27,10 @@
     @classmethod
     def setUpClass(cls):
         super(ImagesMetadataTestJSON, cls).setUpClass()
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
         cls.servers_client = cls.servers_client
         cls.client = cls.images_client
 
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index a74bb68..95ea820 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -30,6 +30,9 @@
     @classmethod
     def setUpClass(cls):
         super(ImagesTestJSON, cls).setUpClass()
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         cls.client = cls.images_client
         cls.servers_client = cls.servers_client
 
@@ -89,26 +92,6 @@
                           '!@#$%^&*()', name, meta)
 
     @attr(type=['negative', 'gate'])
-    def test_create_image_when_server_is_terminating(self):
-        # Return an error when creating image of server that is terminating
-        resp, server = self.create_server(wait_until='ACTIVE')
-        self.servers_client.delete_server(server['id'])
-
-        snapshot_name = rand_name('test-snap-')
-        self.assertRaises(exceptions.Duplicate, self.client.create_image,
-                          server['id'], snapshot_name)
-
-    @attr(type=['negative', 'gate'])
-    def test_create_image_when_server_is_rebooting(self):
-        # Return error when creating an image of server that is rebooting
-        resp, server = self.create_server(wait_until='ACTIVE')
-        self.servers_client.reboot(server['id'], 'HARD')
-
-        snapshot_name = rand_name('test-snap-')
-        self.assertRaises(exceptions.Duplicate, self.client.create_image,
-                          server['id'], snapshot_name)
-
-    @attr(type=['negative', 'gate'])
     def test_create_image_specify_uuid_35_characters_or_less(self):
         # Return an error if Image ID passed is 35 characters or less
         snapshot_name = rand_name('test-snap-')
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 7740cfc..64f1854 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -40,6 +40,9 @@
     def setUpClass(cls):
         super(ImagesOneServerTestJSON, cls).setUpClass()
         cls.client = cls.images_client
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
 
         try:
             resp, cls.server = cls.create_server(wait_until='ACTIVE')
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 5c6b630..b27d710 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -31,6 +31,9 @@
     @classmethod
     def setUpClass(cls):
         super(ListImageFiltersTestJSON, cls).setUpClass()
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         cls.client = cls.images_client
         cls.image_ids = []
 
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index fddad14..c7e23b1 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -25,6 +25,9 @@
     @classmethod
     def setUpClass(cls):
         super(ListImagesTestJSON, cls).setUpClass()
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         cls.client = cls.images_client
 
     @attr(type='smoke')
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index e105121..ab100a3 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -158,7 +158,7 @@
                           self.client.create_security_group, s_name,
                           s_description)
 
-    @testtools.skipIf(config.TempestConfig().network.neutron_available,
+    @testtools.skipIf(config.TempestConfig().service_available.neutron,
                       "Neutron allows duplicate names for security groups")
     @attr(type=['negative', 'gate'])
     def test_security_group_create_with_duplicate_name(self):
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index de095c5..9f66a6c 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -24,7 +24,7 @@
 
     @classmethod
     def setUpClass(cls):
-        if not cls.config.network.neutron_available:
+        if not cls.config.service_available.neutron:
             raise cls.skipException("Neutron is required")
         super(AttachInterfacesTestJSON, cls).setUpClass()
         cls.client = cls.os.interfaces_client
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index db9bdc1..c03c43e 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -59,8 +59,9 @@
         if num_servers > 0:
             username = cls.os.username
             tenant_name = cls.os.tenant_name
-            msg = ("User/tenant %(username)s/%(tenant_name)s already have "
-                   "existing server instances. Skipping test.") % locals()
+            msg = ("User/tenant %(u)s/%(t)s already have "
+                   "existing server instances. Skipping test." %
+                   {'u': username, 't': tenant_name})
             raise cls.skipException(msg)
 
         resp, body = cls.alt_client.list_servers()
@@ -69,8 +70,9 @@
         if num_servers > 0:
             username = cls.alt_manager.username
             tenant_name = cls.alt_manager.tenant_name
-            msg = ("Alt User/tenant %(username)s/%(tenant_name)s already have "
-                   "existing server instances. Skipping test.") % locals()
+            msg = ("Alt User/tenant %(u)s/%(t)s already have "
+                   "existing server instances. Skipping test." %
+                   {'u': username, 't': tenant_name})
             raise cls.skipException(msg)
 
         # The following servers are created for use
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 304a512..8b76f7f 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -112,7 +112,7 @@
         meta = {'rebuild': 'server'}
         new_name = rand_name('server')
         file_contents = 'Test server rebuild.'
-        personality = [{'path': '/etc/rebuild.txt',
+        personality = [{'path': 'rebuild.txt',
                        'contents': base64.b64encode(file_contents)}]
         password = 'rebuildPassw0rd'
         resp, rebuilt_server = self.client.rebuild(self.server_id,
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 35f0fc0..2a5be8c 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -37,7 +37,7 @@
         resp, server = cls.create_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
-    @testtools.skipIf(CONF.network.neutron_available, "This feature is not " +
+    @testtools.skipIf(CONF.service_available.neutron, "This feature is not " +
                       "implemented by Neutron. See bug: #1183436")
     @attr(type='gate')
     def test_list_virtual_interfaces(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index b507e03..6571491 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -37,6 +37,9 @@
     def setUpClass(cls):
         super(AttachVolumeTestJSON, cls).setUpClass()
         cls.device = 'vdb'
+        if not cls.config.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
 
     def _detach(self, server_id, volume_id):
         self.servers_client.detach_volume(server_id, volume_id)
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index 1acc57d..363cd6a 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -28,6 +28,9 @@
     def setUpClass(cls):
         super(VolumesGetTestJSON, cls).setUpClass()
         cls.client = cls.volumes_extensions_client
+        if not cls.config.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
 
     @attr(type='smoke')
     def test_volume_create_get_delete(self):
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index d52349e..02cc4e1 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -36,6 +36,9 @@
     def setUpClass(cls):
         super(VolumesTestJSON, cls).setUpClass()
         cls.client = cls.volumes_extensions_client
+        if not cls.config.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         # Create 3 Volumes
         cls.volume_list = []
         cls.volume_id_list = []
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index de214fc..f1ef5a4 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -28,6 +28,9 @@
     def setUpClass(cls):
         super(VolumesNegativeTest, cls).setUpClass()
         cls.client = cls.volumes_extensions_client
+        if not cls.config.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
 
     @attr(type='gate')
     def test_volume_get_nonexistant_volume_id(self):
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
new file mode 100644
index 0000000..2a20493
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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 tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class UsersTestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @attr(type='smoke')
+    def test_tokens(self):
+        # Valid user's token is authenticated
+        # Create a User
+        u_name = rand_name('user-')
+        u_desc = '%s-description' % u_name
+        u_email = '%s@testmail.tm' % u_name
+        u_password = rand_name('pass-')
+        resp, user = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email)
+        self.assertTrue(resp['status'].startswith('2'))
+        self.addCleanup(self.v3_client.delete_user, user['id'])
+        # Perform Authentication
+        resp, body = self.v3_token.auth(user['id'], u_password)
+        self.assertEqual(resp['status'], '201')
+        subject_token = resp['x-subject-token']
+        # Perform GET Token
+        resp, token_details = self.v3_client.get_token(subject_token)
+        self.assertEqual(resp['status'], '200')
+        self.assertEqual(resp['x-subject-token'], subject_token)
+        self.assertEqual(token_details['user']['id'], user['id'])
+        self.assertEqual(token_details['user']['name'], u_name)
+        # Perform Delete Token
+        resp, _ = self.v3_client.delete_token(subject_token)
+        self.assertRaises(exceptions.Unauthorized, self.v3_client.get_token,
+                          subject_token)
+
+
+class UsersTestXML(UsersTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index db55509..1237ce4 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -32,6 +32,7 @@
         cls.v3_client = os.identity_v3_client
         cls.service_client = os.service_client
         cls.policy_client = os.policy_client
+        cls.v3_token = os.token_v3_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index e62d84b..e27ec13 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -30,6 +30,9 @@
     def setUpClass(cls):
         cls.os = clients.Manager()
         cls.created_images = []
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
 
     @classmethod
     def tearDownClass(cls):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 3b7f9dd..142ad7d 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -44,7 +44,7 @@
     def setUpClass(cls):
         os = clients.Manager()
         cls.network_cfg = os.config.network
-        if not cls.network_cfg.neutron_available:
+        if not cls.config.service_available.neutron:
             raise cls.skipException("Neutron support is required")
         cls.client = os.network_client
         cls.networks = []
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index bf013ec..5a1fb5a 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -26,6 +26,9 @@
 
     @classmethod
     def setUpClass(cls):
+        if not cls.config.service_available.swift:
+            skip_msg = ("%s skipped as swift is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
         cls.os = clients.Manager()
         cls.object_client = cls.os.object_client
         cls.container_client = cls.os.container_client
@@ -42,12 +45,6 @@
 
         cls.data = DataGenerator(cls.identity_admin_client)
 
-        try:
-            cls.account_client.list_account_containers()
-        except exceptions.EndpointNotFound:
-            skip_msg = "No OpenStack Object Storage API endpoint"
-            raise cls.skipException(skip_msg)
-
     @classmethod
     def delete_containers(cls, containers, container_client=None,
                           object_client=None):
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index ffa534a..a0b248c 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -31,7 +31,7 @@
 
         os = clients.OrchestrationManager()
         cls.orchestration_cfg = os.config.orchestration
-        if not cls.orchestration_cfg.heat_available:
+        if not os.config.service_available.heat:
             raise cls.skipException("Heat support is required")
         cls.build_timeout = cls.orchestration_cfg.build_timeout
         cls.build_interval = cls.orchestration_cfg.build_interval
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index fc510cb..a84f9e8 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -19,8 +19,6 @@
 
 from tempest import clients
 from tempest.common import log as logging
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 import tempest.test
 
 LOG = logging.getLogger(__name__)
@@ -34,6 +32,10 @@
     def setUpClass(cls):
         cls.isolated_creds = []
 
+        if not cls.config.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
         if cls.config.compute.allow_tenant_isolation:
             creds = cls._get_isolated_creds()
             username, tenant_name, password = creds
@@ -55,72 +57,17 @@
         cls.snapshots = []
         cls.volumes = []
 
-        skip_msg = ("%s skipped as Cinder endpoint is not available" %
-                    cls.__name__)
-        try:
-            cls.volumes_client.keystone_auth(cls.os.username,
-                                             cls.os.password,
-                                             cls.os.auth_url,
-                                             cls.volumes_client.service,
-                                             cls.os.tenant_name)
-        except exceptions.EndpointNotFound:
-            cls.clear_isolated_creds()
-            raise cls.skipException(skip_msg)
-
-    @classmethod
-    def _get_identity_admin_client(cls):
-        """
-        Returns an instance of the Identity Admin API client
-        """
-        os = clients.ComputeAdminManager()
-        return os.identity_client
-
-    @classmethod
-    def _get_isolated_creds(cls):
-        """
-        Creates a new set of user/tenant/password credentials for a
-        **regular** user of the Volume API so that a test case can
-        operate in an isolated tenant container.
-        """
-        admin_client = cls._get_identity_admin_client()
-        rand_name_root = rand_name(cls.__name__)
-        if cls.isolated_creds:
-            # Main user already created. Create the alt one...
-            rand_name_root += '-alt'
-        username = rand_name_root + "-user"
-        email = rand_name_root + "@example.com"
-        tenant_name = rand_name_root + "-tenant"
-        tenant_desc = tenant_name + "-desc"
-        password = "pass"
-
-        resp, tenant = admin_client.create_tenant(name=tenant_name,
-                                                  description=tenant_desc)
-        resp, user = admin_client.create_user(username,
-                                              password,
-                                              tenant['id'],
-                                              email)
-        # Store the complete creds (including UUID ids...) for later
-        # but return just the username, tenant_name, password tuple
-        # that the various clients will use.
-        cls.isolated_creds.append((user, tenant))
-
-        return username, tenant_name, password
-
-    @classmethod
-    def clear_isolated_creds(cls):
-        if not cls.isolated_creds:
-            return
-        admin_client = cls._get_identity_admin_client()
-
-        for user, tenant in cls.isolated_creds:
-            admin_client.delete_user(user['id'])
-            admin_client.delete_tenant(tenant['id'])
+        cls.volumes_client.keystone_auth(cls.os.username,
+                                         cls.os.password,
+                                         cls.os.auth_url,
+                                         cls.volumes_client.service,
+                                         cls.os.tenant_name)
 
     @classmethod
     def tearDownClass(cls):
         cls.clear_snapshots()
         cls.clear_volumes()
-        cls.clear_isolated_creds()
+        cls._clear_isolated_creds()
 
     @classmethod
     def create_snapshot(cls, volume_id=1, **kwargs):
@@ -201,6 +148,13 @@
             msg = ("Missing Volume Admin API credentials "
                    "in configuration.")
             raise cls.skipException(msg)
-
-        cls.os_adm = clients.AdminManager(interface=cls._interface)
+        if cls.config.compute.allow_tenant_isolation:
+            creds = cls._get_isolated_creds(admin=True)
+            admin_username, admin_tenant_name, admin_password = creds
+            cls.os_adm = clients.Manager(username=admin_username,
+                                         password=admin_password,
+                                         tenant_name=admin_tenant_name,
+                                         interface=cls._interface)
+        else:
+            cls.os_adm = clients.AdminManager(interface=cls._interface)
         cls.client = cls.os_adm.volume_types_client
diff --git a/tempest/cli/README.rst b/tempest/cli/README.rst
index 3eae492..f86adf3 100644
--- a/tempest/cli/README.rst
+++ b/tempest/cli/README.rst
@@ -12,7 +12,7 @@
 Why are these tests in tempest?
 -------------------------------
 These tests exist here because it is extremely difficult to build a
-functional enough environment in the python-*client unit tests to
+functional enough environment in the python-\*client unit tests to
 provide this kind of testing. Because we already put up a cloud in the
 gate with devstack + tempest it was decided it was better to have
 these as a side tree in tempest instead of another QA effort which
diff --git a/tempest/cli/simple_read_only/test_compute.py b/tempest/cli/simple_read_only/test_compute.py
index 561fd00..5dadbeb 100644
--- a/tempest/cli/simple_read_only/test_compute.py
+++ b/tempest/cli/simple_read_only/test_compute.py
@@ -23,7 +23,6 @@
 import tempest.cli
 from tempest.common import log as logging
 
-
 CONF = cfg.CONF
 
 
@@ -69,6 +68,8 @@
     def test_admin_credentials(self):
         self.nova('credentials')
 
+    @testtools.skipIf(CONF.service_available.neutron,
+                      "Neutron does not provide this feature")
     def test_admin_dns_domains(self):
         self.nova('dns-domains')
 
diff --git a/tempest/clients.py b/tempest/clients.py
index d7a740a..2154f8b 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,12 +75,14 @@
     EndPointClientJSON
 from tempest.services.identity.v3.json.identity_client import \
     IdentityV3ClientJSON
+from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON
 from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
 from tempest.services.identity.v3.json.service_client import \
     ServiceClientJSON
 from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
 from tempest.services.identity.v3.xml.identity_client import \
     IdentityV3ClientXML
+from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML
 from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
 from tempest.services.identity.v3.xml.service_client import \
     ServiceClientXML
@@ -239,6 +241,11 @@
     "xml": HypervisorClientXML,
 }
 
+V3_TOKEN_CLIENT = {
+    "json": V3TokenClientJSON,
+    "xml": V3TokenClientXML,
+}
+
 
 class Manager(object):
 
@@ -267,8 +274,9 @@
 
         if None in (self.username, self.password, self.tenant_name):
             msg = ("Missing required credentials. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
+                   "username: %(u)s, password: %(p)s, "
+                   "tenant_name: %(t)s" %
+                   {'u': username, 'p': password, 't': tenant_name})
             raise exceptions.InvalidConfiguration(msg)
 
         self.auth_url = self.config.identity.uri
@@ -319,6 +327,7 @@
                 TENANT_USAGES_CLIENT[interface](*client_args)
             self.policy_client = POLICY_CLIENT[interface](*client_args)
             self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
+            self.token_v3_client = V3_TOKEN_CLIENT[interface](*client_args)
 
             if client_args_v3_auth:
                 self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index cd33a22..4045430 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -125,11 +125,12 @@
                 conn.request(method, conn_url, **kwargs)
             resp = conn.getresponse()
         except socket.gaierror as e:
-            message = "Error finding address for %(url)s: %(e)s" % locals()
+            message = ("Error finding address for %(url)s: %(e)s" %
+                       {'url': url, 'e': e})
             raise exc.EndpointNotFound(message)
         except (socket.error, socket.timeout) as e:
-            endpoint = self.endpoint
-            message = "Error communicating with %(endpoint)s %(e)s" % locals()
+            message = ("Error communicating with %(endpoint)s %(e)s" %
+                       {'endpoint': self.endpoint, 'e': e})
             raise exc.TimeoutException(message)
 
         body_iter = ResponseBodyIterator(resp)
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index fd5d3d0..de2bf43 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -1,3 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    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
 import time
 
diff --git a/tempest/config.py b/tempest/config.py
index cf548f7..9c092be 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -177,6 +177,9 @@
     cfg.IntOpt('ip_version_for_ssh',
                default=4,
                help="IP version used for SSH connections."),
+    cfg.BoolOpt('use_floatingip_for_ssh',
+                default=True,
+                help="Dose the SSH uses Floating IP?"),
     cfg.StrOpt('catalog_type',
                default='compute',
                help="Catalog type of the Compute service."),
@@ -299,9 +302,6 @@
                default="",
                help="Id of the public router that provides external "
                     "connectivity"),
-    cfg.BoolOpt('neutron_available',
-                default=False,
-                help="Whether or not neutron is expected to be available"),
 ]
 
 
@@ -391,9 +391,6 @@
     cfg.IntOpt('build_timeout',
                default=300,
                help="Timeout in seconds to wait for a stack to build."),
-    cfg.BoolOpt('heat_available',
-                default=False,
-                help="Whether or not Heat is expected to be available"),
     cfg.StrOpt('instance_type',
                default='m1.micro',
                help="Instance type for tests. Needs to be big enough for a "
@@ -520,7 +517,12 @@
                help='AKI image file name'),
     cfg.StrOpt('ssh_user',
                default='cirros',
-               help='ssh username for the image file')
+               help='ssh username for the image file'),
+    cfg.IntOpt(
+        'large_ops_number',
+        default=0,
+        help="specifies how many resources to request at once. Used "
+        "for large operations testing.")
 ]
 
 
@@ -530,6 +532,37 @@
         conf.register_opt(opt, group='scenario')
 
 
+service_available_group = cfg.OptGroup(name="service_available",
+                                       title="Available OpenStack Services")
+
+ServiceAvailableGroup = [
+    cfg.BoolOpt('cinder',
+                default=True,
+                help="Whether or not cinder is expected to be available"),
+    cfg.BoolOpt('neutron',
+                default=False,
+                help="Whether or not neutron is expected to be available"),
+    cfg.BoolOpt('glance',
+                default=True,
+                help="Whether or not glance is expected to be available"),
+    cfg.BoolOpt('swift',
+                default=True,
+                help="Whether or not swift is expected to be available"),
+    cfg.BoolOpt('nova',
+                default=True,
+                help="Whether or not nova is expected to be available"),
+    cfg.BoolOpt('heat',
+                default=False,
+                help="Whether or not Heat is expected to be available"),
+]
+
+
+def register_service_available_opts(conf):
+    conf.register_group(scenario_group)
+    for opt in ServiceAvailableGroup:
+        conf.register_opt(opt, group='service_available')
+
+
 @singleton
 class TempestConfig:
     """Provides OpenStack configuration information."""
@@ -561,7 +594,7 @@
         LOG.info("Using tempest config file %s" % path)
 
         if not os.path.exists(path):
-            msg = "Config file %(path)s not found" % locals()
+            msg = "Config file %s not found" % path
             print(RuntimeError(msg), file=sys.stderr)
         else:
             config_files.append(path)
@@ -580,6 +613,7 @@
         register_compute_admin_opts(cfg.CONF)
         register_stress_opts(cfg.CONF)
         register_scenario_opts(cfg.CONF)
+        register_service_available_opts(cfg.CONF)
         self.compute = cfg.CONF.compute
         self.whitebox = cfg.CONF.whitebox
         self.identity = cfg.CONF.identity
@@ -592,6 +626,7 @@
         self.compute_admin = cfg.CONF['compute-admin']
         self.stress = cfg.CONF.stress
         self.scenario = cfg.CONF.scenario
+        self.service_available = cfg.CONF.service_available
         if not self.compute_admin.username:
             self.compute_admin.username = self.identity.admin_username
             self.compute_admin.password = self.identity.admin_password
diff --git a/tempest/manager.py b/tempest/manager.py
index 4a447f3..187e2c6 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -65,6 +65,15 @@
         self.config = tempest.config.TempestConfig()
         self.client_attr_names = []
 
+    # we do this everywhere, have it be part of the super class
+    def _validate_credentials(self, username, password, tenant_name):
+        if None in (username, password, tenant_name):
+            msg = ("Missing required credentials. "
+                   "username: %(u)s, password: %(p)s, "
+                   "tenant_name: %(t)s" %
+                   {'u': username, 'p': password, 't': tenant_name})
+            raise exceptions.InvalidConfiguration(msg)
+
 
 class FuzzClientManager(Manager):
 
@@ -103,11 +112,7 @@
         password = password or self.config.identity.password
         tenant_name = tenant_name or self.config.identity.tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index fe6fbf5..fcd5d0e 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -32,7 +32,6 @@
 from tempest.common import log as logging
 from tempest.common import ssh
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 import tempest.manager
 import tempest.test
 
@@ -76,11 +75,7 @@
         if not tenant_name:
             tenant_name = self.config.identity.tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for compute client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
@@ -131,11 +126,7 @@
         if not tenant_name:
             tenant_name = self.config.identity.admin_tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for identity client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
@@ -157,11 +148,7 @@
         password = self.config.identity.admin_password
         tenant_name = self.config.identity.admin_tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for network client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
@@ -236,7 +223,7 @@
 
     @classmethod
     def check_preconditions(cls):
-        if (cls.config.network.neutron_available):
+        if (cls.config.service_available.neutron):
             cls.enabled = True
             #verify that neutron_available is telling the truth
             try:
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
new file mode 100644
index 0000000..1f75e2f
--- /dev/null
+++ b/tempest/scenario/test_large_ops.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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 tempest.common import log as logging
+from tempest.common.utils.data_utils import rand_name
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestLargeOpsScenario(manager.OfficialClientTest):
+
+    """
+    Test large operations.
+
+    This test below:
+    * Spin up multiple instances in one nova call
+    * as a regular user
+    * TODO: same thing for cinder
+
+    """
+
+    def _wait_for_server_status(self, status):
+        for server in self.servers:
+            self.status_timeout(
+                self.compute_client.servers, server.id, status)
+
+    def _wait_for_volume_status(self, status):
+        volume_id = self.volume.id
+        self.status_timeout(
+            self.volume_client.volumes, volume_id, status)
+
+    def _image_create(self, name, fmt, path, properties={}):
+        name = rand_name('%s-' % name)
+        image_file = open(path, 'rb')
+        self.addCleanup(image_file.close)
+        params = {
+            'name': name,
+            'container_format': fmt,
+            'disk_format': fmt,
+            'is_public': 'True',
+        }
+        params.update(properties)
+        image = self.image_client.images.create(**params)
+        self.addCleanup(self.image_client.images.delete, image)
+        self.assertEqual("queued", image.status)
+        image.update(data=image_file)
+        return image.id
+
+    def glance_image_create(self):
+        aki_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.aki_img_file
+        ari_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ari_img_file
+        ami_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ami_img_file
+        LOG.debug("paths: ami: %s, ari: %s, aki: %s"
+                  % (ami_img_path, ari_img_path, aki_img_path))
+        kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path)
+        ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path)
+        properties = {
+            'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id}
+        }
+        self.image = self._image_create('scenario-ami', 'ami',
+                                        path=ami_img_path,
+                                        properties=properties)
+
+    def nova_boot(self):
+        def delete(servers):
+            [x.delete() for x in servers]
+
+        name = rand_name('scenario-server-')
+        client = self.compute_client
+        flavor_id = self.config.compute.flavor_ref
+        self.servers = client.servers.create(
+            name=name, image=self.image,
+            flavor=flavor_id,
+            min_count=self.config.scenario.large_ops_number)
+        # needed because of bug 1199788
+        self.servers = [x for x in client.servers.list() if name in x.name]
+        self.addCleanup(delete, self.servers)
+        self._wait_for_server_status('ACTIVE')
+
+    def test_large_ops_scenario(self):
+        if self.config.scenario.large_ops_number < 1:
+            return
+        self.glance_image_create()
+        self.nova_boot()
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index f21a00b..76fac82 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -85,17 +85,21 @@
         self.addCleanup(self.compute_client.security_group_rules.delete,
                         sg_rule.id)
 
-    def _ssh_to_server(self, server):
+    def _ssh_to_server(self, server_or_ip):
+        if isinstance(server_or_ip, basestring):
+            ip = server_or_ip
+        else:
+            network_name_for_ssh = self.config.compute.network_for_ssh
+            ip = server_or_ip.networks[network_name_for_ssh][0]
         username = self.config.scenario.ssh_user
-        ip = server.networks[self.config.compute.network_for_ssh][0]
         linux_client = RemoteClient(ip,
                                     username,
                                     pkey=self.keypair.private_key)
 
         return linux_client.ssh_client
 
-    def _write_timestamp(self, server):
-        ssh_client = self._ssh_to_server(server)
+    def _write_timestamp(self, server_or_ip):
+        ssh_client = self._ssh_to_server(server_or_ip)
         ssh_client.exec_command('date > /tmp/timestamp; sync')
         self.timestamp = ssh_client.exec_command('cat /tmp/timestamp')
 
@@ -110,11 +114,19 @@
         self.assertEquals(snapshot_name, snapshot_image.name)
         return image_id
 
-    def _check_timestamp(self, server):
-        ssh_client = self._ssh_to_server(server)
+    def _check_timestamp(self, server_or_ip):
+        ssh_client = self._ssh_to_server(server_or_ip)
         got_timestamp = ssh_client.exec_command('cat /tmp/timestamp')
         self.assertEqual(self.timestamp, got_timestamp)
 
+    def _create_floating_ip(self):
+        floating_ip = self.compute_client.floating_ips.create()
+        self.addCleanup(floating_ip.delete)
+        return floating_ip
+
+    def _set_floating_ip_to_server(self, server, floating_ip):
+        server.add_floating_ip(floating_ip)
+
     def test_snapshot_pattern(self):
         # prepare for booting a instance
         self._add_keypair()
@@ -122,7 +134,12 @@
 
         # boot a instance and create a timestamp file in it
         server = self._boot_image(self.config.compute.image_ref)
-        self._write_timestamp(server)
+        if self.config.compute.use_floatingip_for_ssh:
+            fip_for_server = self._create_floating_ip()
+            self._set_floating_ip_to_server(server, fip_for_server)
+            self._write_timestamp(fip_for_server.ip)
+        else:
+            self._write_timestamp(server)
 
         # snapshot the instance
         snapshot_image_id = self._create_image(server)
@@ -131,4 +148,10 @@
         server_from_snapshot = self._boot_image(snapshot_image_id)
 
         # check the existence of the timestamp file in the second instance
-        self._check_timestamp(server_from_snapshot)
+        if self.config.compute.use_floatingip_for_ssh:
+            fip_for_snapshot = self._create_floating_ip()
+            self._set_floating_ip_to_server(server_from_snapshot,
+                                            fip_for_snapshot)
+            self._check_timestamp(fip_for_snapshot.ip)
+        else:
+            self._check_timestamp(server_from_snapshot)
diff --git a/tempest/services/botoclients.py b/tempest/services/botoclients.py
index 628151a..32ec109 100644
--- a/tempest/services/botoclients.py
+++ b/tempest/services/botoclients.py
@@ -132,6 +132,7 @@
     ALLOWED_METHODS = set(('create_key_pair', 'get_key_pair',
                            'delete_key_pair', 'import_key_pair',
                            'get_all_key_pairs',
+                           'get_all_tags',
                            'create_image', 'get_image',
                            'register_image', 'deregister_image',
                            'get_all_images', 'get_image_attribute',
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index a216b55..90e64e7 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -1,3 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    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 httplib2
 import json
 
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index adbdc83..56a1a72 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -208,3 +208,61 @@
         resp, body = self.get('domains/%s' % domain_id)
         body = json.loads(body)
         return resp, body['domain']
+
+    def get_token(self, resp_token):
+        """Get token details."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = json.loads(body)
+        return resp, body['token']
+
+    def delete_token(self, resp_token):
+        """Deletes token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        creds = {
+            'auth': {
+                'identity': {
+                    'methods': ['password'],
+                    'password': {
+                        'user': {
+                            'id': user_id,
+                            'password': password
+                        }
+                    }
+                }
+            }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(creds)
+        resp, body = self.post("auth/tokens", headers=headers, body=body)
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientJSON, self).request(method, url,
+                                                      headers=headers,
+                                                      body=body)
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 708ee28..571b491 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -22,9 +22,9 @@
 from tempest.common.rest_client import RestClientXML
 from tempest.services.compute.xml.common import Document
 from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
 from tempest.services.compute.xml.common import xml_to_json
 
-
 XMLNS = "http://docs.openstack.org/identity/api/v3"
 
 
@@ -241,3 +241,65 @@
         resp, body = self.get('domains/%s' % domain_id, self.headers)
         body = self._parse_body(etree.fromstring(body))
         return resp, body
+
+    def get_token(self, resp_token):
+        """GET a Token Details."""
+        headers = {'Content-Type': 'application/xml',
+                   'Accept': 'application/xml',
+                   'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_token(self, resp_token):
+        """Delete a Given Token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        user = Element('user',
+                       id=user_id,
+                       password=password)
+        password = Element('password')
+        password.append(user)
+
+        method = Element('method')
+        method.append(Text('password'))
+        methods = Element('methods')
+        methods.append(method)
+        identity = Element('identity')
+        identity.append(methods)
+        identity.append(password)
+        auth = Element('auth')
+        auth.append(identity)
+        headers = {'Content-Type': 'application/xml'}
+        resp, body = self.post("auth/tokens", headers=headers,
+                               body=str(Document(auth)))
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientXML, self).request(method, url,
+                                                     headers=headers,
+                                                     body=body)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c4fe6b1..446a674 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -1,4 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    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 json
+
 from tempest.common.rest_client import RestClient
 
 
diff --git a/tempest/stress/actions/create_destroy_server.py b/tempest/stress/actions/create_destroy_server.py
index 44b149f..68dc148 100644
--- a/tempest/stress/actions/create_destroy_server.py
+++ b/tempest/stress/actions/create_destroy_server.py
@@ -13,22 +13,27 @@
 #    limitations under the License.
 
 from tempest.common.utils.data_utils import rand_name
+import tempest.stress.stressaction as stressaction
 
 
-def create_destroy(manager, logger):
-    image = manager.config.compute.image_ref
-    flavor = manager.config.compute.flavor_ref
-    while True:
+class CreateDestroyServerTest(stressaction.StressAction):
+
+    def setUp(self, **kwargs):
+        self.image = self.manager.config.compute.image_ref
+        self.flavor = self.manager.config.compute.flavor_ref
+
+    def run(self):
         name = rand_name("instance")
-        logger.info("creating %s" % name)
-        resp, server = manager.servers_client.create_server(
-            name, image, flavor)
+        self.logger.info("creating %s" % name)
+        resp, server = self.manager.servers_client.create_server(
+            name, self.image, self.flavor)
         server_id = server['id']
         assert(resp.status == 202)
-        manager.servers_client.wait_for_server_status(server_id, 'ACTIVE')
-        logger.info("created %s" % server_id)
-        logger.info("deleting %s" % name)
-        resp, _ = manager.servers_client.delete_server(server_id)
+        self.manager.servers_client.wait_for_server_status(server_id,
+                                                           'ACTIVE')
+        self.logger.info("created %s" % server_id)
+        self.logger.info("deleting %s" % name)
+        resp, _ = self.manager.servers_client.delete_server(server_id)
         assert(resp.status == 204)
-        manager.servers_client.wait_for_server_termination(server_id)
-        logger.info("deleted %s" % server_id)
+        self.manager.servers_client.wait_for_server_termination(server_id)
+        self.logger.info("deleted %s" % server_id)
diff --git a/tempest/stress/actions/volume_create_delete.py b/tempest/stress/actions/volume_create_delete.py
index e0c95b5..184f870 100644
--- a/tempest/stress/actions/volume_create_delete.py
+++ b/tempest/stress/actions/volume_create_delete.py
@@ -11,20 +11,23 @@
 #    limitations under the License.
 
 from tempest.common.utils.data_utils import rand_name
+import tempest.stress.stressaction as stressaction
 
 
-def create_delete(manager, logger):
-    while True:
+class CreateDeleteTest(stressaction.StressAction):
+
+    def run(self):
         name = rand_name("volume")
-        logger.info("creating %s" % name)
-        resp, volume = manager.volumes_client.create_volume(size=1,
-                                                            display_name=name)
+        self.logger.info("creating %s" % name)
+        resp, volume = self.manager.volumes_client.\
+            create_volume(size=1, display_name=name)
         assert(resp.status == 200)
-        manager.volumes_client.wait_for_volume_status(volume['id'],
-                                                      'available')
-        logger.info("created %s" % volume['id'])
-        logger.info("deleting %s" % name)
-        resp, _ = manager.volumes_client.delete_volume(volume['id'])
+        vol_id = volume['id']
+        status = 'available'
+        self.manager.volumes_client.wait_for_volume_status(vol_id, status)
+        self.logger.info("created %s" % volume['id'])
+        self.logger.info("deleting %s" % name)
+        resp, _ = self.manager.volumes_client.delete_volume(vol_id)
         assert(resp.status == 202)
-        manager.volumes_client.wait_for_resource_deletion(volume['id'])
-        logger.info("deleted %s" % volume['id'])
+        self.manager.volumes_client.wait_for_resource_deletion(vol_id)
+        self.logger.info("deleted %s" % vol_id)
diff --git a/tempest/stress/cleanup.py b/tempest/stress/cleanup.py
index 3b1c871..bfcf34f 100644
--- a/tempest/stress/cleanup.py
+++ b/tempest/stress/cleanup.py
@@ -19,10 +19,11 @@
 from tempest import clients
 
 
-def cleanup():
+def cleanup(logger):
     admin_manager = clients.AdminManager()
 
     _, body = admin_manager.servers_client.list_servers({"all_tenants": True})
+    logger.debug("Cleanup::remove %s servers" % len(body['servers']))
     for s in body['servers']:
         try:
             admin_manager.servers_client.delete_server(s['id'])
@@ -36,6 +37,7 @@
             pass
 
     _, keypairs = admin_manager.keypairs_client.list_keypairs()
+    logger.debug("Cleanup::remove %s keypairs" % len(keypairs))
     for k in keypairs:
         try:
             admin_manager.keypairs_client.delete_keypair(k['name'])
@@ -43,6 +45,7 @@
             pass
 
     _, floating_ips = admin_manager.floating_ips_client.list_floating_ips()
+    logger.debug("Cleanup::remove %s floating ips" % len(floating_ips))
     for f in floating_ips:
         try:
             admin_manager.floating_ips_client.delete_floating_ip(f['id'])
@@ -50,18 +53,43 @@
             pass
 
     _, users = admin_manager.identity_client.get_users()
+    logger.debug("Cleanup::remove %s users" % len(users))
     for user in users:
         if user['name'].startswith("stress_user"):
             admin_manager.identity_client.delete_user(user['id'])
 
     _, tenants = admin_manager.identity_client.list_tenants()
+    logger.debug("Cleanup::remove %s tenants" % len(tenants))
     for tenant in tenants:
         if tenant['name'].startswith("stress_tenant"):
             admin_manager.identity_client.delete_tenant(tenant['id'])
 
+    # We have to delete snapshots first or
+    # volume deletion may block
+
+    _, snaps = admin_manager.snapshots_client.\
+        list_snapshots({"all_tenants": True})
+    logger.debug("Cleanup::remove %s snapshots" % len(snaps))
+    for v in snaps:
+        try:
+            admin_manager.snapshots_client.\
+                wait_for_snapshot_status(v['id'], 'available')
+            admin_manager.snapshots_client.delete_snapshot(v['id'])
+        except Exception:
+            pass
+
+    for v in snaps:
+        try:
+            admin_manager.snapshots_client.wait_for_resource_deletion(v['id'])
+        except Exception:
+            pass
+
     _, vols = admin_manager.volumes_client.list_volumes({"all_tenants": True})
+    logger.debug("Cleanup::remove %s volumes" % len(vols))
     for v in vols:
         try:
+            admin_manager.volumes_client.\
+                wait_for_volume_status(v['id'], 'available')
             admin_manager.volumes_client.delete_volume(v['id'])
         except Exception:
             pass
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 51f159d..d170eb8 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -93,12 +93,12 @@
     return None
 
 
-def get_action_function(path):
-    (module_part, _, function) = path.rpartition('.')
-    return getattr(importlib.import_module(module_part), function)
+def get_action_object(path):
+    (module_part, _, obj_name) = path.rpartition('.')
+    return getattr(importlib.import_module(module_part), obj_name)
 
 
-def stress_openstack(tests, duration):
+def stress_openstack(tests, duration, max_runs=None):
     """
     Workload driver. Executes an action function against a nova-cluster.
 
@@ -116,7 +116,7 @@
             manager = admin_manager
         else:
             manager = clients.Manager()
-        for _ in xrange(test.get('threads', 1)):
+        for p_number in xrange(test.get('threads', 1)):
             if test.get('use_isolated_tenants', False):
                 username = rand_name("stress_user")
                 tenant_name = rand_name("stress_tenant")
@@ -130,18 +130,48 @@
                 manager = clients.Manager(username=username,
                                           password="pass",
                                           tenant_name=tenant_name)
-            target = get_action_function(test['action'])
-            p = multiprocessing.Process(target=target,
-                                        args=(manager, logger),
-                                        kwargs=test.get('kwargs', {}))
-            processes.append(p)
+
+            test_obj = get_action_object(test['action'])
+            test_run = test_obj(manager, logger, max_runs)
+
+            kwargs = test.get('kwargs', {})
+            test_run.setUp(**dict(kwargs.iteritems()))
+
+            logger.debug("calling Target Object %s" %
+                         test_run.__class__.__name__)
+
+            mp_manager = multiprocessing.Manager()
+            shared_statistic = mp_manager.dict()
+            shared_statistic['runs'] = 0
+            shared_statistic['fails'] = 0
+
+            p = multiprocessing.Process(target=test_run.execute,
+                                        args=(shared_statistic,))
+
+            process = {'process': p,
+                       'p_number': p_number,
+                       'action': test['action'],
+                       'statistic': shared_statistic}
+
+            processes.append(process)
             p.start()
     end_time = time.time() + duration
     had_errors = False
     while True:
-        remaining = end_time - time.time()
-        if remaining <= 0:
-            break
+        if max_runs is None:
+            remaining = end_time - time.time()
+            if remaining <= 0:
+                break
+        else:
+            remaining = log_check_interval
+            all_proc_term = True
+            for process in processes:
+                if process['process'].is_alive():
+                    all_proc_term = False
+                    break
+            if all_proc_term:
+                break
+
         time.sleep(min(remaining, log_check_interval))
         if not logfiles:
             continue
@@ -149,8 +179,34 @@
         if errors:
             had_errors = True
             break
-    for p in processes:
-        p.terminate()
+
+    for process in processes:
+        if process['process'].is_alive():
+            process['process'].terminate()
+        process['process'].join()
+
+    sum_fails = 0
+    sum_runs = 0
+
+    logger.info("Statistics (per process):")
+    for process in processes:
+        if process['statistic']['fails'] > 0:
+            had_errors = True
+        sum_runs += process['statistic']['runs']
+        sum_fails += process['statistic']['fails']
+        logger.info(" Process %d (%s): Run %d actions (%d failed)" %
+                    (process['p_number'],
+                     process['action'],
+                     process['statistic']['runs'],
+                     process['statistic']['fails']))
+    logger.info("Summary:")
+    logger.info("Run %d actions (%d failed)" %
+                (sum_runs, sum_fails))
+
     if not had_errors:
         logger.info("cleaning up")
-        cleanup.cleanup()
+        cleanup.cleanup(logger)
+    if had_errors:
+        return 1
+    else:
+        return 0
diff --git a/tempest/stress/etc/sample-test.json b/tempest/stress/etc/sample-test.json
index 5a0189c..494c823 100644
--- a/tempest/stress/etc/sample-test.json
+++ b/tempest/stress/etc/sample-test.json
@@ -1,4 +1,4 @@
-[{"action": "tempest.stress.actions.create_destroy_server.create_destroy",
+[{"action": "tempest.stress.actions.create_destroy_server.CreateDestroyServerTest",
   "threads": 8,
   "use_admin": false,
   "use_isolated_tenants": false,
diff --git a/tempest/stress/etc/stress-tox-job.json b/tempest/stress/etc/stress-tox-job.json
new file mode 100644
index 0000000..159794b
--- /dev/null
+++ b/tempest/stress/etc/stress-tox-job.json
@@ -0,0 +1,13 @@
+[{"action": "tempest.stress.actions.create_destroy_server.CreateDestroyServerTest",
+  "threads": 8,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "kwargs": {}
+  },
+  {"action": "tempest.stress.actions.volume_create_delete.CreateDeleteTest",
+  "threads": 4,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "kwargs": {}
+  }
+]
diff --git a/tempest/stress/etc/volume-create-delete-test.json b/tempest/stress/etc/volume-create-delete-test.json
index ed0aaeb..6325bdc 100644
--- a/tempest/stress/etc/volume-create-delete-test.json
+++ b/tempest/stress/etc/volume-create-delete-test.json
@@ -1,4 +1,4 @@
-[{"action": "tempest.stress.actions.volume_create_delete.create_delete",
+[{"action": "tempest.stress.actions.volume_create_delete.CreateDeleteTest",
   "threads": 4,
   "use_admin": false,
   "use_isolated_tenants": false,
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index ef0ec8e..106049d 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -18,17 +18,35 @@
 
 import argparse
 import json
-
-from tempest.stress import driver
+import sys
 
 
 def main(ns):
+    #NOTE(kodererm): moved import to make "-h" possible without OpenStack
+    from tempest.stress import driver
+    result = 0
     tests = json.load(open(ns.tests, 'r'))
-    driver.stress_openstack(tests, ns.duration)
+    if ns.serial:
+        for test in tests:
+            step_result = driver.stress_openstack([test],
+                                                  ns.duration,
+                                                  ns.number)
+            #NOTE(kodererm): we just save the last result code
+            if (step_result != 0):
+                result = step_result
+    else:
+        driver.stress_openstack(tests, ns.duration, ns.number)
+    return result
 
 
 parser = argparse.ArgumentParser(description='Run stress tests. ')
 parser.add_argument('-d', '--duration', default=300, type=int,
-                    help="Duration of test.")
+                    help="Duration of test in secs.")
+parser.add_argument('-s', '--serial', action='store_true',
+                    help="Trigger running tests serially.")
+parser.add_argument('-n', '--number', type=int,
+                    help="How often an action is executed for each process.")
 parser.add_argument('tests', help="Name of the file with test description.")
-main(parser.parse_args())
+
+if __name__ == "__main__":
+    sys.exit(main(parser.parse_args()))
diff --git a/tempest/stress/stressaction.py b/tempest/stress/stressaction.py
new file mode 100644
index 0000000..77ddd1c
--- /dev/null
+++ b/tempest/stress/stressaction.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# (c) Copyright 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.
+
+import signal
+import sys
+
+
+class StressAction(object):
+
+    def __init__(self, manager, logger, max_runs=None):
+        self.manager = manager
+        self.logger = logger
+        self.max_runs = max_runs
+
+    def _shutdown_handler(self, signal, frame):
+        self.tearDown()
+        sys.exit(0)
+
+    def setUp(self, **kwargs):
+        """This method is called before the run method
+        to help the test initiatlize any structures.
+        kwargs contains arguments passed in from the
+        configuration json file.
+
+        setUp doesn't count against the time duration.
+        """
+        self.logger.debug("setUp")
+
+    def tearDown(self):
+        """This method is called to do any cleanup
+        after the test is complete.
+        """
+        self.logger.debug("tearDown")
+
+    def execute(self, shared_statistic):
+        """This is the main execution entry point called
+        by the driver.   We register a signal handler to
+        allow us to gracefull tearDown, and then exit.
+        We also keep track of how many runs we do.
+        """
+        signal.signal(signal.SIGHUP, self._shutdown_handler)
+        signal.signal(signal.SIGTERM, self._shutdown_handler)
+
+        while self.max_runs is None or (shared_statistic['runs'] <
+                                        self.max_runs):
+            try:
+                self.run()
+            except Exception:
+                shared_statistic['fails'] += 1
+                self.logger.exception("Failure in run")
+            finally:
+                shared_statistic['runs'] += 1
+
+    def run(self):
+        """This method is where the stress test code runs."""
+        raise NotImplemented()
diff --git a/tempest/stress/tools/cleanup.py b/tempest/stress/tools/cleanup.py
index 7139d6c..b6a26cd 100755
--- a/tempest/stress/tools/cleanup.py
+++ b/tempest/stress/tools/cleanup.py
@@ -14,7 +14,15 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
+import logging
+
 from tempest.stress import cleanup
 
+_console = logging.StreamHandler()
+_console.setLevel(logging.DEBUG)
+# add the handler to the root logger
+logger = logging.getLogger('tempest.stress.cleanup')
+logger.addHandler(logging.StreamHandler())
+logger.setLevel(logging.DEBUG)
 
-cleanup.cleanup()
+cleanup.cleanup(logger)
diff --git a/tempest/test.py b/tempest/test.py
index 6be37be..d151c84 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -21,8 +21,11 @@
 import testresources
 import testtools
 
+from tempest import clients
 from tempest.common import log as logging
+from tempest.common.utils.data_utils import rand_name
 from tempest import config
+from tempest import exceptions
 from tempest import manager
 
 LOG = logging.getLogger(__name__)
@@ -65,6 +68,104 @@
         if hasattr(super(BaseTestCase, cls), 'setUpClass'):
             super(BaseTestCase, cls).setUpClass()
 
+    @classmethod
+    def _get_identity_admin_client(cls):
+        """
+        Returns an instance of the Identity Admin API client
+        """
+        os = clients.AdminManager(interface=cls._interface)
+        admin_client = os.identity_client
+        return admin_client
+
+    @classmethod
+    def _get_client_args(cls):
+
+        return (
+            cls.config,
+            cls.config.identity.admin_username,
+            cls.config.identity.admin_password,
+            cls.config.identity.uri
+        )
+
+    @classmethod
+    def _get_isolated_creds(cls, admin=False):
+        """
+        Creates a new set of user/tenant/password credentials for a
+        **regular** user of the Compute API so that a test case can
+        operate in an isolated tenant container.
+        """
+        admin_client = cls._get_identity_admin_client()
+        password = "pass"
+
+        while True:
+            try:
+                rand_name_root = rand_name(cls.__name__)
+                if cls.isolated_creds:
+                # Main user already created. Create the alt or admin one...
+                    if admin:
+                        rand_name_root += '-admin'
+                    else:
+                        rand_name_root += '-alt'
+                tenant_name = rand_name_root + "-tenant"
+                tenant_desc = tenant_name + "-desc"
+
+                resp, tenant = admin_client.create_tenant(
+                    name=tenant_name, description=tenant_desc)
+                break
+            except exceptions.Duplicate:
+                if cls.config.compute.allow_tenant_reuse:
+                    tenant = admin_client.get_tenant_by_name(tenant_name)
+                    LOG.info('Re-using existing tenant %s', tenant)
+                    break
+
+        while True:
+            try:
+                rand_name_root = rand_name(cls.__name__)
+                if cls.isolated_creds:
+                # Main user already created. Create the alt one...
+                    rand_name_root += '-alt'
+                username = rand_name_root + "-user"
+                email = rand_name_root + "@example.com"
+                resp, user = admin_client.create_user(username,
+                                                      password,
+                                                      tenant['id'],
+                                                      email)
+                break
+            except exceptions.Duplicate:
+                if cls.config.compute.allow_tenant_reuse:
+                    user = admin_client.get_user_by_username(tenant['id'],
+                                                             username)
+                    LOG.info('Re-using existing user %s', user)
+                    break
+        # Store the complete creds (including UUID ids...) for later
+        # but return just the username, tenant_name, password tuple
+        # that the various clients will use.
+        cls.isolated_creds.append((user, tenant))
+
+        # Assign admin role if this is for admin creds
+        if admin:
+            _, roles = admin_client.list_roles()
+            role = None
+            try:
+                _, roles = admin_client.list_roles()
+                role = next(r for r in roles if r['name'] == 'admin')
+            except StopIteration:
+                msg = "No admin role found"
+                raise exceptions.NotFound(msg)
+            admin_client.assign_user_role(tenant['id'], user['id'], role['id'])
+
+        return username, tenant_name, password
+
+    @classmethod
+    def _clear_isolated_creds(cls):
+        if not cls.isolated_creds:
+            return
+        admin_client = cls._get_identity_admin_client()
+
+        for user, tenant in cls.isolated_creds:
+            admin_client.delete_user(user['id'])
+            admin_client.delete_tenant(tenant['id'])
+
 
 def call_until_true(func, duration, sleep_for):
     """
@@ -137,7 +238,7 @@
             thing = things.get(thing_id)
             new_status = thing.status
             if new_status == 'ERROR':
-                self.fail("%s failed to get to expected status."
+                self.fail("%s failed to get to expected status. "
                           "In ERROR state."
                           % thing)
             elif new_status == expected_status:
diff --git a/tempest/thirdparty/README.rst b/tempest/thirdparty/README.rst
index 41d31f3..b775817 100644
--- a/tempest/thirdparty/README.rst
+++ b/tempest/thirdparty/README.rst
@@ -1,9 +1,9 @@
 Tempest Guide to Third Party API tests
-========
+======================================
 
 
 What are these tests?
---------
+---------------------
 
 Third party tests are tests for non native OpenStack APIs that are
 part of OpenStack projects. If we ship an API, we're really required
@@ -14,14 +14,14 @@
 
 
 Why are these tests in tempest?
---------
+-------------------------------
 
 If we ship an API in an OpenStack component, there should be tests in
 tempest to exercise it in some way.
 
 
 Scope of these tests
---------
+--------------------
 
 Third party API testing should be limited to the functional testing of
 third party API compliance. Complex scenarios should be avoided, and
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index 89891d2..1201866 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -113,6 +113,53 @@
         self.cancelResourceCleanUp(rcuk)
 
     @attr(type='smoke')
+    def test_run_stop_terminate_instance_with_tags(self):
+        # EC2 run, stop and terminate instance with tags
+        image_ami = self.ec2_client.get_image(self.images["ami"]
+                                              ["image_id"])
+        reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"],
+                                    ramdisk_id=self.images["ari"]["image_id"],
+                                    instance_type=self.instance_type)
+        rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation)
+
+        for instance in reservation.instances:
+            LOG.info("state: %s", instance.state)
+            if instance.state != "running":
+                self.assertInstanceStateWait(instance, "running")
+            instance.add_tag('key1', value='value1')
+
+        tags = self.ec2_client.get_all_tags()
+        self.assertEquals(tags[0].name, 'key1')
+        self.assertEquals(tags[0].value, 'value1')
+
+        tags = self.ec2_client.get_all_tags(filters={'key': 'key1'})
+        self.assertEquals(tags[0].name, 'key1')
+        self.assertEquals(tags[0].value, 'value1')
+
+        tags = self.ec2_client.get_all_tags(filters={'value': 'value1'})
+        self.assertEquals(tags[0].name, 'key1')
+        self.assertEquals(tags[0].value, 'value1')
+
+        tags = self.ec2_client.get_all_tags(filters={'key': 'value2'})
+        self.assertEquals(len(tags), 0)
+
+        for instance in reservation.instances:
+            instance.remove_tag('key1', value='value1')
+
+        tags = self.ec2_client.get_all_tags()
+        self.assertEquals(len(tags), 0)
+
+        for instance in reservation.instances:
+            instance.stop()
+            LOG.info("state: %s", instance.state)
+            if instance.state != "stopped":
+                self.assertInstanceStateWait(instance, "stopped")
+
+        for instance in reservation.instances:
+            instance.terminate()
+        self.cancelResourceCleanUp(rcuk)
+
+    @attr(type='smoke')
     @testtools.skip("Skipped until the Bug #1098891 is resolved")
     def test_run_terminate_instance(self):
         # EC2 run, terminate immediately
diff --git a/test-requirements.txt b/test-requirements.txt
index 2185997..236a473 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,7 +2,7 @@
 pep8==1.4.5
 pyflakes==0.7.2
 flake8==2.0
-hacking>=0.5.3,<0.6
+hacking>=0.5.6,<0.7
 # needed for doc build
+docutils==0.9.1
 sphinx>=1.1.2
-
diff --git a/tools/colorizer.py b/tools/colorizer.py
new file mode 100755
index 0000000..76a3bd3
--- /dev/null
+++ b/tools/colorizer.py
@@ -0,0 +1,333 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013, Nebula, Inc.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+#
+# Colorizer Code is borrowed from Twisted:
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+#
+#    Permission is hereby granted, free of charge, to any person obtaining
+#    a copy of this software and associated documentation files (the
+#    "Software"), to deal in the Software without restriction, including
+#    without limitation the rights to use, copy, modify, merge, publish,
+#    distribute, sublicense, and/or sell copies of the Software, and to
+#    permit persons to whom the Software is furnished to do so, subject to
+#    the following conditions:
+#
+#    The above copyright notice and this permission notice shall be
+#    included in all copies or substantial portions of the Software.
+#
+#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+#    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+#    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+#    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Display a subunit stream through a colorized unittest test runner."""
+
+import heapq
+import subunit
+import sys
+import unittest
+
+import testtools
+
+
+class _AnsiColorizer(object):
+    """
+    A colorizer is an object that loosely wraps around a stream, allowing
+    callers to write text to the stream in a particular color.
+
+    Colorizer classes must implement C{supported()} and C{write(text, color)}.
+    """
+    _colors = dict(black=30, red=31, green=32, yellow=33,
+                   blue=34, magenta=35, cyan=36, white=37)
+
+    def __init__(self, stream):
+        self.stream = stream
+
+    def supported(cls, stream=sys.stdout):
+        """
+        A class method that returns True if the current platform supports
+        coloring terminal output using this method. Returns False otherwise.
+        """
+        if not stream.isatty():
+            return False  # auto color only on TTYs
+        try:
+            import curses
+        except ImportError:
+            return False
+        else:
+            try:
+                try:
+                    return curses.tigetnum("colors") > 2
+                except curses.error:
+                    curses.setupterm()
+                    return curses.tigetnum("colors") > 2
+            except Exception:
+                # guess false in case of error
+                return False
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        """
+        Write the given text to the stream in the given color.
+
+        @param text: Text to be written to the stream.
+
+        @param color: A string label for a color. e.g. 'red', 'white'.
+        """
+        color = self._colors[color]
+        self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
+
+
+class _Win32Colorizer(object):
+    """
+    See _AnsiColorizer docstring.
+    """
+    def __init__(self, stream):
+        import win32console
+        red, green, blue, bold = (win32console.FOREGROUND_RED,
+                                  win32console.FOREGROUND_GREEN,
+                                  win32console.FOREGROUND_BLUE,
+                                  win32console.FOREGROUND_INTENSITY)
+        self.stream = stream
+        self.screenBuffer = win32console.GetStdHandle(
+            win32console.STD_OUT_HANDLE)
+        self._colors = {'normal': red | green | blue,
+                        'red': red | bold,
+                        'green': green | bold,
+                        'blue': blue | bold,
+                        'yellow': red | green | bold,
+                        'magenta': red | blue | bold,
+                        'cyan': green | blue | bold,
+                        'white': red | green | blue | bold}
+
+    def supported(cls, stream=sys.stdout):
+        try:
+            import win32console
+            screenBuffer = win32console.GetStdHandle(
+                win32console.STD_OUT_HANDLE)
+        except ImportError:
+            return False
+        import pywintypes
+        try:
+            screenBuffer.SetConsoleTextAttribute(
+                win32console.FOREGROUND_RED |
+                win32console.FOREGROUND_GREEN |
+                win32console.FOREGROUND_BLUE)
+        except pywintypes.error:
+            return False
+        else:
+            return True
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        color = self._colors[color]
+        self.screenBuffer.SetConsoleTextAttribute(color)
+        self.stream.write(text)
+        self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
+
+
+class _NullColorizer(object):
+    """
+    See _AnsiColorizer docstring.
+    """
+    def __init__(self, stream):
+        self.stream = stream
+
+    def supported(cls, stream=sys.stdout):
+        return True
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        self.stream.write(text)
+
+
+def get_elapsed_time_color(elapsed_time):
+    if elapsed_time > 1.0:
+        return 'red'
+    elif elapsed_time > 0.25:
+        return 'yellow'
+    else:
+        return 'green'
+
+
+class NovaTestResult(testtools.TestResult):
+    def __init__(self, stream, descriptions, verbosity):
+        super(NovaTestResult, self).__init__()
+        self.stream = stream
+        self.showAll = verbosity > 1
+        self.num_slow_tests = 10
+        self.slow_tests = []  # this is a fixed-sized heap
+        self.colorizer = None
+        # NOTE(vish): reset stdout for the terminal check
+        stdout = sys.stdout
+        sys.stdout = sys.__stdout__
+        for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
+            if colorizer.supported():
+                self.colorizer = colorizer(self.stream)
+                break
+        sys.stdout = stdout
+        self.start_time = None
+        self.last_time = {}
+        self.results = {}
+        self.last_written = None
+
+    def _writeElapsedTime(self, elapsed):
+        color = get_elapsed_time_color(elapsed)
+        self.colorizer.write("  %.2f" % elapsed, color)
+
+    def _addResult(self, test, *args):
+        try:
+            name = test.id()
+        except AttributeError:
+            name = 'Unknown.unknown'
+        test_class, test_name = name.rsplit('.', 1)
+
+        elapsed = (self._now() - self.start_time).total_seconds()
+        item = (elapsed, test_class, test_name)
+        if len(self.slow_tests) >= self.num_slow_tests:
+            heapq.heappushpop(self.slow_tests, item)
+        else:
+            heapq.heappush(self.slow_tests, item)
+
+        self.results.setdefault(test_class, [])
+        self.results[test_class].append((test_name, elapsed) + args)
+        self.last_time[test_class] = self._now()
+        self.writeTests()
+
+    def _writeResult(self, test_name, elapsed, long_result, color,
+                     short_result, success):
+        if self.showAll:
+            self.stream.write('    %s' % str(test_name).ljust(66))
+            self.colorizer.write(long_result, color)
+            if success:
+                self._writeElapsedTime(elapsed)
+            self.stream.writeln()
+        else:
+            self.colorizer.write(short_result, color)
+
+    def addSuccess(self, test):
+        super(NovaTestResult, self).addSuccess(test)
+        self._addResult(test, 'OK', 'green', '.', True)
+
+    def addFailure(self, test, err):
+        if test.id() == 'process-returncode':
+            return
+        super(NovaTestResult, self).addFailure(test, err)
+        self._addResult(test, 'FAIL', 'red', 'F', False)
+
+    def addError(self, test, err):
+        super(NovaTestResult, self).addFailure(test, err)
+        self._addResult(test, 'ERROR', 'red', 'E', False)
+
+    def addSkip(self, test, reason=None, details=None):
+        super(NovaTestResult, self).addSkip(test, reason, details)
+        self._addResult(test, 'SKIP', 'blue', 'S', True)
+
+    def startTest(self, test):
+        self.start_time = self._now()
+        super(NovaTestResult, self).startTest(test)
+
+    def writeTestCase(self, cls):
+        if not self.results.get(cls):
+            return
+        if cls != self.last_written:
+            self.colorizer.write(cls, 'white')
+            self.stream.writeln()
+        for result in self.results[cls]:
+            self._writeResult(*result)
+        del self.results[cls]
+        self.stream.flush()
+        self.last_written = cls
+
+    def writeTests(self):
+        time = self.last_time.get(self.last_written, self._now())
+        if not self.last_written or (self._now() - time).total_seconds() > 2.0:
+            diff = 3.0
+            while diff > 2.0:
+                classes = self.results.keys()
+                oldest = min(classes, key=lambda x: self.last_time[x])
+                diff = (self._now() - self.last_time[oldest]).total_seconds()
+                self.writeTestCase(oldest)
+        else:
+            self.writeTestCase(self.last_written)
+
+    def done(self):
+        self.stopTestRun()
+
+    def stopTestRun(self):
+        for cls in list(self.results.iterkeys()):
+            self.writeTestCase(cls)
+        self.stream.writeln()
+        self.writeSlowTests()
+
+    def writeSlowTests(self):
+        # Pare out 'fast' tests
+        slow_tests = [item for item in self.slow_tests
+                      if get_elapsed_time_color(item[0]) != 'green']
+        if slow_tests:
+            slow_total_time = sum(item[0] for item in slow_tests)
+            slow = ("Slowest %i tests took %.2f secs:"
+                    % (len(slow_tests), slow_total_time))
+            self.colorizer.write(slow, 'yellow')
+            self.stream.writeln()
+            last_cls = None
+            # sort by name
+            for elapsed, cls, name in sorted(slow_tests,
+                                             key=lambda x: x[1] + x[2]):
+                if cls != last_cls:
+                    self.colorizer.write(cls, 'white')
+                    self.stream.writeln()
+                last_cls = cls
+                self.stream.write('    %s' % str(name).ljust(68))
+                self._writeElapsedTime(elapsed)
+                self.stream.writeln()
+
+    def printErrors(self):
+        if self.showAll:
+            self.stream.writeln()
+        self.printErrorList('ERROR', self.errors)
+        self.printErrorList('FAIL', self.failures)
+
+    def printErrorList(self, flavor, errors):
+        for test, err in errors:
+            self.colorizer.write("=" * 70, 'red')
+            self.stream.writeln()
+            self.colorizer.write(flavor, 'red')
+            self.stream.writeln(": %s" % test.id())
+            self.colorizer.write("-" * 70, 'red')
+            self.stream.writeln()
+            self.stream.writeln("%s" % err)
+
+
+test = subunit.ProtocolTestCase(sys.stdin, passthrough=None)
+
+if sys.version_info[0:2] <= (2, 6):
+    runner = unittest.TextTestRunner(verbosity=2)
+else:
+    runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult)
+
+if runner.run(test).wasSuccessful():
+    exit_code = 0
+else:
+    exit_code = 1
+sys.exit(exit_code)
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
new file mode 100755
index 0000000..a5a6076
--- /dev/null
+++ b/tools/pretty_tox.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+TESTRARGS=$1
+python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit2pyunit
diff --git a/tox.ini b/tox.ini
index 964dbca..eb1ef4b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,24 +3,16 @@
 
 [testenv]
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
+         LANG=en_US.UTF-8
+         LANGUAGE=en_US:en
+         LC_ALL=C
 
 [testenv:all]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
 commands =
-  nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-all.xml -sv tempest
+  python setup.py testr --slowest
+
 
 [testenv:full]
 sitepackages = True
@@ -34,6 +26,12 @@
 commands =
   nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
 
+[testenv:testr-full]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+commands =
+  sh tools/pretty_tox.sh 'tempest.api tempest.scenario tempest.thirdparty tempest.cli'
+
 [testenv:smoke]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
@@ -46,7 +44,6 @@
 commands =
    nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --attr=type=smoke --xunit-file=nosetests-smoke.xml tempest
 
-
 [testenv:coverage]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
@@ -65,9 +62,7 @@
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
 commands =
-    python -m tempest/stress/run_stress tempest/stress/etc/sample-test.json -d 60
-    python -m tempest/stress/run_stress tempest/stress/etc/volume-create-delete-test.json -d 60
-
+    python -m tempest/stress/run_stress tempest/stress/etc/stress-tox-job.json -d 3600
 
 [testenv:venv]
 commands = {posargs}
@@ -83,6 +78,7 @@
 local-check-factory = tempest.hacking.checks.factory
 
 [flake8]
+# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved.  For further detail see https://review.openstack.org/#/c/36788/
 ignore = E125,H302,H404
 show-source = True
 exclude = .git,.venv,.tox,dist,doc,openstack,*egg