Merge "Test for flavor-access-list with private/public flavor"
diff --git a/.mailmap b/.mailmap
index 9a22c71..5c37a5e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -2,6 +2,7 @@
 Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
 Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
 Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
 <brian.waldon@rackspace.com> <bcwaldon@gmail.com>
 Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
 <jeblair@hp.com> <corvus@inaugust.com>
diff --git a/HACKING.rst b/HACKING.rst
index 7d995c3..377c647 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -163,3 +163,26 @@
 - If the execution of a set of tests is required to be serialized then locking
   can be used to perform this. See AggregatesAdminTest in
   tempest.api.compute.admin for an example of using locking.
+
+Stress Tests in Tempest
+-----------------------
+Any tempest test case can be flagged as a stress test. With this flag it will
+be automatically discovery and used in the stress test runs. The stress test
+framework itself is a facility to spawn and control worker processes in order
+to find race conditions (see ``tempest/stress/`` for more information). Please
+note that these stress tests can't be used for benchmarking purposes since they
+don't measure any performance characteristics.
+
+Example::
+
+  @stresstest(class_setup_per='process')
+  def test_this_and_that(self):
+    ...
+
+This will flag the test ``test_this_and_that`` as a stress test. The parameter
+``class_setup_per`` gives control when the setUpClass function should be called.
+
+Good candidates for stress tests are:
+
+- Scenario tests
+- API tests that have a wide focus
diff --git a/README.rst b/README.rst
index 4161cc6..0996022 100644
--- a/README.rst
+++ b/README.rst
@@ -6,6 +6,33 @@
 Scenarios, and other specific tests useful in validating an OpenStack
 deployment.
 
+Design Principles
+----------
+Tempest Design Principles that we strive to live by.
+
+- Tempest should be able to run against any OpenStack cloud, be it a
+  one node devstack install, a 20 node lxc cloud, or a 1000 node kvm
+  cloud.
+- Tempest should be explicit in testing features. It is easy to auto
+  discover features of a cloud incorrectly, and give people an
+  incorrect assessment of their cloud. Explicit is always better.
+- Tempest uses OpenStack public interfaces. Tests in Tempest should
+  only touch public interfaces, API calls (native or 3rd party),
+  public CLI or libraries.
+- Tempest should not touch private or implementation specific
+  interfaces. This means not directly going to the database, not
+  directly hitting the hypervisors, not testing extensions not
+  included in the OpenStack base. If there is some feature of
+  OpenStack that is not verifiable through standard interfaces, this
+  should be considered a possible enhancement.
+- Tempest strives for complete coverage of the OpenStack API and
+  common scenarios that demonstrate a working cloud.
+- Tempest drives load in an OpenStack cloud. By including a broad
+  array of API and scenario tests Tempest can be reused in whole or in
+  parts as load generation for an OpenStack cloud.
+- Tempest should attempt to clean up after itself, whenever possible
+  we should tear down resources when done.
+- Tempest should be self testing.
 
 Quickstart
 ----------
diff --git a/etc/README.txt b/etc/README.txt
deleted file mode 100644
index c7e5e6e..0000000
--- a/etc/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Copy config.ini.sample to config.ini, and update it to reflect your environment.
diff --git a/etc/TEMPEST_README.txt b/etc/TEMPEST_README.txt
deleted file mode 100644
index 50fa688..0000000
--- a/etc/TEMPEST_README.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-To run:
--rename the /etc/tempest.conf.sample file to tempest.conf
--Set the fields in the file to values relevant to your system
--Set the "authentication" value (basic or keystone_v2 currently supported)
--From the root directory of the project, run "./run_tests.sh" this will
-create the venv to install the project dependencies and run nosetests tempest
-to run all the tests
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 115a2b5..fc4f9cd 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -2,7 +2,7 @@
 #log_config = /opt/stack/tempest/etc/logging.conf.sample
 
 # disable logging to the stderr
-use_stderr = False
+use_stderr = false
 
 # log file
 log_file = tempest.log
@@ -23,7 +23,7 @@
 catalog_type = identity
 # Ignore SSL certificate validation failures? Use when in testing
 # environments that have self-signed SSL certs.
-disable_ssl_certificate_validation = False
+disable_ssl_certificate_validation = false
 # URL for where to find the OpenStack Identity API endpoint (Keystone)
 uri = http://127.0.0.1:5000/v2.0/
 # URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
@@ -131,48 +131,57 @@
 ssh_channel_timeout = 60
 
 # Dose the SSH uses Floating IP?
-use_floatingip_for_ssh = True
+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"
 catalog_type = compute
 
+# The type of endpoint for a Compute v3 API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "computev3"
+catalog_v3_type = computev3
+
 # The name of a region for compute. If empty or commented-out, the value of
 # identity.region is used instead. If no such region is found in the service
 # catalog, the first found one is used.
 #region = RegionOne
 
+# Expected first device name when a volume is attached to an instance
+volume_device_name = vdb
+
+[compute-feature-enabled]
+# Do we run the Nova V3 API tests?
+api_v3 = false
+
 # Does the Compute API support creation of images?
-create_image_enabled = true
+create_image = true
 
 # For resize to work with libvirt/kvm, one of the following must be true:
-# Single node: allow_resize_to_same_host=True must be set in nova.conf
+# Single node: allow_resize_to_same_host=true must be set in nova.conf
 # Cluster: the 'nova' user must have scp access between cluster nodes
-resize_available = true
+resize = true
 
 # Does the compute API support changing the admin password?
-change_password_available=true
+change_password = false
 
 # Run live migration tests (requires 2 hosts)
-live_migration_available = false
+live_migration = false
 
 # Use block live migration (Otherwise, non-block migration will be
 # performed, which requires XenServer pools in case of using XS)
-use_block_migration_for_live_migration = false
+block_migration_for_live_migration = false
 
 # Supports iSCSI block migration - depends on a XAPI supporting
 # relax-xsm-sr-check
-block_migrate_supports_cinder_iscsi = false
+block_migrate_cinder_iscsi = false
 
 # When set to false, disk config tests are forced to skip
-disk_config_enabled = true
+disk_config = true
 
 # When set to false, flavor extra data tests are forced to skip
-flavor_extra_enabled = true
-
-# Expected first device name when a volume is attached to an instance
-volume_device_name = vdb
+flavor_extra = true
 
 [compute-admin]
 # This should be the username of a user WITH administrative privileges
@@ -197,12 +206,15 @@
 # catalog, the first found one is used.
 #region = RegionOne
 
-# The version of the OpenStack Images API to use
-api_version = 1
-
 # HTTP image to use for glance http image testing
 http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
 
+[image-feature-enabled]
+# Is the image api_v1 enabled
+api_v1 = True
+# Is the image api_v2 enabled
+api_v2 = True
+
 [network]
 # This section contains configuration options used when executing tests
 # against the OpenStack Network API.
@@ -258,10 +270,8 @@
 # Number of seconds to time out on waiting for a volume
 # to be available or reach an expected status
 build_timeout = 300
-# Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf)
-# They must have different volume_backend_name (backend1_name and backend2_name
-# have to be different)
-multi_backend_enabled = false
+# If multi_backend is enabled there must be 2 volume_backend_names (
+# backend1_name and backend2_name) which have to be different)
 backend1_name = BACKEND_1
 backend2_name = BACKEND_2
 # Protocol and vendor of volume backend to target when testing volume-types.
@@ -269,6 +279,10 @@
 storage_protocol = iSCSI
 vendor_name = Open Source
 
+[volume-feature-enabled]
+#Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf)
+multi_backend = false
+
 [object-storage]
 # This section contains configuration options used when executing tests
 # against the OpenStack Object Storage API.
@@ -291,14 +305,16 @@
 # Number of seconds to wait while looping to check the status of a
 # container to container synchronization
 container_sync_interval = 5
-# Set to True if the Account Quota middleware is enabled
-accounts_quotas_available = True
-# Set to True if the Container Quota middleware is enabled
-container_quotas_available = True
 
 # Set operator role for tests that require creating a container
 operator_role = Member
 
+[object-feature-enabled]
+# Set to True if the Account Quota middleware is enabled
+accounts_quotas = True
+# Set to True if the Container Quota middleware is enabled
+container_quotas = True
+
 [boto]
 # This section contains configuration options used when executing tests
 # with boto.
@@ -403,7 +419,7 @@
 
 [cli]
 # Enable cli tests
-enabled = True
+enabled = true
 # directory where python client binaries are located
 cli_dir = /usr/local/bin
 # Number of seconds to wait on a CLI timeout
@@ -411,19 +427,21 @@
 
 [service_available]
 # Whether or not cinder is expected to be available
-cinder = True
+cinder = true
 # Whether or not neutron is expected to be available
 neutron = false
 # Whether or not glance is expected to be available
-glance = True
+glance = true
 # Whether or not swift is expected to be available
-swift = True
+swift = true
 # Whether or not nova is expected to be available
-nova = True
+nova = true
 # Whether or not Heat is expected to be available
 heat = false
+# Whether or not Ceilometer is expected to be available
+ceilometer = True
 # Whether or not horizon is expected to be available
-horizon = True
+horizon = true
 
 [stress]
 # Maximum number of instances to create during test
@@ -431,8 +449,8 @@
 # Time (in seconds) between log file error checks
 log_check_interval = 60
 # The default number of threads created while stress test
-default_thread_number_per_action=4
+default_thread_number_per_action = 4
 
 [debug]
 # Enable diagnostic commands
-enable = True
+enable = true
diff --git a/openstack-common.conf b/openstack-common.conf
index ff84404..38d58ee 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,10 +1,12 @@
 [DEFAULT]
 
 # The list of modules to copy from openstack-common
+module=config
 module=install_venv_common
 module=lockutils
 module=log
 module=importlib
+module=fixture
 
 # The base module to hold the copy of openstack.common
 base=tempest
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index 2c21740..a528754 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -22,11 +22,11 @@
 LOG = logging.getLogger(__name__)
 
 CONFIG = config.TempestConfig()
-CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
-RESIZE_AVAILABLE = CONFIG.compute.resize_available
-CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-DISK_CONFIG_ENABLED = CONFIG.compute.disk_config_enabled
-FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute.flavor_extra_enabled
+CREATE_IMAGE_ENABLED = CONFIG.compute_feature_enabled.create_image
+RESIZE_AVAILABLE = CONFIG.compute_feature_enabled.resize
+CHANGE_PASSWORD_AVAILABLE = CONFIG.compute_feature_enabled.change_password
+DISK_CONFIG_ENABLED = CONFIG.compute_feature_enabled.disk_config
+FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute_feature_enabled.flavor_extra
 MULTI_USER = True
 
 
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 75b8dad..05bb457 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -80,13 +80,7 @@
         self.assertEqual(flavor['rxtx_factor'], self.rxtx)
         self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'],
                          self.ephemeral)
-        if self._interface == "xml":
-            XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
-                "flavor_access/api/v2"
-            key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
-            self.assertEqual(flavor[key], "True")
-        if self._interface == "json":
-            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+        self.assertEqual(flavor['os-flavor-access:is_public'], True)
 
         # Verify flavor is retrieved
         resp, flavor = self.client.get_flavor_details(new_flavor_id)
@@ -156,6 +150,14 @@
     def test_create_list_flavor_without_extra_data(self):
         # Create a flavor and ensure it is listed
         # This operation requires the user to have 'admin' role
+
+        def verify_flavor_response_extension(flavor):
+            # check some extensions for the flavor create/show/detail response
+            self.assertEqual(flavor['swap'], '')
+            self.assertEqual(int(flavor['rxtx_factor']), 1)
+            self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
+            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+
         flavor_name = rand_name(self.flavor_name_prefix)
         new_flavor_id = rand_int_id(start=1000)
 
@@ -171,26 +173,20 @@
         self.assertEqual(flavor['vcpus'], self.vcpus)
         self.assertEqual(flavor['disk'], self.disk)
         self.assertEqual(int(flavor['id']), new_flavor_id)
-        self.assertEqual(flavor['swap'], '')
-        self.assertEqual(int(flavor['rxtx_factor']), 1)
-        self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
-        if self._interface == "xml":
-            XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
-                "flavor_access/api/v2"
-            key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
-            self.assertEqual(flavor[key], "True")
-        if self._interface == "json":
-            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+        verify_flavor_response_extension(flavor)
 
         # Verify flavor is retrieved
         resp, flavor = self.client.get_flavor_details(new_flavor_id)
         self.assertEqual(resp.status, 200)
         self.assertEqual(flavor['name'], flavor_name)
+        verify_flavor_response_extension(flavor)
+
         # Check if flavor is present in list
-        resp, flavors = self.client.list_flavors_with_detail()
+        resp, flavors = self.user_client.list_flavors_with_detail()
         self.assertEqual(resp.status, 200)
         for flavor in flavors:
             if flavor['name'] == flavor_name:
+                verify_flavor_response_extension(flavor)
                 flag = True
         self.assertTrue(flag)
 
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 0fd4d11..403a946 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -19,7 +19,6 @@
 from tempest.api.compute import base
 from tempest.common.utils.data_utils import rand_int_id
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 from tempest.test import attr
 
 
@@ -64,9 +63,9 @@
         super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
 
     @attr(type='gate')
-    def test_flavor_set_get_unset_keys(self):
-        # Test to SET, GET UNSET flavor extra spec as a user
-        # with admin privileges.
+    def test_flavor_set_get_update_show_unset_keys(self):
+        # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
+        # spec as a user with admin privileges.
         # Assigning extra specs values that are to be set
         specs = {"key1": "value1", "key2": "value2"}
         # SET extra specs to the flavor created in setUp
@@ -79,55 +78,55 @@
             self.client.get_flavor_extra_spec(self.flavor['id'])
         self.assertEqual(get_resp.status, 200)
         self.assertEqual(get_body, specs)
+
+        # UPDATE the value of the extra specs key1
+        update_resp, update_body = \
+            self.client.update_flavor_extra_spec(self.flavor['id'],
+                                                 "key1",
+                                                 key1="value")
+        self.assertEqual(update_resp.status, 200)
+        self.assertEqual({"key1": "value"}, update_body)
+
         # GET a key value and verify
-        get_resp, get_body = \
+        show_resp, get_body = \
             self.client.get_flavor_extra_spec_with_key(self.flavor['id'],
-                                                       "key2")
+                                                       "key1")
+        self.assertEqual(show_resp.status, 200)
+        self.assertEqual(get_body, 'value')
+
+        # GET extra specs and verify the value of the key2
+        # is the same as before
+        get_resp, get_body = \
+            self.client.get_flavor_extra_spec(self.flavor['id'])
         self.assertEqual(get_resp.status, 200)
-        self.assertEqual(get_body, specs['key2'])
+        self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
+
         # UNSET extra specs that were set in this test
         unset_resp, _ = \
             self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
         self.assertEqual(unset_resp.status, 200)
-
-    @attr(type=['negative', 'gate'])
-    def test_flavor_non_admin_set_keys(self):
-        # Test to SET flavor extra spec as a user without admin privileges.
-        specs = {"key1": "value1", "key2": "value2"}
-        self.assertRaises(exceptions.Unauthorized,
-                          self.flavors_client.set_flavor_extra_spec,
-                          self.flavor['id'],
-                          specs)
+        unset_resp, _ = \
+            self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
+        self.assertEqual(unset_resp.status, 200)
 
     @attr(type='gate')
-    def test_flavor_non_admin_get_keys(self):
+    def test_flavor_non_admin_get_all_keys_and_specified_key(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
             self.flavor['id'], specs)
         resp, body = self.flavors_client.get_flavor_extra_spec(
             self.flavor['id'])
         self.assertEqual(resp.status, 200)
+
         for key in specs:
             self.assertEqual(body[key], specs[key])
 
-    @attr(type=['negative', 'gate'])
-    def test_flavor_non_admin_unset_keys(self):
-        specs = {"key1": "value1", "key2": "value2"}
-        set_resp, set_body = self.client.set_flavor_extra_spec(
-            self.flavor['id'], specs)
-
-        self.assertRaises(exceptions.Unauthorized,
-                          self.flavors_client.unset_flavor_extra_spec,
-                          self.flavor['id'],
-                          'key1')
-
-    @attr(type=['negative', 'gate'])
-    def test_flavor_unset_nonexistent_key(self):
-        nonexistent_key = rand_name('flavor_key')
-        self.assertRaises(exceptions.NotFound,
-                          self.client.unset_flavor_extra_spec,
-                          self.flavor['id'],
-                          nonexistent_key)
+        get_resp, get_body = \
+            self.flavors_client.get_flavor_extra_spec_with_key(
+                self.flavor['id'],
+                "key1")
+        self.assertEqual(get_resp.status, 200)
+        self.assertEqual("value1", get_body)
 
 
 class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
new file mode 100644
index 0000000..8d62a2a
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+# Copyright 2013 IBM Corp.
+#
+#    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 import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_int_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+    """the Negative tests for FlavorsExtraSpecs."""
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
+        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+            msg = "FlavorExtraData extension not enabled."
+            raise cls.skipException(msg)
+
+        cls.client = cls.os_adm.flavors_client
+        flavor_name = rand_name('test_flavor')
+        ram = 512
+        vcpus = 1
+        disk = 10
+        ephemeral = 10
+        cls.new_flavor_id = rand_int_id(start=1000)
+        swap = 1024
+        rxtx = 1
+        # Create a flavor
+        resp, cls.flavor = cls.client.create_flavor(flavor_name,
+                                                    ram, vcpus,
+                                                    disk,
+                                                    cls.new_flavor_id,
+                                                    ephemeral=ephemeral,
+                                                    swap=swap, rxtx=rxtx)
+
+    @classmethod
+    def tearDownClass(cls):
+        resp, body = cls.client.delete_flavor(cls.flavor['id'])
+        cls.client.wait_for_resource_deletion(cls.flavor['id'])
+        super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_set_keys(self):
+        # Test to SET flavor extra spec as a user without admin privileges.
+        specs = {"key1": "value1", "key2": "value2"}
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.set_flavor_extra_spec,
+                          self.flavor['id'],
+                          specs)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_unset_keys(self):
+        specs = {"key1": "value1", "key2": "value2"}
+        set_resp, set_body = self.client.set_flavor_extra_spec(
+            self.flavor['id'], specs)
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.unset_flavor_extra_spec,
+                          self.flavor['id'],
+                          'key1')
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_unset_nonexistent_key(self):
+        nonexistent_key = rand_name('flavor_key')
+        self.assertRaises(exceptions.NotFound,
+                          self.client.unset_flavor_extra_spec,
+                          self.flavor['id'],
+                          nonexistent_key)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_get_nonexistent_key(self):
+        self.assertRaises(exceptions.NotFound,
+                          self.flavors_client.get_flavor_extra_spec_with_key,
+                          self.flavor['id'],
+                          "nonexistent_key")
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_update_mismatch_key(self):
+        # the key will be updated should be match the key in the body
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_flavor_extra_spec,
+                          self.flavor['id'],
+                          "key2",
+                          key1="value")
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_update_more_key(self):
+        # there should be just one item in the request body
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_flavor_extra_spec,
+                          self.flavor['id'],
+                          "key1",
+                          key1="value",
+                          key2="value")
+
+
+class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 8e451a0..48b8d12 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -32,13 +32,6 @@
         super(HostsAdminTestJSON, cls).setUpClass()
         cls.client = cls.os_adm.hosts_client
 
