Merge "Move InvalidConfiguration exception to tempest.lib"
diff --git a/.coveragerc b/.coveragerc
index 51482d3..449e62c 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,4 +1,4 @@
 [run]
 branch = True
 source = tempest
-omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api_schema/*,tempest/api/*
+omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api/*
diff --git a/README.rst b/README.rst
index f1dac1c..fc4de5e 100644
--- a/README.rst
+++ b/README.rst
@@ -196,18 +196,18 @@
 from a remote system running python 2.7. (or deploy a cloud guest in your cloud
 that has python 2.7)
 
-Python 3.4
+Python 3.x
 ----------
 
 Starting during the Liberty release development cycle work began on enabling
 Tempest to run under both Python 2.7 and Python 3.4. Tempest strives to fully
-support running with Python 3.4. A gating unit test job was added to also run
-Tempest's unit tests under Python 3.4. This means that the Tempest code at
-least imports under Python 3.4 and things that have unit test coverage will
-work on Python 3.4. However, because large parts of Tempest are self-verifying
-there might be uncaught issues running on Python 3.4. So until there is a gating
-job which does a full Tempest run using Python 3.4 there isn't any guarantee
-that running Tempest under Python 3.4 is bug free.
+support running with Python 3.4 and newer. A gating unit test job was added to
+also run Tempest's unit tests under Python 3. This means that the Tempest
+code at least imports under Python 3.4 and things that have unit test coverage
+will work on Python 3.4. However, because large parts of Tempest are
+self-verifying there might be uncaught issues running on Python 3. So until
+there is a gating job which does a full Tempest run using Python 3 there
+isn't any guarantee that running Tempest under Python 3 is bug free.
 
 Legacy run method
 -----------------
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 676a217..cfe7f4c 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -13,6 +13,13 @@
 it. Tests which aren't executed either because of configuration or skips should
 not be accepted.
 
+If a new test is added that depends on a new config option (like a feature
+flag), the commit message must reference a change in DevStack or DevStack-Gate
+that enables the execution of this newly introduced test. This reference could
+either be a `Cross-Repository Dependency <http://docs.openstack.org/infra/
+manual/developers.html#cross-repository-dependencies>`_ or a simple link
+to a Gerrit review.
+
 
 Unit Tests
 ----------
diff --git a/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml b/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
new file mode 100644
index 0000000..056e199
--- /dev/null
+++ b/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Define volume service clients as libraries.
+    The following volume service clients are defined as library interface,
+    so the other projects can use these modules as stable libraries without
+    any maintenance changes.
+
+    * volumes_client(v1)
+    * volumes_client(v2)
diff --git a/releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml b/releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml
new file mode 100644
index 0000000..c2d9a9b
--- /dev/null
+++ b/releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml
@@ -0,0 +1,7 @@
+---
+deprecations:
+  - The *api_extensions* config option in the *compute-feature-enabled* group is
+    now deprecated. This option will be removed from tempest when all the
+    OpenStack releases supported by tempest no longer support the API extensions
+    mechanism. This was removed from Nova during the Newton cycle, so this will
+    be removed at the Mitaka EOL.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 4522a17..140263c 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -275,3 +275,6 @@
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
 # texinfo_no_detailmenu = False
+
+# -- Options for Internationalization output ------------------------------
+locale_dirs = ['locale/']
diff --git a/requirements.txt b/requirements.txt
index 4af8bb3..fa6c413 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,7 +15,6 @@
 oslo.utils>=3.16.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
-testscenarios>=0.4 # Apache-2.0/BSD
 PyYAML>=3.10.0 # MIT
 python-subunit>=0.0.18 # Apache-2.0/BSD
 stevedore>=1.17.1 # Apache-2.0
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 4e9bb88..72d5b18 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -128,8 +128,7 @@
     def test_live_block_migration_paused(self):
         self._test_live_migration(state='PAUSED')
 
-    @decorators.skip_because(bug="1549511",
-                             condition=CONF.service_available.neutron)
+    @decorators.skip_because(bug="1524898")
     @test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
     @test.services('volume')
     def test_volume_backed_live_migration(self):
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
deleted file mode 100644
index 83f8e19..0000000
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest.api.compute import base
-from tempest.api_schema.request.compute.v2 import flavors
-from tempest import config
-from tempest import test
-
-
-CONF = config.CONF
-
-load_tests = test.NegativeAutoTest.load_tests
-
-
-@test.SimpleNegativeAutoTest
-class FlavorsListWithDetailsNegativeTestJSON(base.BaseV2ComputeTest,
-                                             test.NegativeAutoTest):
-    _service = CONF.compute.catalog_type
-    _schema = flavors.flavor_list
-
-
-@test.SimpleNegativeAutoTest
-class FlavorDetailsNegativeTestJSON(base.BaseV2ComputeTest,
-                                    test.NegativeAutoTest):
-    _service = CONF.compute.catalog_type
-    _schema = flavors.flavors_details
-
-    @classmethod
-    def resource_setup(cls):
-        super(FlavorDetailsNegativeTestJSON, cls).resource_setup()
-        cls.set_resource("flavor", cls.flavor_ref)
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 5738293..222bf18 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -41,8 +41,8 @@
 
     @classmethod
     def resource_cleanup(cls):
-        for i in range(3):
-            cls.client.delete_floating_ip(cls.floating_ip_id[i])
+        for f_id in cls.floating_ip_id:
+            cls.client.delete_floating_ip(f_id)
         super(FloatingIPDetailsTestJSON, cls).resource_cleanup()
 
     @test.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 7b978ab..7fd23fc 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -27,26 +27,6 @@
 
 class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
 
-    def tearDown(self):
-        """Terminate test instances created after a test is executed."""
-        self.server_check_teardown()
-        super(ImagesOneServerTestJSON, self).tearDown()
-
-    def setUp(self):
-        # NOTE(afazekas): Normally we use the same server with all test cases,
-        # but if it has an issue, we build a new one
-        super(ImagesOneServerTestJSON, self).setUp()
-        # Check if the server is in a clean state after test
-        try:
-            waiters.wait_for_server_status(self.servers_client,
-                                           self.server_id, 'ACTIVE')
-        except Exception:
-            LOG.exception('server %s timed out to become ACTIVE. rebuilding'
-                          % self.server_id)
-            # Rebuild server if cannot reach the ACTIVE state
-            # Usually it means the server had a serious accident
-            self.__class__.server_id = self.rebuild_server(self.server_id)
-
     @classmethod
     def skip_checks(cls):
         super(ImagesOneServerTestJSON, cls).skip_checks()
@@ -74,6 +54,18 @@
         flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
         return flavor['disk']
 
+    @classmethod
+    def _rebuild_server_when_fails(cls, server_id):
+        try:
+            waiters.wait_for_server_status(cls.servers_client,
+                                           server_id, 'ACTIVE')
+        except Exception:
+            LOG.exception('server %s timed out to become ACTIVE. rebuilding'
+                          % server_id)
+            # Rebuild server if cannot reach the ACTIVE state
+            # Usually it means the server had a serious accident
+            cls.server_id = cls.rebuild_server(server_id)
+
     @test.idempotent_id('3731d080-d4c5-4872-b41a-64d0d0021314')
     def test_create_delete_image(self):
 
@@ -103,6 +95,7 @@
         # Verify the image was deleted correctly
         self.client.delete_image(image_id)
         self.client.wait_for_resource_deletion(image_id)
+        self.addCleanup(self._rebuild_server_when_fails, self.server_id)
 
     @test.idempotent_id('3b7c6fe4-dfe7-477c-9243-b06359db51e6')
     def test_create_image_specify_multibyte_character_image_name(self):
@@ -116,3 +109,4 @@
         body = self.client.create_image(self.server_id, name=utf8_name)
         image_id = data_utils.parse_image_id(body.response['location'])
         self.addCleanup(self.client.delete_image, image_id)
+        self.addCleanup(self._rebuild_server_when_fails, self.server_id)
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
new file mode 100644
index 0000000..b7fa0fe
--- /dev/null
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -0,0 +1,41 @@
+# Copyright 2016 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 config
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+CONF = config.CONF
+
+
+class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(AttachVolumeNegativeTest, cls).skip_checks()
+        if not CONF.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @test.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
+    def test_delete_attached_volume(self):
+        server = self.create_test_server(wait_until='ACTIVE')
+        volume = self.create_volume()
+
+        path = "/dev/%s" % CONF.compute.volume_device_name
+        self.attach_volume(server, volume, device=path)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.delete_volume, volume['id'])
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index d2ab237..b3555b6 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -44,7 +44,7 @@
         body = self.admin_networks_client.list_dhcp_agents_on_hosting_network(
             self.network['id'])
         agents = body['agents']
-        self.assertIsNotNone(agents)
+        self.assertNotEmpty(agents, "no dhcp agent")
         agent = agents[0]
         self.assertTrue(self._check_network_in_dhcp_agent(
             self.network['id'], agent))
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 3825f84..819ef90 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -296,7 +296,7 @@
                           subnet_id)
 
         # Since create_subnet adds the subnet to the delete list, and it is
-        # is actually deleted here - this will create and issue, hence remove
+        # actually deleted here - this will create and issue, hence remove
         # it from the list.
         self.subnets.pop()
 
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index ba416e4..98a4f63 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -190,11 +190,14 @@
         gw_port = list_body['ports'][0]
         fixed_ips = gw_port['fixed_ips']
         self.assertGreaterEqual(len(fixed_ips), 1)
+        # Assert that all of the IPs from the router gateway port
+        # are allocated from a valid public subnet.
         public_net_body = self.admin_networks_client.show_network(
             CONF.network.public_network_id)
-        public_subnet_id = public_net_body['network']['subnets'][0]
-        self.assertIn(public_subnet_id,
-                      map(lambda x: x['subnet_id'], fixed_ips))
+        public_subnet_ids = public_net_body['network']['subnets']
+        for fixed_ip in fixed_ips:
+            subnet_id = fixed_ip['subnet_id']
+            self.assertIn(subnet_id, public_subnet_ids)
 
     @test.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79')
     def test_update_router_set_gateway(self):
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 646bc68..99f0a6b 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -43,10 +43,7 @@
                        "vendor_name": vendor}
         # Create two volume_types
         for i in range(2):
-            vol_type_name = data_utils.rand_name(
-                self.__class__.__name__ + '-volume-type')
             vol_type = self.create_volume_type(
-                name=vol_type_name,
                 extra_specs=extra_specs)
             volume_types.append(vol_type)
         params = {self.name_field: vol_name,
@@ -124,8 +121,7 @@
         # Create/get/delete encryption type.
         provider = "LuksEncryptor"
         control_location = "front-end"
-        name = data_utils.rand_name(self.__class__.__name__ + '-volume-type')
-        body = self.create_volume_type(name=name)
+        body = self.create_volume_type()
         # Create encryption type
         encryption_type = \
             self.admin_encryption_types_client.create_encryption_type(
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 8b7ceff..fdff2df 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 from tempest.api.volume import base
-from tempest.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
 from tempest import test
 
@@ -24,8 +23,7 @@
     @classmethod
     def resource_setup(cls):
         super(VolumeTypesExtraSpecsV2Test, cls).resource_setup()
-        vol_type_name = data_utils.rand_name(cls.__name__ + '-Volume-type')
-        cls.volume_type = cls.create_volume_type(name=vol_type_name)
+        cls.volume_type = cls.create_volume_type()
 
     @test.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
     def test_volume_type_extra_specs_list(self):
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 2e07457..8040322 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -24,10 +24,8 @@
     @classmethod
     def resource_setup(cls):
         super(ExtraSpecsNegativeV2Test, cls).resource_setup()
-        vol_type_name = data_utils.rand_name(cls.__name__ + '-Volume-type')
         cls.extra_specs = {"spec1": "val1"}
-        cls.volume_type = cls.create_volume_type(name=vol_type_name,
-                                                 extra_specs=cls.extra_specs)
+        cls.volume_type = cls.create_volume_type(extra_specs=cls.extra_specs)
 
     @test.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
     def test_update_no_body(self):
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 9686473..e7a3f62 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -14,8 +14,6 @@
 #    under the License.
 
 from tempest.api.volume import base
-from tempest.common.utils import data_utils as utils
-from tempest.common import waiters
 from tempest import config
 from tempest import test
 
@@ -34,54 +32,28 @@
         super(VolumesActionsV2Test, cls).resource_setup()
 
         # Create a test shared volume for tests
-        vol_name = utils.rand_name(cls.__name__ + '-Volume')
-        cls.name_field = cls.special_fields['name_field']
-        params = {cls.name_field: vol_name, 'size': CONF.volume.volume_size}
-
-        cls.volume = cls.client.create_volume(**params)['volume']
-        waiters.wait_for_volume_status(cls.client,
-                                       cls.volume['id'], 'available')
-
-    @classmethod
-    def resource_cleanup(cls):
-        # Delete the test volume
-        cls.delete_volume(cls.client, cls.volume['id'])
-
-        super(VolumesActionsV2Test, cls).resource_cleanup()
-
-    def _reset_volume_status(self, volume_id, status):
-        # Reset the volume status
-        body = self.admin_volume_client.reset_volume_status(volume_id,
-                                                            status=status)
-        return body
+        cls.volume = cls.create_volume()
 
     def tearDown(self):
         # Set volume's status to available after test
-        self._reset_volume_status(self.volume['id'], status='available')
+        self.admin_volume_client.reset_volume_status(
+            self.volume['id'], status='available')
         super(VolumesActionsV2Test, self).tearDown()
 
-    def _create_temp_volume(self):
-        # Create a temp volume for force delete tests
-        vol_name = utils.rand_name(self.__class__.__name__ + '-Volume')
-        params = {self.name_field: vol_name, 'size': CONF.volume.volume_size}
-        temp_volume = self.client.create_volume(**params)['volume']
-        waiters.wait_for_volume_status(self.client,
-                                       temp_volume['id'], 'available')
-
-        return temp_volume
-
     def _create_reset_and_force_delete_temp_volume(self, status=None):
         # Create volume, reset volume status, and force delete temp volume
-        temp_volume = self._create_temp_volume()
+        temp_volume = self.create_volume()
         if status:
-            self._reset_volume_status(temp_volume['id'], status)
+            self.admin_volume_client.reset_volume_status(
+                temp_volume['id'], status=status)
         self.admin_volume_client.force_delete_volume(temp_volume['id'])
         self.client.wait_for_resource_deletion(temp_volume['id'])
 
     @test.idempotent_id('d063f96e-a2e0-4f34-8b8a-395c42de1845')
     def test_volume_reset_status(self):
         # test volume reset status : available->error->available
-        self._reset_volume_status(self.volume['id'], 'error')
+        self.admin_volume_client.reset_volume_status(
+            self.volume['id'], status='error')
         volume_get = self.admin_volume_client.show_volume(
             self.volume['id'])['volume']
         self.assertEqual('error', volume_get['status'])
diff --git a/tempest/api_schema/__init__.py b/tempest/api_schema/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/request/__init__.py b/tempest/api_schema/request/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/request/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/request/compute/__init__.py b/tempest/api_schema/request/compute/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/request/compute/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/request/compute/flavors.py b/tempest/api_schema/request/compute/flavors.py
deleted file mode 100644
index adaaf27..0000000
--- a/tempest/api_schema/request/compute/flavors.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# (c) 2014 Deutsche Telekom AG
-#    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.
-
-common_flavor_details = {
-    "name": "get-flavor-details",
-    "http-method": "GET",
-    "url": "flavors/%s",
-    "resources": [
-        {"name": "flavor", "expected_result": 404}
-    ]
-}
-
-common_flavor_list = {
-    "name": "list-flavors-with-detail",
-    "http-method": "GET",
-    "url": "flavors/detail",
-    "json-schema": {
-        "type": "object",
-        "properties": {
-        }
-    }
-}
-
-common_admin_flavor_create = {
-    "name": "flavor-create",
-    "http-method": "POST",
-    "admin_client": True,
-    "url": "flavors",
-    "default_result_code": 400,
-    "json-schema": {
-        "type": "object",
-        "properties": {
-           "flavor": {
-               "type": "object",
-               "properties": {
-                   "name": {"type": "string",
-                            "exclude_tests": ["gen_str_min_length"]},
-                   "ram": {"type": "integer", "minimum": 1},
-                   "vcpus": {"type": "integer", "minimum": 1},
-                   "disk": {"type": "integer"},
-                   "id": {"type": "integer",
-                          "exclude_tests": ["gen_none", "gen_string"]
-                          },
-                   }
-               }
-        }
-    }
-}
diff --git a/tempest/api_schema/request/compute/v2/__init__.py b/tempest/api_schema/request/compute/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/request/compute/v2/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/request/compute/v2/flavors.py b/tempest/api_schema/request/compute/v2/flavors.py
deleted file mode 100644
index bc459ad..0000000
--- a/tempest/api_schema/request/compute/v2/flavors.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# (c) 2014 Deutsche Telekom AG
-#    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 copy
-
-from tempest.api_schema.request.compute import flavors
-
-flavors_details = copy.deepcopy(flavors.common_flavor_details)
-
-flavor_list = copy.deepcopy(flavors.common_flavor_list)
-
-flavor_create = copy.deepcopy(flavors.common_admin_flavor_create)
-
-flavor_list["json-schema"]["properties"] = {
-    "minRam": {
-        "type": "integer",
-        "results": {
-            "gen_none": 400,
-            "gen_string": 400
-        }
-    },
-    "minDisk": {
-        "type": "integer",
-        "results": {
-            "gen_none": 400,
-            "gen_string": 400
-        }
-    }
-}
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index baa36a2..99185d2 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -14,13 +14,13 @@
 
 import os
 import shutil
-import subprocess
 import sys
 
 from cliff import command
 from oslo_config import generator
 from oslo_log import log as logging
 from six import moves
+from testrepository import commands
 
 from tempest.cmd import workspace
 
@@ -167,7 +167,8 @@
         self.generate_testr_conf(local_dir)
         # setup local testr working dir
         if not os.path.isdir(testr_dir):
-            subprocess.call(['testr', 'init'], cwd=local_dir)
+            commands.run_argv(['testr', 'init', '-d', local_dir], sys.stdin,
+                              sys.stdout, sys.stderr)
 
     def take_action(self, parsed_args):
         workspace_manager = workspace.WorkspaceManager(
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 236953c..5fa8b74 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -135,6 +135,12 @@
             workspace_mgr = workspace.WorkspaceManager(
                 parsed_args.workspace_path)
             path = workspace_mgr.get_workspace(parsed_args.workspace)
+            if not path:
+                sys.exit(
+                    "The %r workspace isn't registered in "
+                    "%r. Use 'tempest init' to "
+                    "register the workspace." %
+                    (parsed_args.workspace, workspace_mgr.path))
             os.chdir(path)
             # NOTE(mtreinish): tempest init should create a .testrepository dir
             # but since workspaces can be imported let's sanity check and
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index b36cf4e..3c58648 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -72,7 +72,10 @@
 
     @lockutils.synchronized('workspaces', external=True)
     def get_workspace(self, name):
-        """Returns the workspace that has the given name"""
+        """Returns the workspace that has the given name
+
+        If the workspace isn't registered then `None` is returned.
+        """
         self._populate()
         return self.workspaces.get(name)
 
diff --git a/tempest/config.py b/tempest/config.py
index 98cfb40..8ce38f9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -245,7 +245,7 @@
                     "projects. If multiple networks are available for a "
                     "project, this is the network which will be used for "
                     "creating servers if tempest does not create a network or "
-                    "s network is not specified elsewhere. It may be used for "
+                    "a network is not specified elsewhere. It may be used for "
                     "ssh validation only if floating IPs are disabled."),
     cfg.StrOpt('catalog_type',
                default='compute',
@@ -318,7 +318,13 @@
                 help='A list of enabled compute extensions with a special '
                      'entry all which indicates every extension is enabled. '
                      'Each extension should be specified with alias name. '
-                     'Empty list indicates all extensions are disabled'),
+                     'Empty list indicates all extensions are disabled',
+                     deprecated_for_removal=True,
+                     deprecated_reason='The Nova extensions API and mechanism '
+                                       'is deprecated. This option will be '
+                                       'removed when all releases supported '
+                                       'by tempest no longer contain the Nova '
+                                       'extensions API and mechanism.'),
     cfg.BoolOpt('change_password',
                 default=False,
                 help="Does the test environment support changing the admin "
@@ -341,12 +347,10 @@
                 help="Does the test environment support suspend/resume?"),
     cfg.BoolOpt('cold_migration',
                 default=True,
-                help="Does the test environment support cold migration "
-                     "available?"),
+                help="Does the test environment support cold migration?"),
     cfg.BoolOpt('live_migration',
                 default=True,
-                help="Does the test environment support live migration "
-                     "available?"),
+                help="Does the test environment support live migration?"),
     cfg.BoolOpt('metadata_service',
                 default=True,
                 help="Does the test environment support metadata service? "
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
similarity index 98%
rename from tempest/services/volume/base/base_volumes_client.py
rename to tempest/lib/services/volume/v1/volumes_client.py
index 1cb1ef5..3df8da4 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -21,11 +21,9 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class BaseVolumesClient(rest_client.RestClient):
+class VolumesClient(rest_client.RestClient):
     """Base client class to send CRUD Volume API requests"""
 
-    create_resp = 200
-
     def _prepare_params(self, params):
         """Prepares params for use in get or _ext_get methods.
 
@@ -69,7 +67,7 @@
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
-        self.expected_success(self.create_resp, resp.status)
+        self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume(self, volume_id, **kwargs):
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
similarity index 84%
copy from tempest/services/volume/base/base_volumes_client.py
copy to tempest/lib/services/volume/v2/volumes_client.py
index 1cb1ef5..b1930e1 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -21,10 +21,9 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class BaseVolumesClient(rest_client.RestClient):
-    """Base client class to send CRUD Volume API requests"""
-
-    create_resp = 200
+class VolumesClient(rest_client.RestClient):
+    """Client class to send CRUD Volume V2 API requests"""
+    api_version = "v2"
 
     def _prepare_params(self, params):
         """Prepares params for use in get or _ext_get methods.
@@ -69,7 +68,7 @@
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
-        self.expected_success(self.create_resp, resp.status)
+        self.expected_success(202, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume(self, volume_id, **kwargs):
@@ -293,3 +292,49 @@
         post_body = json.dumps({'os-retype': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
         self.expected_success(202, resp.status)
+
+    def update_volume_image_metadata(self, volume_id, **kwargs):
+        """Update image metadata for the volume.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-blockstorage-v2.html
+                              #setVolumeimagemetadata
+        """
+        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_image_metadata(self, volume_id, key_name):
+        """Delete image metadata item for the volume."""
+        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_pools(self, detail=False):
+        # List all the volumes pools (hosts)
+        url = 'scheduler-stats/get_pools'
+        if detail:
+            url += '?detail=True'
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_backend_capabilities(self, host):
+        """Shows capabilities for a storage back end.
+
+         Output params: see http://developer.openstack.org/
+                            api-ref-blockstorage-v2.html
+                            #showBackendCapabilities
+        """
+        url = 'capabilities/%s' % host
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index 376ab72..7fb3ed3 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -24,7 +24,7 @@
 from tempest.lib.services.volume.v1.services_client import ServicesClient
 from tempest.lib.services.volume.v1.snapshots_client import SnapshotsClient
 from tempest.lib.services.volume.v1.types_client import TypesClient
-from tempest.services.volume.v1.json.volumes_client import VolumesClient
+from tempest.lib.services.volume.v1.volumes_client import VolumesClient
 
 __all__ = ['AvailabilityZoneClient', 'EncryptionTypesClient',
            'ExtensionsClient', 'HostsClient', 'QuotasClient',
diff --git a/tempest/services/volume/v1/json/volumes_client.py b/tempest/services/volume/v1/json/volumes_client.py
deleted file mode 100644
index 7782043..0000000
--- a/tempest/services/volume/v1/json/volumes_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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.services.volume.base import base_volumes_client
-
-
-class VolumesClient(base_volumes_client.BaseVolumesClient):
-    """Client class to send CRUD Volume V1 API requests"""
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index 5774977..8edaf2a 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -24,7 +24,7 @@
 from tempest.lib.services.volume.v2.services_client import ServicesClient
 from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient
 from tempest.lib.services.volume.v2.types_client import TypesClient
-from tempest.services.volume.v2.json.volumes_client import VolumesClient
+from tempest.lib.services.volume.v2.volumes_client import VolumesClient
 
 __all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
            'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
deleted file mode 100644
index f21a1a3..0000000
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# 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 oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-from tempest.services.volume.base import base_volumes_client
-
-
-class VolumesClient(base_volumes_client.BaseVolumesClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-    create_resp = 202
-
-    def update_volume_image_metadata(self, volume_id, **kwargs):
-        """Update image metadata for the volume.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-blockstorage-v2.html
-                              #setVolumeimagemetadata
-        """
-        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_image_metadata(self, volume_id, key_name):
-        """Delete image metadata item for the volume."""
-        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_pools(self, detail=False):
-        # List all the volumes pools (hosts)
-        url = 'scheduler-stats/get_pools'
-        if detail:
-            url += '?detail=True'
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_backend_capabilities(self, host):
-        """Shows capabilities for a storage back end.
-
-         Output params: see http://developer.openstack.org/
-                            api-ref-blockstorage-v2.html
-                            #showBackendCapabilities
-        """
-        url = 'capabilities/%s' % host
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/test.py b/tempest/test.py
index 609f1f6..6dc065c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -16,28 +16,21 @@
 import atexit
 import functools
 import os
-import re
 import sys
 
 import debtcollector.moves
 import fixtures
 from oslo_log import log as logging
-from oslo_serialization import jsonutils as json
-from oslo_utils import importutils
 import six
-from six.moves import urllib
-import testscenarios
 import testtools
 
 from tempest import clients
 from tempest.common import cred_client
 from tempest.common import credentials_factory as credentials
 from tempest.common import fixed_network
-import tempest.common.generator.valid_generator as valid
 import tempest.common.validation_resources as vresources
 from tempest import config
 from tempest import exceptions
-from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
@@ -649,224 +642,6 @@
         self.assertTrue(len(list) > 0, msg)
 
 
-class NegativeAutoTest(BaseTestCase):
-
-    _resources = {}
-
-    @classmethod
-    def setUpClass(cls):
-        super(NegativeAutoTest, cls).setUpClass()
-        os = cls.get_client_manager(credential_type='primary')
-        cls.client = os.negative_client
-
-    @staticmethod
-    def load_tests(*args):
-        """Wrapper for testscenarios
-
-        To set the mandatory scenarios variable only in case a real test
-        loader is in place. Will be automatically called in case the variable
-        "load_tests" is set.
-        """
-        if getattr(args[0], 'suiteClass', None) is not None:
-            loader, standard_tests, pattern = args
-        else:
-            standard_tests, module, loader = args
-        for test in testtools.iterate_tests(standard_tests):
-            schema = getattr(test, '_schema', None)
-            if schema is not None:
-                setattr(test, 'scenarios',
-                        NegativeAutoTest.generate_scenario(schema))
-        return testscenarios.load_tests_apply_scenarios(*args)
-
-    @staticmethod
-    def generate_scenario(description):
-        """Generates the test scenario list for a given description.
-
-        :param description: A file or dictionary with the following entries:
-            name (required) name for the api
-            http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
-            url (required) the url to be appended to the catalog url with '%s'
-                for each resource mentioned
-            resources: (optional) A list of resource names such as "server",
-                "flavor", etc. with an element for each '%s' in the url. This
-                method will call self.get_resource for each element when
-                constructing the positive test case template so negative
-                subclasses are expected to return valid resource ids when
-                appropriate.
-            json-schema (optional) A valid json schema that will be used to
-                create invalid data for the api calls. For "GET" and "HEAD",
-                the data is used to generate query strings appended to the url,
-                otherwise for the body of the http call.
-        """
-        LOG.debug(description)
-        generator = importutils.import_class(
-            CONF.negative.test_generator)()
-        generator.validate_schema(description)
-        schema = description.get("json-schema", None)
-        resources = description.get("resources", [])
-        scenario_list = []
-        expected_result = None
-        for resource in resources:
-            if isinstance(resource, dict):
-                expected_result = resource['expected_result']
-                resource = resource['name']
-            LOG.debug("Add resource to test %s" % resource)
-            scn_name = "inv_res_%s" % (resource)
-            scenario_list.append((scn_name, {
-                "resource": (resource, data_utils.rand_uuid()),
-                "expected_result": expected_result
-            }))
-        if schema is not None:
-            for scenario in generator.generate_scenarios(schema):
-                scenario_list.append((scenario['_negtest_name'],
-                                      scenario))
-        LOG.debug(scenario_list)
-        return scenario_list
-
-    def execute(self, description):
-        """Execute a http call
-
-        Execute a http call on an api that are expected to
-        result in client errors. First it uses invalid resources that are part
-        of the url, and then invalid data for queries and http request bodies.
-
-        :param description: A json file or dictionary with the following
-        entries:
-            name (required) name for the api
-            http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
-            url (required) the url to be appended to the catalog url with '%s'
-                for each resource mentioned
-            resources: (optional) A list of resource names such as "server",
-                "flavor", etc. with an element for each '%s' in the url. This
-                method will call self.get_resource for each element when
-                constructing the positive test case template so negative
-                subclasses are expected to return valid resource ids when
-                appropriate.
-            json-schema (optional) A valid json schema that will be used to
-                create invalid data for the api calls. For "GET" and "HEAD",
-                the data is used to generate query strings appended to the url,
-                otherwise for the body of the http call.
-
-        """
-        LOG.info("Executing %s" % description["name"])
-        LOG.debug(description)
-        generator = importutils.import_class(
-            CONF.negative.test_generator)()
-        schema = description.get("json-schema", None)
-        method = description["http-method"]
-        url = description["url"]
-        expected_result = None
-        if "default_result_code" in description:
-            expected_result = description["default_result_code"]
-
-        resources = [self.get_resource(r) for
-                     r in description.get("resources", [])]
-
-        if hasattr(self, "resource"):
-            # Note(mkoderer): The resources list already contains an invalid
-            # entry (see get_resource).
-            # We just send a valid json-schema with it
-            valid_schema = None
-            if schema:
-                valid_schema = \
-                    valid.ValidTestGenerator().generate_valid(schema)
-            new_url, body = self._http_arguments(valid_schema, url, method)
-        elif hasattr(self, "_negtest_name"):
-            schema_under_test = \
-                valid.ValidTestGenerator().generate_valid(schema)
-            local_expected_result = \
-                generator.generate_payload(self, schema_under_test)
-            if local_expected_result is not None:
-                expected_result = local_expected_result
-            new_url, body = \
-                self._http_arguments(schema_under_test, url, method)
-        else:
-            raise Exception("testscenarios are not active. Please make sure "
-                            "that your test runner supports the load_tests "
-                            "mechanism")
-
-        if "admin_client" in description and description["admin_client"]:
-            if not credentials.is_admin_available(
-                    identity_version=self.get_identity_version()):
-                msg = ("Missing Identity Admin API credentials in"
-                       "configuration.")
-                raise self.skipException(msg)
-            creds = self.credentials_provider.get_admin_creds()
-            os_adm = clients.Manager(credentials=creds)
-            client = os_adm.negative_client
-        else:
-            client = self.client
-        resp, resp_body = client.send_request(method, new_url,
-                                              resources, body=body)
-        self._check_negative_response(expected_result, resp.status, resp_body)
-
-    def _http_arguments(self, json_dict, url, method):
-        LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method))
-        if not json_dict:
-            return url, None
-        elif method in ["GET", "HEAD", "PUT", "DELETE"]:
-            return "%s?%s" % (url, urllib.parse.urlencode(json_dict)), None
-        else:
-            return url, json.dumps(json_dict)
-
-    def _check_negative_response(self, expected_result, result, body):
-        self.assertTrue(result >= 400 and result < 500 and result != 413,
-                        "Expected client error, got %s:%s" %
-                        (result, body))
-        self.assertTrue(expected_result is None or expected_result == result,
-                        "Expected %s, got %s:%s" %
-                        (expected_result, result, body))
-
-    @classmethod
-    def set_resource(cls, name, resource):
-        """Register a resource for a test
-
-        This function can be used in setUpClass context to register a resource
-        for a test.
-
-        :param name: The name of the kind of resource such as "flavor", "role",
-            etc.
-        :resource: The id of the resource
-        """
-        cls._resources[name] = resource
-
-    def get_resource(self, name):
-        """Return a valid uuid for a type of resource.
-
-        If a real resource is needed as part of a url then this method should
-        return one. Otherwise it can return None.
-
-        :param name: The name of the kind of resource such as "flavor", "role",
-            etc.
-        """
-        if isinstance(name, dict):
-            name = name['name']
-        if hasattr(self, "resource") and self.resource[0] == name:
-            LOG.debug("Return invalid resource (%s) value: %s" %
-                      (self.resource[0], self.resource[1]))
-            return self.resource[1]
-        if name in self._resources:
-            return self._resources[name]
-        return None
-
-
-def SimpleNegativeAutoTest(klass):
-    """This decorator registers a test function on basis of the class name."""
-    @attr(type=['negative'])
-    def generic_test(self):
-        if hasattr(self, '_schema'):
-            self.execute(self._schema)
-
-    cn = klass.__name__
-    cn = cn.replace('JSON', '')
-    cn = cn.replace('Test', '')
-    # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
-    lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
-    func_name = 'test_%s' % lower_cn
-    setattr(klass, func_name, generic_test)
-    return klass
-
-
 call_until_true = debtcollector.moves.moved_function(
     test_utils.call_until_true, 'call_until_true', __name__,
     version='Newton', removal_version='Ocata')
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 772391f..7ac347d 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -18,6 +18,7 @@
 import subprocess
 import tempfile
 
+import fixtures
 import mock
 
 from tempest.cmd import run
@@ -122,3 +123,32 @@
         # too.
         subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run'], 1)
+
+
+class TestTakeAction(base.TestCase):
+    def test_workspace_not_registered(self):
+        class Exception_(Exception):
+            pass
+
+        m_exit = self.useFixture(fixtures.MockPatch('sys.exit')).mock
+        # sys.exit must not continue (or exit)
+        m_exit.side_effect = Exception_
+
+        workspace = self.getUniqueString()
+
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.config_file = []
+
+        # Override $HOME so that empty workspace gets created in temp dir.
+        self.useFixture(fixtures.TempHomeDir())
+
+        # Force use of the temporary home directory.
+        parsed_args.workspace_path = None
+
+        # Simulate --workspace argument.
+        parsed_args.workspace = workspace
+
+        self.assertRaises(Exception_, tempest_run.take_action, parsed_args)
+        exit_msg = m_exit.call_args[0][0]
+        self.assertIn(workspace, exit_msg)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index a56f837..a826337 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -18,7 +18,7 @@
 
 from tempest.common import waiters
 from tempest import exceptions
-from tempest.services.volume.base import base_volumes_client
+from tempest.lib.services.volume.v2 import volumes_client
 from tempest.tests import base
 import tempest.tests.utils as utils
 
@@ -57,7 +57,7 @@
     def test_wait_for_volume_status_error_restoring(self, mock_sleep):
         # Tests that the wait method raises VolumeRestoreErrorException if
         # the volume status is 'error_restoring'.
-        client = mock.Mock(spec=base_volumes_client.BaseVolumesClient,
+        client = mock.Mock(spec=volumes_client.VolumesClient,
                            build_interval=1)
         volume1 = {'volume': {'status': 'restoring-backup'}}
         volume2 = {'volume': {'status': 'error_restoring'}}
diff --git a/tempest/tests/negative/test_negative_auto_test.py b/tempest/tests/negative/test_negative_auto_test.py
deleted file mode 100644
index 44ce567..0000000
--- a/tempest/tests/negative/test_negative_auto_test.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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 import config
-import tempest.test as test
-from tempest.tests import base
-from tempest.tests import fake_config
-
-
-class TestNegativeAutoTest(base.TestCase):
-    # Fake entries
-    _service = 'compute'
-
-    fake_input_desc = {"name": "list-flavors-with-detail",
-                       "http-method": "GET",
-                       "url": "flavors/detail",
-                       "json-schema": {"type": "object",
-                                       "properties":
-                                       {"minRam": {"type": "integer"},
-                                        "minDisk": {"type": "integer"}}
-                                       },
-                       "resources": ["flavor", "volume", "image"]
-                       }
-
-    def setUp(self):
-        super(TestNegativeAutoTest, self).setUp()
-        self.useFixture(fake_config.ConfigFixture())
-        self.patchobject(config, 'TempestConfigPrivate',
-                         fake_config.FakePrivate)
-
-    def _check_prop_entries(self, result, entry):
-        entries = [a for a in result if entry in a[0]]
-        self.assertIsNotNone(entries)
-        self.assertGreater(len(entries), 1)
-        for entry in entries:
-            self.assertIsNotNone(entry[1]['_negtest_name'])
-
-    def _check_resource_entries(self, result, entry):
-        entries = [a for a in result if entry in a[0]]
-        self.assertIsNotNone(entries)
-        self.assertIs(len(entries), 3)
-        for entry in entries:
-            self.assertIsNotNone(entry[1]['resource'])
-
-    def test_generate_scenario(self):
-        scenarios = test.NegativeAutoTest.\
-            generate_scenario(self.fake_input_desc)
-        self.assertIsInstance(scenarios, list)
-        for scenario in scenarios:
-            self.assertIsInstance(scenario, tuple)
-            self.assertIsInstance(scenario[0], str)
-            self.assertIsInstance(scenario[1], dict)
-        self._check_prop_entries(scenarios, "minRam")
-        self._check_prop_entries(scenarios, "minDisk")
-        self._check_resource_entries(scenarios, "inv_res")
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 8c5d861..17dbea0 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import mock
 from oslo_config import cfg
 from oslotest import mockpatch
 import testtools
@@ -232,22 +231,6 @@
                           service='bad_service')
 
 
-class TestSimpleNegativeDecorator(BaseDecoratorsTest):
-    @test.SimpleNegativeAutoTest
-    class FakeNegativeJSONTest(test.NegativeAutoTest):
-        _schema = {}
-
-    def test_testfunc_exist(self):
-        self.assertIn("test_fake_negative", dir(self.FakeNegativeJSONTest))
-
-    @mock.patch('tempest.test.NegativeAutoTest.execute')
-    def test_testfunc_calls_execute(self, mock):
-        obj = self.FakeNegativeJSONTest("test_fake_negative")
-        self.assertIn("test_fake_negative", dir(obj))
-        obj.test_fake_negative()
-        mock.assert_called_once_with(self.FakeNegativeJSONTest._schema)
-
-
 class TestConfigDecorators(BaseDecoratorsTest):
     def setUp(self):
         super(TestConfigDecorators, self).setUp()