Merge "Race condition in ListImageFilters tests"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 937bbd3..6101466 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -43,7 +43,7 @@
 #logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
 
 # list of logger=LEVEL pairs (list value)
-#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO
+#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,paramiko=INFO
 
 # publish error events (boolean value)
 #publish_errors=false
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index e5a0d4d..a822fae 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -160,14 +160,6 @@
     - module: "cinder.brick.local_dev.lvm"
       message: "Can't remove open logical volume"
 
-q-dhpc:
-    - module: "neutron.common.legacy"
-      message: "Skipping unknown group key: firewall_driver"
-    - module: "neutron.agent.dhcp_agent"
-      message: "Unable to enable dhcp"
-    - module: "neutron.agent.dhcp_agent"
-      message: " Network .* RPC info call failed"
-
 ceilometer-collector:
     - module: "stevedore.extension"
       message: ".*"
diff --git a/run_tests.sh b/run_tests.sh
index 5c8ce7d..3c9c051 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,7 +11,6 @@
   echo "  -u, --update             Update the virtual environment with any newer package versions"
   echo "  -s, --smoke              Only run smoke tests"
   echo "  -t, --serial             Run testr serially"
-  echo "  -c, --nova-coverage      Enable Nova coverage collection"
   echo "  -C, --config             Config file location"
   echo "  -p, --pep8               Just run pep8"
   echo "  -h, --help               Print this usage message"
@@ -31,13 +30,12 @@
 no_site_packages=0
 force=0
 wrapper=""
-nova_coverage=0
 config_file=""
 update=0
 logging=0
 logging_config=etc/logging.conf
 
-if ! options=$(getopt -o VNnfustcphdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,nova-coverage,pep8,help,debug,config:,logging,logging-config: -- "$@")
+if ! options=$(getopt -o VNnfustphdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,pep8,help,debug,config:,logging,logging-config: -- "$@")
 then
     # parse error
     usage
@@ -55,7 +53,6 @@
     -f|--force) force=1;;
     -u|--update) update=1;;
     -d|--debug) set -o xtrace;;
-    -c|--nova-coverage) let nova_coverage=1;;
     -C|--config) config_file=$2; shift;;
     -p|--pep8) let just_pep8=1;;
     -s|--smoke) testrargs+="smoke"; noseargs+="--attr=type=smoke";;
@@ -131,16 +128,6 @@
   ${wrapper} flake8
 }
 
-function run_coverage_start {
-  echo "Starting nova-coverage"
-  ${wrapper} python tools/tempest_coverage.py -c start
-}
-
-function run_coverage_report {
-  echo "Generating nova-coverage report"
-  ${wrapper} python tools/tempest_coverage.py -c report
-}
-
 if [ $never_venv -eq 0 ]
 then
   # Remove the virtual environment if --force used
@@ -176,11 +163,6 @@
     exit
 fi
 
-if [ $nova_coverage -eq 1 ]; then
-    run_coverage_start
-fi
-
-
 py_version=`${wrapper} python --version 2>&1`
 if [[ $py_version =~ "2.6" ]] ; then
     run_tests_nose
@@ -189,10 +171,6 @@
 fi
 retval=$?
 
-if [ $nova_coverage -eq 1 ]; then
-    run_coverage_report
-fi
-
 if [ -z "$testrargs" ]; then
     run_pep8
 fi
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index 327d8b8..6122758 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -17,7 +17,6 @@
 #    under the License.
 
 from tempest.api.compute import base
-from tempest import exceptions
 from tempest.test import attr
 
 
@@ -41,11 +40,6 @@
         self.assertEqual(200, resp.status)
         self.assertNotEqual(0, len(services))
 
-    @attr(type=['negative', 'gate'])
-    def test_list_services_with_non_admin_user(self):
-        self.assertRaises(exceptions.Unauthorized,
-                          self.non_admin_client.list_services)
-
     @attr(type='gate')
     def test_get_service_by_service_binary_name(self):
         binary_name = 'nova-compute'
@@ -74,15 +68,6 @@
         # on order.
         self.assertEqual(sorted(s1), sorted(s2))
 
-    @attr(type=['negative', 'gate'])
-    def test_get_service_by_invalid_params(self):
-        # return all services if send the request with invalid parameter
-        resp, services = self.client.list_services()
-        params = {'xxx': 'nova-compute'}
-        resp, services_xxx = self.client.list_services(params)
-        self.assertEqual(200, resp.status)
-        self.assertEqual(len(services), len(services_xxx))
-
     @attr(type='gate')
     def test_get_service_by_service_and_host_name(self):
         resp, services = self.client.list_services()
@@ -95,41 +80,6 @@
         self.assertEqual(host_name, services[0]['host'])
         self.assertEqual(binary_name, services[0]['binary'])
 