-    def _get_host_name(self):
-        resp, hosts = self.client.list_hosts()
-        self.assertEqual(200, resp.status)
-        self.assertTrue(len(hosts) >= 1)
-        hostname = hosts[0]['host_name']
-        return hostname
-
     @attr(type='gate')
     def test_list_hosts(self):
         resp, hosts = self.client.list_hosts()
@@ -77,18 +70,24 @@
 
     @attr(type='gate')
     def test_show_host_detail(self):
-        hostname = self._get_host_name()
-
-        resp, resources = self.client.show_host_detail(hostname)
+        resp, hosts = self.client.list_hosts()
         self.assertEqual(200, resp.status)
-        self.assertTrue(len(resources) >= 1)
-        host_resource = resources[0]['resource']
-        self.assertIsNotNone(host_resource)
-        self.assertIsNotNone(host_resource['cpu'])
-        self.assertIsNotNone(host_resource['disk_gb'])
-        self.assertIsNotNone(host_resource['memory_mb'])
-        self.assertIsNotNone(host_resource['project'])
-        self.assertEqual(hostname, host_resource['host'])
+
+        hosts = [host for host in hosts if host['service'] == 'compute']
+        self.assertTrue(len(hosts) >= 1)
+
+        for host in hosts:
+            hostname = host['host_name']
+            resp, resources = self.client.show_host_detail(hostname)
+            self.assertEqual(200, resp.status)
+            self.assertTrue(len(resources) >= 1)
+            host_resource = resources[0]['resource']
+            self.assertIsNotNone(host_resource)
+            self.assertIsNotNone(host_resource['cpu'])
+            self.assertIsNotNone(host_resource['disk_gb'])
+            self.assertIsNotNone(host_resource['memory_mb'])
+            self.assertIsNotNone(host_resource['project'])
+            self.assertEqual(hostname, host_resource['host'])
 
 
 class HostsAdminTestXML(HostsAdminTestJSON):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 5679a45..d185a8b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -72,19 +72,6 @@
                 pass
 
     @classmethod
-    def rebuild_server(cls, **kwargs):
-        # Destroy an existing server and creates a new one
-        try:
-            cls.servers_client.delete_server(cls.server_id)
-            cls.servers_client.wait_for_server_termination(cls.server_id)
-        except Exception as exc:
-            LOG.exception(exc)
-            pass
-        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
-        cls.server_id = server['id']
-        cls.password = server['adminPass']
-
-    @classmethod
     def clear_images(cls):
         for image_id in cls.images:
             try:
@@ -132,25 +119,6 @@
 
         return resp, body
 
-    @classmethod
-    def create_image_from_server(cls, server_id, **kwargs):
-        """Wrapper utility that returns an image created from the server."""
-        name = rand_name(cls.__name__ + "-image")
-        if 'name' in kwargs:
-            name = kwargs.pop('name')
-
-        resp, image = cls.images_client.create_image(
-            server_id, name)
-        image_id = parse_image_id(resp['location'])
-        cls.images.append(image_id)
-
-        if 'wait_until' in kwargs:
-            cls.images_client.wait_for_image_status(image_id,
-                                                    kwargs['wait_until'])
-            resp, image = cls.images_client.get_image(image_id)
-
-        return resp, image
-
     def wait_for(self, condition):
         """Repeatedly calls condition() until a timeout."""
         start_time = int(time.time())
@@ -191,6 +159,38 @@
         cls.hypervisor_client = cls.os.hypervisor_client
         cls.servers_client_v3_auth = cls.os.servers_client_v3_auth
 
+    @classmethod
+    def create_image_from_server(cls, server_id, **kwargs):
+        """Wrapper utility that returns an image created from the server."""
+        name = rand_name(cls.__name__ + "-image")
+        if 'name' in kwargs:
+            name = kwargs.pop('name')
+
+        resp, image = cls.images_client.create_image(
+            server_id, name)
+        image_id = parse_image_id(resp['location'])
+        cls.images.append(image_id)
+
+        if 'wait_until' in kwargs:
+            cls.images_client.wait_for_image_status(image_id,
+                                                    kwargs['wait_until'])
+            resp, image = cls.images_client.get_image(image_id)
+
+        return resp, image
+
+    @classmethod
+    def rebuild_server(cls, **kwargs):
+        # Destroy an existing server and creates a new one
+        try:
+            cls.servers_client.delete_server(cls.server_id)
+            cls.servers_client.wait_for_server_termination(cls.server_id)
+        except Exception as exc:
+            LOG.exception(exc)
+            pass
+        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
+        cls.server_id = server['id']
+        cls.password = server['adminPass']
+
 
 class BaseV2ComputeAdminTest(BaseV2ComputeTest):
     """Base test case class for Compute Admin V2 API tests."""
@@ -215,3 +215,76 @@
                                          interface=cls._interface)
         else:
             cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+
+class BaseV3ComputeTest(BaseComputeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV3ComputeTest, cls).setUpClass()
+        if not cls.config.compute_feature_enabled.api_v3:
+            skip_msg = ("%s skipped as nova v3 api is not available" %
+                        cls.__name__)
+            raise cls.skipException(skip_msg)
+
+        cls.servers_client = cls.os.servers_v3_client
+        cls.images_client = cls.os.image_client
+
+    @classmethod
+    def create_image_from_server(cls, server_id, **kwargs):
+        """Wrapper utility that returns an image created from the server."""
+        name = rand_name(cls.__name__ + "-image")
+        if 'name' in kwargs:
+            name = kwargs.pop('name')
+
+        resp, image = cls.servers_client.create_image(
+            server_id, name)
+        image_id = parse_image_id(resp['location'])
+        cls.images.append(image_id)
+
+        if 'wait_until' in kwargs:
+            cls.images_client.wait_for_image_status(image_id,
+                                                    kwargs['wait_until'])
+            resp, image = cls.images_client.get_image_meta(image_id)
+
+        return resp, image
+
+    @classmethod
+    def rebuild_server(cls, **kwargs):
+        # Destroy an existing server and creates a new one
+        try:
+            cls.servers_client.delete_server(cls.server_id)
+            cls.servers_client.wait_for_server_termination(cls.server_id)
+        except Exception as exc:
+            LOG.exception(exc)
+            pass
+        resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
+        cls.server_id = server['id']
+        cls.password = server['admin_pass']
+
+
+class BaseV3ComputeAdminTest(BaseV3ComputeTest):
+    """Base test case class for all Compute Admin API V3 tests."""
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV3ComputeAdminTest, cls).setUpClass()
+        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)
+        if cls.config.compute.allow_tenant_isolation:
+            creds = cls.isolated_creds.get_admin_creds()
+            admin_username, admin_tenant_name, admin_password = creds
+            os_adm = clients.Manager(username=admin_username,
+                                     password=admin_password,
+                                     tenant_name=admin_tenant_name,
+                                     interface=cls._interface)
+        else:
+            os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+        cls.os_adm = os_adm
+        cls.severs_admin_client = cls.os_adm.servers_v3_client
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index bef45a7..088d3ae 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -188,7 +188,9 @@
         # changes-since returns all instances, including deleted.
         num_expected = (len(self.existing_fixtures) +
                         len(self.deleted_fixtures))
-        self.assertEqual(num_expected, len(body['servers']))
+        self.assertEqual(num_expected, len(body['servers']),
+                         "Number of servers %d is wrong in %s" %
+                         (num_expected, body['servers']))
 
     @attr(type=['negative', 'gate'])
     def test_list_servers_by_changes_since_invalid_date(self):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6f50a02..961737a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -32,7 +32,8 @@
 
 class ServerActionsTestJSON(base.BaseV2ComputeTest):
     _interface = 'json'
-    resize_available = tempest.config.TempestConfig().compute.resize_available
+    resize_available = tempest.config.TempestConfig().\
+        compute_feature_enabled.resize
     run_ssh = tempest.config.TempestConfig().compute.run_ssh
 
     def setUp(self):
@@ -252,7 +253,6 @@
         self.assertEqual(202, resp.status)
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
-    @skip_because(bug="1233026")
     @attr(type='gate')
     def test_lock_unlock_server(self):
         # Lock the server,try server stop(exceptions throw),unlock it and retry
@@ -262,7 +262,7 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(server['status'], 'ACTIVE')
         # Locked server is not allowed to be stopped by non-admin user
-        self.assertRaises(exceptions.BadRequest,
+        self.assertRaises(exceptions.Conflict,
                           self.servers_client.stop, self.server_id)
         resp, server = self.servers_client.unlock_server(self.server_id)
         self.assertEqual(202, resp.status)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 0f753a0..c6e000c 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -359,6 +359,36 @@
                           self.client.get_console_output,
                           nonexistent_server, 10)
 
+    @attr(type=['negative', 'gate'])
+    def test_force_delete_nonexistent_server_id(self):
+        non_existent_server_id = str(uuid.uuid4())
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.force_delete_server,
+                          non_existent_server_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_force_delete_server_invalid_state(self):
+        # we can only force-delete a server in 'soft-delete' state
+        self.assertRaises(exceptions.Conflict,
+                          self.client.force_delete_server,
+                          self.server_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_restore_nonexistent_server_id(self):
+        non_existent_server_id = str(uuid.uuid4())
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.restore_soft_deleted_server,
+                          non_existent_server_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_restore_server_invalid_state(self):
+        # we can only restore-delete a server in 'soft-delete' state
+        self.assertRaises(exceptions.Conflict,
+                          self.client.restore_soft_deleted_server,
+                          self.server_id)
+
 
 class ServersNegativeTestXML(ServersNegativeTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/test_live_block_migration.py b/tempest/api/compute/test_live_block_migration.py
index bb8b372..7f68ab5 100644
--- a/tempest/api/compute/test_live_block_migration.py
+++ b/tempest/api/compute/test_live_block_migration.py
@@ -59,7 +59,8 @@
     def _migrate_server_to(self, server_id, dest_host):
         _resp, body = self.admin_servers_client.live_migrate_server(
             server_id, dest_host,
-            self.config.compute.use_block_migration_for_live_migration)
+            self.config.compute_feature_enabled.
+            block_migration_for_live_migration)
         return body
 
     def _get_host_other_than(self, host):
@@ -97,7 +98,7 @@
             self.volumes_client.wait_for_volume_status(volume_id, 'available')
         self.volumes_client.delete_volume(volume_id)
 
-    @testtools.skipIf(not CONF.compute.live_migration_available,
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
                       'Live migration not available')
     @attr(type='gate')
     def test_live_block_migration(self):
@@ -112,7 +113,7 @@
         self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
         self.assertEqual(target_host, self._get_host_for_server(server_id))
 
-    @testtools.skipIf(not CONF.compute.live_migration_available,
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
                       'Live migration not available')
     @attr(type='gate')
     def test_invalid_host_for_migration(self):
@@ -124,10 +125,12 @@
                           server_id, target_host)
         self.assertEqual('ACTIVE', self._get_server_status(server_id))
 
-    @testtools.skipIf(not CONF.compute.live_migration_available or
-                      not CONF.compute.use_block_migration_for_live_migration,
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
+                      CONF.compute_feature_enabled.
+                      block_migration_for_live_migration,
                       'Block Live migration not available')
-    @testtools.skipIf(not CONF.compute.block_migrate_supports_cinder_iscsi,
+    @testtools.skipIf(not CONF.compute_feature_enabled.
+                      block_migrate_cinder_iscsi,
                       'Block Live migration not configured for iSCSI')
     @attr(type='gate')
     def test_iscsi_volume(self):
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 4617bd2..475d055 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -58,6 +58,16 @@
                          sorted(quota_set.keys()))
         self.assertEqual(quota_set['id'], self.tenant_id)
 
