Merge "Allow out of quota failure status code to be 413 or 403"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index f80fc1b..08f3fd4 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -411,6 +411,10 @@
 # Does the test environment support pausing? (boolean value)
 #pause=true
 
+# Does the test environment support shelving/unshelving?
+# (boolean value)
+#shelve=true
+
 # Does the test environment support suspend/resume? (boolean
 # value)
 #suspend=true
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 9e34922..9c8271f 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -70,6 +70,8 @@
         self.assertEqual('204', resp['status'])
         self.client.wait_for_server_termination(server['id'])
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type='gate')
     def test_delete_server_while_in_shelved_state(self):
         # Delete a server while it's VM state is Shelved
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 71fcbff..ee525e7 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -403,6 +403,8 @@
         self.assertEqual(202, resp.status)
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type='gate')
     def test_shelve_unshelve_server(self):
         resp, server = self.client.shelve_server(self.server_id)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index d3297ce..792b523 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -425,6 +425,8 @@
                           self.client.restore_soft_deleted_server,
                           self.server_id)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_shelve_non_existent_server(self):
         # shelve a non existent server
@@ -432,6 +434,8 @@
         self.assertRaises(exceptions.NotFound, self.client.shelve_server,
                           nonexistent_server)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_shelve_shelved_server(self):
         # shelve a shelved server.
@@ -460,6 +464,8 @@
 
         self.client.unshelve_server(self.server_id)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_unshelve_non_existent_server(self):
         # unshelve a non existent server
@@ -467,6 +473,8 @@
         self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
                           nonexistent_server)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_unshelve_server_invalid_state(self):
         # unshelve an active server.
diff --git a/tempest/api/compute/v3/servers/test_delete_server.py b/tempest/api/compute/v3/servers/test_delete_server.py
index add69ab..e2b47ee 100644
--- a/tempest/api/compute/v3/servers/test_delete_server.py
+++ b/tempest/api/compute/v3/servers/test_delete_server.py
@@ -68,6 +68,8 @@
         self.assertEqual('204', resp['status'])
         self.client.wait_for_server_termination(server['id'])
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type='gate')
     def test_delete_server_while_in_shelved_state(self):
         # Delete a server while it's VM state is Shelved
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 3ee8050..4404043 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -394,6 +394,8 @@
         self.assertEqual(202, resp.status)
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type='gate')
     def test_shelve_unshelve_server(self):
         resp, server = self.client.shelve_server(self.server_id)
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 90deaa9..f8ff7c8 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -397,6 +397,8 @@
                           self.client.restore_soft_deleted_server,
                           self.server_id)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_shelve_non_existent_server(self):
         # shelve a non existent server
@@ -404,6 +406,8 @@
         self.assertRaises(exceptions.NotFound, self.client.shelve_server,
                           nonexistent_server)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_shelve_shelved_server(self):
         # shelve a shelved server.
@@ -431,6 +435,8 @@
 
         self.client.unshelve_server(self.server_id)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_unshelve_non_existent_server(self):
         # unshelve a non existent server
@@ -438,6 +444,8 @@
         self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
                           nonexistent_server)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
     @test.attr(type=['negative', 'gate'])
     def test_unshelve_server_invalid_state(self):
         # unshelve an active server.
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index cfb5a3d..65085b9 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -40,6 +40,7 @@
         cls._data_sources = []
         cls._job_binary_internals = []
         cls._job_binaries = []
+        cls._jobs = []
 
     @classmethod
     def tearDownClass(cls):
@@ -47,12 +48,13 @@
                               cls.client.delete_cluster_template)
         cls.cleanup_resources(getattr(cls, '_node_group_templates', []),
                               cls.client.delete_node_group_template)
-        cls.cleanup_resources(getattr(cls, '_data_sources', []),
-                              cls.client.delete_data_source)
-        cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
-                              cls.client.delete_job_binary_internal)
+        cls.cleanup_resources(getattr(cls, '_jobs', []), cls.client.delete_job)
         cls.cleanup_resources(getattr(cls, '_job_binaries', []),
                               cls.client.delete_job_binary)