-    @attr(type=['negative', 'gate'])
-    def test_get_service_by_invalid_service_and_valid_host(self):
-        resp, services = self.client.list_services()
-        host_name = services[0]['host']
-        params = {'host': host_name, 'binary': 'xxx'}
-        resp, services = self.client.list_services(params)
-        self.assertEqual(200, resp.status)
-        self.assertEqual(0, len(services))
-
-    @attr(type=['negative', 'gate'])
-    def test_get_service_with_valid_service_and_invalid_host(self):
-        resp, services = self.client.list_services()
-        binary_name = services[0]['binary']
-        params = {'host': 'xxx', 'binary': binary_name}
-        resp, services = self.client.list_services(params)
-        self.assertEqual(200, resp.status)
-        self.assertEqual(0, len(services))
-
-    @attr(type='gate')
-    def test_service_enable_disable(self):
-        resp, services = self.client.list_services()
-        host_name = services[0]['host']
-        binary_name = services[0]['binary']
-
-        resp, service = self.client.disable_service(host_name, binary_name)
-        self.assertEqual(200, resp.status)
-        params = {'host': host_name, 'binary': binary_name}
-        resp, services = self.client.list_services(params)
-        self.assertEqual('disabled', services[0]['status'])
-
-        resp, service = self.client.enable_service(host_name, binary_name)
-        self.assertEqual(200, resp.status)
-        resp, services = self.client.list_services(params)
-        self.assertEqual('enabled', services[0]['status'])
-
 
 class ServicesAdminTestXML(ServicesAdminTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
new file mode 100644
index 0000000..da1482e
--- /dev/null
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -0,0 +1,70 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+    """
+    Tests Services API. List and Enable/Disable require admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ServicesAdminNegativeTestJSON, cls).setUpClass()
+        cls.client = cls.os_adm.services_client
+        cls.non_admin_client = cls.services_client
+
+    @attr(type=['negative', 'gate'])
+    def test_list_services_with_non_admin_user(self):
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.list_services)
+
+    @attr(type=['negative', 'gate'])
+    def test_get_service_by_invalid_params(self):
+        # return all services if send the request with invalid parameter
+        resp, services = self.client.list_services()
+        params = {'xxx': 'nova-compute'}
+        resp, services_xxx = self.client.list_services(params)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(len(services), len(services_xxx))
+
+    @attr(type=['negative', 'gate'])
+    def test_get_service_by_invalid_service_and_valid_host(self):
+        resp, services = self.client.list_services()
+        host_name = services[0]['host']
+        params = {'host': host_name, 'binary': 'xxx'}
+        resp, services = self.client.list_services(params)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(0, len(services))
+
+    @attr(type=['negative', 'gate'])
+    def test_get_service_with_valid_service_and_invalid_host(self):
+        resp, services = self.client.list_services()
+        binary_name = services[0]['binary']
+        params = {'host': 'xxx', 'binary': binary_name}
+        resp, services = self.client.list_services(params)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(0, len(services))
+
+
+class ServicesAdminNegativeTestXML(ServicesAdminNegativeTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 7c2aa4a..3c00851 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -244,6 +244,7 @@
         cls.interfaces_client = cls.os.interfaces_v3_client
         cls.hypervisor_client = cls.os.hypervisor_v3_client
         cls.tenant_usages_client = cls.os.tenant_usages_v3_client
+        cls.volumes_client = cls.os.volumes_client
 
     @classmethod
     def create_image_from_server(cls, server_id, **kwargs):
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
new file mode 100644
index 0000000..b866db1
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -0,0 +1,112 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
+
+    """
+    Tests Flavor Access API extension.
+    Add and remove Flavor Access require admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsAccessTestJSON, 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
+        admin_client = cls._get_identity_admin_client()
+        cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+                                                     tenant_name)
+        cls.tenant_id = cls.tenant['id']
+        cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+                                                         flavors_client.
+                                                         tenant_name)
+        cls.adm_tenant_id = cls.adm_tenant['id']
+        cls.flavor_name_prefix = 'test_flavor_access_'
+        cls.ram = 512
+        cls.vcpus = 1
+        cls.disk = 10
+
+    @attr(type='gate')
+    def test_flavor_access_list_with_private_flavor(self):
+        # Test to list flavor access successfully by querying private flavor
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+        self.assertEqual(resp.status, 200)
+        resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
+        self.assertEqual(resp.status, 200)
+        self.assertEqual(len(flavor_access), 1, str(flavor_access))
+        first_flavor = flavor_access[0]
+        self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
+        self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+
+    @attr(type='gate')
+    def test_flavor_access_add_remove(self):
+        # Test to add and remove flavor access to a given tenant.
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+        # Add flavor access to a tenant.
+        resp_body = {
+            "tenant_id": str(self.tenant_id),
+            "flavor_id": str(new_flavor['id']),
+        }
+        add_resp, add_body = \
+            self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+        self.assertEqual(add_resp.status, 200)
+        self.assertIn(resp_body, add_body)
+
+        # The flavor is present in list.
+        resp, flavors = self.flavors_client.list_flavors_with_detail()
+        self.assertEqual(resp.status, 200)
+        self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+        # Remove flavor access from a tenant.
+        remove_resp, remove_body = \
+            self.client.remove_flavor_access(new_flavor['id'], self.tenant_id)
+        self.assertEqual(remove_resp.status, 200)
+        self.assertNotIn(resp_body, remove_body)
+
+        # The flavor is not present in list.
+        resp, flavors = self.flavors_client.list_flavors_with_detail()
+        self.assertEqual(resp.status, 200)
+        self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+
+class FlavorsAdminTestXML(FlavorsAccessTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
new file mode 100644
index 0000000..340c1c7
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -0,0 +1,153 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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.
+
+import uuid
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+    """
+    Tests Flavor Access API extension.
+    Add and remove Flavor Access require admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsAccessNegativeTestJSON, 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
+        admin_client = cls._get_identity_admin_client()
+        cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+                                                     tenant_name)
+        cls.tenant_id = cls.tenant['id']
+        cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+                                                         flavors_client.
+                                                         tenant_name)
+        cls.adm_tenant_id = cls.adm_tenant['id']
+        cls.flavor_name_prefix = 'test_flavor_access_'
+        cls.ram = 512
+        cls.vcpus = 1
+        cls.disk = 10
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_access_list_with_public_flavor(self):
+        # Test to list flavor access with exceptions by querying public flavor
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='True')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+        self.assertEqual(resp.status, 200)
+        self.assertRaises(exceptions.NotFound,
+                          self.client.list_flavor_access,
+                          new_flavor_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_add(self):
+        # Test to add flavor access as a user without admin privileges.
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.add_flavor_access,
+                          new_flavor['id'],
+                          self.tenant_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_remove(self):
+        # Test to remove flavor access as a user without admin privileges.
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+        # Add flavor access to a tenant.
+        self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+        self.addCleanup(self.client.remove_flavor_access,
+                        new_flavor['id'], self.tenant_id)
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.remove_flavor_access,
+                          new_flavor['id'],
+                          self.tenant_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_add_flavor_access_duplicate(self):
+        # Create a new flavor.
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+        # Add flavor access to a tenant.
+        self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+        self.addCleanup(self.client.remove_flavor_access,
+                        new_flavor['id'], self.tenant_id)
+
+        # An exception should be raised when adding flavor access to the same
+        # tenant
+        self.assertRaises(exceptions.Conflict,
+                          self.client.add_flavor_access,
+                          new_flavor['id'],
+                          self.tenant_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_remove_flavor_access_not_found(self):
+        # Create a new flavor.
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        new_flavor_id = data_utils.rand_int_id(start=1000)
+        resp, new_flavor = self.client.create_flavor(flavor_name,
+                                                     self.ram, self.vcpus,
+                                                     self.disk,
+                                                     new_flavor_id,
+                                                     is_public='False')
+        self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+        # An exception should be raised when flavor access is not found
+        self.assertRaises(exceptions.NotFound,
+                          self.client.remove_flavor_access,
+                          new_flavor['id'],
+                          str(uuid.uuid4()))
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
new file mode 100644
index 0000000..49b0429
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
@@ -0,0 +1,132 @@
+# 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.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
+
+    """
+    Tests Flavor Extra Spec API extension.
+    SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
+    GET Flavor Extra specs can be performed even by without admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsExtraSpecsTestJSON, 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 = data_utils.rand_name('test_flavor')
+        ram = 512
+        vcpus = 1
+        disk = 10
+        ephemeral = 10
+        cls.new_flavor_id = data_utils.rand_int_id(start=1000)
+        swap = 1024
+        rxtx = 1
+        # Create a flavor so as to set/get/unset extra specs
+        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(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
+
+    @attr(type='gate')
+    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
+        set_resp, set_body = \
+            self.client.set_flavor_extra_spec(self.flavor['id'], specs)
+        self.assertEqual(set_resp.status, 200)
+        self.assertEqual(set_body, specs)
+        # GET extra specs and verify
+        get_resp, get_body = \
+            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 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, {"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)
+        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_all_keys(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='gate')
+    def test_flavor_non_admin_get_specific_key(self):
+        specs = {"key1": "value1", "key2": "value2"}
+        resp, body = self.client.set_flavor_extra_spec(
+            self.flavor['id'], specs)
+        self.assertEqual(resp.status, 200)
+        self.assertEqual(body['key1'], 'value1')
+        self.assertIn('key2', body)
+        resp, body = self.flavors_client.get_flavor_extra_spec_with_key(
+            self.flavor['id'], 'key1')
+        self.assertEqual(resp.status, 200)
+        self.assertEqual(body['key1'], 'value1')
+        self.assertNotIn('key2', body)
+
+
+class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
new file mode 100644
index 0000000..d7e1f9f
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,136 @@
+# 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 import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+    """
+    Negative Tests Flavor Extra Spec API extension.
+    SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
+    """
+
+    _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 = data_utils.rand_name('test_flavor')
+        ram = 512
+        vcpus = 1
+        disk = 10
+        ephemeral = 10
+        cls.new_flavor_id = data_utils.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_update_specific_key(self):
+        # non admin user is not allowed to update flavor extra spec
+        specs = {"key1": "value1", "key2": "value2"}
+        resp, body = self.client.set_flavor_extra_spec(
+            self.flavor['id'], specs)
+        self.assertEqual(resp.status, 200)
+        self.assertEqual(body['key1'], 'value1')
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.
+                          update_flavor_extra_spec,
+                          self.flavor['id'],
+                          'key1',
+                          key1='value1_new')
+
+    @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 = data_utils.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/v3/servers/test_server_metadata.py b/tempest/api/compute/v3/servers/test_server_metadata.py
new file mode 100644
index 0000000..ee0f4a9
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_metadata.py
@@ -0,0 +1,217 @@
+# 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.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerMetadataTestJSON(base.BaseV2ComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ServerMetadataTestJSON, cls).setUpClass()
+        cls.client = cls.servers_client
+        cls.quotas = cls.quotas_client
+        cls.admin_client = cls._get_identity_admin_client()
+        resp, tenants = cls.admin_client.list_tenants()
+        cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+                         cls.client.tenant_name][0]
+        resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
+
+        cls.server_id = server['id']
+
+    def setUp(self):
+        super(ServerMetadataTestJSON, self).setUp()
+        meta = {'key1': 'value1', 'key2': 'value2'}
+        resp, _ = self.client.set_server_metadata(self.server_id, meta)
+        self.assertEqual(resp.status, 200)
+
+    @attr(type='gate')
+    def test_list_server_metadata(self):
+        # All metadata key/value pairs for a server should be returned
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+
+        # Verify the expected metadata items are in the list
+        self.assertEqual(200, resp.status)
+        expected = {'key1': 'value1', 'key2': 'value2'}
+        self.assertEqual(expected, resp_metadata)
+
+    @attr(type='gate')
+    def test_set_server_metadata(self):
+        # The server's metadata should be replaced with the provided values
+        # Create a new set of metadata for the server
+        req_metadata = {'meta2': 'data2', 'meta3': 'data3'}
+        resp, metadata = self.client.set_server_metadata(self.server_id,
+                                                         req_metadata)
+        self.assertEqual(200, resp.status)
+
+        # Verify the expected values are correct, and that the
+        # previous values have been removed
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+        self.assertEqual(resp_metadata, req_metadata)
+
+    @attr(type='gate')
+    def test_server_create_metadata_key_too_long(self):
+        # Attempt to start a server with a meta-data key that is > 255
+        # characters
+
+        # Try a few values
+        for sz in [256, 257, 511, 1023]:
+            key = "k" * sz
+            meta = {key: 'data1'}
+            self.assertRaises(exceptions.OverLimit,
+                              self.create_test_server,
+                              meta=meta)
+
+        # no teardown - all creates should fail
+
+    @attr(type='gate')
+    def test_update_server_metadata(self):
+        # The server's metadata values should be updated to the
+        # provided values
+        meta = {'key1': 'alt1', 'key3': 'value3'}
+        resp, metadata = self.client.update_server_metadata(self.server_id,
+                                                            meta)
+        self.assertEqual(200, resp.status)
+
+        # Verify the values have been updated to the proper values
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+        expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
+        self.assertEqual(expected, resp_metadata)
+
+    @attr(type='gate')
+    def test_update_metadata_empty_body(self):
+        # The original metadata should not be lost if empty metadata body is
+        # passed
+        meta = {}
+        _, metadata = self.client.update_server_metadata(self.server_id, meta)
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+        expected = {'key1': 'value1', 'key2': 'value2'}
+        self.assertEqual(expected, resp_metadata)
+
+    @attr(type='gate')
+    def test_get_server_metadata_item(self):
+        # The value for a specific metadata key should be returned
+        resp, meta = self.client.get_server_metadata_item(self.server_id,
+                                                          'key2')
+        self.assertEqual('value2', meta['key2'])
+
+    @attr(type='gate')
+    def test_set_server_metadata_item(self):
+        # The item's value should be updated to the provided value
+        # Update the metadata value
+        meta = {'nova': 'alt'}
+        resp, body = self.client.set_server_metadata_item(self.server_id,
+                                                          'nova', meta)
+        self.assertEqual(200, resp.status)
+
+        # Verify the meta item's value has been updated
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+        expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'}
+        self.assertEqual(expected, resp_metadata)
+
+    @attr(type='gate')
+    def test_delete_server_metadata_item(self):
+        # The metadata value/key pair should be deleted from the server
+        resp, meta = self.client.delete_server_metadata_item(self.server_id,
+                                                             'key1')
+        self.assertEqual(204, resp.status)
+
+        # Verify the metadata item has been removed
+        resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+        expected = {'key2': 'value2'}
+        self.assertEqual(expected, resp_metadata)
+
+    @attr(type=['negative', 'gate'])
+    def test_server_metadata_negative(self):
+        # Blank key should trigger an error.
+        meta = {'': 'data1'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_test_server,
+                          meta=meta)
+
+        # GET on a non-existent server should not succeed
+        self.assertRaises(exceptions.NotFound,
+                          self.client.get_server_metadata_item, 999, 'test2')
+
+        # List metadata on a non-existent server should not succeed
+        self.assertRaises(exceptions.NotFound,
+                          self.client.list_server_metadata, 999)
+
+        # Raise BadRequest if key in uri does not match
+        # the key passed in body.
+        meta = {'testkey': 'testvalue'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.set_server_metadata_item,
+                          self.server_id, 'key', meta)
+
+        # Set metadata on a non-existent server should not succeed
+        meta = {'meta1': 'data1'}
+        self.assertRaises(exceptions.NotFound,
+                          self.client.set_server_metadata, 999, meta)
+
+        # An update should not happen for a non-existent image
+        meta = {'key1': 'value1', 'key2': 'value2'}
+        self.assertRaises(exceptions.NotFound,
+                          self.client.update_server_metadata, 999, meta)
+
+        # Blank key should trigger an error
+        meta = {'': 'data1'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_server_metadata,
+                          self.server_id, meta=meta)
+
+        # Should not be able to delete metadata item from a non-existent server
+        self.assertRaises(exceptions.NotFound,
+                          self.client.delete_server_metadata_item, 999, 'd')
+
+        # Raise a 413 OverLimit exception while exceeding metadata items limit
+        # for tenant.
+        _, quota_set = self.quotas.get_quota_set(self.tenant_id)
+        quota_metadata = quota_set['metadata_items']
+        req_metadata = {}
+        for num in range(1, quota_metadata + 2):
+            req_metadata['key' + str(num)] = 'val' + str(num)
+        self.assertRaises(exceptions.OverLimit,
+                          self.client.set_server_metadata,
+                          self.server_id, req_metadata)
+
+        # Raise a 413 OverLimit exception while exceeding metadata items limit
+        # for tenant (update).
+        self.assertRaises(exceptions.OverLimit,
+                          self.client.update_server_metadata,
+                          self.server_id, req_metadata)
+
+        # Raise a bad request error for blank key.
+        # set_server_metadata will replace all metadata with new value
+        meta = {'': 'data1'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.set_server_metadata,
+                          self.server_id, meta=meta)
+
+        # Raise a bad request error for a missing metadata field
+        # set_server_metadata will replace all metadata with new value
+        meta = {'meta1': 'data1'}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.set_server_metadata,
+                          self.server_id, meta=meta, no_metadata_field=True)
+
+
+class ServerMetadataTestXML(ServerMetadataTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_personality.py b/tempest/api/compute/v3/servers/test_server_personality.py
new file mode 100644
index 0000000..c6d2e44
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_personality.py
@@ -0,0 +1,68 @@
+# 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
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ServerPersonalityTestJSON, cls).setUpClass()
+        cls.client = cls.servers_client
+        cls.user_client = cls.limits_client
+
+    @attr(type='gate')
+    def test_personality_files_exceed_limit(self):
+        # Server creation should fail if greater than the maximum allowed
+        # number of files are injected into the server.
+        file_contents = 'This is a test file.'
+        personality = []
+        max_file_limit = \
+            self.user_client.get_specific_absolute_limit("maxPersonality")
+        for i in range(0, int(max_file_limit) + 1):
+            path = 'etc/test' + str(i) + '.txt'
+            personality.append({'path': path,
+                                'contents': base64.b64encode(file_contents)})
+        self.assertRaises(exceptions.OverLimit, self.create_test_server,
+                          personality=personality)
+
+    @attr(type='gate')
+    def test_can_create_server_with_max_number_personality_files(self):
+        # Server should be created successfully if maximum allowed number of
+        # files is injected into the server during creation.
+        file_contents = 'This is a test file.'
+        max_file_limit = \
+            self.user_client.get_specific_absolute_limit("maxPersonality")
+        person = []
+        for i in range(0, int(max_file_limit)):
+            path = 'etc/test' + str(i) + '.txt'
+            person.append({
+                'path': path,
+                'contents': base64.b64encode(file_contents),
+            })
+        resp, server = self.create_test_server(personality=person)
+        self.assertEqual('202', resp['status'])
+
+
+class ServerPersonalityTestXML(ServerPersonalityTestJSON):
+    _interface = "xml"
diff --git a/tempest/api/compute/v3/servers/test_server_rescue.py b/tempest/api/compute/v3/servers/test_server_rescue.py
index 1008670..eebd4d8 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue.py
@@ -16,87 +16,69 @@
 #    under the License.
 
 from tempest.api.compute import base
-from tempest.common.utils import data_utils
 from tempest import exceptions
 from tempest.test import attr
 
 
-class ServerRescueTestJSON(base.BaseV2ComputeTest):
+class ServerRescueV3TestJSON(base.BaseV3ComputeTest):
     _interface = 'json'
 
     @classmethod
     def setUpClass(cls):
-        super(ServerRescueTestJSON, cls).setUpClass()
+        super(ServerRescueV3TestJSON, cls).setUpClass()
         cls.device = 'vdf'
 
-        # Floating IP creation
-        resp, body = cls.floating_ips_client.create_floating_ip()
-        cls.floating_ip_id = str(body['id']).strip()
-        cls.floating_ip = str(body['ip']).strip()
-
-        # Security group creation
-        cls.sg_name = data_utils.rand_name('sg')
-        cls.sg_desc = data_utils.rand_name('sg-desc')
-        resp, cls.sg = \
-            cls.security_groups_client.create_security_group(cls.sg_name,
-                                                             cls.sg_desc)
-        cls.sg_id = cls.sg['id']
-
         # Create a volume and wait for it to become ready for attach
         resp, cls.volume_to_attach = \
-            cls.volumes_extensions_client.create_volume(1,
-                                                        display_name=
-                                                        'test_attach')
-        cls.volumes_extensions_client.wait_for_volume_status(
+            cls.volumes_client.create_volume(1,
+                                             display_name=
+                                             'test_attach')
+        cls.volumes_client.wait_for_volume_status(
             cls.volume_to_attach['id'], 'available')
 
         # Create a volume and wait for it to become ready for attach
         resp, cls.volume_to_detach = \
-            cls.volumes_extensions_client.create_volume(1,
-                                                        display_name=
-                                                        'test_detach')
-        cls.volumes_extensions_client.wait_for_volume_status(
+            cls.volumes_client.create_volume(1,
+                                             display_name=
+                                             'test_detach')
+        cls.volumes_client.wait_for_volume_status(
             cls.volume_to_detach['id'], 'available')
 
         # Server for positive tests
         resp, server = cls.create_test_server(wait_until='BUILD')
         resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
         cls.server_id = server['id']
-        cls.password = server['adminPass']
+        cls.password = server['admin_password']
         cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
 
         # Server for negative tests
         cls.rescue_id = resc_server['id']
-        cls.rescue_password = resc_server['adminPass']
+        cls.rescue_password = resc_server['admin_password']
 
         cls.servers_client.rescue_server(
             cls.rescue_id, cls.rescue_password)
         cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE')
 
     def setUp(self):
-        super(ServerRescueTestJSON, self).setUp()
+        super(ServerRescueV3TestJSON, self).setUp()
 
     @classmethod
     def tearDownClass(cls):
-        # Deleting the floating IP which is created in this method
-        cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
-        client = cls.volumes_extensions_client
+        client = cls.volumes_client
         client.delete_volume(str(cls.volume_to_attach['id']).strip())
         client.delete_volume(str(cls.volume_to_detach['id']).strip())
-        resp, cls.sg = cls.security_groups_client.delete_security_group(
-            cls.sg_id)
-        super(ServerRescueTestJSON, cls).tearDownClass()
+        super(ServerRescueV3TestJSON, cls).tearDownClass()
 
     def tearDown(self):
-        super(ServerRescueTestJSON, self).tearDown()
+        super(ServerRescueV3TestJSON, self).tearDown()
 
     def _detach(self, server_id, volume_id):
         self.servers_client.detach_volume(server_id, volume_id)
-        self.volumes_extensions_client.wait_for_volume_status(volume_id,
-                                                              'available')
+        self.volumes_client.wait_for_volume_status(volume_id,
+                                                   'available')
 
     def _delete(self, volume_id):
-        self.volumes_extensions_client.delete_volume(volume_id)
+        self.volumes_client.delete_volume(volume_id)
 
     def _unrescue(self, server_id):
         resp, body = self.servers_client.unrescue_server(server_id)
@@ -112,7 +94,7 @@
     def test_rescue_unrescue_instance(self):
         resp, body = self.servers_client.rescue_server(
             self.server_id, self.password)
-        self.assertEqual(200, resp.status)
+        self.assertEqual(202, resp.status)
         self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
         resp, body = self.servers_client.unrescue_server(self.server_id)
         self.assertEqual(202, resp.status)
@@ -169,7 +151,7 @@
         self.servers_client.attach_volume(self.server_id,
                                           self.volume_to_detach['id'],
                                           device='/dev/%s' % self.device)
-        self.volumes_extensions_client.wait_for_volume_status(
+        self.volumes_client.wait_for_volume_status(
             self.volume_to_detach['id'], 'in-use')
 
         # Rescue the server
@@ -186,48 +168,6 @@
                           self.server_id,
                           self.volume_to_detach['id'])
 
-    @attr(type='gate')
-    def test_rescued_vm_associate_dissociate_floating_ip(self):
-        # Rescue the server
-        self.servers_client.rescue_server(
-            self.server_id, self.password)
-        self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
-        self.addCleanup(self._unrescue, self.server_id)
 
-        # Association of floating IP to a rescued vm
-        client = self.floating_ips_client
-        resp, body = client.associate_floating_ip_to_server(self.floating_ip,
-                                                            self.server_id)
-        self.assertEqual(202, resp.status)
-
-        # Disassociation of floating IP that was associated in this method
-        resp, body = \
-            client.disassociate_floating_ip_from_server(self.floating_ip,
-                                                        self.server_id)
-        self.assertEqual(202, resp.status)
-
-    @attr(type='gate')
-    def test_rescued_vm_add_remove_security_group(self):
-        # Rescue the server
-        self.servers_client.rescue_server(
-            self.server_id, self.password)
-        self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
-
-        # Add Security group
-        resp, body = self.servers_client.add_security_group(self.server_id,
-                                                            self.sg_name)
-        self.assertEqual(202, resp.status)
-
-        # Delete Security group
-        resp, body = self.servers_client.remove_security_group(self.server_id,
-                                                               self.sg_name)
-        self.assertEqual(202, resp.status)
-
-        # Unrescue the server
-        resp, body = self.servers_client.unrescue_server(self.server_id)
-        self.assertEqual(202, resp.status)
-        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
-
-
-class ServerRescueTestXML(ServerRescueTestJSON):
+class ServerRescueV3TestXML(ServerRescueV3TestJSON):
     _interface = 'xml'
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index b4128e2..c1b3391 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -107,6 +107,7 @@
                                                    object_name, data)
 
         self.assertEqual(resp["status"], "201")
+        self.assertHeaders(resp, 'Object', 'PUT')
 
     @testtools.skipIf(not accounts_quotas_available,
                       "Account Quotas middleware not available")
@@ -141,6 +142,7 @@
                                                             headers, "")
 
             self.assertEqual(resp["status"], "204")
+            self.assertHeaders(resp, 'Account', 'POST')
 
     @testtools.skipIf(not accounts_quotas_available,
                       "Account Quotas middleware not available")
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 18000b9..b2dc20f 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -86,6 +86,7 @@
         resp, _ = self.object_client.create_object(
             self.container_name, object_name, 'data')
         self.assertEqual(resp['status'], '201')
+        self.assertHeaders(resp, 'Object', 'PUT')
         # trying to get object with non authorized user token
         self.assertRaises(exceptions.Unauthorized,
                           self.custom_object_client.get_object,
@@ -100,6 +101,7 @@
         resp, _ = self.object_client.create_object(
             self.container_name, object_name, 'data')
         self.assertEqual(resp['status'], '201')
+        self.assertHeaders(resp, 'Object', 'PUT')
         # trying to delete object with non-authorized user token
         self.assertRaises(exceptions.Unauthorized,
                           self.custom_object_client.delete_object,
@@ -115,11 +117,13 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # create object
         object_name = data_utils.rand_name(name='Object')
         resp, _ = self.object_client.create_object(self.container_name,
                                                    object_name, 'data')
         self.assertEqual(resp['status'], '201')
+        self.assertHeaders(resp, 'Object', 'PUT')
         # Trying to read the object without rights
         self.assertRaises(exceptions.Unauthorized,
                           self.custom_object_client.get_object,
@@ -135,6 +139,7 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # Trying to write the object without rights
         object_name = data_utils.rand_name(name='Object')
         self.assertRaises(exceptions.Unauthorized,
@@ -153,16 +158,19 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # create object
         object_name = data_utils.rand_name(name='Object')
         resp, _ = self.object_client.create_object(self.container_name,
                                                    object_name, 'data')
         self.assertEqual(resp['status'], '201')
+        self.assertHeaders(resp, 'Object', 'PUT')
         # Trying to read the object with rights
         resp, _ = self.custom_object_client.get_object(
             self.container_name, object_name,
             metadata=self.custom_headers)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'GET')
 
     @attr(type='smoke')
     def test_write_object_with_rights(self):
@@ -174,6 +182,7 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # Trying to write the object with rights
         object_name = data_utils.rand_name(name='Object')
         resp, _ = self.custom_object_client.create_object(
@@ -181,6 +190,7 @@
             object_name, 'data',
             metadata=self.custom_headers)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'PUT')
 
     @attr(type=['negative', 'smoke'])
     def test_write_object_without_write_rights(self):
@@ -193,6 +203,7 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # Trying to write the object without write rights
         object_name = data_utils.rand_name(name='Object')
         self.assertRaises(exceptions.Unauthorized,
@@ -212,11 +223,13 @@
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp_meta, 'Container', 'POST')
         # create object
         object_name = data_utils.rand_name(name='Object')
         resp, _ = self.object_client.create_object(self.container_name,
                                                    object_name, 'data')
         self.assertEqual(resp['status'], '201')
+        self.assertHeaders(resp, 'Object', 'PUT')
         # Trying to delete the object without write rights
         self.assertRaises(exceptions.Unauthorized,
                           self.custom_object_client.delete_object,
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 04536fe..c7b5e28 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -69,6 +69,7 @@
         resp, _ = self.object_client.create_object(
             self.container_name, object_name, data)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'PUT')
 
         nafter = self._get_bytes_used()
         self.assertEqual(nbefore + len(data), nafter)
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 1eea30a..9f9abd8 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -15,6 +15,7 @@
 # under the License.
 
 from tempest.api.object_storage import base
+from tempest.common import custom_matchers
 from tempest.common.utils import data_utils
 from tempest import test
 
@@ -60,6 +61,8 @@
         resp, body = self.custom_account_client.request("GET",
                                                         self.container_name)
         self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        # This request is equivalent to GET object
+        self.assertHeaders(resp, 'Object', 'GET')
         self.assertEqual(body, self.object_data)
 
         # clean up before exiting
@@ -82,6 +85,15 @@
         resp, body = self.custom_account_client.request("GET",
                                                         self.container_name)
         self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        # The target of the request is not any Swift resource. Therefore, the
+        # existence of response header is checked without a custom matcher.
+        self.assertIn('content-length', resp)
+        self.assertIn('content-type', resp)
+        self.assertIn('x-trans-id', resp)
+        self.assertIn('date', resp)
+        # Check only the format of common headers with custom matcher
+        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
         self.assertIn(self.object_name, body)
 
         # clean up before exiting
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index 0ae7e46..51ecd16 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -18,6 +18,7 @@
 
 from tempest.api.object_storage import base
 from tempest import clients
+from tempest.common import custom_matchers
 from tempest import config
 from tempest.test import attr
 from tempest.test import HTTP_SUCCESS
@@ -80,3 +81,12 @@
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
         self.assertTrue(body.startswith(self.xml_start) and
                         body.endswith(self.xml_end))
+
+        # The target of the request is not any Swift resource. Therefore, the
+        # existence of response header is checked without a custom matcher.
+        self.assertIn('content-length', resp)
+        self.assertIn('content-type', resp)
+        self.assertIn('x-trans-id', resp)
+        self.assertIn('date', resp)
+        # Check only the format of common headers with custom matcher
+        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index 798ea4f..7bbdd1e 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -19,6 +19,7 @@
 
 from tempest.api.object_storage import base
 from tempest import clients
+from tempest.common import custom_matchers
 from tempest.test import attr
 from tempest.test import HTTP_SUCCESS
 
@@ -63,3 +64,12 @@
 
         # The status is expected to be 200
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
+        # The target of the request is not any Swift resource. Therefore, the
+        # existence of response header is checked without a custom matcher.
+        self.assertIn('content-length', resp)
+        self.assertIn('content-type', resp)
+        self.assertIn('x-trans-id', resp)
+        self.assertIn('date', resp)
+        # Check only the format of common headers with custom matcher
+        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 6fc3853..4958f70 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -62,10 +62,13 @@
             self.object_client.list_object_metadata(self.container_name,
                                                     object_name)
         self.assertEqual(resp['status'], '200')
+        self.assertHeaders(resp, 'Object', 'HEAD')
         self.assertIn('x-delete-at', resp)
         resp, body = self.object_client.get_object(self.container_name,
                                                    object_name)
         self.assertEqual(resp['status'], '200')
+        self.assertHeaders(resp, 'Object', 'GET')
+        self.assertIn('x-delete-at', resp)
         # check data
         self.assertEqual(body, data)
         # sleep for over 5 seconds, so that object expires
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index 63393e4..817c892 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -109,11 +109,13 @@
         resp, body = self.object_client.http_obj.request(url, "POST",
                                                          body, headers=headers)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, "Object", "POST")
 
         # Ensure object is available
         resp, body = self.object_client.get("%s/%s%s" % (
             self.container_name, self.object_name, "testfile"))
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, "Object", "GET")
         self.assertEqual(body, "hello world")
 
     @attr(type=['gate', 'negative'])
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index c8ce57a..bb03932 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -113,13 +113,14 @@
 
         # trying to get object using temp url within expiry time
         resp, body = self.object_client.get_object_using_temp_url(url)
-
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'GET')
         self.assertEqual(body, self.data)
 
         # Testing a HEAD on this Temp URL
         resp, body = self.object_client.head(url)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'HEAD')
 
     @attr(type='gate')
     def test_get_object_using_temp_url_key_2(self):
@@ -157,10 +158,12 @@
             url, new_data)
 
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'PUT')
 
         # Testing a HEAD on this Temp URL
         resp, body = self.object_client.head(url)
         self.assertIn(int(resp['status']), HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'HEAD')
 
         # Validate that the content of the object has been modified
         url = self._get_temp_url(self.container_name,
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index c47e1b6..d706eef 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -34,6 +34,7 @@
     def assertContainer(self, container, count, byte, versioned):
         resp, _ = self.container_client.list_container_metadata(container)
         self.assertEqual(resp['status'], ('204'))
+        self.assertHeaders(resp, 'Container', 'HEAD')
         header_value = resp.get('x-container-object-count', 'Missing Header')
         self.assertEqual(header_value, count)
         header_value = resp.get('x-container-bytes-used', 'Missing Header')
@@ -49,6 +50,7 @@
             vers_container_name)
         self.containers.append(vers_container_name)
         self.assertIn(resp['status'], ('202', '201'))
+        self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
 
         base_container_name = data_utils.rand_name(name='TestBaseContainer')
@@ -59,6 +61,7 @@
             metadata_prefix='')
         self.containers.append(base_container_name)
         self.assertIn(resp['status'], ('202', '201'))
+        self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(base_container_name, '0', '0',
                              vers_container_name)
         object_name = data_utils.rand_name(name='TestObject')
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 5cf8ed6..abb44ef 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -132,6 +132,7 @@
                     'boto=WARN',
                     'suds=INFO',
                     'keystone=INFO',
+                    'paramiko=INFO'
                 ],
                 help='list of logger=LEVEL pairs'),
     cfg.BoolOpt('publish_errors',
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
new file mode 100644
index 0000000..00d6f8a
--- /dev/null
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -0,0 +1,156 @@
+# 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 json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class FlavorsClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(FlavorsClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def list_flavors(self, params=None):
+        url = 'flavors'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['flavors']
+
+    def list_flavors_with_detail(self, params=None):
+        url = 'flavors/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['flavors']
+
+    def get_flavor_details(self, flavor_id):
+        resp, body = self.get("flavors/%s" % str(flavor_id))
+        body = json.loads(body)
+        return resp, body['flavor']
+
+    def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+        """Creates a new flavor or instance type."""
+        post_body = {
+            'name': name,
+            'ram': ram,
+            'vcpus': vcpus,
+            'disk': disk,
+            'id': flavor_id,
+        }
+        if kwargs.get('ephemeral'):
+            post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral')
+        if kwargs.get('swap'):
+            post_body['swap'] = kwargs.get('swap')
+        if kwargs.get('rxtx'):
+            post_body['rxtx_factor'] = kwargs.get('rxtx')
+        if kwargs.get('is_public'):
+            post_body['os-flavor-access:is_public'] = kwargs.get('is_public')
+        post_body = json.dumps({'flavor': post_body})
+        resp, body = self.post('flavors', post_body, self.headers)
+
+        body = json.loads(body)
+        return resp, body['flavor']
+
+    def delete_flavor(self, flavor_id):
+        """Deletes the given flavor."""
+        return self.delete("flavors/%s" % str(flavor_id))
+
+    def is_resource_deleted(self, id):
+        # Did not use get_flavor_details(id) for verification as it gives
+        # 200 ok even for deleted id. LP #981263
+        # we can remove the loop here and use get by ID when bug gets sortedout
+        resp, flavors = self.list_flavors_with_detail()
+        for flavor in flavors:
+            if flavor['id'] == id:
+                return False
+        return True
+
+    def set_flavor_extra_spec(self, flavor_id, specs):
+        """Sets extra Specs to the mentioned flavor."""
+        post_body = json.dumps({'extra_specs': specs})
+        resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['extra_specs']
+
+    def get_flavor_extra_spec(self, flavor_id):
+        """Gets extra Specs details of the mentioned flavor."""
+        resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
+        body = json.loads(body)
+        return resp, body['extra_specs']
+
+    def get_flavor_extra_spec_with_key(self, flavor_id, key):
+        """Gets extra Specs key-value of the mentioned flavor and key."""
+        resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+                              key))
+        body = json.loads(body)
+        return resp, body
+
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Update specified extra Specs of the mentioned flavor and key."""
+        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),
+                           key))
+
+    def list_flavor_access(self, flavor_id):
+        """Gets flavor access information given the flavor id."""
+        resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+                              self.headers)
+        body = json.loads(body)
+        return resp, body['flavor_access']
+
+    def add_flavor_access(self, flavor_id, tenant_id):
+        """Add flavor access for the specified tenant."""
+        post_body = {
+            'addTenantAccess': {
+                'tenant': tenant_id
+            }
+        }
+        post_body = json.dumps(post_body)
+        resp, body = self.post('flavors/%s/action' % flavor_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['flavor_access']
+
+    def remove_flavor_access(self, flavor_id, tenant_id):
+        """Remove flavor access from the specified tenant."""
+        post_body = {
+            'removeTenantAccess': {
+                'tenant': tenant_id
+            }
+        }
+        post_body = json.dumps(post_body)
+        resp, body = self.post('flavors/%s/action' % flavor_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['flavor_access']
diff --git a/tempest/services/compute/v3/json/limits_client.py b/tempest/services/compute/v3/json/limits_client.py
new file mode 100644
index 0000000..3e53e3e
--- /dev/null
+++ b/tempest/services/compute/v3/json/limits_client.py
@@ -0,0 +1,40 @@
+# 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 json
+from tempest.common.rest_client import RestClient
+
+
+class LimitsClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(LimitsClientJSON, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def get_absolute_limits(self):
+        resp, body = self.get("limits")
+        body = json.loads(body)
+        return resp, body['limits']['absolute']
+
+    def get_specific_absolute_limit(self, absolute_limit):
+        resp, body = self.get("limits")
+        body = json.loads(body)
+        if absolute_limit not in body['limits']['absolute']:
+            return None
+        else:
+            return body['limits']['absolute'][absolute_limit]
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index cddbb53..a7fcc6d 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -121,7 +121,7 @@
             post_body['access_ip_v6'] = access_ip_v6
 
         if disk_config is not None:
-            post_body['OS-DCF:diskConfig'] = disk_config
+            post_body['os-disk-config:disk_config'] = disk_config
 
         post_body = json.dumps({'server': post_body})
         resp, body = self.put("servers/%s" % str(server_id),
@@ -309,14 +309,6 @@
         """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 ."""
 
diff --git a/tempest/services/compute/v3/xml/flavors_client.py b/tempest/services/compute/v3/xml/flavors_client.py
new file mode 100644
index 0000000..a1c74d9
--- /dev/null
+++ b/tempest/services/compute/v3/xml/flavors_client.py
@@ -0,0 +1,220 @@
+# 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 urllib
+
+from lxml import etree
+
+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
+
+
+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/v2"
+
+
+class FlavorsClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(FlavorsClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def _format_flavor(self, f):
+        flavor = {'links': []}
+        for k, v in f.items():
+            if k == 'id':
+                flavor['id'] = v
+                continue
+
+            if k == 'link':
+                flavor['links'].append(v)
+                continue
+
+            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)
+                continue
+
+            try:
+                v = int(v)
+            except ValueError:
+                try:
+                    v = float(v)
+                except ValueError:
+                    pass
+
+            flavor[k] = v
+
+        return flavor
+
+    def _parse_array(self, node):
+        return [self._format_flavor(xml_to_json(x)) for x in node]
+
+    def _list_flavors(self, url, params):
+        if params:
+            url += "?%s" % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        flavors = self._parse_array(etree.fromstring(body))
+        return resp, flavors
+
+    def list_flavors(self, params=None):
+        url = 'flavors'
+        return self._list_flavors(url, params)
+
+    def list_flavors_with_detail(self, params=None):
+        url = 'flavors/detail'
+        return self._list_flavors(url, params)
+
+    def get_flavor_details(self, flavor_id):
+        resp, body = self.get("flavors/%s" % str(flavor_id), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        flavor = self._format_flavor(body)
+        return resp, flavor
+
+    def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+        """Creates a new flavor or instance type."""
+        flavor = Element("flavor",
+                         xmlns=XMLNS_11,
+                         ram=ram,
+                         vcpus=vcpus,
+                         disk=disk,
+                         id=flavor_id,
+                         name=name)
+        if kwargs.get('rxtx'):
+            flavor.add_attr('rxtx_factor', kwargs.get('rxtx'))
+        if kwargs.get('swap'):
+            flavor.add_attr('swap', kwargs.get('swap'))
+        if kwargs.get('ephemeral'):
+            flavor.add_attr('OS-FLV-EXT-DATA:ephemeral',
+                            kwargs.get('ephemeral'))
+        if kwargs.get('is_public'):
+            flavor.add_attr('os-flavor-access:is_public',
+                            kwargs.get('is_public'))
+        flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
+        flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS)
+        resp, body = self.post('flavors', str(Document(flavor)), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        flavor = self._format_flavor(body)
+        return resp, flavor
+
+    def delete_flavor(self, flavor_id):
+        """Deletes the given flavor."""
+        return self.delete("flavors/%s" % str(flavor_id), self.headers)
+
+    def is_resource_deleted(self, id):
+        # Did not use get_flavor_details(id) for verification as it gives
+        # 200 ok even for deleted id. LP #981263
+        # we can remove the loop here and use get by ID when bug gets sortedout
+        resp, flavors = self.list_flavors_with_detail()
+        for flavor in flavors:
+            if flavor['id'] == id:
+                return False
+        return True
+
+    def set_flavor_extra_spec(self, flavor_id, specs):
+        """Sets extra Specs to the mentioned flavor."""
+        extra_specs = Element("extra_specs")
+        for key in specs.keys():
+            extra_specs.add_attr(key, specs[key])
+        resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+                               str(Document(extra_specs)), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def get_flavor_extra_spec(self, flavor_id):
+        """Gets extra Specs of the mentioned flavor."""
+        resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id,
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def get_flavor_extra_spec_with_key(self, flavor_id, key):
+        """Gets extra Specs key-value of the mentioned flavor and key."""
+        resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' %
+                                  (str(flavor_id), key), self.headers)
+        body = {}
+        element = etree.fromstring(xml_body)
+        key = element.get('key')
+        body[key] = xml_to_json(element)
+        return resp, body
+
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Update extra Specs details of the mentioned flavor and key."""
+        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),
+                           key))
+
+    def _parse_array_access(self, node):
+        return [xml_to_json(x) for x in node]
+
+    def list_flavor_access(self, flavor_id):
+        """Gets flavor access information given the flavor id."""
+        resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+                              self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def add_flavor_access(self, flavor_id, tenant_id):
+        """Add flavor access for the specified tenant."""
+        doc = Document()
+        server = Element("addTenantAccess")
+        doc.append(server)
+        server.add_attr("tenant", tenant_id)
+        resp, body = self.post('flavors/%s/action' % str(flavor_id),
+                               str(doc), self.headers)
+        body = self._parse_array_access(etree.fromstring(body))
+        return resp, body
+
+    def remove_flavor_access(self, flavor_id, tenant_id):
+        """Remove flavor access from the specified tenant."""
+        doc = Document()
+        server = Element("removeTenantAccess")
+        doc.append(server)
+        server.add_attr("tenant", tenant_id)
+        resp, body = self.post('flavors/%s/action' % str(flavor_id),
+                               str(doc), self.headers)
+        body = self._parse_array_access(etree.fromstring(body))
+        return resp, body
diff --git a/tempest/services/compute/v3/xml/limits_client.py b/tempest/services/compute/v3/xml/limits_client.py
new file mode 100644
index 0000000..704de52
--- /dev/null
+++ b/tempest/services/compute/v3/xml/limits_client.py
@@ -0,0 +1,55 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 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.
+
+from lxml import objectify
+
+from tempest.common.rest_client import RestClientXML
+
+NS = "{http://docs.openstack.org/common/api/v1.0}"
+
+
+class LimitsClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(LimitsClientXML, self).__init__(config, username, password,
+                                              auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def get_absolute_limits(self):
+        resp, body = self.get("limits", self.headers)
+        body = objectify.fromstring(body)
+        lim = NS + 'absolute'
+        ret = {}
+
+        for el in body[lim].iterchildren():
+            attributes = el.attrib
+            ret[attributes['name']] = attributes['value']
+        return resp, ret
+
+    def get_specific_absolute_limit(self, absolute_limit):
+        resp, body = self.get("limits", self.headers)
+        body = objectify.fromstring(body)
+        lim = NS + 'absolute'
+        ret = {}
+
+        for el in body[lim].iterchildren():
+            attributes = el.attrib
+            ret[attributes['name']] = attributes['value']
+        if absolute_limit not in ret:
+            return None
+        else:
+            return ret[absolute_limit]
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index 2ad5849..7af4161 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -254,6 +254,10 @@
             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 disk_config is not None:
+            server.add_attr('xmlns:os-disk-config', "http://docs.openstack.org"
+                            "/compute/ext/disk_config/api/v3")
+            server.add_attr("os-disk-config:disk_config", disk_config)
         if meta is not None:
             metadata = Element("metadata")
             server.append(metadata)
@@ -511,12 +515,6 @@
                                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 ."""
 
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index e5cc281..067b994 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -24,6 +24,7 @@
 from unittest import loader
 
 from tempest.openstack.common import log as logging
+from tempest.stress import driver
 
 LOG = logging.getLogger(__name__)
 
@@ -68,8 +69,6 @@
 
 
 def main(ns):
-    # NOTE(mkoderer): moved import to make "-h" possible without OpenStack
-    from tempest.stress import driver
     result = 0
     if not ns.all:
         tests = json.load(open(ns.tests, 'r'))
diff --git a/test-requirements.txt b/test-requirements.txt
index 41a784e..5cdb819 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,3 +5,4 @@
 python-subunit
 oslo.sphinx
 mox>=0.5.3
+mock>=1.0
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
deleted file mode 100755
index ef2eacd..0000000
--- a/tools/tempest_coverage.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 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 json
-import os
-import shutil
-import sys
-
-from oslo.config import cfg
-
-from tempest.common.rest_client import RestClient
-from tempest import config
-
-CONF = config.TempestConfig()
-
-
-class CoverageClientJSON(RestClient):
-
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(CoverageClientJSON, self).__init__(config, username, password,
-                                                 auth_url, tenant_name)
-        self.service = self.config.compute.catalog_type
-
-    def start_coverage(self):
-        post_body = {
-            'start': {},
-        }
-        post_body = json.dumps(post_body)
-        return self.post('os-coverage/action', post_body, self.headers)
-
-    def start_coverage_combine(self):
-        post_body = {
-            'start': {
-                'combine': True,
-            },
-        }
-        post_body = json.dumps(post_body)
-        return self.post('os-coverage/action', post_body, self.headers)
-
-    def stop_coverage(self):
-        post_body = {
-            'stop': {},
-        }
-        post_body = json.dumps(post_body)
-        resp, body = self.post('os-coverage/action', post_body, self.headers)
-        body = json.loads(body)
-        return resp, body
-
-    def report_coverage_xml(self, file=None):
-        post_body = {
-            'report': {
-                'file': 'coverage.report',
-                'xml': True,
-            },
-        }
-        if file:
-            post_body['report']['file'] = file
-        post_body = json.dumps(post_body)
-        resp, body = self.post('os-coverage/action', post_body, self.headers)
-        body = json.loads(body)
-        return resp, body
-
-    def report_coverage(self, file=None):
-        post_body = {
-            'report': {
-                'file': 'coverage.report',
-            },
-        }
-        if file:
-            post_body['report']['file'] = file
-        post_body = json.dumps(post_body)
-        resp, body = self.post('os-coverage/action', post_body, self.headers)
-        body = json.loads(body)
-        return resp, body
-
-    def report_coverage_html(self, file=None):
-        post_body = {
-            'report': {
-                'file': 'coverage.report',
-                'html': True,
-            },
-        }
-        if file:
-            post_body['report']['file'] = file
-        post_body = json.dumps(post_body)
-        resp, body = self.post('os-coverage/action', post_body, self.headers)
-        body = json.loads(body)
-        return resp, body
-
-
-def parse_opts(argv):
-    cli_opts = [
-        cfg.StrOpt('command',
-                   short='c',
-                   default='',
-                   help="This required argument is used to specify the "
-                        "coverage command to run. Only 'start', "
-                        "'stop', or 'report' are valid fields."),
-        cfg.StrOpt('filename',
-                   default='tempest-coverage',
-                   help="Specify a filename to be used for generated report "
-                        "files"),
-        cfg.BoolOpt('xml',
-                    default=False,
-                    help='Generate XML reports instead of text'),
-        cfg.BoolOpt('html',
-                    default=False,
-                    help='Generate HTML reports instead of text'),
-        cfg.BoolOpt('combine',
-                    default=False,
-                    help='Generate a single report for all services'),
-        cfg.StrOpt('output',
-                   short='o',
-                   default=None,
-                   help='Optional directory to copy generated coverage data or'
-                        ' reports into. This directory must not already exist '
-                        'it will be created')
-    ]
-    CLI = cfg.ConfigOpts()
-    CLI.register_cli_opts(cli_opts)
-    CLI(argv[1:])
-    return CLI
-
-
-def main(argv):
-    CLI = parse_opts(argv)
-    client_args = (CONF, CONF.identity.admin_username,
-                   CONF.identity.admin_password, CONF.identity.uri,
-                   CONF.identity.admin_tenant_name)
-    coverage_client = CoverageClientJSON(*client_args)
-
-    if CLI.command == 'start':
-        if CLI.combine:
-            coverage_client.start_coverage_combine()
-        else:
-            coverage_client.start_coverage()
-
-    elif CLI.command == 'stop':
-        resp, body = coverage_client.stop_coverage()
-        if not resp['status'] == '200':
-            print('coverage stop failed with: %s:' % (resp['status'] + ': '
-                                                      + body))
-            exit(int(resp['status']))
-        path = body['path']
-        if CLI.output:
-            shutil.copytree(path, CLI.output)
-        else:
-            print("Data files located at: %s" % path)
-
-    elif CLI.command == 'report':
-        if CLI.xml:
-            resp, body = coverage_client.report_coverage_xml(file=CLI.filename)
-        elif CLI.html:
-            resp, body = coverage_client.report_coverage_html(
-                file=CLI.filename)
-        else:
-            resp, body = coverage_client.report_coverage(file=CLI.filename)
-        if not resp['status'] == '200':
-            print('coverage report failed with: %s:' % (resp['status'] + ': '
-                                                        + body))
-            exit(int(resp['status']))
-        path = body['path']
-        if CLI.output:
-            if CLI.html:
-                shutil.copytree(path, CLI.output)
-            else:
-                path = os.path.dirname(path)
-                shutil.copytree(path, CLI.output)
-        else:
-            if not CLI.html:
-                path = os.path.dirname(path)
-            print('Report files located at: %s' % path)
-
-    else:
-        print('Invalid command')
-        exit(1)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/tox.ini b/tox.ini
index 6efac78..e5698d2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -88,13 +88,6 @@
 commands =
    sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
 
-[testenv:coverage]
-sitepackages = True
-commands =
-   python -m tools/tempest_coverage -c start --combine
-   sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli))'
-   python -m tools/tempest_coverage -c report --html {posargs}
-
 [testenv:stress]
 sitepackages = True
 commands =