+    @attr(type='smoke')
+    def test_compare_tenant_quotas_with_default_quotas(self):
+        # Tenants are created with the default quota values
+        resp, defualt_quota_set = \
+            self.client.get_default_quota_set(self.tenant_id)
+        self.assertEqual(200, resp.status)
+        resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(defualt_quota_set, tenant_quota_set)
+
 
 class QuotasTestXML(QuotasTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/v3/__init__.py b/tempest/api/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/__init__.py
diff --git a/tempest/api/compute/v3/images/__init__.py b/tempest/api/compute/v3/images/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/images/__init__.py
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
new file mode 100644
index 0000000..f3dfeec
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -0,0 +1,165 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+#
+#    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 import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils.data_utils import parse_image_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesV3TestJSON(base.BaseV3ComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ImagesV3TestJSON, 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
+
+        if compute.MULTI_USER:
+            if cls.config.compute.allow_tenant_isolation:
+                creds = cls.isolated_creds.get_alt_creds()
+                username, tenant_name, password = creds
+                cls.alt_manager = clients.Manager(username=username,
+                                                  password=password,
+                                                  tenant_name=tenant_name)
+            else:
+                # Use the alt_XXX credentials in the config file
+                cls.alt_manager = clients.AltManager()
+            cls.alt_client = cls.alt_manager.images_client
+
+    def __create_image__(self, server_id, name, meta=None):
+        resp, body = self.servers_client.create_image(server_id, name, meta)
+        image_id = parse_image_id(resp['location'])
+        self.addCleanup(self.client.delete_image, image_id)
+        self.client.wait_for_image_status(image_id, 'ACTIVE')
+        return resp, body
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_deleted_server(self):
+        # An image should not be created if the server instance is removed
+        resp, server = self.create_server(wait_until='ACTIVE')
+
+        # Delete server before trying to create server
+        self.servers_client.delete_server(server['id'])
+        self.servers_client.wait_for_server_termination(server['id'])
+        # Create a new image after server is deleted
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        self.assertRaises(exceptions.NotFound,
+                          self.__create_image__,
+                          server['id'], name, meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_invalid_server(self):
+        # An image should not be created with invalid server id
+        # Create a new image with invalid server id
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        resp = {}
+        resp['status'] = None
+        self.assertRaises(exceptions.NotFound, self.__create_image__,
+                          '!@#$%^&*()', name, meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_stopped_server(self):
+        resp, server = self.create_server(wait_until='ACTIVE')
+        self.servers_client.stop(server['id'])
+        self.servers_client.wait_for_server_status(server['id'],
+                                                   'SHUTOFF')
+        self.addCleanup(self.servers_client.delete_server, server['id'])
+        snapshot_name = rand_name('test-snap-')
+        resp, image = self.create_image_from_server(server['id'],
+                                                    name=snapshot_name,
+                                                    wait_until='active')
+        self.addCleanup(self.client.delete_image, image['id'])
+        self.assertEqual(snapshot_name, image['name'])
+
+    @attr(type='gate')
+    def test_delete_queued_image(self):
+        snapshot_name = rand_name('test-snap-')
+        resp, server = self.create_server(wait_until='ACTIVE')
+        self.addCleanup(self.servers_client.delete_server, server['id'])
+        resp, image = self.create_image_from_server(server['id'],
+                                                    name=snapshot_name,
+                                                    wait_until='queued')
+        resp, body = self.client.delete_image(image['id'])
+        self.assertEqual('200', resp['status'])
+
+    @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-')
+        test_uuid = ('a' * 35)
+        self.assertRaises(exceptions.NotFound,
+                          self.servers_client.create_image,
+                          test_uuid, snapshot_name)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_specify_uuid_37_characters_or_more(self):
+        # Return an error if Image ID passed is 37 characters or more
+        snapshot_name = rand_name('test-snap-')
+        test_uuid = ('a' * 37)
+        self.assertRaises(exceptions.NotFound,
+                          self.servers_client.create_image,
+                          test_uuid, snapshot_name)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_with_invalid_image_id(self):
+        # An image should not be deleted with invalid image id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          '!@$%^&*()')
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_non_existent_image(self):
+        # Return an error while trying to delete a non-existent image
+
+        non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          non_existent_image_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_blank_id(self):
+        # Return an error while trying to delete an image with blank Id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_non_hex_string_id(self):
+        # Return an error while trying to delete an image with non hex id
+        image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          image_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_negative_image_id(self):
+        # Return an error while trying to delete an image with negative id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_id_is_over_35_character_limit(self):
+        # Return an error while trying to delete image with id over limit
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
+
+
+class ImagesV3TestXML(ImagesV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/__init__.py b/tempest/api/compute/v3/servers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/servers/__init__.py
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
new file mode 100644
index 0000000..fb4214a
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -0,0 +1,278 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import base64
+import time
+
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class ServerActionsV3TestJSON(base.BaseV3ComputeTest):
+    _interface = 'json'
+    resize_available = tempest.config.TempestConfig().\
+        compute_feature_enabled.resize
+    run_ssh = tempest.config.TempestConfig().compute.run_ssh
+
+    def setUp(self):
+        # NOTE(afazekas): Normally we use the same server with all test cases,
+        # but if it has an issue, we build a new one
+        super(ServerActionsV3TestJSON, self).setUp()
+        # Check if the server is in a clean state after test
+        try:
+            self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+        except Exception:
+            # Rebuild server if something happened to it during a test
+            self.rebuild_server()
+
+    @classmethod
+    def setUpClass(cls):
+        super(ServerActionsV3TestJSON, cls).setUpClass()
+        cls.client = cls.servers_client
+        cls.rebuild_server()
+
+    @testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
+                          'Change password not available.')
+    @attr(type='gate')
+    def test_change_server_password(self):
+        # The server's password should be set to the provided password
+        new_password = 'Newpass1234'
+        resp, body = self.client.change_password(self.server_id, new_password)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Verify that the user can authenticate with the new password
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, new_password)
+            self.assertTrue(linux_client.can_authenticate())
+
+    @attr(type='smoke')
+    def test_reboot_server_hard(self):
+        # The server should be power cycled
+        if self.run_ssh:
+            # Get the time the server was last rebooted,
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            boot_time = linux_client.get_boot_time()
+
+        resp, body = self.client.reboot(self.server_id, 'HARD')
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Log in and verify the boot time has changed
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            new_boot_time = linux_client.get_boot_time()
+            self.assertGreater(new_boot_time, boot_time)
+
+    @skip_because(bug="1014647")
+    @attr(type='smoke')
+    def test_reboot_server_soft(self):
+        # The server should be signaled to reboot gracefully
+        if self.run_ssh:
+            # Get the time the server was last rebooted,
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            boot_time = linux_client.get_boot_time()
+
+        resp, body = self.client.reboot(self.server_id, 'SOFT')
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Log in and verify the boot time has changed
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            new_boot_time = linux_client.get_boot_time()
+            self.assertGreater(new_boot_time, boot_time)
+
+    @attr(type='smoke')
+    def test_rebuild_server(self):
+        # The server should be rebuilt using the provided image and data
+        meta = {'rebuild': 'server'}
+        new_name = rand_name('server')
+        file_contents = 'Test server rebuild.'
+        personality = [{'path': 'rebuild.txt',
+                       'contents': base64.b64encode(file_contents)}]
+        password = 'rebuildPassw0rd'
+        resp, rebuilt_server = self.client.rebuild(self.server_id,
+                                                   self.image_ref_alt,
+                                                   name=new_name,
+                                                   metadata=meta,
+                                                   personality=personality,
+                                                   admin_pass=password)
+
+        # Verify the properties in the initial response are correct
+        self.assertEqual(self.server_id, rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+        self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id']))
+
+        # Verify the server properties after the rebuild completes
+        self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
+        resp, server = self.client.get_server(rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+        self.assertEqual(new_name, rebuilt_server['name'])
+
+        if self.run_ssh:
+            # Verify that the user can authenticate with the provided password
+            linux_client = RemoteClient(server, self.ssh_user, password)
+            self.assertTrue(linux_client.can_authenticate())
+
+    def _detect_server_image_flavor(self, server_id):
+        # Detects the current server image flavor ref.
+        resp, server = self.client.get_server(self.server_id)
+        current_flavor = server['flavor']['id']
+        new_flavor_ref = self.flavor_ref_alt \
+            if int(current_flavor) == self.flavor_ref else self.flavor_ref
+        return int(current_flavor), int(new_flavor_ref)
+
+    @testtools.skipIf(not resize_available, 'Resize not available.')
+    @attr(type='smoke')
+    def test_resize_server_confirm(self):
+        # The server's RAM and disk space should be modified to that of
+        # the provided flavor
+
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+        self.client.confirm_resize(self.server_id)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        resp, server = self.client.get_server(self.server_id)
+        self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
+
+    @testtools.skipIf(not resize_available, 'Resize not available.')
+    @attr(type='gate')
+    def test_resize_server_revert(self):
+        # The server's RAM and disk space should return to its original
+        # values after a resize is reverted
+
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+        self.client.revert_resize(self.server_id)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        # Need to poll for the id change until lp#924371 is fixed
+        resp, server = self.client.get_server(self.server_id)
+        start = int(time.time())
+
+        while int(server['flavor']['id']) != previous_flavor_ref:
+            time.sleep(self.build_interval)
+            resp, server = self.client.get_server(self.server_id)
+
+            if int(time.time()) - start >= self.build_timeout:
+                message = 'Server %s failed to revert resize within the \
+                required time (%s s).' % (self.server_id, self.build_timeout)
+                raise exceptions.TimeoutException(message)
+
+    @attr(type='gate')
+    def test_get_console_output(self):
+        # Positive test:Should be able to GET the console output
+        # for a given server_id and number of lines
+        def get_output():
+            resp, output = self.servers_client.get_console_output(
+                self.server_id, 10)
+            self.assertEqual(200, resp.status)
+            self.assertTrue(output, "Console output was empty.")
+            lines = len(output.split('\n'))
+            self.assertEqual(lines, 10)
+        self.wait_for(get_output)
+
+    @skip_because(bug="1014683")
+    @attr(type='gate')
+    def test_get_console_output_server_id_in_reboot_status(self):
+        # Positive test:Should be able to GET the console output
+        # for a given server_id in reboot status
+        resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
+        self.servers_client.wait_for_server_status(self.server_id,
+                                                   'REBOOT')
+        resp, output = self.servers_client.get_console_output(self.server_id,
+                                                              10)
+        self.assertEqual(200, resp.status)
+        self.assertIsNotNone(output)
+        lines = len(output.split('\n'))
+        self.assertEqual(lines, 10)
+
+    @attr(type='gate')
+    def test_pause_unpause_server(self):
+        resp, server = self.client.pause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'PAUSED')
+        resp, server = self.client.unpause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_suspend_resume_server(self):
+        resp, server = self.client.suspend_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+        resp, server = self.client.resume_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_stop_start_server(self):
+        resp, server = self.servers_client.stop(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+        resp, server = self.servers_client.start(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_lock_unlock_server(self):
+        # Lock the server,try server stop(exceptions throw),unlock it and retry
+        resp, server = self.servers_client.lock_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        resp, server = self.servers_client.get_server(self.server_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(server['status'], 'ACTIVE')
+        # Locked server is not allowed to be stopped by non-admin user
+        self.assertRaises(exceptions.Conflict,
+                          self.servers_client.stop, self.server_id)
+        resp, server = self.servers_client.unlock_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        resp, server = self.servers_client.stop(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+        resp, server = self.servers_client.start(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+
+class ServerActionsV3TestXML(ServerActionsV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 4f54a15..ab0cb00 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -74,17 +74,6 @@
         cls.created_images.append(image['id'])
         return resp, image
 
-    @classmethod
-    def _check_version(cls, version):
-        __, versions = cls.client.get_versions()
-        if version == 'v2.0':
-            if 'v2.0' in versions:
-                return True
-        elif version == 'v1.0':
-            if 'v1.1' in versions or 'v1.0' in versions:
-                return True
-        return False
-
 
 class BaseV1ImageTest(BaseImageTest):
 
@@ -92,7 +81,7 @@
     def setUpClass(cls):
         super(BaseV1ImageTest, cls).setUpClass()
         cls.client = cls.os.image_client
-        if not cls._check_version('v1.0'):
+        if not cls.config.image_feature_enabled.api_v1:
             msg = "Glance API v1 not supported"
             raise cls.skipException(msg)
 
@@ -103,6 +92,6 @@
     def setUpClass(cls):
         super(BaseV2ImageTest, cls).setUpClass()
         cls.client = cls.os.image_client_v2
-        if not cls._check_version('v2.0'):
+        if not cls.config.image_feature_enabled.api_v2:
             msg = "Glance API v2 not supported"
             raise cls.skipException(msg)
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index eb3535f..ee6d656 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -20,6 +20,7 @@
 import random
 
 from tempest.api.image import base
+from tempest.common.utils import data_utils
 from tempest import exceptions
 from tempest.test import attr
 
@@ -42,29 +43,46 @@
                           'test', 'bare', 'wrong')
 
     @attr(type='gate')
-    def test_register_then_upload(self):
-        # Register, then upload an image
-        resp, body = self.create_image(name='New Name',
+    def test_register_upload_get_image_file(self):
+
+        """
+        Here we test these functionalities - Register image,
+        upload the image file, get image and get image file api's
+        """
+
+        image_name = data_utils.rand_name('image')
+        resp, body = self.create_image(name=image_name,
                                        container_format='bare',
                                        disk_format='raw',
                                        visibility='public')
         self.assertIn('id', body)
         image_id = body.get('id')
         self.assertIn('name', body)
-        self.assertEqual('New Name', body.get('name'))
+        self.assertEqual(image_name, body['name'])
         self.assertIn('visibility', body)
-        self.assertTrue(body.get('visibility') == 'public')
+        self.assertEqual('public', body['visibility'])
         self.assertIn('status', body)
-        self.assertEqual('queued', body.get('status'))
+        self.assertEqual('queued', body['status'])
 
         # Now try uploading an image file
-        image_file = StringIO.StringIO(('*' * 1024))
+        file_content = '*' * 1024
+        image_file = StringIO.StringIO(file_content)
         resp, body = self.client.store_image(image_id, image_file)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+
+        # Now try to get image details
+        resp, body = self.client.get_image(image_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(image_id, body['id'])
+        self.assertEqual(image_name, body['name'])
         self.assertIn('size', body)
         self.assertEqual(1024, body.get('size'))
 
+        # Now try get image file
+        resp, body = self.client.get_image_file(image_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(file_content, body)
+
 
 class ListImagesTest(base.BaseV2ImageTest):
 
@@ -107,6 +125,6 @@
             self.assertIn(image, image_list)
 
     @attr(type=['negative', 'gate'])
-    def test_get_image_meta_by_null_id(self):
+    def test_get_image_by_null_id(self):
         self.assertRaises(exceptions.NotFound,
-                          self.client.get_image_metadata, '')
+                          self.client.get_image, '')
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 7e3bde4..e37e462 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -33,13 +33,13 @@
         # Creating image tag and verify it.
         resp, body = self.client.add_image_tag(image_id, tag)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+        resp, body = self.client.get_image(image_id)
         self.assertEqual(resp.status, 200)
         self.assertIn(tag, body['tags'])
 
         # Deleting image tag and verify it.
         resp = self.client.delete_image_tag(image_id, tag)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+        resp, body = self.client.get_image(image_id)
         self.assertEqual(resp.status, 200)
         self.assertNotIn(tag, body['tags'])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 159c4f5..ed915c1 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -215,3 +215,19 @@
         vpnservice = body['vpnservice']
         cls.vpnservices.append(vpnservice)
         return vpnservice
+
+
+class BaseAdminNetworkTest(BaseNetworkTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseAdminNetworkTest, cls).setUpClass()
+        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 Administrative Network API credentials "
+                   "in configuration.")
+            raise cls.skipException(msg)
+        cls.admin_manager = clients.AdminManager(interface=cls._interface)
+        cls.admin_client = cls.admin_manager.network_client
diff --git a/tempest/api/network/base_security_groups.py b/tempest/api/network/base_security_groups.py
new file mode 100644
index 0000000..5ab1748
--- /dev/null
+++ b/tempest/api/network/base_security_groups.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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.network import base
+from tempest.common.utils import data_utils
+
+
+class BaseSecGroupTest(base.BaseNetworkTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseSecGroupTest, cls).setUpClass()
+
+    def _create_security_group(self):
+        # Create a security group
+        name = data_utils.rand_name('secgroup-')
+        resp, group_create_body = self.client.create_security_group(name)
+        self.assertEqual('201', resp['status'])
+        self.addCleanup(self._delete_security_group,
+                        group_create_body['security_group']['id'])
+        self.assertEqual(group_create_body['security_group']['name'], name)
+        return group_create_body, name
+
+    def _delete_security_group(self, secgroup_id):
+        resp, _ = self.client.delete_security_group(secgroup_id)
+        self.assertEqual(204, resp.status)
+        # Asserting that the security group is not found in the list
+        # after deletion
+        resp, list_body = self.client.list_security_groups()
+        self.assertEqual('200', resp['status'])
+        secgroup_list = list()
+        for secgroup in list_body['security_groups']:
+            secgroup_list.append(secgroup['id'])
+        self.assertNotIn(secgroup_id, secgroup_list)
+
+    def _delete_security_group_rule(self, rule_id):
+        resp, _ = self.client.delete_security_group_rule(rule_id)
+        self.assertEqual(204, resp.status)
+        # Asserting that the security group is not found in the list
+        # after deletion
+        resp, list_body = self.client.list_security_group_rules()
+        self.assertEqual('200', resp['status'])
+        rules_list = list()
+        for rule in list_body['security_group_rules']:
+            rules_list.append(rule['id'])
+        self.assertNotIn(rule_id, rules_list)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 2cfbf61..512d065 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -20,7 +20,10 @@
 from tempest.test import attr
 
 
-class RoutersTest(base.BaseNetworkTest):
+class RoutersTest(base.BaseAdminNetworkTest):
+    # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+    # as some router operations, such as enabling or disabling SNAT
+    # require admin credentials by default
     _interface = 'json'
 
     @classmethod
@@ -130,3 +133,99 @@
             interface['port_id'])
         self.assertEqual(show_port_body['port']['device_id'],
                          router['id'])
+
+    def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
+        resp, show_body = self.client.show_router(router_id)
+        self.assertEqual('200', resp['status'])
+        actual_ext_gw_info = show_body['router']['external_gateway_info']
+        if exp_ext_gw_info is None:
+            self.assertIsNone(actual_ext_gw_info)
+            return
+        # Verify only keys passed in exp_ext_gw_info
+        for k, v in exp_ext_gw_info.iteritems():
+            self.assertEqual(v, actual_ext_gw_info[k])
+
+    def _verify_gateway_port(self, router_id):
+        resp, list_body = self.admin_client.list_ports(
+            network_id=self.network_cfg.public_network_id,
+            device_id=router_id)
+        self.assertEqual(len(list_body['ports']), 1)
+        gw_port = list_body['ports'][0]
+        fixed_ips = gw_port['fixed_ips']
+        self.assertEqual(len(fixed_ips), 1)
+        resp, public_net_body = self.admin_client.show_network(
+            self.network_cfg.public_network_id)
+        public_subnet_id = public_net_body['network']['subnets'][0]
+        self.assertEqual(fixed_ips[0]['subnet_id'], public_subnet_id)
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway(self):
+        router = self.create_router(rand_name('router-'))
+        self.client.update_router(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id})
+        # Verify operation - router
+        resp, show_body = self.client.show_router(router['id'])
+        self.assertEqual('200', resp['status'])
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway_with_snat_explicit(self):
+        router = self.create_router(rand_name('router-'))
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': True})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': True})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway_without_snat(self):
+        router = self.create_router(rand_name('router-'))
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': False})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': False})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_unset_gateway(self):
+        router = self.create_router(
+            rand_name('router-'),
+            external_network_id=self.network_cfg.public_network_id)
+        self.client.update_router(router['id'], external_gateway_info={})
+        self._verify_router_gateway(router['id'])
+        # No gateway port expected
+        resp, list_body = self.admin_client.list_ports(
+            network_id=self.network_cfg.public_network_id,
+            device_id=router['id'])
+        self.assertFalse(list_body['ports'])
+
+    @attr(type='smoke')
+    def test_update_router_reset_gateway_without_snat(self):
+        router = self.create_router(
+            rand_name('router-'),
+            external_network_id=self.network_cfg.public_network_id)
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': False})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': False})
+        self._verify_gateway_port(router['id'])
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 9218f0c..9b0a3de 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -15,42 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.network import base
-from tempest.common.utils import data_utils
+from tempest.api.network import base_security_groups as base
 from tempest.test import attr
 
 
-class SecGroupTest(base.BaseNetworkTest):
+class SecGroupTest(base.BaseSecGroupTest):
     _interface = 'json'
 
-    @classmethod
-    def setUpClass(cls):
-        super(SecGroupTest, cls).setUpClass()
-
-    def _delete_security_group(self, secgroup_id):
-        resp, _ = self.client.delete_security_group(secgroup_id)
-        self.assertEqual(204, resp.status)
-        # Asserting that the security group is not found in the list
-        # after deletion
-        resp, list_body = self.client.list_security_groups()
-        self.assertEqual('200', resp['status'])
-        secgroup_list = list()
-        for secgroup in list_body['security_groups']:
-            secgroup_list.append(secgroup['id'])
-        self.assertNotIn(secgroup_id, secgroup_list)
-
-    def _delete_security_group_rule(self, rule_id):
-        resp, _ = self.client.delete_security_group_rule(rule_id)
-        self.assertEqual(204, resp.status)
-        # Asserting that the security group is not found in the list
-        # after deletion
-        resp, list_body = self.client.list_security_group_rules()
-        self.assertEqual('200', resp['status'])
-        rules_list = list()
-        for rule in list_body['security_group_rules']:
-            rules_list.append(rule['id'])
-        self.assertNotIn(rule_id, rules_list)
-
     @attr(type='smoke')
     def test_list_security_groups(self):
         # Verify the that security group belonging to tenant exist in list
@@ -66,13 +37,7 @@
 
     @attr(type='smoke')
     def test_create_show_delete_security_group(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, name = self._create_security_group()
 
         # Show details of the created security group
         resp, show_body = self.client.show_security_group(
@@ -90,13 +55,7 @@
 
     @attr(type='smoke')
     def test_create_show_delete_security_group_rule(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         # Create rules for each protocol
         protocols = ['tcp', 'udp', 'icmp']
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index daeb89f..cb0c247 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,46 +15,39 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.network import test_security_groups as base
-from tempest.common.utils import data_utils
+from tempest.api.network import base_security_groups as base
 from tempest import exceptions
 from tempest.test import attr
 import uuid
 
 
-class NegativeSecGroupTest(base.SecGroupTest):
+class NegativeSecGroupTest(base.BaseSecGroupTest):
     _interface = 'json'
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_show_non_existent_security_group(self):
         non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound, self.client.show_security_group,
                           non_exist_id)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_show_non_existent_security_group_rule(self):
         non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound,
                           self.client.show_security_group_rule,
                           non_exist_id)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_delete_non_existent_security_group(self):
-        non_exist_id = 'fictional-id'
+        non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound,
                           self.client.delete_security_group,
                           non_exist_id
                           )
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_create_security_group_rule_with_bad_protocol(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         #Create rule with bad protocol name
         pname = 'bad_protocol_name'
@@ -63,20 +56,15 @@
                           group_create_body['security_group']['id'],
                           protocol=pname)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_create_security_group_rule_with_invalid_ports(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         #Create rule with invalid ports
         states = [(-16, 80, 'Invalid value for port -16'),
                   (80, 79, 'port_range_min must be <= port_range_max'),
-                  (80, 65536, 'Invalid value for port 65536')]
+                  (80, 65536, 'Invalid value for port 65536'),
+                  (-16, 65536, 'Invalid value for port')]
         for pmin, pmax, msg in states:
             ex = self.assertRaises(exceptions.BadRequest,
                                    self.client.create_security_group_rule,
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 65fe1ac..a90d3f4 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -20,14 +20,14 @@
 from tempest import clients
 from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
-import tempest.config
+from tempest import config
 from tempest import exceptions
 from tempest.test import attr
 
 
 class AccountQuotasTest(base.BaseObjectTest):
     accounts_quotas_available = \
-        tempest.config.TempestConfig().object_storage.accounts_quotas_available
+        config.TempestConfig().object_storage_feature_enabled.accounts_quotas
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 31fe711..2e0d76a 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -33,7 +33,7 @@
 class ContainerQuotasTest(base.BaseObjectTest):
     """Attemps to test the perfect behavior of quotas in a container."""
     container_quotas_available = \
-        config.TempestConfig().object_storage.container_quotas_available
+        config.TempestConfig().object_storage_feature_enabled.container_quotas
 
     def setUp(self):
         """Creates and sets a container with quotas.
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
new file mode 100644
index 0000000..798ea4f
--- /dev/null
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# 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.object_storage import base
+from tempest import clients
+from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
+
+
+class HealthcheckTest(base.BaseObjectTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(HealthcheckTest, cls).setUpClass()
+
+        # creates a test user. The test user will set its base_url to the Swift
+        # endpoint and test the healthcheck feature.
+        cls.data.setup_test_user()
+
+        cls.os_test_user = clients.Manager(
+            cls.data.test_user,
+            cls.data.test_password,
+            cls.data.test_tenant)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.data.teardown_all()
+        super(HealthcheckTest, cls).tearDownClass()
+
+    def setUp(self):
+        super(HealthcheckTest, self).setUp()
+        client = self.os_test_user.account_client
+        client._set_auth()
+
+        # Turning http://.../v1/foobar into http://.../
+        client.base_url = "/".join(client.base_url.split("/")[:-2])
+
+    def tearDown(self):
+        # clear the base_url for subsequent requests
+        self.os_test_user.account_client.base_url = None
+        super(HealthcheckTest, self).tearDown()
+
+    @attr('gate')
+    def test_get_healthcheck(self):
+
+        resp, _ = self.os_test_user.account_client.get("healthcheck", {})
+
+        # The status is expected to be 200
+        self.assertIn(int(resp['status']), HTTP_SUCCESS)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 797aa71..eada639 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -28,7 +28,7 @@
     @classmethod
     def setUpClass(cls):
         super(VolumeMultiBackendTest, cls).setUpClass()
-        if not cls.config.volume.multi_backend_enabled:
+        if not cls.config.volume_feature_enabled.multi_backend:
             raise cls.skipException("Cinder multi-backend feature disabled")
 
         cls.backend1_name = cls.config.volume.backend1_name
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
new file mode 100644
index 0000000..4063eef
--- /dev/null
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -0,0 +1,89 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD
+# 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.volume.base import BaseVolumeAdminTest
+from tempest.common.utils import data_utils as utils
+from tempest.test import attr
+
+
+class VolumesActionsTest(BaseVolumeAdminTest):
+    _interface = "json"
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesActionsTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+
+        # Create admin volume client
+        cls.admin_volume_client = cls.os_adm.volumes_client
+
+        # Create a test shared volume for tests
+        vol_name = utils.rand_name(cls.__name__ + '-Volume-')
+
+        resp, cls.volume = cls.client.create_volume(size=1,
+                                                    display_name=vol_name)
+        cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+
+    @classmethod
+    def tearDownClass(cls):
+        # Delete the test volume
+        cls.client.delete_volume(cls.volume['id'])
+        cls.client.wait_for_resource_deletion(cls.volume['id'])
+
+        super(VolumesActionsTest, cls).tearDownClass()
+
+    def _reset_volume_status(self, volume_id, status):
+        #Reset the volume status
+        resp, body = self.admin_volume_client.reset_volume_status(volume_id,
+                                                                  status)
+        return resp, body
+
+    def tearDown(self):
+        # Set volume's status to available after test
+        self._reset_volume_status(self.volume['id'], 'available')
+        super(VolumesActionsTest, self).tearDown()
+
+    @attr(type='gate')
+    def test_volume_reset_status(self):
+        # test volume reset status : available->error->available
+        resp, body = self._reset_volume_status(self.volume['id'], 'error')
+        self.assertEqual(202, resp.status)
+        resp_get, volume_get = self.admin_volume_client.get_volume(
+            self.volume['id'])
+        self.assertEqual('error', volume_get['status'])
+
+    @attr(type='gate')
+    def test_volume_begin_detaching(self):
+        # test volume begin detaching : available -> detaching -> available
+        resp, body = self.client.volume_begin_detaching(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        resp_get, volume_get = self.client.get_volume(self.volume['id'])
+        self.assertEqual('detaching', volume_get['status'])
+
+    @attr(type='gate')
+    def test_volume_roll_detaching(self):
+        # test volume roll detaching : detaching -> in-use -> available
+        resp, body = self.client.volume_begin_detaching(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        resp, body = self.client.volume_roll_detaching(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        resp_get, volume_get = self.client.get_volume(self.volume['id'])
+        self.assertEqual('in-use', volume_get['status'])
+
+
+class VolumesActionsTestXML(VolumesActionsTest):
+    _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 09131e2..30c2c74 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -16,7 +16,7 @@
 #    under the License.
 
 from tempest.api.volume.base import BaseVolumeTest
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
 from tempest.test import attr
 from tempest.test import services
 from tempest.test import stresstest
@@ -32,8 +32,8 @@
         cls.image_client = cls.os.image_client
 
         # Create a test shared instance and volume for attach/detach tests
-        srv_name = rand_name(cls.__name__ + '-Instance-')
-        vol_name = rand_name(cls.__name__ + '-Volume-')
+        srv_name = data_utils.rand_name(cls.__name__ + '-Instance-')
+        vol_name = data_utils.rand_name(cls.__name__ + '-Volume-')
         resp, cls.server = cls.servers_client.create_server(srv_name,
                                                             cls.image_ref,
                                                             cls.flavor_ref)
@@ -102,7 +102,7 @@
         # it is shared with the other tests. After it is uploaded in Glance,
         # there is no way to delete it from Cinder, so we delete it from Glance
         # using the Glance image_client and from Cinder via tearDownClass.
-        image_name = rand_name('Image-')
+        image_name = data_utils.rand_name('Image-')
         resp, body = self.client.upload_volume(self.volume['id'],
                                                image_name,
                                                self.config.volume.disk_format)
@@ -112,6 +112,34 @@
         self.image_client.wait_for_image_status(image_id, 'active')
         self.client.wait_for_volume_status(self.volume['id'], 'available')
 
+    @attr(type='gate')
+    def test_volume_extend(self):
+        # Extend Volume Test.
+        extend_size = int(self.volume['size']) + 1
+        resp, body = self.client.extend_volume(self.volume['id'], extend_size)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_volume_status(self.volume['id'], 'available')
+        resp, volume = self.client.get_volume(self.volume['id'])
+        self.assertEqual(200, resp.status)
+        self.assertEqual(int(volume['size']), extend_size)
+
+    @attr(type='gate')
+    def test_reserve_unreserve_volume(self):
+        # Mark volume as reserved.
+        resp, body = self.client.reserve_volume(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        # To get the volume info
+        resp, body = self.client.get_volume(self.volume['id'])
+        self.assertEqual(200, resp.status)
+        self.assertIn('attaching', body['status'])
+        # Unmark volume as reserved.
+        resp, body = self.client.unreserve_volume(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        # To get the volume info
+        resp, body = self.client.get_volume(self.volume['id'])
+        self.assertEqual(200, resp.status)
+        self.assertIn('available', body['status'])
+
 
 class VolumesActionsTestXML(VolumesActionsTest):
     _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 02adc5d..538d5be 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -177,6 +177,66 @@
                           self.client.detach_volume,
                           'xxx')
 
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_size_smaller_than_original_size(self):
+        # Extend volume with smaller size than original size.
+        extend_size = 0
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_non_number_size(self):
+        # Extend volume when size is non number.
+        extend_size = 'abc'
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_None_size(self):
+        # Extend volume with None size.
+        extend_size = None
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_nonexistent_volume_id(self):
+        # Extend volume size when volume is nonexistent.
+        extend_size = int(self.volume['size']) + 1
+        self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+                          str(uuid.uuid4()), extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_without_passing_volume_id(self):
+        # Extend volume size when passing volume id is None.
+        extend_size = int(self.volume['size']) + 1
+        self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+                          None, extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_reserve_volume_with_nonexistent_volume_id(self):
+        self.assertRaises(exceptions.NotFound,
+                          self.client.reserve_volume,
+                          str(uuid.uuid4()))
+
+    @attr(type=['negative', 'gate'])
+    def test_unreserve_volume_with_nonexistent_volume_id(self):
+        self.assertRaises(exceptions.NotFound,
+                          self.client.unreserve_volume,
+                          str(uuid.uuid4()))
+
+    @attr(type=['negative', 'gate'])
+    def test_reserve_volume_with_negative_volume_status(self):
+        # Mark volume as reserved.
+        resp, body = self.client.reserve_volume(self.volume['id'])
+        self.assertEqual(202, resp.status)
+        # Mark volume which is marked as reserved before
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.reserve_volume,
+                          self.volume['id'])
+        # Unmark volume as reserved.
+        resp, body = self.client.unreserve_volume(self.volume['id'])
+        self.assertEqual(202, resp.status)
+
 
 class VolumesNegativeTestXML(VolumesNegativeTest):
     _interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
new file mode 100644
index 0000000..04a4774
--- /dev/null
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -0,0 +1,44 @@
+# 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 uuid
+
+from tempest.api.volume import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class VolumesSnapshotNegativeTest(base.BaseVolumeTest):
+    _interface = "json"
+
+    @attr(type=['negative', 'gate'])
+    def test_create_snapshot_with_nonexistent_volume_id(self):
+        # Create a snapshot with nonexistent volume id
+        s_name = data_utils.rand_name('snap')
+        self.assertRaises(exceptions.NotFound,
+                          self.snapshots_client.create_snapshot,
+                          str(uuid.uuid4()), display_name=s_name)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_snapshot_without_passing_volume_id(self):
+        # Create a snapshot without passing volume id
+        s_name = data_utils.rand_name('snap')
+        self.assertRaises(exceptions.NotFound,
+                          self.snapshots_client.create_snapshot,
+                          None, display_name=s_name)
+
+
+class VolumesSnapshotNegativeTestXML(VolumesSnapshotNegativeTest):
+    _interface = "xml"
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index b082b1e..bd1b44f 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -33,7 +33,7 @@
                 default=True,
                 help="enable cli tests"),
     cfg.StrOpt('cli_dir',
-               default='/usr/local/bin/',
+               default='/usr/local/bin',
                help="directory where python client binaries are located"),
     cfg.IntOpt('timeout',
                default=15,
@@ -80,6 +80,12 @@
         return self.cmd_with_auth(
             'glance', action, flags, params, admin, fail_ok)
 
+    def ceilometer(self, action, flags='', params='', admin=True,
+                   fail_ok=False):
+        """Executes ceilometer command for the given action."""
+        return self.cmd_with_auth(
+            'ceilometer', action, flags, params, admin, fail_ok)
+
     def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
         """Executes cinder command for the given action."""
         return self.cmd_with_auth(
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
new file mode 100644
index 0000000..7f2864f
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -0,0 +1,51 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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 oslo.config import cfg
+
+import tempest.cli
+from tempest.openstack.common import log as logging
+
+CONF = cfg.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyCeilometerClientTest(tempest.cli.ClientTestBase):
+    """Basic, read-only tests for Ceilometer CLI client.
+
+    Checks return values and output of read-only commands.
+    These tests do not presume any content, nor do they create
+    their own. They only verify the structure of output if present.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        if (not CONF.service_available.ceilometer):
+            msg = ("Skiping all Ceilometer cli tests because it is"
+                   "not available")
+            raise cls.skipException(msg)
+        super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass()
+
+    def test_ceilometer_meter_list(self):
+        self.ceilometer('meter-list')
+
+    def test_ceilometer_resource_list(self):
+        self.ceilometer('resource-list')
+
+    def test_ceilometermeter_alarm_list(self):
+        self.ceilometer('alarm-list')
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index d02c60b..a5a229c 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -18,9 +18,13 @@
 import re
 import subprocess
 
+from oslo.config import cfg
+
 import tempest.cli
 from tempest.openstack.common import log as logging
 
+CONF = cfg.CONF
+
 
 LOG = logging.getLogger(__name__)
 
@@ -45,6 +49,14 @@
             'ID', 'Name', 'Disk Format', 'Container Format',
             'Size', 'Status'])
 