+        cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
+                              cls.client.delete_job_binary_internal)
+        cls.cleanup_resources(getattr(cls, '_data_sources', []),
+                              cls.client.delete_data_source)
         cls.clear_isolated_creds()
         super(BaseDataProcessingTest, cls).tearDownClass()
 
@@ -132,6 +134,7 @@
 
         return resp_body
 
+    @classmethod
     def create_job_binary(cls, name, url, extra=None, **kwargs):
         """Creates watched job binary with specified params.
 
@@ -144,3 +147,18 @@
         cls._job_binaries.append(resp_body['id'])
 
         return resp_body
+
+    @classmethod
+    def create_job(cls, name, job_type, mains, libs=None, **kwargs):
+        """Creates watched job with specified params.
+
+        It supports passing additional params using kwargs and returns created
+        object. All resources created in this method will be automatically
+        removed in tearDownClass method.
+        """
+        _, resp_body = cls.client.create_job(name,
+                                             job_type, mains, libs, **kwargs)
+        # store id of created job
+        cls._jobs.append(resp_body['id'])
+
+        return resp_body
diff --git a/tempest/api/data_processing/test_jobs.py b/tempest/api/data_processing/test_jobs.py
new file mode 100644
index 0000000..8591dbd
--- /dev/null
+++ b/tempest/api/data_processing/test_jobs.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+#    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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class JobTest(dp_base.BaseDataProcessingTest):
+    """Link to the API documentation is http://docs.openstack.org/developer/
+    sahara/restapi/rest_api_v1.1_EDP.html#jobs
+    """
+    @classmethod
+    @test.safe_setup
+    def setUpClass(cls):
+        super(JobTest, cls).setUpClass()
+        # create job binary
+        job_binary = {
+            'name': data_utils.rand_name('sahara-job-binary'),
+            'url': 'swift://sahara-container.sahara/example.jar',
+            'description': 'Test job binary',
+            'extra': {
+                'user': cls.os.credentials.username,
+                'password': cls.os.credentials.password
+            }
+        }
+        resp_body = cls.create_job_binary(**job_binary)
+        job_binary_id = resp_body['id']
+
+        cls.job = {
+            'job_type': 'Pig',
+            'mains': [job_binary_id]
+        }
+
+    def _create_job(self, job_name=None):
+        """Creates Job with optional name specified.
+
+        It creates job and ensures job name. Returns id and name of created
+        job.
+        """
+        if not job_name:
+            # generate random name if it's not specified
+            job_name = data_utils.rand_name('sahara-job')
+
+        # create job
+        resp_body = self.create_job(job_name, **self.job)
+
+        # ensure that job created successfully
+        self.assertEqual(job_name, resp_body['name'])
+
+        return resp_body['id'], job_name
+
+    @test.attr(type='smoke')
+    def test_job_create(self):
+        self._create_job()
+
+    @test.attr(type='smoke')
+    def test_job_list(self):
+        job_info = self._create_job()
+
+        # check for job in list
+        _, jobs = self.client.list_jobs()
+        jobs_info = [(job['id'], job['name']) for job in jobs]
+        self.assertIn(job_info, jobs_info)
+
+    @test.attr(type='smoke')
+    def test_job_get(self):
+        job_id, job_name = self._create_job()
+
+        # check job fetch by id
+        _, job = self.client.get_job(job_id)
+        self.assertEqual(job_name, job['name'])
+
+    @test.attr(type='smoke')
+    def test_job_delete(self):
+        job_id, _ = self._create_job()
+
+        # delete the job by id
+        self.client.delete_job(job_id)
diff --git a/tempest/api/identity/admin/test_roles.py b/tempest/api/identity/admin/test_roles.py
index 7a6d07f..492d56f 100644
--- a/tempest/api/identity/admin/test_roles.py
+++ b/tempest/api/identity/admin/test_roles.py
@@ -49,7 +49,7 @@
 
     @test.attr(type='gate')
     def test_list_roles(self):
-        # Return a list of all roles
+        """Return a list of all roles."""
         _, body = self.client.list_roles()
         found = [role for role in body if role in self.data.roles]
         self.assertTrue(any(found))
@@ -57,7 +57,7 @@
 
     @test.attr(type='gate')
     def test_role_create_delete(self):
-        # Role should be created, verified, and deleted
+        """Role should be created, verified, and deleted."""
         role_name = data_utils.rand_name(name='role-test-')
         _, body = self.client.create_role(role_name)
         self.assertEqual(role_name, body['name'])
@@ -74,7 +74,7 @@
 
     @test.attr(type='gate')
     def test_get_role_by_id(self):
-        # Get a role by its id
+        """Get a role by its id."""
         self.data.setup_test_role()
         role_id = self.data.role['id']
         role_name = self.data.role['name']
@@ -84,7 +84,7 @@
 
     @test.attr(type='gate')
     def test_assign_user_role(self):
-        # Assign a role to a user on a tenant
+        """Assign a role to a user on a tenant."""
         (user, tenant, role) = self._get_role_params()
         self.client.assign_user_role(tenant['id'], user['id'], role['id'])
         _, roles = self.client.list_user_roles(tenant['id'], user['id'])
@@ -92,7 +92,7 @@
 
     @test.attr(type='gate')
     def test_remove_user_role(self):
-        # Remove a role assigned to a user on a tenant
+        """Remove a role assigned to a user on a tenant."""
         (user, tenant, role) = self._get_role_params()
         _, user_role = self.client.assign_user_role(tenant['id'],
                                                     user['id'], role['id'])
@@ -101,7 +101,7 @@
 
     @test.attr(type='gate')
     def test_list_user_roles(self):
-        # List roles assigned to a user on tenant
+        """List roles assigned to a user on tenant."""
         (user, tenant, role) = self._get_role_params()
         self.client.assign_user_role(tenant['id'], user['id'], role['id'])
         _, roles = self.client.list_user_roles(tenant['id'], user['id'])
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
index 2a170c7..9b15c51 100644
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -47,6 +47,7 @@
     @test.services("image")
     @testtools.skipIf(not CONF.image_feature_enabled.api_v1,
                       "Glance api v1 is disabled")
+    @test.skip_because(bug='1351627')
     def test_check_glance_v1_notifications(self):
         _, body = self.create_image(self.image_client)
         self.image_client.update_image(body['id'], data='data')
@@ -62,6 +63,7 @@
     @test.services("image")
     @testtools.skipIf(not CONF.image_feature_enabled.api_v2,
                       "Glance api v2 is disabled")
+    @test.skip_because(bug='1351627')
     def test_check_glance_v2_notifications(self):
         _, body = self.create_image(self.image_client_v2)
 
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index abf3c6b..b7de767 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -71,6 +71,9 @@
                 msg = "Volume API v2 is disabled"
                 raise cls.skipException(msg)
             cls.volumes_client = cls.os.volumes_v2_client
+            cls.volumes_extension_client = cls.os.volumes_v2_extension_client
+            cls.availability_zone_client = (
+                cls.os.volume_v2_availability_zone_client)
             # Special fields and resp code for cinder v2
             cls.special_fields = {'name_field': 'name',
                                   'descrip_field': 'description',
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index fe8f96e..25b7b85 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -17,16 +17,15 @@
 from tempest import test
 
 
-class AvailabilityZoneTestJSON(base.BaseVolumeV1Test):
+class AvailabilityZoneV2TestJSON(base.BaseVolumeTest):
 
     """