+    def test_glance_member_list(self):
+        tenant_name = '--tenant-id %s' % self.identity.admin_tenant_name
+        out = self.glance('member-list',
+                          params=tenant_name)
+        endpoints = self.parser.listing(out)
+        self.assertTableStruct(endpoints,
+                               ['Image ID', 'Member ID', 'Can Share'])
+
     def test_glance_help(self):
         help_text = self.glance('help')
         lines = help_text.split('\n')
@@ -64,3 +76,14 @@
                                'member-add', 'member-create', 'member-delete',
                                'member-list'))
         self.assertFalse(wanted_commands - commands)
+
+    # Optional arguments:
+
+    def test_glance_version(self):
+        self.glance('', flags='--version')
+
+    def test_glance_debug_list(self):
+        self.glance('image-list', flags='--debug')
+
+    def test_glance_timeout(self):
+        self.glance('image-list', flags='--timeout %d' % CONF.cli.timeout)
diff --git a/tempest/cli/simple_read_only/test_nova_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
index 9a33556..524db5d 100644
--- a/tempest/cli/simple_read_only/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -55,11 +55,11 @@
                          self.nova_manage('', '--version', merge_stderr=True))
 
     def test_debug_flag(self):
-        self.assertNotEqual("", self.nova_manage('instance_type list',
+        self.assertNotEqual("", self.nova_manage('flavor list',
                             '--debug'))
 
     def test_verbose_flag(self):
-        self.assertNotEqual("", self.nova_manage('instance_type list',
+        self.assertNotEqual("", self.nova_manage('flavor list',
                             '--verbose'))
 
     # test actions
@@ -68,8 +68,6 @@
 
     def test_flavor_list(self):
         self.assertNotEqual("", self.nova_manage('flavor list'))
-        self.assertEqual(self.nova_manage('instance_type list'),
-                         self.nova_manage('flavor list'))
 
     def test_db_archive_deleted_rows(self):
         # make sure command doesn't error out
diff --git a/tempest/clients.py b/tempest/clients.py
index dd104a7..156df30 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -46,6 +46,9 @@
     TenantUsagesClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.servers_client import \
+    ServersV3ClientJSON
+from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
 from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
 from tempest.services.compute.xml.availability_zone_client import \
     AvailabilityZoneClientXML
@@ -167,6 +170,7 @@
 
         if interface == 'xml':
             self.servers_client = ServersClientXML(*client_args)
+            self.servers_v3_client = ServersV3ClientXML(*client_args)
             self.limits_client = LimitsClientXML(*client_args)
             self.images_client = ImagesClientXML(*client_args)
             self.keypairs_client = KeyPairsClientXML(*client_args)
@@ -205,6 +209,7 @@
 
         elif interface == 'json':
             self.servers_client = ServersClientJSON(*client_args)
+            self.servers_v3_client = ServersV3ClientJSON(*client_args)
             self.limits_client = LimitsClientJSON(*client_args)
             self.images_client = ImagesClientJSON(*client_args)
             self.keypairs_client = KeyPairsClientJSON(*client_args)
diff --git a/tempest/common/tempest_fixtures.py b/tempest/common/tempest_fixtures.py
index ebc9ad3..73c02e8 100644
--- a/tempest/common/tempest_fixtures.py
+++ b/tempest/common/tempest_fixtures.py
@@ -15,7 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.openstack.common import lockutils
+from tempest.openstack.common.fixture import lockutils
 
 
 class LockFixture(lockutils.LockFixture):
diff --git a/tempest/config.py b/tempest/config.py
index 062eb12..76461fb 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -137,28 +137,6 @@
                default="password",
                help="Password used to authenticate to an instance using "
                     "the alternate image."),
-    cfg.BoolOpt('resize_available',
-                default=False,
-                help="Does the test environment support resizing?"),
-    cfg.BoolOpt('live_migration_available',
-                default=False,
-                help="Does the test environment support live migration "
-                     "available?"),
-    cfg.BoolOpt('use_block_migration_for_live_migration',
-                default=False,
-                help="Does the test environment use block devices for live "
-                     "migration"),
-    cfg.BoolOpt('block_migrate_supports_cinder_iscsi',
-                default=False,
-                help="Does the test environment block migration support "
-                     "cinder iSCSI volumes"),
-    cfg.BoolOpt('change_password_available',
-                default=False,
-                help="Does the test environment support changing the admin "
-                     "password?"),
-    cfg.BoolOpt('create_image_enabled',
-                default=False,
-                help="Does the test environment support snapshots?"),
     cfg.IntOpt('build_interval',
                default=10,
                help="Time in seconds between build status checks."),
@@ -208,22 +186,57 @@
                     "of identity.region is used instead. If no such region "
                     "is found in the service catalog, the first found one is "
                     "used."),
+    cfg.StrOpt('catalog_v3_type',
+               default='computev3',
+               help="Catalog type of the Compute v3 service."),
     cfg.StrOpt('path_to_private_key',
                default=None,
                help="Path to a private key file for SSH access to remote "
                     "hosts"),
-    cfg.BoolOpt('disk_config_enabled',
-                default=True,
-                help="If false, skip disk config tests"),
-    cfg.BoolOpt('flavor_extra_enabled',
-                default=True,
-                help="If false, skip flavor extra data test"),
     cfg.StrOpt('volume_device_name',
                default='vdb',
                help="Expected device name when a volume is attached to "
                     "an instance")
 ]
 
+compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
+                                      title="Enabled Compute Service Features")
+
+ComputeFeaturesGroup = [
+    cfg.BoolOpt('api_v3',
+                default=False,
+                help="If false, skip all nova v3 tests."),
+    cfg.BoolOpt('disk_config',
+                default=True,
+                help="If false, skip disk config tests"),
+    cfg.BoolOpt('flavor_extra',
+                default=True,
+                help="If false, skip flavor extra data test"),
+    cfg.BoolOpt('change_password',
+                default=False,
+                help="Does the test environment support changing the admin "
+                     "password?"),
+    cfg.BoolOpt('create_image',
+                default=False,
+                help="Does the test environment support snapshots?"),
+    cfg.BoolOpt('resize',
+                default=False,
+                help="Does the test environment support resizing?"),
+    cfg.BoolOpt('live_migration',
+                default=False,
+                help="Does the test environment support live migration "
+                     "available?"),
+    cfg.BoolOpt('block_migration_for_live_migration',
+                default=False,
+                help="Does the test environment use block devices for live "
+                     "migration"),
+    cfg.BoolOpt('block_migrate_cinder_iscsi',
+                default=False,
+                help="Does the test environment block migration support "
+                     "cinder iSCSI volumes")
+]
+
+
 compute_admin_group = cfg.OptGroup(name='compute-admin',
                                    title="Compute Admin Options")
 
@@ -245,9 +258,6 @@
                            title="Image Service Options")
 
 ImageGroup = [
-    cfg.StrOpt('api_version',
-               default='1',
-               help="Version of the API"),
     cfg.StrOpt('catalog_type',
                default='image',
                help='Catalog type of the Image service.'),
@@ -263,6 +273,17 @@
                help='http accessible image')
 ]
 
+image_feature_group = cfg.OptGroup(name='image-feature-enabled',
+                                   title='Enabled image service features')
+
+ImageFeaturesGroup = [
+    cfg.BoolOpt('api_v2',
+                default=True,
+                help="Is the v2 image API enabled"),
+    cfg.BoolOpt('api_v1',
+                default=True,
+                help="Is the v1 image API enabled"),
+]
 
 network_group = cfg.OptGroup(name='network',
                              title='Network Service Options')
@@ -309,7 +330,7 @@
                help='Timeout in seconds to wait for a volume to become'
                     'available.'),
     cfg.StrOpt('catalog_type',
-               default='Volume',
+               default='volume',
                help="Catalog type of the Volume Service"),
     cfg.StrOpt('region',
                default='',
@@ -317,9 +338,6 @@
                     "of identity.region is used instead. If no such region "
                     "is found in the service catalog, the first found one is "
                     "used."),
-    cfg.BoolOpt('multi_backend_enabled',
-                default=False,
-                help="Runs Cinder multi-backend test (requires 2 backends)"),
     cfg.StrOpt('backend1_name',
                default='BACKEND_1',
                help="Name of the backend1 (must be declared in cinder.conf)"),
@@ -337,6 +355,15 @@
                help='Disk format to use when copying a volume to image'),
 ]
 
+volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
+                                    title='Enabled Cinder Features')
+
+VolumeFeaturesGroup = [
+    cfg.BoolOpt('multi_backend',
+                default=False,
+                help="Runs Cinder multi-backend test (requires 2 backends)")
+]
+
 
 object_storage_group = cfg.OptGroup(name='object-storage',
                                     title='Object Storage Service Options')
@@ -351,27 +378,34 @@
                     "value of identity.region is used instead. If no such "
                     "region is found in the service catalog, the first found "
                     "one is used."),
-    cfg.StrOpt('container_sync_timeout',
+    cfg.IntOpt('container_sync_timeout',
                default=120,
                help="Number of seconds to time on waiting for a container"
                     "to container synchronization complete."),
-    cfg.StrOpt('container_sync_interval',
+    cfg.IntOpt('container_sync_interval',
                default=5,
                help="Number of seconds to wait while looping to check the"
                     "status of a container to container synchronization"),
-    cfg.BoolOpt('accounts_quotas_available',
-                default=True,
-                help="Set to True if the Account Quota middleware is enabled"),
-    cfg.BoolOpt('container_quotas_available',
-                default=True,
-                help="Set to True if the container quota middleware "
-                     "is enabled"),
     cfg.StrOpt('operator_role',
                default='Member',
                help="Role to add to users created for swift tests to "
                     "enable creating containers"),
 ]
 
+object_storage_feature_group = cfg.OptGroup(
+    name='object-storage-feature-enabled',
+    title='Enabled object-storage features')
+
+ObjectStoreFeaturesGroup = [
+    cfg.BoolOpt('container_quotas',
+                default=True,
+                help="Set to True if the container quota middleware "
+                     "is enabled"),
+    cfg.BoolOpt('accounts_quotas',
+                default=True,
+                help="Set to True if the Account Quota middleware is enabled"),
+]
+
 
 orchestration_group = cfg.OptGroup(name='orchestration',
                                    title='Orchestration Service Options')
@@ -499,10 +533,10 @@
     cfg.StrOpt('target_logfiles',
                default=None,
                help='regexp for list of log files.'),
-    cfg.StrOpt('log_check_interval',
+    cfg.IntOpt('log_check_interval',
                default=60,
                help='time (in seconds) between log file error checks.'),
-    cfg.StrOpt('default_thread_number_per_action',
+    cfg.IntOpt('default_thread_number_per_action',
                default=4,
                help='The number of threads created while stress test.')
 ]
@@ -557,6 +591,9 @@
     cfg.BoolOpt('heat',
                 default=False,
                 help="Whether or not Heat is expected to be available"),
+    cfg.BoolOpt('ceilometer',
+                default=True,
+                help="Whether or not Ceilometer is expected to be available"),
     cfg.BoolOpt('horizon',
                 default=True,
                 help="Whether or not Horizon is expected to be available"),
@@ -611,11 +648,18 @@
         LOG.info("Using tempest config file %s" % path)
 
         register_opt_group(cfg.CONF, compute_group, ComputeGroup)
+        register_opt_group(cfg.CONF, compute_features_group,
+                           ComputeFeaturesGroup)
         register_opt_group(cfg.CONF, identity_group, IdentityGroup)
         register_opt_group(cfg.CONF, image_group, ImageGroup)
+        register_opt_group(cfg.CONF, image_feature_group, ImageFeaturesGroup)
         register_opt_group(cfg.CONF, network_group, NetworkGroup)
         register_opt_group(cfg.CONF, volume_group, VolumeGroup)
+        register_opt_group(cfg.CONF, volume_feature_group,
+                           VolumeFeaturesGroup)
         register_opt_group(cfg.CONF, object_storage_group, ObjectStoreGroup)
+        register_opt_group(cfg.CONF, object_storage_feature_group,
+                           ObjectStoreFeaturesGroup)
         register_opt_group(cfg.CONF, orchestration_group, OrchestrationGroup)
         register_opt_group(cfg.CONF, dashboard_group, DashboardGroup)
         register_opt_group(cfg.CONF, boto_group, BotoGroup)
@@ -626,11 +670,16 @@
                            ServiceAvailableGroup)
         register_opt_group(cfg.CONF, debug_group, DebugGroup)
         self.compute = cfg.CONF.compute
+        self.compute_feature_enabled = cfg.CONF['compute-feature-enabled']
         self.identity = cfg.CONF.identity
         self.images = cfg.CONF.image
+        self.image_feature_enabled = cfg.CONF['image-feature-enabled']
         self.network = cfg.CONF.network
         self.volume = cfg.CONF.volume
+        self.volume_feature_enabled = cfg.CONF['volume-feature-enabled']
         self.object_storage = cfg.CONF['object-storage']
+        self.object_storage_feature_enabled = cfg.CONF[
+            'object-storage-feature-enabled']
         self.orchestration = cfg.CONF.orchestration
         self.dashboard = cfg.CONF.dashboard
         self.boto = cfg.CONF.boto
diff --git a/tempest/openstack/common/config/__init__.py b/tempest/openstack/common/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/config/__init__.py
diff --git a/tempest/openstack/common/config/generator.py b/tempest/openstack/common/config/generator.py
new file mode 100644
index 0000000..373f9a6
--- /dev/null
+++ b/tempest/openstack/common/config/generator.py
@@ -0,0 +1,268 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 SINA 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.
+#
+
+"""Extracts OpenStack config option info from module(s)."""
+
+from __future__ import print_function
+
+import imp
+import os
+import re
+import socket
+import sys
+import textwrap
+
+from oslo.config import cfg
+
+from tempest.openstack.common import gettextutils
+from tempest.openstack.common import importutils
+
+gettextutils.install('tempest')
+
+STROPT = "StrOpt"
+BOOLOPT = "BoolOpt"
+INTOPT = "IntOpt"
+FLOATOPT = "FloatOpt"
+LISTOPT = "ListOpt"
+MULTISTROPT = "MultiStrOpt"
+
+OPT_TYPES = {
+    STROPT: 'string value',
+    BOOLOPT: 'boolean value',
+    INTOPT: 'integer value',
+    FLOATOPT: 'floating point value',
+    LISTOPT: 'list value',
+    MULTISTROPT: 'multi valued',
+}
+
+OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
+                                              FLOATOPT, LISTOPT,
+                                              MULTISTROPT]))
+
+PY_EXT = ".py"
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                       "../../../../"))
+WORDWRAP_WIDTH = 60
+
+
+def generate(srcfiles):
+    mods_by_pkg = dict()
+    for filepath in srcfiles:
+        pkg_name = filepath.split(os.sep)[1]
+        mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
+                            os.path.basename(filepath).split('.')[0]])
+        mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
+    # NOTE(lzyeval): place top level modules before packages
+    pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
+    pkg_names.sort()
+    ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
+    ext_names.sort()
+    pkg_names.extend(ext_names)
+
+    # opts_by_group is a mapping of group name to an options list
+    # The options list is a list of (module, options) tuples
+    opts_by_group = {'DEFAULT': []}
+
+    for module_name in os.getenv(
+            "OSLO_CONFIG_GENERATOR_EXTRA_MODULES", "").split(','):
+        module = _import_module(module_name)
+        if module:
+            for group, opts in _list_opts(module):
+                opts_by_group.setdefault(group, []).append((module_name, opts))
+
+    for pkg_name in pkg_names:
+        mods = mods_by_pkg.get(pkg_name)
+        mods.sort()
+        for mod_str in mods:
+            if mod_str.endswith('.__init__'):
+                mod_str = mod_str[:mod_str.rfind(".")]
+
+            mod_obj = _import_module(mod_str)
+            if not mod_obj:
+                raise RuntimeError("Unable to import module %s" % mod_str)
+
+            for group, opts in _list_opts(mod_obj):
+                opts_by_group.setdefault(group, []).append((mod_str, opts))
+
+    print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
+    for group, opts in opts_by_group.items():
+        print_group_opts(group, opts)
+
+
+def _import_module(mod_str):
+    try:
+        if mod_str.startswith('bin.'):
+            imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
+            return sys.modules[mod_str[4:]]
+        else:
+            return importutils.import_module(mod_str)
+    except ImportError as ie:
+        sys.stderr.write("%s\n" % str(ie))
+        return None
+    except Exception:
+        return None
+
+
+def _is_in_group(opt, group):
+    "Check if opt is in group."
+    for key, value in group._opts.items():
+        if value['opt'] == opt:
+            return True
+    return False
+
+
+def _guess_groups(opt, mod_obj):
+    # is it in the DEFAULT group?
+    if _is_in_group(opt, cfg.CONF):
+        return 'DEFAULT'
+
+    # what other groups is it in?
+    for key, value in cfg.CONF.items():
+        if isinstance(value, cfg.CONF.GroupAttr):
+            if _is_in_group(opt, value._group):
+                return value._group.name
+
+    raise RuntimeError(
+        "Unable to find group for option %s, "
+        "maybe it's defined twice in the same group?"
+        % opt.name
+    )
+
+
+def _list_opts(obj):
+    def is_opt(o):
+        return (isinstance(o, cfg.Opt) and
+                not isinstance(o, cfg.SubCommandOpt))
+
+    opts = list()
+    for attr_str in dir(obj):
+        attr_obj = getattr(obj, attr_str)
+        if is_opt(attr_obj):
+            opts.append(attr_obj)
+        elif (isinstance(attr_obj, list) and
+              all(map(lambda x: is_opt(x), attr_obj))):
+            opts.extend(attr_obj)
+
+    ret = {}
+    for opt in opts:
+        ret.setdefault(_guess_groups(opt, obj), []).append(opt)
+    return ret.items()
+
+
+def print_group_opts(group, opts_by_module):
+    print("[%s]" % group)
+    print('')
+    for mod, opts in opts_by_module:
+        print('#')
+        print('# Options defined in %s' % mod)
+        print('#')
+        print('')
+        for opt in opts:
+            _print_opt(opt)
+        print('')
+
+
+def _get_my_ip():
+    try:
+        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        csock.connect(('8.8.8.8', 80))
+        (addr, port) = csock.getsockname()
+        csock.close()
+        return addr
+    except socket.error:
+        return None
+
+
+def _sanitize_default(name, value):
+    """Set up a reasonably sensible default for pybasedir, my_ip and host."""
+    if value.startswith(sys.prefix):
+        # NOTE(jd) Don't use os.path.join, because it is likely to think the
+        # second part is an absolute pathname and therefore drop the first
+        # part.
+        value = os.path.normpath("/usr/" + value[len(sys.prefix):])
+    elif value.startswith(BASEDIR):
+        return value.replace(BASEDIR, '/usr/lib/python/site-packages')
+    elif BASEDIR in value:
+        return value.replace(BASEDIR, '')
+    elif value == _get_my_ip():
+        return '10.0.0.1'
+    elif value == socket.gethostname() and 'host' in name:
+        return 'tempest'
+    elif value.strip() != value:
+        return '"%s"' % value
+    return value
+
+
+def _print_opt(opt):
+    opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
+    if not opt_help:
+        sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
+        opt_help = ""
+    opt_type = None
+    try:
+        opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
+    except (ValueError, AttributeError) as err:
+        sys.stderr.write("%s\n" % str(err))
+        sys.exit(1)
+    opt_help += ' (' + OPT_TYPES[opt_type] + ')'
+    print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
+    if opt.deprecated_opts:
+        for deprecated_opt in opt.deprecated_opts:
+            if deprecated_opt.name:
+                deprecated_group = (deprecated_opt.group if
+                                    deprecated_opt.group else "DEFAULT")
+                print('# Deprecated group/name - [%s]/%s' %
+                      (deprecated_group,
+                       deprecated_opt.name))
+    try:
+        if opt_default is None:
+            print('#%s=<None>' % opt_name)
+        elif opt_type == STROPT:
+            assert(isinstance(opt_default, basestring))
+            print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
+                                                          opt_default)))
+        elif opt_type == BOOLOPT:
+            assert(isinstance(opt_default, bool))
+            print('#%s=%s' % (opt_name, str(opt_default).lower()))
+        elif opt_type == INTOPT:
+            assert(isinstance(opt_default, int) and
+                   not isinstance(opt_default, bool))
+            print('#%s=%s' % (opt_name, opt_default))
+        elif opt_type == FLOATOPT:
+            assert(isinstance(opt_default, float))
+            print('#%s=%s' % (opt_name, opt_default))
+        elif opt_type == LISTOPT:
+            assert(isinstance(opt_default, list))
+            print('#%s=%s' % (opt_name, ','.join(opt_default)))
+        elif opt_type == MULTISTROPT:
+            assert(isinstance(opt_default, list))
+            if not opt_default:
+                opt_default = ['']
+            for default in opt_default:
+                print('#%s=%s' % (opt_name, default))
+        print('')
+    except Exception:
+        sys.stderr.write('Error in option "%s"\n' % opt_name)
+        sys.exit(1)
+
+
+def main():
+    generate(sys.argv[1:])
+
+if __name__ == '__main__':
+    main()
diff --git a/tempest/openstack/common/excutils.py b/tempest/openstack/common/excutils.py
index db37660..c7bce72 100644
--- a/tempest/openstack/common/excutils.py
+++ b/tempest/openstack/common/excutils.py
@@ -79,7 +79,7 @@
             try:
                 return infunc(*args, **kwargs)
             except Exception as exc:
-                this_exc_message = unicode(exc)
+                this_exc_message = six.u(str(exc))
                 if this_exc_message == last_exc_message:
                     exc_count += 1
                 else:
diff --git a/tempest/openstack/common/fileutils.py b/tempest/openstack/common/fileutils.py
index 6cf68ba..15530af 100644
--- a/tempest/openstack/common/fileutils.py
+++ b/tempest/openstack/common/fileutils.py
@@ -19,6 +19,7 @@
 import contextlib
 import errno
 import os
+import tempfile
 
 from tempest.openstack.common import excutils
 from tempest.openstack.common.gettextutils import _  # noqa
@@ -109,3 +110,30 @@
     state at all (for unit tests)
     """
     return file(*args, **kwargs)
+
+
+def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
+    """Create temporary file or use existing file.
+
+    This util is needed for creating temporary file with
+    specified content, suffix and prefix. If path is not None,
+    it will be used for writing content. If the path doesn't
+    exist it'll be created.
+
+    :param content: content for temporary file.
+    :param path: same as parameter 'dir' for mkstemp
+    :param suffix: same as parameter 'suffix' for mkstemp
+    :param prefix: same as parameter 'prefix' for mkstemp
+
+    For example: it can be used in database tests for creating
+    configuration files.
+    """
+    if path:
+        ensure_tree(path)
+
+    (fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
+    try:
+        os.write(fd, content)
+    finally:
+        os.close(fd)
+    return path
diff --git a/tempest/openstack/common/fixture/__init__.py b/tempest/openstack/common/fixture/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/fixture/__init__.py
diff --git a/tempest/openstack/common/fixture/config.py b/tempest/openstack/common/fixture/config.py
new file mode 100644
index 0000000..7b044ef
--- /dev/null
+++ b/tempest/openstack/common/fixture/config.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Mirantis, Inc.
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+import fixtures
+from oslo.config import cfg
+import six
+
+
+class Config(fixtures.Fixture):
+    """Override some configuration values.
+
+    The keyword arguments are the names of configuration options to
+    override and their values.
+
+    If a group argument is supplied, the overrides are applied to
+    the specified configuration option group.
+
+    All overrides are automatically cleared at the end of the current
+    test by the reset() method, which is registred by addCleanup().
+    """
+
+    def __init__(self, conf=cfg.CONF):
+        self.conf = conf
+
+    def setUp(self):
+        super(Config, self).setUp()
+        self.addCleanup(self.conf.reset)
+
+    def config(self, **kw):
+        group = kw.pop('group', None)
+        for k, v in six.iteritems(kw):
+            self.conf.set_override(k, v, group)
diff --git a/tempest/openstack/common/fixture/lockutils.py b/tempest/openstack/common/fixture/lockutils.py
new file mode 100644
index 0000000..21b4a48
--- /dev/null
+++ b/tempest/openstack/common/fixture/lockutils.py
@@ -0,0 +1,53 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import fixtures
+
+from tempest.openstack.common.lockutils import lock
+
+
+class LockFixture(fixtures.Fixture):
+    """External locking fixture.
+
+    This fixture is basically an alternative to the synchronized decorator with
+    the external flag so that tearDowns and addCleanups will be included in
+    the lock context for locking between tests. The fixture is recommended to
+    be the first line in a test method, like so::
+
+        def test_method(self):
+            self.useFixture(LockFixture)
+                ...
+
+    or the first line in setUp if all the test methods in the class are
+    required to be serialized. Something like::
+
+        class TestCase(testtools.testcase):
+            def setUp(self):
+                self.useFixture(LockFixture)
+                super(TestCase, self).setUp()
+                    ...
+
+    This is because addCleanups are put on a LIFO queue that gets run after the
+    test method exits. (either by completing or raising an exception)
+    """
+    def __init__(self, name, lock_file_prefix=None):
+        self.mgr = lock(name, lock_file_prefix, True)
+
+    def setUp(self):
+        super(LockFixture, self).setUp()
+        self.addCleanup(self.mgr.__exit__, None, None, None)
+        self.mgr.__enter__()
diff --git a/tempest/openstack/common/fixture/mockpatch.py b/tempest/openstack/common/fixture/mockpatch.py
new file mode 100644
index 0000000..cd0d6ca
--- /dev/null
+++ b/tempest/openstack/common/fixture/mockpatch.py
@@ -0,0 +1,51 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+import mock
+
+
+class PatchObject(fixtures.Fixture):
+    """Deal with code around mock."""
+
+    def __init__(self, obj, attr, **kwargs):
+        self.obj = obj
+        self.attr = attr
+        self.kwargs = kwargs
+
+    def setUp(self):
+        super(PatchObject, self).setUp()
+        _p = mock.patch.object(self.obj, self.attr, **self.kwargs)
+        self.mock = _p.start()
+        self.addCleanup(_p.stop)
+
+
+class Patch(fixtures.Fixture):
+
+    """Deal with code around mock.patch."""
+
+    def __init__(self, obj, **kwargs):
+        self.obj = obj
+        self.kwargs = kwargs
+
+    def setUp(self):
+        super(Patch, self).setUp()
+        _p = mock.patch(self.obj, **self.kwargs)
+        self.mock = _p.start()
+        self.addCleanup(_p.stop)
diff --git a/tempest/openstack/common/fixture/moxstubout.py b/tempest/openstack/common/fixture/moxstubout.py
new file mode 100644
index 0000000..a0e74fd
--- /dev/null
+++ b/tempest/openstack/common/fixture/moxstubout.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+import mox
+
+
+class MoxStubout(fixtures.Fixture):
+    """Deal with code around mox and stubout as a fixture."""
+
+    def setUp(self):
+        super(MoxStubout, self).setUp()
+        # emulate some of the mox stuff, we can't use the metaclass
+        # because it screws with our generators
+        self.mox = mox.Mox()
+        self.stubs = self.mox.stubs
+        self.addCleanup(self.mox.UnsetStubs)
+        self.addCleanup(self.mox.VerifyAll)
diff --git a/tempest/openstack/common/gettextutils.py b/tempest/openstack/common/gettextutils.py
index cbf570a..2939ed9 100644
--- a/tempest/openstack/common/gettextutils.py
+++ b/tempest/openstack/common/gettextutils.py
@@ -60,6 +60,8 @@
     if USE_LAZY:
         return Message(msg, 'tempest')
     else:
+        if six.PY3:
+            return _t.gettext(msg)
         return _t.ugettext(msg)
 
 
@@ -105,13 +107,17 @@
             """
             return Message(msg, domain)
 
-        import __builtin__
-        __builtin__.__dict__['_'] = _lazy_gettext
+        from six import moves
+        moves.builtins.__dict__['_'] = _lazy_gettext
     else:
         localedir = '%s_LOCALEDIR' % domain.upper()
-        gettext.install(domain,
-                        localedir=os.environ.get(localedir),
-                        unicode=True)
+        if six.PY3:
+            gettext.install(domain,
+                            localedir=os.environ.get(localedir))
+        else:
+            gettext.install(domain,
+                            localedir=os.environ.get(localedir),
+                            unicode=True)
 
 
 class Message(_userString.UserString, object):
@@ -121,8 +127,8 @@
         self._msg = msg
         self._left_extra_msg = ''
         self._right_extra_msg = ''
+        self._locale = None
         self.params = None
-        self.locale = None
         self.domain = domain
 
     @property
@@ -142,8 +148,13 @@
                                        localedir=localedir,
                                        fallback=True)
 
+        if six.PY3:
+            ugettext = lang.gettext
+        else:
+            ugettext = lang.ugettext
+
         full_msg = (self._left_extra_msg +
-                    lang.ugettext(self._msg) +
+                    ugettext(self._msg) +
                     self._right_extra_msg)
 
         if self.params is not None:
@@ -151,6 +162,33 @@
 
         return six.text_type(full_msg)
 
+    @property
+    def locale(self):
+        return self._locale
+
+    @locale.setter
+    def locale(self, value):
+        self._locale = value
+        if not self.params:
+            return
+
+        # This Message object may have been constructed with one or more
+        # Message objects as substitution parameters, given as a single
+        # Message, or a tuple or Map containing some, so when setting the
+        # locale for this Message we need to set it for those Messages too.
+        if isinstance(self.params, Message):
+            self.params.locale = value
+            return
+        if isinstance(self.params, tuple):
+            for param in self.params:
+                if isinstance(param, Message):
+                    param.locale = value
+            return
+        if isinstance(self.params, dict):
+            for param in self.params.values():
+                if isinstance(param, Message):
+                    param.locale = value
+
     def _save_dictionary_parameter(self, dict_param):
         full_msg = self.data
         # look for %(blah) fields in string;