-    Tests Availability Zone API List
+    Tests Availability Zone V2 API List
     """
-    _interface = 'json'
 
     @classmethod
     def setUpClass(cls):
-        super(AvailabilityZoneTestJSON, cls).setUpClass()
+        super(AvailabilityZoneV2TestJSON, cls).setUpClass()
         cls.client = cls.availability_zone_client
 
     @test.attr(type='gate')
@@ -37,5 +36,13 @@
         self.assertTrue(len(availability_zone) > 0)
 
 
-class AvailabilityZoneTestXML(AvailabilityZoneTestJSON):
+class AvailabilityZoneV2TestXML(AvailabilityZoneV2TestJSON):
+    _interface = 'xml'
+
+
+class AvailabilityZoneV1TestJSON(AvailabilityZoneV2TestJSON):
+    _api_version = 1
+
+
+class AvailabilityZoneV1TestXML(AvailabilityZoneV1TestJSON):
     _interface = 'xml'
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index ce019a2..ff00dd1 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -25,8 +25,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class ExtensionsTestJSON(base.BaseVolumeV1Test):
-    _interface = 'json'
+class ExtensionsV2TestJSON(base.BaseVolumeTest):
 
     @test.attr(type='gate')
     def test_list_extensions(self):
@@ -46,5 +45,13 @@
             raise self.skipException('There are not any extensions configured')
 
 
-class ExtensionsTestXML(ExtensionsTestJSON):
+class ExtensionsV2TestXML(ExtensionsV2TestJSON):
+    _interface = 'xml'
+
+
+class ExtensionsV1TestJSON(ExtensionsV2TestJSON):
+    _api_version = 1
+
+
+class ExtensionsV1TestXML(ExtensionsV1TestJSON):
     _interface = 'xml'
diff --git a/tempest/clients.py b/tempest/clients.py
index 519e686..0edcdf4 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -181,7 +181,15 @@
     ExtensionsClientJSON as VolumeExtensionClientJSON
 from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
 from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.v2.json.availability_zone_client import \
+    VolumeV2AvailabilityZoneClientJSON
+from tempest.services.volume.v2.json.extensions_client import \
+    ExtensionsV2ClientJSON as VolumeV2ExtensionClientJSON
 from tempest.services.volume.v2.json.volumes_client import VolumesV2ClientJSON
+from tempest.services.volume.v2.xml.availability_zone_client import \
+    VolumeV2AvailabilityZoneClientXML
+from tempest.services.volume.v2.xml.extensions_client import \
+    ExtensionsV2ClientXML as VolumeV2ExtensionClientXML
 from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML
 from tempest.services.volume.xml.admin.volume_hosts_client import \
     VolumeHostsClientXML
@@ -270,6 +278,8 @@
                 self.auth_provider)
             self.volumes_extension_client = VolumeExtensionClientXML(
                 self.auth_provider)
+            self.volumes_v2_extension_client = VolumeV2ExtensionClientXML(
+                self.auth_provider)
             if CONF.service_available.ceilometer:
                 self.telemetry_client = TelemetryClientXML(
                     self.auth_provider)
@@ -277,6 +287,8 @@
             self.token_v3_client = V3TokenClientXML()
             self.volume_availability_zone_client = \
                 VolumeAvailabilityZoneClientXML(self.auth_provider)
+            self.volume_v2_availability_zone_client = \
+                VolumeV2AvailabilityZoneClientXML(self.auth_provider)
 
         elif self.interface == 'json':
             self.certificates_client = CertificatesClientJSON(
@@ -362,6 +374,8 @@
                 self.auth_provider)
             self.volumes_extension_client = VolumeExtensionClientJSON(
                 self.auth_provider)
+            self.volumes_v2_extension_client = VolumeV2ExtensionClientJSON(
+                self.auth_provider)
             self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider)
             self.database_flavors_client = DatabaseFlavorsClientJSON(
                 self.auth_provider)
@@ -378,6 +392,8 @@
             self.negative_client.service = service
             self.volume_availability_zone_client = \
                 VolumeAvailabilityZoneClientJSON(self.auth_provider)
+            self.volume_v2_availability_zone_client = \
+                VolumeV2AvailabilityZoneClientJSON(self.auth_provider)
 
         else:
             msg = "Unsupported interface type `%s'" % interface
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 67b92b0..3616a82 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -309,6 +309,14 @@
     return name, fname
 
 
+def _get_image_by_name(client, name):
+    r, body = client.images.image_list()
+    for image in body:
+        if name == image['name']:
+            return image
+    return None
+
+
 def create_images(images):
     if not images:
         return
@@ -317,9 +325,7 @@
         client = client_for_user(image['owner'])
 
         # only upload a new image if the name isn't there
-        r, body = client.images.image_list()
-        names = [x['name'] for x in body]
-        if image['name'] in names:
+        if _get_image_by_name(client, image['name']):
             LOG.info("Image '%s' already exists" % image['name'])
             continue
 
@@ -345,6 +351,20 @@
         client.images.store_image(image_id, open(fname, 'r'))
 
 
+def destroy_images(images):
+    if not images:
+        return
+    LOG.info("Destroying images")
+    for image in images:
+        client = client_for_user(image['owner'])
+
+        response = _get_image_by_name(client, image['name'])
+        if not response:
+            LOG.info("Image '%s' does not exists" % image['name'])
+            continue
+        client.images.delete_image(response['id'])
+
+
 #######################
 #
 # SERVERS
@@ -359,14 +379,6 @@
     return None
 
 
-def _get_image_by_name(client, name):
-    r, body = client.images.image_list()
-    for image in body:
-        if name == image['name']:
-            return image
-    return None
-
-
 def _get_flavor_by_name(client, name):
     r, body = client.flavors.list_flavors()
     for flavor in body:
@@ -478,13 +490,14 @@
     # destroy_volumes
 
     destroy_servers(RES['servers'])
-    LOG.warn("Destroy mode incomplete")
-    # destroy_images
+    destroy_images(RES['images'])
     # destroy_objects
 
     # destroy_users
     # destroy_tenants
 
+    LOG.warn("Destroy mode incomplete")
+
 
 def get_options():
     global OPTS
diff --git a/tempest/config.py b/tempest/config.py
index 01bc243..db54269 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -286,6 +286,9 @@
     cfg.BoolOpt('pause',
                 default=True,
                 help="Does the test environment support pausing?"),
+    cfg.BoolOpt('shelve',
+                default=True,
+                help="Does the test environment support shelving/unshelving?"),
     cfg.BoolOpt('suspend',
                 default=True,
                 help="Does the test environment support suspend/resume?"),
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index 1fe0cf1..7acbae7 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -258,3 +258,38 @@
 
         uri = 'job-binaries/%s/data' % job_binary_id
         return self._request_and_check_resp(self.get, uri, 200)
+
+    def list_jobs(self):
+        """List all jobs for a user."""
+
+        uri = 'jobs'
+        return self._request_check_and_parse_resp(self.get, uri, 200, 'jobs')
+
+    def get_job(self, job_id):
+        """Returns the details of a single job."""
+
+        uri = 'jobs/%s' % job_id
+        return self._request_check_and_parse_resp(self.get, uri, 200, 'job')
+
+    def create_job(self, name, job_type, mains, libs=None, **kwargs):
+        """Creates job with specified params.
+
+        It supports passing additional params using kwargs and returns created
+        object.
+        """
+        uri = 'jobs'
+        body = kwargs.copy()
+        body.update({
+            'name': name,
+            'type': job_type,
+            'mains': mains,
+            'libs': libs or list(),
+        })
+        return self._request_check_and_parse_resp(self.post, uri, 202,
+                                                  'job', body=json.dumps(body))
+
+    def delete_job(self, job_id):
+        """Deletes the specified job by id."""
+
+        uri = 'jobs/%s' % job_id
+        return self._request_and_check_resp(self.delete, uri, 204)
diff --git a/tempest/services/volume/json/availability_zone_client.py b/tempest/services/volume/json/availability_zone_client.py
index 6839d3a..f2e7c5c 100644
--- a/tempest/services/volume/json/availability_zone_client.py
+++ b/tempest/services/volume/json/availability_zone_client.py
@@ -21,10 +21,10 @@
 CONF = config.CONF
 
 
-class VolumeAvailabilityZoneClientJSON(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientJSON(rest_client.RestClient):
 
     def __init__(self, auth_provider):
-        super(VolumeAvailabilityZoneClientJSON, self).__init__(
+        super(BaseVolumeAvailabilityZoneClientJSON, self).__init__(
             auth_provider)
         self.service = CONF.volume.catalog_type
 
@@ -32,3 +32,9 @@
         resp, body = self.get('os-availability-zone')
         body = json.loads(body)
         return resp, body['availabilityZoneInfo']
+
+
+class VolumeAvailabilityZoneClientJSON(BaseVolumeAvailabilityZoneClientJSON):
+    """
+    Volume V1 availability zone client.
+    """
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/json/extensions_client.py
index 9e182ea..e3ff00b 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/json/extensions_client.py
@@ -21,10 +21,10 @@
 CONF = config.CONF
 
 
-class ExtensionsClientJSON(rest_client.RestClient):
+class BaseExtensionsClientJSON(rest_client.RestClient):
 
     def __init__(self, auth_provider):
-        super(ExtensionsClientJSON, self).__init__(auth_provider)
+        super(BaseExtensionsClientJSON, self).__init__(auth_provider)
         self.service = CONF.volume.catalog_type
 
     def list_extensions(self):
@@ -32,3 +32,9 @@
         resp, body = self.get(url)
         body = json.loads(body)
         return resp, body['extensions']
+
+
+class ExtensionsClientJSON(BaseExtensionsClientJSON):
+    """
+    Volume V1 extensions client.
+    """
diff --git a/tempest/services/volume/v2/json/availability_zone_client.py b/tempest/services/volume/v2/json/availability_zone_client.py
new file mode 100644
index 0000000..047ba1b
--- /dev/null
+++ b/tempest/services/volume/v2/json/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 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 tempest.services.volume.json import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientJSON(
+        availability_zone_client.BaseVolumeAvailabilityZoneClientJSON):
+
+    def __init__(self, auth_provider):
+        super(VolumeV2AvailabilityZoneClientJSON, self).__init__(
+            auth_provider)
+
+        self.api_version = "v2"
diff --git a/tempest/services/volume/v2/json/extensions_client.py b/tempest/services/volume/v2/json/extensions_client.py
new file mode 100644
index 0000000..cc5244c
--- /dev/null
+++ b/tempest/services/volume/v2/json/extensions_client.py
@@ -0,0 +1,24 @@
+# Copyright 2014 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 tempest.services.volume.json import extensions_client
+
+
+class ExtensionsV2ClientJSON(extensions_client.BaseExtensionsClientJSON):
+
+    def __init__(self, auth_provider):
+        super(ExtensionsV2ClientJSON, self).__init__(auth_provider)
+
+        self.api_version = "v2"
diff --git a/tempest/services/volume/v2/xml/availability_zone_client.py b/tempest/services/volume/v2/xml/availability_zone_client.py
new file mode 100644
index 0000000..68ca39b
--- /dev/null
+++ b/tempest/services/volume/v2/xml/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 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 tempest.services.volume.xml import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientXML(
+        availability_zone_client.BaseVolumeAvailabilityZoneClientXML):
+
+    def __init__(self, auth_provider):
+        super(VolumeV2AvailabilityZoneClientXML, self).__init__(
+            auth_provider)
+
+        self.api_version = "v2"
diff --git a/tempest/services/volume/v2/xml/extensions_client.py b/tempest/services/volume/v2/xml/extensions_client.py
new file mode 100644
index 0000000..13f333c
--- /dev/null
+++ b/tempest/services/volume/v2/xml/extensions_client.py
@@ -0,0 +1,24 @@
+# Copyright 2014 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 tempest.services.volume.xml import extensions_client
+
+
+class ExtensionsV2ClientXML(extensions_client.BaseExtensionsClientXML):
+
+    def __init__(self, auth_provider):
+        super(ExtensionsV2ClientXML, self).__init__(auth_provider)
+
+        self.api_version = "v2"
diff --git a/tempest/services/volume/xml/availability_zone_client.py b/tempest/services/volume/xml/availability_zone_client.py
index e4a004a..a883ef5 100644
--- a/tempest/services/volume/xml/availability_zone_client.py
+++ b/tempest/services/volume/xml/availability_zone_client.py
@@ -22,11 +22,11 @@
 CONF = config.CONF
 
 
-class VolumeAvailabilityZoneClientXML(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientXML(rest_client.RestClient):
     TYPE = "xml"
 
     def __init__(self, auth_provider):
-        super(VolumeAvailabilityZoneClientXML, self).__init__(
+        super(BaseVolumeAvailabilityZoneClientXML, self).__init__(
             auth_provider)
         self.service = CONF.volume.catalog_type
 
@@ -37,3 +37,9 @@
         resp, body = self.get('os-availability-zone')
         availability_zone = self._parse_array(etree.fromstring(body))
         return resp, availability_zone
+
+
+class VolumeAvailabilityZoneClientXML(BaseVolumeAvailabilityZoneClientXML):
+    """
+    Volume V1 availability zone client.
+    """
diff --git a/tempest/services/volume/xml/extensions_client.py b/tempest/services/volume/xml/extensions_client.py
index 2986fcd..fe8b7cb 100644
--- a/tempest/services/volume/xml/extensions_client.py
+++ b/tempest/services/volume/xml/extensions_client.py
@@ -22,11 +22,11 @@
 CONF = config.CONF
 
 
-class ExtensionsClientXML(rest_client.RestClient):
+class BaseExtensionsClientXML(rest_client.RestClient):
     TYPE = "xml"
 
     def __init__(self, auth_provider):
-        super(ExtensionsClientXML, self).__init__(auth_provider)
+        super(BaseExtensionsClientXML, self).__init__(auth_provider)
         self.service = CONF.volume.catalog_type
 
     def _parse_array(self, node):
@@ -40,3 +40,9 @@
         resp, body = self.get(url)
         body = self._parse_array(etree.fromstring(body))
         return resp, body
+
+
+class ExtensionsClientXML(BaseExtensionsClientXML):
+    """
+    Volume V1 extensions client.
+    """
diff --git a/tempest/test.py b/tempest/test.py
index 5b7330b..95ae23f 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -623,7 +623,6 @@
     while now < timeout:
         if func():
             return True
-        LOG.debug("Sleeping for %d seconds", sleep_for)
         time.sleep(sleep_for)
         now = time.time()
     return False
diff --git a/tox.ini b/tox.ini
index 4f2465a..9c32121 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,23 +6,28 @@
 [testenv]
 setenv = VIRTUAL_ENV={envdir}
          OS_TEST_PATH=./tempest/test_discover
+         PYTHONHASHSEED=0
 usedevelop = True
 install_command = pip install -U {opts} {packages}
 
 [testenv:py26]
 setenv = OS_TEST_PATH=./tempest/tests
+         PYTHONHASHSEED=0
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:py33]
 setenv = OS_TEST_PATH=./tempest/tests
+         PYTHONHASHSEED=0
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:py27]
 setenv = OS_TEST_PATH=./tempest/tests
+         PYTHONHASHSEED=0
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:cover]
 setenv = OS_TEST_PATH=./tempest/tests
+         PYTHONHASHSEED=0
 commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
 
 [testenv:all]