@@ -169,7 +207,7 @@
                     params[key] = copy.deepcopy(dict_param[key])
                 except TypeError:
                     # cast uncopyable thing to unicode string
-                    params[key] = unicode(dict_param[key])
+                    params[key] = six.text_type(dict_param[key])
 
         return params
 
@@ -188,7 +226,7 @@
             try:
                 self.params = copy.deepcopy(other)
             except TypeError:
-                self.params = unicode(other)
+                self.params = six.text_type(other)
 
         return self
 
@@ -197,11 +235,13 @@
         return self.data
 
     def __str__(self):
+        if six.PY3:
+            return self.__unicode__()
         return self.data.encode('utf-8')
 
     def __getstate__(self):
         to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
-                   'domain', 'params', 'locale']
+                   'domain', 'params', '_locale']
         new_dict = self.__dict__.fromkeys(to_copy)
         for attr in to_copy:
             new_dict[attr] = copy.deepcopy(self.__dict__[attr])
@@ -289,13 +329,21 @@
 
 
 def get_localized_message(message, user_locale):
-    """Gets a localized version of the given message in the given locale."""
+    """Gets a localized version of the given message in the given locale.
+
+    If the message is not a Message object the message is returned as-is.
+    If the locale is None the message is translated to the default locale.
+
+    :returns: the translated message in unicode, or the original message if
+              it could not be translated
+    """
+    translated = message
     if isinstance(message, Message):
-        if user_locale:
-            message.locale = user_locale
-        return unicode(message)
-    else:
-        return message
+        original_locale = message.locale
+        message.locale = user_locale
+        translated = six.text_type(message)
+        message.locale = original_locale
+    return translated
 
 
 class LocaleHandler(logging.Handler):
diff --git a/tempest/openstack/common/jsonutils.py b/tempest/openstack/common/jsonutils.py
index c568a06..b589545 100644
--- a/tempest/openstack/common/jsonutils.py
+++ b/tempest/openstack/common/jsonutils.py
@@ -46,6 +46,7 @@
 
 import six
 
+from tempest.openstack.common import gettextutils
 from tempest.openstack.common import importutils
 from tempest.openstack.common import timeutils
 
@@ -135,6 +136,8 @@
 
         if convert_datetime and isinstance(value, datetime.datetime):
             return timeutils.strtime(value)
+        elif isinstance(value, gettextutils.Message):
+            return value.data
         elif hasattr(value, 'iteritems'):
             return recursive(dict(value.iteritems()), level=level + 1)
         elif hasattr(value, '__iter__'):
diff --git a/tempest/openstack/common/lockutils.py b/tempest/openstack/common/lockutils.py
index a55fd94..8ea8766 100644
--- a/tempest/openstack/common/lockutils.py
+++ b/tempest/openstack/common/lockutils.py
@@ -24,7 +24,6 @@
 import time
 import weakref
 
-import fixtures
 from oslo.config import cfg
 
 from tempest.openstack.common import fileutils
@@ -242,13 +241,14 @@
     def wrap(f):
         @functools.wraps(f)
         def inner(*args, **kwargs):
-            with lock(name, lock_file_prefix, external, lock_path):
-                LOG.debug(_('Got semaphore / lock "%(function)s"'),
+            try:
+                with lock(name, lock_file_prefix, external, lock_path):
+                    LOG.debug(_('Got semaphore / lock "%(function)s"'),
+                              {'function': f.__name__})
+                    return f(*args, **kwargs)
+            finally:
+                LOG.debug(_('Semaphore / lock released "%(function)s"'),
                           {'function': f.__name__})
-                return f(*args, **kwargs)
-
-            LOG.debug(_('Semaphore / lock released "%(function)s"'),
-                      {'function': f.__name__})
         return inner
     return wrap
 
@@ -276,36 +276,3 @@
     """
 
     return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
-
-
-class LockFixture(fixtures.Fixture):
-    """External locking fixture.
-
-    This fixture is basically an alternative to the synchronized decorator with
-    the external flag so that tearDowns and addCleanups will be included in
-    the lock context for locking between tests. The fixture is recommended to
-    be the first line in a test method, like so::
-
-        def test_method(self):
-            self.useFixture(LockFixture)
-                ...
-
-    or the first line in setUp if all the test methods in the class are
-    required to be serialized. Something like::
-
-        class TestCase(testtools.testcase):
-            def setUp(self):
-                self.useFixture(LockFixture)
-                super(TestCase, self).setUp()
-                    ...
-
-    This is because addCleanups are put on a LIFO queue that gets run after the
-    test method exits. (either by completing or raising an exception)
-    """
-    def __init__(self, name, lock_file_prefix=None):
-        self.mgr = lock(name, lock_file_prefix, True)
-
-    def setUp(self):
-        super(LockFixture, self).setUp()
-        self.addCleanup(self.mgr.__exit__, None, None, None)
-        self.mgr.__enter__()
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 4133c30..5cf8ed6 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -39,6 +39,7 @@
 import traceback
 
 from oslo.config import cfg
+import six
 from six import moves
 
 from tempest.openstack.common.gettextutils import _  # noqa
@@ -131,7 +132,6 @@
                     'boto=WARN',
                     'suds=INFO',
                     'keystone=INFO',
-                    'eventlet.wsgi.server=WARN'
                 ],
                 help='list of logger=LEVEL pairs'),
     cfg.BoolOpt('publish_errors',
@@ -207,6 +207,8 @@
         binary = binary or _get_binary_name()
         return '%s.log' % (os.path.join(logdir, binary),)
 
+    return None
+
 
 class BaseLoggerAdapter(logging.LoggerAdapter):
 
@@ -249,6 +251,13 @@
             self.warn(stdmsg, *args, **kwargs)
 
     def process(self, msg, kwargs):
+        # NOTE(mrodden): catch any Message/other object and
+        #                coerce to unicode before they can get
+        #                to the python logging and possibly
+        #                cause string encoding trouble
+        if not isinstance(msg, six.string_types):
+            msg = six.text_type(msg)
+
         if 'extra' not in kwargs:
             kwargs['extra'] = {}
         extra = kwargs['extra']
@@ -260,14 +269,14 @@
             extra.update(_dictify_context(context))
 
         instance = kwargs.pop('instance', None)
+        instance_uuid = (extra.get('instance_uuid', None) or
+                         kwargs.pop('instance_uuid', None))
         instance_extra = ''
         if instance:
             instance_extra = CONF.instance_format % instance
-        else:
-            instance_uuid = kwargs.pop('instance_uuid', None)
-            if instance_uuid:
-                instance_extra = (CONF.instance_uuid_format
-                                  % {'uuid': instance_uuid})
+        elif instance_uuid:
+            instance_extra = (CONF.instance_uuid_format
+                              % {'uuid': instance_uuid})
         extra.update({'instance': instance_extra})
 
         extra.update({"project": self.project})
diff --git a/tempest/openstack/common/timeutils.py b/tempest/openstack/common/timeutils.py
index 60f02bc..98d877d 100644
--- a/tempest/openstack/common/timeutils.py
+++ b/tempest/openstack/common/timeutils.py
@@ -117,12 +117,15 @@
 utcnow.override_time = None
 
 
-def set_time_override(override_time=datetime.datetime.utcnow()):
+def set_time_override(override_time=None):
     """Overrides utils.utcnow.
 
     Make it return a constant time or a list thereof, one at a time.
+
+    :param override_time: datetime instance or list thereof. If not
+                          given, defaults to the current UTC time.
     """
-    utcnow.override_time = override_time
+    utcnow.override_time = override_time or datetime.datetime.utcnow()
 
 
 def advance_time_delta(timedelta):
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 853b1ba..112c8a2 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -35,7 +35,7 @@
     def setUpClass(cls):
         super(TestServerAdvancedOps, cls).setUpClass()
 
-        if not cls.config.compute.resize_available:
+        if not cls.config.compute_feature_enabled.resize:
             msg = "Skipping test - resize not available on this host"
             raise cls.skipException(msg)
 
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 6c786d5..588e5cd 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -109,6 +109,14 @@
         body = json.loads(body)
         return resp, body[key]
 
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Gets specified extra Specs details of the mentioned flavor."""
+        resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+                              (flavor_id, key),
+                              json.dumps(kwargs), self.headers)
+        body = json.loads(body)
+        return resp, body
+
     def unset_flavor_extra_spec(self, flavor_id, key):
         """Unsets extra Specs from the mentioned flavor."""
         return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 07bb6ce..55a4a1b 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -390,3 +390,11 @@
                               (str(server_id), str(request_id)))
         body = json.loads(body)
         return resp, body['instanceAction']
+
+    def force_delete_server(self, server_id, **kwargs):
+        """Force delete a server."""
+        return self.action(server_id, 'forceDelete', None, **kwargs)
+
+    def restore_soft_deleted_server(self, server_id, **kwargs):
+        """Restore a soft-deleted server."""
+        return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/v3/__init__.py b/tempest/services/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/__init__.py
diff --git a/tempest/services/compute/v3/json/__init__.py b/tempest/services/compute/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/json/__init__.py
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
new file mode 100644
index 0000000..a005edb
--- /dev/null
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -0,0 +1,396 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import json
+import time
+import urllib
+
+from tempest.common.rest_client import RestClient
+from tempest.common import waiters
+from tempest import exceptions
+
+
+class ServersV3ClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url,
+                 tenant_name=None, auth_version='v2'):
+        super(ServersV3ClientJSON, self).__init__(config, username, password,
+                                                  auth_url, tenant_name,
+                                                  auth_version=auth_version)
+        self.service = self.config.compute.catalog_v3_type
+
+    def create_server(self, name, image_ref, flavor_ref, **kwargs):
+        """
+        Creates an instance of a server.
+        name (Required): The name of the server.
+        image_ref (Required): Reference to the image used to build the server.
+        flavor_ref (Required): The flavor used to build the server.
+        Following optional keyword arguments are accepted:
+        admin_pass: Sets the initial root password.
+        key_name: Key name of keypair that was created earlier.
+        meta: A dictionary of values to be used as metadata.
+        personality: A list of dictionaries for files to be injected into
+        the server.
+        security_groups: A list of security group dicts.
+        networks: A list of network dicts with UUID and fixed_ip.
+        user_data: User data for instance.
+        availability_zone: Availability zone in which to launch instance.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
+        min_count: Count of minimum number of instances to launch.
+        max_count: Count of maximum number of instances to launch.
+        disk_config: Determines if user or admin controls disk configuration.
+        return_reservation_id: Enable/Disable the return of reservation id
+        """
+        post_body = {
+            'name': name,
+            'image_ref': image_ref,
+            'flavor_ref': flavor_ref
+        }
+
+        for option in ['personality', 'admin_pass', 'key_name',
+                       'security_groups', 'networks',
+                       ('os-user-data:user_data', 'user_data'),
+                       ('os-availability-zone:availability_zone',
+                        'availability_zone'),
+                       'access_ip_v4', 'access_ip_v6',
+                       ('os-multiple-create:min_count', 'min_count'),
+                       ('os-multiple-create:max_count', 'max_count'),
+                       ('metadata', 'meta'),
+                       ('os-disk-config:disk_config', 'disk_config'),
+                       ('os-multiple-create:return_reservation_id',
+                       'return_reservation_id')]:
+            if isinstance(option, tuple):
+                post_param = option[0]
+                key = option[1]
+            else:
+                post_param = option
+                key = option
+            value = kwargs.get(key)
+            if value is not None:
+                post_body[post_param] = value
+        post_body = json.dumps({'server': post_body})
+        resp, body = self.post('servers', post_body, self.headers)
+
+        body = json.loads(body)
+        # NOTE(maurosr): this deals with the case of multiple server create
+        # with return reservation id set True
+        if 'reservation_id' in body:
+            return resp, body
+        return resp, body['server']
+
+    def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+                      access_ip_v6=None, disk_config=None):
+        """
+        Updates the properties of an existing server.
+        server_id: The id of an existing server.
+        name: The name of the server.
+        personality: A list of files to be injected into the server.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
+        """
+
+        post_body = {}
+
+        if meta is not None:
+            post_body['metadata'] = meta
+
+        if name is not None:
+            post_body['name'] = name
+
+        if access_ip_v4 is not None:
+            post_body['access_ip_v4'] = access_ip_v4
+
+        if access_ip_v6 is not None:
+            post_body['access_ip_v6'] = access_ip_v6
+
+        if disk_config is not None:
+            post_body['OS-DCF:diskConfig'] = disk_config
+
+        post_body = json.dumps({'server': post_body})
+        resp, body = self.put("servers/%s" % str(server_id),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['server']
+
+    def get_server(self, server_id):
+        """Returns the details of an existing server."""
+        resp, body = self.get("servers/%s" % str(server_id))
+        body = json.loads(body)
+        return resp, body['server']
+
+    def delete_server(self, server_id):
+        """Deletes the given server."""
+        return self.delete("servers/%s" % str(server_id))
+
+    def list_servers(self, params=None):
+        """Lists all servers for a user."""
+
+        url = 'servers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
+
+    def list_servers_with_detail(self, params=None):
+        """Lists all servers in detail for a user."""
+
+        url = 'servers/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
+
+    def wait_for_server_status(self, server_id, status):
+        """Waits for a server to reach a given status."""
+        return waiters.wait_for_server_status(self, server_id, status)
+
+    def wait_for_server_termination(self, server_id, ignore_error=False):
+        """Waits for server to reach termination."""
+        start_time = int(time.time())
+        while True:
+            try:
+                resp, body = self.get_server(server_id)
+            except exceptions.NotFound:
+                return
+
+            server_status = body['status']
+            if server_status == 'ERROR' and not ignore_error:
+                raise exceptions.BuildErrorException(server_id=server_id)
+
+            if int(time.time()) - start_time >= self.build_timeout:
+                raise exceptions.TimeoutException
+
+            time.sleep(self.build_interval)
+
+    def list_addresses(self, server_id):
+        """Lists all addresses for a server."""
+        resp, body = self.get("servers/%s/ips" % str(server_id))
+        body = json.loads(body)
+        return resp, body['addresses']
+
+    def list_addresses_by_network(self, server_id, network_id):
+        """Lists all addresses of a specific network type for a server."""
+        resp, body = self.get("servers/%s/ips/%s" %
+                              (str(server_id), network_id))
+        body = json.loads(body)
+        return resp, body
+
+    def action(self, server_id, action_name, response_key, **kwargs):
+        post_body = json.dumps({action_name: kwargs})
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               post_body, self.headers)
+        if response_key is not None:
+            body = json.loads(body)[response_key]
+        return resp, body
+
+    def change_password(self, server_id, admin_password):
+        """Changes the root password for the server."""
+        return self.action(server_id, 'change_password', None,
+                           admin_password=admin_password)
+
+    def reboot(self, server_id, reboot_type):
+        """Reboots a server."""
+        return self.action(server_id, 'reboot', None, type=reboot_type)
+
+    def rebuild(self, server_id, image_ref, **kwargs):
+        """Rebuilds a server with a new image."""
+        kwargs['image_ref'] = image_ref
+        if 'disk_config' in kwargs:
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+            del kwargs['disk_config']
+        return self.action(server_id, 'rebuild', 'server', **kwargs)
+
+    def resize(self, server_id, flavor_ref, **kwargs):
+        """Changes the flavor of a server."""
+        kwargs['flavor_ref'] = flavor_ref
+        if 'disk_config' in kwargs:
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+            del kwargs['disk_config']
+        return self.action(server_id, 'resize', None, **kwargs)
+
+    def confirm_resize(self, server_id, **kwargs):
+        """Confirms the flavor change for a server."""
+        return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+    def revert_resize(self, server_id, **kwargs):
+        """Reverts a server back to its original flavor."""
+        return self.action(server_id, 'revert_resize', None, **kwargs)
+
+    def create_image(self, server_id, name, meta=None):
+        """Creates an image of the original server."""
+
+        post_body = {
+            'create_image': {
+                'name': name,
+            }
+        }
+
+        if meta is not None:
+            post_body['create_image']['metadata'] = meta
+
+        post_body = json.dumps(post_body)
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               post_body, self.headers)
+        return resp, body
+
+    def list_server_metadata(self, server_id):
+        resp, body = self.get("servers/%s/metadata" % str(server_id))
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+        if no_metadata_field:
+            post_body = ""
+        else:
+            post_body = json.dumps({'metadata': meta})
+        resp, body = self.put('servers/%s/metadata' % str(server_id),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def update_server_metadata(self, server_id, meta):
+        post_body = json.dumps({'metadata': meta})
+        resp, body = self.post('servers/%s/metadata' % str(server_id),
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def get_server_metadata_item(self, server_id, key):
+        resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def set_server_metadata_item(self, server_id, key, meta):
+        post_body = json.dumps({'metadata': meta})
+        resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def delete_server_metadata_item(self, server_id, key):
+        resp, body = self.delete("servers/%s/metadata/%s" %
+                                 (str(server_id), key))
+        return resp, body
+
+    def stop(self, server_id, **kwargs):
+        return self.action(server_id, 'stop', None, **kwargs)
+
+    def start(self, server_id, **kwargs):
+        return self.action(server_id, 'start', None, **kwargs)
+
+    def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+        """Attaches a volume to a server instance."""
+        return self.action(server_id, 'attach', None, volume_id=volume_id,
+                           device=device)
+
+    def detach_volume(self, server_id, volume_id):
+        """Detaches a volume from a server instance."""
+        return self.action(server_id, 'detach', None, volume_id=volume_id)
+
+    def add_security_group(self, server_id, name):
+        """Adds a security group to the server."""
+        return self.action(server_id, 'add_security_group', None, name=name)
+
+    def remove_security_group(self, server_id, name):
+        """Removes a security group from the server."""
+        return self.action(server_id, 'remove_security_group', None, name=name)
+
+    def live_migrate_server(self, server_id, dest_host, use_block_migration):
+        """This should be called with administrator privileges ."""
+
+        migrate_params = {
+            "disk_over_commit": False,
+            "block_migration": use_block_migration,
+            "host": dest_host
+        }
+
+        req_body = json.dumps({'migrate_live': migrate_params})
+
+        resp, body = self.post("servers/%s/action" % str(server_id),
+                               req_body, self.headers)
+        return resp, body
+
+    def migrate_server(self, server_id, **kwargs):
+        """Migrates a server to a new host."""
+        return self.action(server_id, 'migrate', None, **kwargs)
+
+    def lock_server(self, server_id, **kwargs):
+        """Locks the given server."""
+        return self.action(server_id, 'lock', None, **kwargs)
+
+    def unlock_server(self, server_id, **kwargs):
+        """UNlocks the given server."""
+        return self.action(server_id, 'unlock', None, **kwargs)
+
+    def suspend_server(self, server_id, **kwargs):
+        """Suspends the provded server."""
+        return self.action(server_id, 'suspend', None, **kwargs)
+
+    def resume_server(self, server_id, **kwargs):
+        """Un-suspends the provded server."""
+        return self.action(server_id, 'resume', None, **kwargs)
+
+    def pause_server(self, server_id, **kwargs):
+        """Pauses the provded server."""
+        return self.action(server_id, 'pause', None, **kwargs)
+
+    def unpause_server(self, server_id, **kwargs):
+        """Un-pauses the provded server."""
+        return self.action(server_id, 'unpause', None, **kwargs)
+
+    def reset_state(self, server_id, state='error'):
+        """Resets the state of a server to active/error."""
+        return self.action(server_id, 'reset_state', None, state=state)
+
+    def get_console_output(self, server_id, length):
+        return self.action(server_id, 'get_console_output', 'output',
+                           length=length)
+
+    def rescue_server(self, server_id, adminPass=None):
+        """Rescue the provided server."""
+        return self.action(server_id, 'rescue', None, admin_pass=adminPass)
+
+    def unrescue_server(self, server_id):
+        """Unrescue the provided server."""
+        return self.action(server_id, 'unrescue', None)
+
+    def get_server_diagnostics(self, server_id):
+        """Get the usage data for a server."""
+        resp, body = self.get("servers/%s/os-server-diagnostics" %
+                              str(server_id))
+        return resp, json.loads(body)
+
+    def list_instance_actions(self, server_id):
+        """List the provided server action."""
+        resp, body = self.get("servers/%s/os-instance-actions" %
+                              str(server_id))
+        body = json.loads(body)
+        return resp, body['instance_actions']
+
+    def get_instance_action(self, server_id, request_id):
+        """Returns the action details of the provided server."""
+        resp, body = self.get("servers/%s/os-instance-actions/%s" %
+                              (str(server_id), str(request_id)))
+        body = json.loads(body)
+        return resp, body['instance_action']
diff --git a/tempest/services/compute/v3/xml/__init__.py b/tempest/services/compute/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/xml/__init__.py
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
new file mode 100644
index 0000000..6f38b6a
--- /dev/null
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -0,0 +1,623 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 IBM Corp.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import time
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+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
+from tempest.services.compute.xml.common import XMLNS_V3
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _translate_ip_xml_json(ip):
+    """
+    Convert the address version to int.
+    """
+    ip = dict(ip)
+    version = ip.get('version')
+    if version:
+        ip['version'] = int(version)
+    if ip.get('type'):
+        ip['type'] = ip.get('type')
+    if ip.get('mac_addr'):
+        ip['mac_addr'] = ip.get('mac_addr')
+    return ip
+
+
+def _translate_network_xml_to_json(network):
+    return [_translate_ip_xml_json(ip.attrib)
+            for ip in network.findall('{%s}ip' % XMLNS_V3)]
+
+
+def _translate_addresses_xml_to_json(xml_addresses):
+    return dict((network.attrib['id'], _translate_network_xml_to_json(network))
+                for network in xml_addresses.findall('{%s}network' % XMLNS_V3))
+
+
+def _translate_server_xml_to_json(xml_dom):
+    """Convert server XML to server JSON.
+
+    The addresses collection does not convert well by the dumb xml_to_json.
+    This method does some pre and post-processing to deal with that.
+
+    Translate XML addresses subtree to JSON.
+
+    Having xml_doc similar to
+    <api:server  xmlns:api="http://docs.openstack.org/compute/api/v3">
+        <api:addresses>
+            <api:network id="foo_novanetwork">
+                <api:ip version="4" addr="192.168.0.4"/>
+            </api:network>
+            <api:network id="bar_novanetwork">
+                <api:ip version="4" addr="10.1.0.4"/>
+                <api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
+            </api:network>
+        </api:addresses>
+    </api:server>
+
+    the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
+    something like
+
+    {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
+                                       {'addr': '2001:0:0:1:2:3:4:5',
+                                        'version': 6}],
+                   'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
+    """
+    nsmap = {'api': XMLNS_V3}
+    addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
+    if addresses:
+        if len(addresses) > 1:
+            raise ValueError('Expected only single `addresses` element.')
+        json_addresses = _translate_addresses_xml_to_json(addresses[0])
+        json = xml_to_json(xml_dom)
+        json['addresses'] = json_addresses
+    else:
+        json = xml_to_json(xml_dom)
+    disk_config = ('{http://docs.openstack.org'
+                   '/compute/ext/disk_config/api/v3}disk_config')
+    terminated_at = ('{http://docs.openstack.org/'
+                     'compute/ext/os-server-usage/api/v3}terminated_at')
+    launched_at = ('{http://docs.openstack.org'
+                   '/compute/ext/os-server-usage/api/v3}launched_at')
+    power_state = ('{http://docs.openstack.org'
+                   '/compute/ext/extended_status/api/v3}power_state')
+    availability_zone = ('{http://docs.openstack.org'
+                         '/compute/ext/extended_availability_zone/api/v3}'
+                         'availability_zone')
+    vm_state = ('{http://docs.openstack.org'
+                '/compute/ext/extended_status/api/v3}vm_state')
+    task_state = ('{http://docs.openstack.org'
+                  '/compute/ext/extended_status/api/v3}task_state')
+    if disk_config in json:
+        json['os-disk-config:disk_config'] = json.pop(disk_config)
+    if terminated_at in json:
+        json['os-server-usage:terminated_at'] = json.pop(terminated_at)
+    if launched_at in json:
+        json['os-server-usage:launched_at'] = json.pop(launched_at)
+    if power_state in json:
+        json['os-extended-status:power_state'] = json.pop(power_state)
+    if availability_zone in json:
+        json['os-extended-availability-zone:availability_zone'] = json.pop(
+            availability_zone)
+    if vm_state in json:
+        json['os-extended-status:vm_state'] = json.pop(vm_state)
+    if task_state in json:
+        json['os-extended-status:task_state'] = json.pop(task_state)
+    return json
+
+
+class ServersV3ClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url,
+                 tenant_name=None, auth_version='v2'):
+        super(ServersV3ClientXML, self).__init__(config, username, password,
+                                                 auth_url, tenant_name,
+                                                 auth_version=auth_version)
+        self.service = self.config.compute.catalog_v3_type
+
+    def _parse_key_value(self, node):
+        """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+        data = {}
+        for node in node.getchildren():
+            data[node.get('key')] = node.text
+        return data
+
+    def _parse_links(self, node, json):
+        del json['link']
+        json['links'] = []
+        for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
+            json['links'].append(xml_to_json(linknode))
+
+    def _parse_server(self, body):
+        json = _translate_server_xml_to_json(body)
+
+        if 'metadata' in json and json['metadata']:
+            # NOTE(danms): if there was metadata, we need to re-parse
+            # that as a special type
+            metadata_tag = body.find('{%s}metadata' % XMLNS_V3)
+            json["metadata"] = self._parse_key_value(metadata_tag)
+        if 'link' in json:
+            self._parse_links(body, json)
+        for sub in ['image', 'flavor']:
+            if sub in json and 'link' in json[sub]:
+                self._parse_links(body, json[sub])
+        return json
+
+    def _parse_xml_virtual_interfaces(self, xml_dom):
+        """
+        Return server's virtual interfaces XML as JSON.
+        """
+        data = {"virtual_interfaces": []}
+        for iface in xml_dom.getchildren():
+            data["virtual_interfaces"].append(
+                {"id": iface.get("id"),
+                 "mac_address": iface.get("mac_address")})
+        return data
+
+    def get_server(self, server_id):
+        """Returns the details of an existing server."""
+        resp, body = self.get("servers/%s" % str(server_id), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def lock_server(self, server_id, **kwargs):
+        """Locks the given server."""
+        return self.action(server_id, 'lock', None, **kwargs)
+
+    def unlock_server(self, server_id, **kwargs):
+        """Unlocks the given server."""
+        return self.action(server_id, 'unlock', None, **kwargs)
+
+    def suspend_server(self, server_id, **kwargs):
+        """Suspends the provided server."""
+        return self.action(server_id, 'suspend', None, **kwargs)
+
+    def resume_server(self, server_id, **kwargs):
+        """Un-suspends the provided server."""
+        return self.action(server_id, 'resume', None, **kwargs)
+
+    def pause_server(self, server_id, **kwargs):
+        """Pauses the provided server."""
+        return self.action(server_id, 'pause', None, **kwargs)
+
+    def unpause_server(self, server_id, **kwargs):
+        """Un-pauses the provided server."""
+        return self.action(server_id, 'unpause', None, **kwargs)
+
+    def reset_state(self, server_id, state='error'):
+        """Resets the state of a server to active/error."""
+        return self.action(server_id, 'reset_state', None, state=state)
+
+    def delete_server(self, server_id):
+        """Deletes the given server."""
+        return self.delete("servers/%s" % str(server_id))
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            array.append(xml_to_json(child))
+        return array
+
+    def list_servers(self, params=None):
+        url = 'servers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        servers = self._parse_array(etree.fromstring(body))
+        return resp, {"servers": servers}
+
+    def list_servers_with_detail(self, params=None):
+        url = 'servers/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        servers = self._parse_array(etree.fromstring(body))
+        return resp, {"servers": servers}
+
+    def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+                      access_ip_v6=None, disk_config=None):
+        doc = Document()
+        server = Element("server")
+        doc.append(server)
+
+        if name is not None:
+            server.add_attr("name", name)
+        if access_ip_v4 is not None:
+            server.add_attr("access_ip_v4", access_ip_v4)
+        if access_ip_v6 is not None:
+            server.add_attr("access_ip_v6", access_ip_v6)
+        if meta is not None:
+            metadata = Element("metadata")
+            server.append(metadata)
+            for k, v in meta:
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        resp, body = self.put('servers/%s' % str(server_id),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def create_server(self, name, image_ref, flavor_ref, **kwargs):
+        """
+        Creates an instance of a server.
+        name (Required): The name of the server.
+        image_ref (Required): Reference to the image used to build the server.
+        flavor_ref (Required): The flavor used to build the server.
+        Following optional keyword arguments are accepted:
+        admin_password: Sets the initial root password.
+        key_name: Key name of keypair that was created earlier.
+        meta: A dictionary of values to be used as metadata.
+        personality: A list of dictionaries for files to be injected into
+        the server.
+        security_groups: A list of security group dicts.
+        networks: A list of network dicts with UUID and fixed_ip.
+        user_data: User data for instance.
+        availability_zone: Availability zone in which to launch instance.
+        access_ip_v4: The IPv4 access address for the server.
+        access_ip_v6: The IPv6 access address for the server.
+        min_count: Count of minimum number of instances to launch.
+        max_count: Count of maximum number of instances to launch.
+        disk_config: Determines if user or admin controls disk configuration.
+        return_reservation_id: Enable/Disable the return of reservation id.
+        """
+        server = Element("server",
+                         imageRef=image_ref,
+                         xmlns=XMLNS_V3,
+                         flavor_ref=flavor_ref,
+                         image_ref=image_ref,
+                         name=name)
+        attrs = ["admin_pass", "access_ip_v4", "access_ip_v6", "key_name",
+                 ("os-user-data:user_data",
+                  'user_data',
+                  'xmlns:os-user-data',
+                  "http://docs.openstack.org/compute/ext/userdata/api/v3"),
+                 ("os-availability-zone:availability_zone",
+                  'availability_zone',
+                  'xmlns:os-availability-zone',
+                  "http://docs.openstack.org/compute/ext/"
+                  "availabilityzone/api/v3"),
+                 ("os-multiple-create:min_count",
+                  'min_count',
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-multiple-create:max_count",
+                  'max_count',
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-multiple-create:return_reservation_id",
+                  "return_reservation_id",
+                  'xmlns:os-multiple-create',
+                  "http://docs.openstack.org/compute/ext/"
+                  "multiplecreate/api/v3"),
+                 ("os-disk-config:disk_config",
+                  "disk_config",
+                  "xmlns:os-disk-config",
+                  "http://docs.openstack.org/"
+                  "compute/ext/disk_config/api/v3")]
+
+        for attr in attrs:
+            if isinstance(attr, tuple):
+                post_param = attr[0]
+                key = attr[1]
+                value = kwargs.get(key)
+                if value is not None:
+                    server.add_attr(attr[2], attr[3])
+                    server.add_attr(post_param, value)
+            else:
+                post_param = attr
+                key = attr
+                value = kwargs.get(key)
+                if value is not None:
+                    server.add_attr(post_param, value)
+
+        if 'security_groups' in kwargs:
+            secgroups = Element("security_groups")
+            server.append(secgroups)
+            for secgroup in kwargs['security_groups']:
+                s = Element("security_group", name=secgroup['name'])
+                secgroups.append(s)
+
+        if 'networks' in kwargs:
+            networks = Element("networks")
+            server.append(networks)
+            for network in kwargs['networks']:
+                s = Element("network", uuid=network['uuid'],
+                            fixed_ip=network['fixed_ip'])
+                networks.append(s)
+
+        if 'meta' in kwargs:
+            metadata = Element("metadata")
+            server.append(metadata)
+            for k, v in kwargs['meta'].items():
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        if 'personality' in kwargs:
+            personality = Element('personality')
+            server.append(personality)
+            for k in kwargs['personality']:
+                temp = Element('file', path=k['path'])
+                temp.append(Text(k['contents']))
+                personality.append(temp)
+
+        resp, body = self.post('servers', str(Document(server)), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def wait_for_server_status(self, server_id, status):
+        """Waits for a server to reach a given status."""
+        return waiters.wait_for_server_status(self, server_id, status)
+
+    def wait_for_server_termination(self, server_id, ignore_error=False):
+        """Waits for server to reach termination."""
+        start_time = int(time.time())
+        while True:
+            try:
+                resp, body = self.get_server(server_id)
+            except exceptions.NotFound:
+                return
+
+            server_status = body['status']
+            if server_status == 'ERROR' and not ignore_error:
+                raise exceptions.BuildErrorException
+
+            if int(time.time()) - start_time >= self.build_timeout:
+                raise exceptions.TimeoutException
+
+            time.sleep(self.build_interval)
+
+    def _parse_network(self, node):
+        addrs = []
+        for child in node.getchildren():
+            addrs.append({'version': int(child.get('version')),
+                         'addr': child.get('addr')})
+        return {node.get('id'): addrs}
+
+    def list_addresses(self, server_id):
+        """Lists all addresses for a server."""
+        resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
+
+        networks = {}
+        xml_list = etree.fromstring(body)
+        for child in xml_list.getchildren():
+            network = self._parse_network(child)
+            networks.update(**network)
+
+        return resp, networks
+
+    def list_addresses_by_network(self, server_id, network_id):
+        """Lists all addresses of a specific network type for a server."""
+        resp, body = self.get("servers/%s/ips/%s" % (str(server_id),
+                                                     network_id),
+                              self.headers)
+        network = self._parse_network(etree.fromstring(body))
+
+        return resp, network
+
+    def action(self, server_id, action_name, response_key, **kwargs):
+        if 'xmlns' not in kwargs:
+            kwargs['xmlns'] = XMLNS_V3
+        doc = Document((Element(action_name, **kwargs)))
+        resp, body = self.post("servers/%s/action" % server_id,
+                               str(doc), self.headers)
+        if response_key is not None:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def change_password(self, server_id, password):
+        return self.action(server_id, "change_password", None,
+                           admin_pass=password)
+
+    def reboot(self, server_id, reboot_type):
+        return self.action(server_id, "reboot", None, type=reboot_type)
+
+    def rebuild(self, server_id, image_ref, **kwargs):
+        kwargs['image_ref'] = image_ref
+        if 'disk_config' in kwargs:
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+            del kwargs['disk_config']
+            kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+                                             "compute/ext/disk_config/api/v3"
+            kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+        if 'xmlns' not in kwargs:
+            kwargs['xmlns'] = XMLNS_V3
+
+        attrs = kwargs.copy()
+        if 'metadata' in attrs:
+            del attrs['metadata']
+        rebuild = Element("rebuild",
+                          **attrs)
+
+        if 'metadata' in kwargs:
+            metadata = Element("metadata")
+            rebuild.append(metadata)
+            for k, v in kwargs['metadata'].items():
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        resp, body = self.post('servers/%s/action' % server_id,
+                               str(Document(rebuild)), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def resize(self, server_id, flavor_ref, **kwargs):
+        if 'disk_config' in kwargs:
+            kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+            del kwargs['disk_config']
+            kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+                                             "compute/ext/disk_config/api/v3"
+            kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+        kwargs['flavor_ref'] = flavor_ref
+        return self.action(server_id, 'resize', None, **kwargs)
+
+    def confirm_resize(self, server_id, **kwargs):
+        return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+    def revert_resize(self, server_id, **kwargs):
+        return self.action(server_id, 'revert_resize', None, **kwargs)
+
+    def stop(self, server_id, **kwargs):
+        return self.action(server_id, 'stop', None, **kwargs)
+
+    def start(self, server_id, **kwargs):
+        return self.action(server_id, 'start', None, **kwargs)
+
+    def create_image(self, server_id, name, meta=None):
+        """Creates an image of the original server."""
+        post_body = Element('create_image', name=name)
+
+        if meta:
+            metadata = Element('metadata')
+            post_body.append(metadata)
+            for k, v in meta.items():
+                data = Element('meta', key=k)
+                data.append(Text(v))
+                metadata.append(data)
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               str(Document(post_body)), self.headers)
+        return resp, body
+
+    def add_security_group(self, server_id, name):
+        return self.action(server_id, 'add_security_group', None, name=name)
+
+    def remove_security_group(self, server_id, name):
+        return self.action(server_id, 'remove_security_group', None, name=name)
+
+    def live_migrate_server(self, server_id, dest_host, use_block_migration):
+        """This should be called with administrator privileges ."""
+
+        req_body = Element("migrate_live",
+                           xmlns=XMLNS_V3,
+                           disk_over_commit=False,
+                           block_migration=use_block_migration,
+                           host=dest_host)
+
+        resp, body = self.post("servers/%s/action" % str(server_id),
+                               str(Document(req_body)), self.headers)
+        return resp, body
+
+    def list_server_metadata(self, server_id):
+        resp, body = self.get("servers/%s/metadata" % str(server_id),
+                              self.headers)
+        body = self._parse_key_value(etree.fromstring(body))
+        return resp, body
+
+    def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+        doc = Document()
+        if not no_metadata_field:
+            metadata = Element("metadata")
+            doc.append(metadata)
+            for k, v in meta.items():
+                meta_element = Element("meta", key=k)
+                meta_element.append(Text(v))
+                metadata.append(meta_element)
+        resp, body = self.put('servers/%s/metadata' % str(server_id),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def update_server_metadata(self, server_id, meta):
+        doc = Document()
+        metadata = Element("metadata")
+        doc.append(metadata)
+        for k, v in meta.items():
+            meta_element = Element("meta", key=k)
+            meta_element.append(Text(v))
+            metadata.append(meta_element)
+        resp, body = self.post("/servers/%s/metadata" % str(server_id),
+                               str(doc), headers=self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def get_server_metadata_item(self, server_id, key):
+        resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key),
+                              headers=self.headers)
+        return resp, dict([(etree.fromstring(body).attrib['key'],
+                            xml_to_json(etree.fromstring(body)))])
+
+    def set_server_metadata_item(self, server_id, key, meta):
+        doc = Document()
+        for k, v in meta.items():
+            meta_element = Element("meta", key=k)
+            meta_element.append(Text(v))
+            doc.append(meta_element)
+        resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def delete_server_metadata_item(self, server_id, key):
+        resp, body = self.delete("servers/%s/metadata/%s" %
+                                 (str(server_id), key))
+        return resp, body
+
+    def get_console_output(self, server_id, length):
+        return self.action(server_id, 'get_console_output', 'output',
+                           length=length)
+
+    def rescue_server(self, server_id, admin_pass=None):
+        """Rescue the provided server."""
+        return self.action(server_id, 'rescue', None, admin_pass=admin_pass)
+
+    def unrescue_server(self, server_id):
+        """Unrescue the provided server."""
+        return self.action(server_id, 'unrescue', None)
+
+    def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+        return self.action(server_id, "attach", None, volume_id=volume_id,
+                           device=device)
+
+    def detach_volume(self, server_id, volume_id):
+        return self.action(server_id, "detach", None, volume_id=volume_id)
+
+    def get_server_diagnostics(self, server_id):
+        """Get the usage data for a server."""
+        resp, body = self.get("servers/%s/os-server-diagnostics" % server_id,
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def list_instance_actions(self, server_id):
+        """List the provided server action."""
+        resp, body = self.get("servers/%s/os-instance-actions" % server_id,
+                              self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def get_instance_action(self, server_id, request_id):
+        """Returns the action details of the provided server."""
+        resp, body = self.get("servers/%s/os-instance-actions/%s" %
+                              (server_id, request_id), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index ad79ed6..860dd5b 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -18,6 +18,7 @@
 import collections
 
 XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
+XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
 
 
 # NOTE(danms): This is just a silly implementation to help make generating
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index e74632a..d4c456e 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -22,6 +22,7 @@
 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
 from tempest.services.compute.xml.common import XMLNS_11
 
@@ -29,7 +30,7 @@
 XMLNS_OS_FLV_EXT_DATA = \
     "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
 XMLNS_OS_FLV_ACCESS = \
-    "http://docs.openstack.org/compute/ext/flavor_access/api/v1.1"
+    "http://docs.openstack.org/compute/ext/flavor_access/api/v2"
 
 
 class FlavorsClientXML(RestClientXML):
@@ -49,6 +50,10 @@
             if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
                 k = 'OS-FLV-EXT-DATA:ephemeral'
 
+            if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
+                k = 'os-flavor-access:is_public'
+                v = True if v == 'True' else False
+
             if k == 'extra_specs':
                 k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
                 flavor[k] = dict(v)
@@ -155,6 +160,21 @@
         body = xml_to_json(etree.fromstring(body))
         return resp, body
 
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Gets specified extra Specs details of the mentioned flavor."""
+        doc = Document()
+        for (k, v) in kwargs.items():
+            element = Element(k)
+            doc.append(element)
+            value = Text(v)
+            element.append(value)
+
+        resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+                              (flavor_id, key),
+                              str(doc), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, {key: body}
+
     def unset_flavor_extra_spec(self, flavor_id, key):
         """Unsets an extra spec based on the mentioned flavor and key."""
         return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 43de4ef..e21bfc4 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -600,3 +600,11 @@
                               (server_id, request_id), self.headers)
         body = xml_to_json(etree.fromstring(body))
         return resp, body
+
+    def force_delete_server(self, server_id, **kwargs):
+        """Force delete a server."""
+        return self.action(server_id, 'forceDelete', None, **kwargs)
+
+    def restore_soft_deleted_server(self, server_id, **kwargs):
+        """Restore a soft-deleted server."""
+        return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 41b9e81..b19f65d 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -175,7 +175,7 @@
 
     def delete_image(self, image_id):
         url = 'v1/images/%s' % image_id
-        self.delete(url)
+        return self.delete(url)
 
     def image_list(self, **kwargs):
         url = 'v1/images'
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index 62b8ff6..342a09c 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -100,7 +100,7 @@
         self._validate_schema(body, type='images')
         return resp, body['images']
 
-    def get_image_metadata(self, image_id):
+    def get_image(self, image_id):
         url = 'v2/images/%s' % image_id
         resp, body = self.get(url)
         body = json.loads(body)
@@ -108,7 +108,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_image_metadata(id)
+            self.get_image(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 92c1faf..e7cd33f 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -123,8 +123,12 @@
         resp, body = self.delete(uri, self.headers)
         return resp, body
 
-    def list_ports(self):
+    def list_ports(self, **filters):
         uri = '%s/ports' % (self.uri_prefix)
+        filter_items = ["%s=%s" % (k, v) for (k, v) in filters.iteritems()]
+        querystring = "&".join(filter_items)
+        if querystring:
+            uri = "%s?%s" % (uri, querystring)
         resp, body = self.get(uri, self.headers)
         body = json.loads(body)
         return resp, body
@@ -223,7 +227,7 @@
         body = json.loads(body)
         return resp, body
 
-    def update_router(self, router_id, **kwargs):
+    def _update_router(self, router_id, set_enable_snat, **kwargs):
         uri = '%s/routers/%s' % (self.uri_prefix, router_id)
         resp, body = self.get(uri, self.headers)
         body = json.loads(body)
@@ -231,15 +235,34 @@
         update_body['name'] = kwargs.get('name', body['router']['name'])
         update_body['admin_state_up'] = kwargs.get(
             'admin_state_up', body['router']['admin_state_up'])
-        # Must uncomment/modify these lines once LP question#233187 is solved
-        # update_body['external_gateway_info'] = kwargs.get(
-        # 'external_gateway_info', body['router']['external_gateway_info'])
+        cur_gw_info = body['router']['external_gateway_info']
+        if cur_gw_info and not set_enable_snat:
+            cur_gw_info.pop('enable_snat', None)
+        update_body['external_gateway_info'] = kwargs.get(
+            'external_gateway_info', body['router']['external_gateway_info'])
         update_body = dict(router=update_body)
         update_body = json.dumps(update_body)
         resp, body = self.put(uri, update_body, self.headers)
         body = json.loads(body)
         return resp, body
 
+    def update_router(self, router_id, **kwargs):
+        """Update a router leaving enable_snat to its default value."""
+        # If external_gateway_info contains enable_snat the request will fail
+        # with 404 unless executed with admin client, and therefore we instruct
+        # _update_router to not set this attribute
+        # NOTE(salv-orlando): The above applies as long as Neutron's default
+        # policy is to restrict enable_snat usage to admins only.
+        return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+    def update_router_with_snat_gw_info(self, router_id, **kwargs):
+        """Update a router passing also the enable_snat attribute.
+
+        This method must be execute with admin credentials, otherwise the API
+        call will return a 404 error.
+        """
+        return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
     def add_router_interface_with_subnet_id(self, router_id, subnet_id):
         uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
               router_id)
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 62a6e24..670492a 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -128,6 +128,22 @@
         resp, body = self.post(url, post_body, self.headers)
         return resp, body
 
+    def reserve_volume(self, volume_id):
+        """Reserves a volume."""
+        post_body = {}
+        post_body = json.dumps({'os-reserve': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
+
+    def unreserve_volume(self, volume_id):
+        """Restore a reserved volume ."""
+        post_body = {}
+        post_body = json.dumps({'os-unreserve': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
+
     def wait_for_volume_status(self, volume_id, status):
         """Waits for a Volume to reach a given status."""
         resp, body = self.get_volume(volume_id)
@@ -154,3 +170,34 @@
         except exceptions.NotFound:
             return True
         return False
+
+    def extend_volume(self, volume_id, extend_size):
+        """Extend a volume."""
+        post_body = {
+            'new_size': extend_size
+        }
+        post_body = json.dumps({'os-extend': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
+
+    def reset_volume_status(self, volume_id, status):
+        """Reset the Specified Volume's Status."""
+        post_body = json.dumps({'os-reset_status': {"status": status}})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+                               self.headers)
+        return resp, body
+
+    def volume_begin_detaching(self, volume_id):
+        """Volume Begin Detaching."""
+        post_body = json.dumps({'os-begin_detaching': {}})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+                               self.headers)
+        return resp, body
+
+    def volume_roll_detaching(self, volume_id):
+        """Volume Roll Detaching."""
+        post_body = json.dumps({'os-roll_detaching': {}})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+                               self.headers)
+        return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index b59ec03..0edf7f3 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -227,3 +227,60 @@
         resp, body = self.post(url, str(Document(post_body)), self.headers)
         volume = xml_to_json(etree.fromstring(body))
         return resp, volume
+
+    def extend_volume(self, volume_id, extend_size):
+        """Extend a volume."""
+        post_body = Element("os-extend",
+                            new_size=extend_size)
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def reset_volume_status(self, volume_id, status):
+        """Reset the Specified Volume's Status."""
+        post_body = Element("os-reset_status",
+                            status=status
+                            )
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def volume_begin_detaching(self, volume_id):
+        """Volume Begin Detaching."""
+        post_body = Element("os-begin_detaching")
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def volume_roll_detaching(self, volume_id):
+        """Volume Roll Detaching."""
+        post_body = Element("os-roll_detaching")
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def reserve_volume(self, volume_id):
+        """Reserves a volume."""
+        post_body = Element("os-reserve")
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def unreserve_volume(self, volume_id):
+        """Restore a reserved volume ."""
+        post_body = Element("os-unreserve")
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index ae86f6e..20e58d4 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -26,6 +26,16 @@
 To activate logging on your console please make sure that you activate `use_stderr`
 in tempest.conf or use the default `logging.conf.sample` file.
 
+Running default stress test set
+-------------------------------
+
+The stress test framework can automatically discover test inside the tempest
+test suite. All test flag with the `@stresstest` decorator will be executed.
+In order to use this discovery you have to be in the tempest root directory
+and execute the following:
+
+	tempest/stress/run_stress.py -a -d 30
+
 Running the sample test
 -----------------------
 
diff --git a/tempest/test.py b/tempest/test.py
index edba709..6ae7925 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -165,7 +165,11 @@
     if at_exit_set:
         raise RuntimeError("tearDownClass does not calls the super's "
                            "tearDownClass in these classes: "
-                           + str(at_exit_set))
+                           + str(at_exit_set) + "\n"
+                           "If you see the exception, with another "
+                           "exception please do not report this one!"
+                           "If you are changing tempest code, make sure you",
+                           "are calling the super class's tearDownClass!")
 
 atexit.register(validate_tearDownClass)
 
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
new file mode 100644
index 0000000..ba83cf4
--- /dev/null
+++ b/tempest/tests/base.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import os
+
+import fixtures
+import testtools
+
+from tempest.openstack.common.fixture import moxstubout
+
+
+class TestCase(testtools.TestCase):
+
+    def setUp(self):
+        super(TestCase, self).setUp()
+        if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
+                os.environ.get('OS_STDOUT_CAPTURE') == '1'):
+            stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+        if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
+                os.environ.get('OS_STDERR_CAPTURE') == '1'):
+            stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+
+        mox_fixture = self.useFixture(moxstubout.MoxStubout())
+        self.mox = mox_fixture.mox
+        self.stubs = mox_fixture.stubs
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
new file mode 100644
index 0000000..ab0d114
--- /dev/null
+++ b/tempest/tests/test_list_tests.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    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 subprocess
+
+from tempest.tests import base
+
+
+class TestTestList(base.TestCase):
+
+    def test_no_import_errors(self):
+        import_failures = []
+        p = subprocess.Popen(['testr', 'list-tests'], stdout=subprocess.PIPE)
+        ids = p.stdout.read()
+        ids = ids.split('\n')
+        for test_id in ids:
+            if re.match('(\w+\.){3}\w+', test_id):
+                if not test_id.startswith('tempest.'):
+                    fail_id = test_id.split('unittest.loader.ModuleImport'
+                                            'Failure.')[1]
+                    import_failures.append(fail_id)
+        error_message = ("The following tests have import failures and aren't"
+                         " being run with test filters %s" % import_failures)
+        self.assertFalse(import_failures, error_message)
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index dbe3420..dbf1809 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -18,12 +18,13 @@
 import shutil
 import subprocess
 import tempfile
-import testtools
+
+from tempest.tests import base
 
 DEVNULL = open(os.devnull, 'wb')
 
 
-class TestWrappers(testtools.TestCase):
+class TestWrappers(base.TestCase):
     def setUp(self):
         super(TestWrappers, self).setUp()
         # Setup test dirs
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 5295e44..5ae21c8 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -193,11 +193,10 @@
 class BotoTestCase(tempest.test.BaseTestCase):
     """Recommended to use as base class for boto related test."""
 
-    conclusion = decision_maker()
-
     @classmethod
     def setUpClass(cls):
         super(BotoTestCase, cls).setUpClass()
+        cls.conclusion = decision_maker()
         # The trash contains cleanup functions and paramaters in tuples
         # (function, *args, **kwargs)
         cls._resource_trash_bin = {}
diff --git a/test-requirements.txt b/test-requirements.txt
index 8aa6ed9..fbe7e43 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,3 +4,4 @@
 sphinx>=1.1.2
 python-subunit
 oslo.sphinx
+mox>=0.5.3
diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh
new file mode 100755
index 0000000..b86e0c2
--- /dev/null
+++ b/tools/config/generate_sample.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+print_hint() {
+    echo "Try \`${0##*/} --help' for more information." >&2
+}
+
+PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:o: \
+                 --long help,base-dir:,package-name:,output-dir: -- "$@")
+
+if [ $? != 0 ] ; then print_hint ; exit 1 ; fi
+
+eval set -- "$PARSED_OPTIONS"
+
+while true; do
+    case "$1" in
+        -h|--help)
+            echo "${0##*/} [options]"
+            echo ""
+            echo "options:"
+            echo "-h, --help                show brief help"
+            echo "-b, --base-dir=DIR        project base directory"
+            echo "-p, --package-name=NAME   project package name"
+            echo "-o, --output-dir=DIR      file output directory"
+            exit 0
+            ;;
+        -b|--base-dir)
+            shift
+            BASEDIR=`echo $1 | sed -e 's/\/*$//g'`
+            shift
+            ;;
+        -p|--package-name)
+            shift
+            PACKAGENAME=`echo $1`
+            shift
+            ;;
+        -o|--output-dir)
+            shift
+            OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'`
+            shift
+            ;;
+        --)
+            break
+            ;;
+    esac
+done
+
+BASEDIR=${BASEDIR:-`pwd`}
+if ! [ -d $BASEDIR ]
+then
+    echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1
+elif [[ $BASEDIR != /* ]]
+then
+    BASEDIR=$(cd "$BASEDIR" && pwd)
+fi
+
+PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}}
+TARGETDIR=$BASEDIR/$PACKAGENAME
+if ! [ -d $TARGETDIR ]
+then
+    echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1
+fi
+
+OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc}
+# NOTE(bnemec): Some projects put their sample config in etc/,
+#               some in etc/$PACKAGENAME/
+if [ -d $OUTPUTDIR/$PACKAGENAME ]
+then
+    OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME
+elif ! [ -d $OUTPUTDIR ]
+then
+    echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2
+    exit 1
+fi
+
+BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'`
+find $TARGETDIR -type f -name "*.pyc" -delete
+FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \
+        -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u)
+
+EXTRA_MODULES_FILE="`dirname $0`/oslo.config.generator.rc"
+if test -r "$EXTRA_MODULES_FILE"
+then
+    source "$EXTRA_MODULES_FILE"
+fi
+
+export EVENTLET_NO_GREENDNS=yes
+
+OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs)
+[ "$OS_VARS" ] && eval "unset \$OS_VARS"
+DEFAULT_MODULEPATH=tempest.openstack.common.config.generator
+MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
+OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
+python -m $MODULEPATH $FILES > $OUTPUTFILE
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 92d66ae..1bab88a 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -121,9 +121,6 @@
 
         self.pip_install('-r', self.requirements, '-r', self.test_requirements)
 
-    def post_process(self):
-        self.get_distro().post_process()
-
     def parse_args(self, argv):
         """Parses command-line arguments."""
         parser = optparse.OptionParser()
@@ -156,14 +153,6 @@
                  ' requires virtualenv, please install it using your'
                  ' favorite package management tool' % self.project)
 
-    def post_process(self):
-        """Any distribution-specific post-processing gets done here.
-
-        In particular, this is useful for applying patches to code inside
-        the venv.
-        """
-        pass
-
 
 class Fedora(Distro):
     """This covers all Fedora-based distributions.
@@ -175,10 +164,6 @@
         return self.run_command_with_code(['rpm', '-q', pkg],
                                           check_exit_code=False)[1] == 0
 
-    def apply_patch(self, originalfile, patchfile):
-        self.run_command(['patch', '-N', originalfile, patchfile],
-                         check_exit_code=False)
-
     def install_virtualenv(self):
         if self.check_cmd('virtualenv'):
             return
@@ -187,27 +172,3 @@
             self.die("Please install 'python-virtualenv'.")
 
         super(Fedora, self).install_virtualenv()
-
-    def post_process(self):
-        """Workaround for a bug in eventlet.
-
-        This currently affects RHEL6.1, but the fix can safely be
-        applied to all RHEL and Fedora distributions.
-
-        This can be removed when the fix is applied upstream.
-
-        Nova: https://bugs.launchpad.net/nova/+bug/884915
-        Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
-        RHEL: https://bugzilla.redhat.com/958868
-        """
-
-        if os.path.exists('contrib/redhat-eventlet.patch'):
-            # Install "patch" program if it's not there
-            if not self.check_pkg('patch'):
-                self.die("Please install 'patch'.")
-
-            # Apply the eventlet patch
-            self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
-                                          'site-packages',
-                                          'eventlet/green/subprocess.py'),
-                             'contrib/redhat-eventlet.patch')
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
new file mode 100755
index 0000000..1b5fe68
--- /dev/null
+++ b/tools/verify_tempest_config.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    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 sys
+
+from tempest import clients
+from tempest import config
+
+
+CONF = config.TempestConfig()
+
+#Dicts matching extension names to config options
+NOVA_EXTENSIONS = {
+    'disk_config': 'DiskConfig',
+    'change_password': 'ServerPassword',
+    'flavor_extra': 'FlavorExtraSpecs'
+}
+
+
+def verify_glance_api_versions(os):
+    # Check glance api versions
+    __, versions = os.image_client.get_versions()
+    if CONF.image_feature_enabled.api_v1 != ('v1.1' in versions or 'v1.0' in
+                                             versions):
+        print 'Config option image api_v1 should be change to: %s' % (
+            not CONF.image_feature_enabled.api_v1)
+    if CONF.image_feature_enabled.api_v2 != ('v2.0' in versions):
+        print 'Config option image api_v2 should be change to: %s' % (
+            not CONF.image_feature_enabled.api_v2)
+
+
+def verify_extensions(os):
+    results = {}
+    extensions_client = os.extensions_client
+    __, resp = extensions_client.list_extensions()
+    resp = resp['extensions']
+    extensions = map(lambda x: x['name'], resp)
+    results['nova_features'] = {}
+    for extension in NOVA_EXTENSIONS.keys():
+        if NOVA_EXTENSIONS[extension] in extensions:
+            results['nova_features'][extension] = True
+        else:
+            results['nova_features'][extension] = False
+    return results
+
+
+def display_results(results):
+    for option in NOVA_EXTENSIONS.keys():
+        config_value = getattr(CONF.compute_feature_enabled, option)
+        if config_value != results['nova_features'][option]:
+            print "Config option: %s should be changed to: %s" % (
+                option, not config_value)
+
+
+def main(argv):
+    os = clients.ComputeAdminManager(interface='json')
+    results = verify_extensions(os)
+    verify_glance_api_versions(os)
+    display_results(results)
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/tox.ini b/tox.ini
index d93112c..a3c781b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -81,7 +81,7 @@
 # https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
 # job would fail if we moved it to parallel.
 commands =
-   sh tools/pretty_tox_serial.sh 'smoke {posargs}'
+   sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
 
 [testenv:coverage]
 sitepackages = True