Adds Cinder client

This change adds Cinder client in services/volume

* Keeps existing tests for Compute Extensions in tempest/tests/compute
* Copies existing volumes test to tests/volume and refactors them to test the
Cinder API, and adds a BaseVolumesTest class
* Renames the Nova Extensions' volumes_client to volumes_extensions_client
* Adds build_interval and build_timeout parameters in config for Cinder
specific tests
* Renames build_interval and build_timeout environment variables in the
Devstack template file to COMPUTE_BUILD_INTERVAL and COMPUTE_BUILD_TIMEOUT
* Adds volume specific environment variables VOLUME_BUILD_INTERVAL AND
VOLUME_BUILD_TIMEOUT to the Devstack template file.

Fixes LP Bug #1026190

Change-Id: I14d980ada1ddb29e8147f990aaf239fdcaae5eb6
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 8e7d8fb..8d3f6c9 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -62,10 +62,10 @@
 flavor_ref_alt = 2
 
 # Number of seconds to wait while looping to check the status of an
-# instance or volume that is building.
+# instance that is building.
 build_interval = 10
 
-# Number of seconds to time out on waiting for an instance or volume
+# Number of seconds to time out on waiting for an instance
 # to build or reach an expected status
 build_timeout = 600
 
@@ -125,7 +125,7 @@
 path_to_private_key = /home/user/.ssh/id_rsa
 
 # Connection string to the database of Compute service
-db_uri = mysql:///user:pass@localhost/nova
+db_uri = mysql://user:pass@localhost/nova
 
 [image]
 # This section contains configuration options used when executing tests
@@ -167,7 +167,10 @@
 [network]
 # This section contains configuration options used when executing tests
 # against the OpenStack Network API.
+
+# Version of the Quantum API
 api_version = v1.1
+# Catalog type of the Quantum Service
 catalog_type = network
 
 [identity-admin]
@@ -181,3 +184,18 @@
 password = pass
 # The above administrative user's tenant name
 tenant_name = admin
+
+[volume]
+# This section contains the configuration options used when executng tests
+# against the OpenStack Block Storage API service
+
+# The type of endpoint for a Cinder or Block Storage API service.
+# Unless you have a custom Keystone service catalog implementation, you
+# probably want to leave this value as "volume"
+catalog_type = volume
+# Number of seconds to wait while looping to check the status of a
+# volume that is being made available
+build_interval = 10
+# Number of seconds to time out on waiting for a volume
+# to be available or reach an expected status
+build_timeout = 300
diff --git a/etc/tempest.conf.tpl b/etc/tempest.conf.tpl
index ac1664a..ecb020a 100644
--- a/etc/tempest.conf.tpl
+++ b/etc/tempest.conf.tpl
@@ -58,12 +58,12 @@
 flavor_ref_alt = %FLAVOR_REF_ALT%
 
 # Number of seconds to wait while looping to check the status of an
-# instance or volume that is building.
-build_interval = %BUILD_INTERVAL%
+# instance that is building.
+build_interval = %COMPUTE_BUILD_INTERVAL%
 
-# Number of seconds to time out on waiting for an instance or volume
+# Number of seconds to time out on waiting for an instance
 # to build or reach an expected status
-build_timeout = %BUILD_TIMEOUT%
+build_timeout = %COMPUTE_BUILD_TIMEOUT%
 
 # The type of endpoint for a Compute API service. Unless you have a
 # custom Keystone service catalog implementation, you probably want to leave
@@ -154,3 +154,18 @@
 password = %IDENTITY_ADMIN_PASSWORD%
 # The above administrative user's tenant name
 tenant_name = %IDENTITY_ADMIN_TENANT_NAME%
+
+[volume]
+# This section contains the configuration options used when executing tests
+# against the OpenStack Block Storage API service
+
+# The type of endpoint for a Cinder or Block Storage API service.
+# Unless you have a custom Keystone service catalog implementation, you
+# probably want to leave this value as "volume"
+catalog_type = %VOLUME_CATALOG_TYPE%
+# Number of seconds to wait while looping to check the status of a
+# volume that is being made available
+build_interval = %VOLUME_BUILD_INTERVAL%
+# Number of seconds to time out on waiting for a volume
+# to be available or reach an expected status
+build_timeout = %VOLUME_BUILD_TIMEOUT%
diff --git a/tempest/config.py b/tempest/config.py
index 003f03e..38455b2 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -208,7 +208,7 @@
 
     @property
     def build_timeout(self):
-        """Timeout in seconds to wait for an entity to build."""
+        """Timeout in seconds to wait for an instance to build."""
         return float(self.get("build_timeout", 300))
 
     @property
@@ -355,6 +355,30 @@
         return self.get("api_version", "v1.1")
 
 
+class VolumeConfig(BaseConfig):
+    """Provides configuration information for connecting to an OpenStack Block
+    Storage Service.
+    """
+
+    SECTION_NAME = "volume"
+
+    @property
+    def build_interval(self):
+        """Time in seconds between volume availability checks."""
+        return float(self.get("build_interval", 10))
+
+    @property
+    def build_timeout(self):
+        """Timeout in seconds to wait for a volume to become available."""
+        return float(self.get("build_timeout", 300))
+
+    @property
+    def catalog_type(self):
+        """Catalog type of the Volume Service"""
+        return self.get("catalog_type", 'volume')
+
+
+# TODO(jaypipes): Move this to a common utils (not data_utils...)
 def singleton(cls):
     """Simple wrapper for classes that should only have a single instance"""
     instances = {}
@@ -402,6 +426,7 @@
         self.identity_admin = IdentityAdminConfig(self._conf)
         self.images = ImagesConfig(self._conf)
         self.network = NetworkConfig(self._conf)
+        self.volume = VolumeConfig(self._conf)
 
     def load_config(self, path):
         """Read configuration from given path and return a config object."""
diff --git a/tempest/manager.py b/tempest/manager.py
index 3856eed..758f42c 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -25,6 +25,7 @@
 from tempest import exceptions
 # Tempest REST Fuzz testing client libs
 from tempest.services.network.json import network_client
+from tempest.services.volume.json import volumes_client
 from tempest.services.nova.json import images_client
 from tempest.services.nova.json import flavors_client
 from tempest.services.nova.json import servers_client
@@ -33,7 +34,7 @@
 from tempest.services.nova.json import security_groups_client
 from tempest.services.nova.json import floating_ips_client
 from tempest.services.nova.json import keypairs_client
-from tempest.services.nova.json import volumes_client
+from tempest.services.nova.json import volumes_extensions_client
 from tempest.services.nova.json import console_output_client
 
 NetworkClient = network_client.NetworkClient
@@ -45,6 +46,7 @@
 SecurityGroupsClient = security_groups_client.SecurityGroupsClient
 FloatingIPsClient = floating_ips_client.FloatingIPsClient
 KeyPairsClient = keypairs_client.KeyPairsClientJSON
+VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClient
 VolumesClient = volumes_client.VolumesClient
 ConsoleOutputsClient = console_output_client.ConsoleOutputsClient
 
@@ -199,6 +201,7 @@
         self.keypairs_client = KeyPairsClient(*client_args)
         self.security_groups_client = SecurityGroupsClient(*client_args)
         self.floating_ips_client = FloatingIPsClient(*client_args)
+        self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
         self.volumes_client = VolumesClient(*client_args)
         self.console_outputs_client = ConsoleOutputsClient(*client_args)
         self.network_client = NetworkClient(*client_args)
diff --git a/tempest/openstack.py b/tempest/openstack.py
index 241d8bd..945ce3a 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -22,6 +22,7 @@
 from tempest.services.image import service as image_service
 from tempest.services.network.json.network_client import NetworkClient
 from tempest.services.nova.json.flavors_client import FlavorsClientJSON
+from tempest.services.volume.json.volumes_client import VolumesClient
 from tempest.services.nova.json.images_client import ImagesClient
 from tempest.services.nova.json.limits_client import LimitsClientJSON
 from tempest.services.nova.json.servers_client import ServersClientJSON
@@ -30,7 +31,8 @@
 import SecurityGroupsClient
 from tempest.services.nova.json.floating_ips_client import FloatingIPsClient
 from tempest.services.nova.json.keypairs_client import KeyPairsClientJSON
-from tempest.services.nova.json.volumes_client import VolumesClient
+from tempest.services.nova.json.volumes_extensions_client \
+import VolumesExtensionsClient
 from tempest.services.nova.json.console_output_client \
 import ConsoleOutputsClient
 from tempest.services.nova.xml.flavors_client import FlavorsClientXML
@@ -82,23 +84,24 @@
 
         # If no creds are provided, we fall back on the defaults
         # in the config file for the Compute API.
-        username = username or self.config.compute.username
-        password = password or self.config.compute.password
-        tenant_name = tenant_name or self.config.compute.tenant_name
+        self.username = username or self.config.compute.username
+        self.password = password or self.config.compute.password
+        self.tenant_name = tenant_name or self.config.compute.tenant_name
 
-        if None in (username, password, tenant_name):
+        if None in (self.username, self.password, self.tenant_name):
             msg = ("Missing required credentials. "
                    "username: %(username)s, password: %(password)s, "
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        auth_url = self.config.identity.auth_url
+        self.auth_url = self.config.identity.auth_url
 
         if self.config.identity.strategy == 'keystone':
-            client_args = (self.config, username, password, auth_url,
-                           tenant_name)
+            client_args = (self.config, self.username, self.password,
+                           self.auth_url, self.tenant_name)
         else:
-            client_args = (self.config, username, password, auth_url)
+            client_args = (self.config, self.username, self.password,
+                           self.auth_url)
 
         try:
             self.servers_client = SERVERS_CLIENTS[interface](*client_args)
@@ -112,9 +115,10 @@
         self.extensions_client = ExtensionsClient(*client_args)
         self.security_groups_client = SecurityGroupsClient(*client_args)
         self.floating_ips_client = FloatingIPsClient(*client_args)
-        self.volumes_client = VolumesClient(*client_args)
         self.console_outputs_client = ConsoleOutputsClient(*client_args)
         self.network_client = NetworkClient(*client_args)
+        self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
+        self.volumes_client = VolumesClient(*client_args)
 
 
 class AltManager(Manager):
diff --git a/tempest/services/nova/json/volumes_client.py b/tempest/services/nova/json/volumes_extensions_client.py
similarity index 89%
rename from tempest/services/nova/json/volumes_client.py
rename to tempest/services/nova/json/volumes_extensions_client.py
index fd577b7..9b3590b 100644
--- a/tempest/services/nova/json/volumes_client.py
+++ b/tempest/services/nova/json/volumes_extensions_client.py
@@ -4,14 +4,15 @@
 import time
 
 
-class VolumesClient(RestClient):
+class VolumesExtensionsClient(RestClient):
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(VolumesClient, self).__init__(config, username, password,
-                                           auth_url, tenant_name)
+        super(VolumesExtensionsClient, self).__init__(config, username,
+                                                      password, auth_url,
+                                                      tenant_name)
         self.service = self.config.compute.catalog_type
-        self.build_interval = self.config.compute.build_interval
-        self.build_timeout = self.config.compute.build_timeout
+        self.build_interval = self.config.volume.build_interval
+        self.build_timeout = self.config.volume.build_timeout
 
     def list_volumes(self, params=None):
         """List all the volumes created"""
diff --git a/tempest/services/volume/__init__.py b/tempest/services/volume/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/volume/__init__.py
diff --git a/tempest/services/volume/json/__init__.py b/tempest/services/volume/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/volume/json/__init__.py
diff --git a/tempest/services/nova/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
similarity index 70%
copy from tempest/services/nova/json/volumes_client.py
copy to tempest/services/volume/json/volumes_client.py
index fd577b7..68c7a86 100644
--- a/tempest/services/nova/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -1,35 +1,56 @@
-from tempest import exceptions
-from tempest.common.rest_client import RestClient
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
 import json
 import time
 
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+
 
 class VolumesClient(RestClient):
+    """
+    Client class to send CRUD Volume API requests to a Cinder endpoint
+    """
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
         super(VolumesClient, self).__init__(config, username, password,
                                            auth_url, tenant_name)
-        self.service = self.config.compute.catalog_type
-        self.build_interval = self.config.compute.build_interval
-        self.build_timeout = self.config.compute.build_timeout
+
+        self.service = self.config.volume.catalog_type
+        self.build_interval = self.config.volume.build_interval
+        self.build_timeout = self.config.volume.build_timeout
 
     def list_volumes(self, params=None):
         """List all the volumes created"""
-        url = 'os-volumes'
+        url = 'volumes'
         if params != None:
             param_list = []
             for param, value in params.iteritems():
                 param_list.append("%s=%s&" % (param, value))
 
             url += '?' + ' '.join(param_list)
-
         resp, body = self.get(url)
         body = json.loads(body)
         return resp, body['volumes']
 
     def list_volumes_with_detail(self, params=None):
-        """List all the details of volumes"""
-        url = 'os-volumes/detail'
+        """List the details of all volumes"""
+        url = 'volumes/detail'
         if params != None:
             param_list = []
             for param, value in params.iteritems():
@@ -43,7 +64,7 @@
 
     def get_volume(self, volume_id):
         """Returns the details of a single volume"""
-        url = "os-volumes/%s" % str(volume_id)
+        url = "volumes/%s" % str(volume_id)
         resp, body = self.get(url)
         body = json.loads(body)
         return resp, body['volume']
@@ -63,18 +84,18 @@
             }
 
         post_body = json.dumps({'volume': post_body})
-        resp, body = self.post('os-volumes', post_body, self.headers)
+        resp, body = self.post('volumes', post_body, self.headers)
         body = json.loads(body)
         return resp, body['volume']
 
     def delete_volume(self, volume_id):
         """Deletes the Specified Volume"""
-        return self.delete("os-volumes/%s" % str(volume_id))
+        return self.delete("volumes/%s" % str(volume_id))
 
     def wait_for_volume_status(self, volume_id, status):
         """Waits for a Volume to reach a given status"""
         resp, body = self.get_volume(volume_id)
-        volume_name = body['displayName']
+        volume_name = body['display_name']
         volume_status = body['status']
         start = int(time.time())
 
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 5e6eb7d..84269d6 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -17,6 +17,7 @@
 
 import logging
 import time
+import nose
 
 import unittest2 as unittest
 
@@ -60,6 +61,7 @@
         cls.security_groups_client = os.security_groups_client
         cls.console_outputs_client = os.console_outputs_client
         cls.limits_client = os.limits_client
+        cls.volumes_extensions_client = os.volumes_extensions_client
         cls.volumes_client = os.volumes_client
         cls.build_interval = cls.config.compute.build_interval
         cls.build_timeout = cls.config.compute.build_timeout
diff --git a/tempest/tests/compute/test_volumes_get.py b/tempest/tests/compute/test_volumes_get.py
index a7f21cb..cda943d 100644
--- a/tempest/tests/compute/test_volumes_get.py
+++ b/tempest/tests/compute/test_volumes_get.py
@@ -16,7 +16,6 @@
 #    under the License.
 
 from nose.plugins.attrib import attr
-import unittest2 as unittest
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.tests.compute.base import BaseComputeTest
@@ -27,7 +26,7 @@
     @classmethod
     def setUpClass(cls):
         super(VolumesGetTest, cls).setUpClass()
-        cls.client = cls.volumes_client
+        cls.client = cls.volumes_extensions_client
 
     @attr(type='smoke')
     def test_volume_create_get_delete(self):
@@ -64,6 +63,7 @@
                              fetched_volume['metadata'],
                              'The fetched Volume is different '
                              'from the created Volume')
+
         finally:
             #Delete the Volume created in this method
             resp, _ = self.client.delete_volume(volume['id'])
diff --git a/tempest/tests/compute/test_volumes_list.py b/tempest/tests/compute/test_volumes_list.py
index c3bde15..679a23b 100644
--- a/tempest/tests/compute/test_volumes_list.py
+++ b/tempest/tests/compute/test_volumes_list.py
@@ -15,30 +15,26 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import unittest2 as unittest
-
 import nose
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.tests.compute.base import BaseComputeTest
 
 
-class VolumesTest(BaseComputeTest):
+class VolumesListTest(BaseComputeTest):
 
     """
     This test creates a number of 1G volumes. To run successfully,
     ensure that the backing file for the volume group that Nova uses
-    has space for at least 3 1G volumes! Devstack, by default, creates
-    a 2G volume backing file, which causes this test to fail because
-    the third volume gets created in ERROR state (out of disk space in
-    volume group...). If you are running a Devstack environment, set
-    VOLUME_BACKING_FILE_SIZE=4G in your localrc
+    has space for at least 3 1G volumes!
+    If you are running a Devstack environment, ensure that the
+    VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc
     """
 
     @classmethod
     def setUpClass(cls):
-        super(VolumesTest, cls).setUpClass()
-        cls.client = cls.volumes_client
+        super(VolumesListTest, cls).setUpClass()
+        cls.client = cls.volumes_extensions_client
         # Create 3 Volumes
         cls.volume_list = list()
         cls.volume_id_list = list()
@@ -75,7 +71,7 @@
         # Delete the created Volumes
         for volume in cls.volume_list:
             resp, _ = cls.client.delete_volume(volume['id'])
-        super(VolumesTest, cls).tearDownClass()
+        super(VolumesListTest, cls).tearDownClass()
 
     def test_volume_list(self):
         """Should return the list of Volumes"""
diff --git a/tempest/tests/compute/test_volumes_negative.py b/tempest/tests/compute/test_volumes_negative.py
index 8d67fff..ea2811c 100644
--- a/tempest/tests/compute/test_volumes_negative.py
+++ b/tempest/tests/compute/test_volumes_negative.py
@@ -17,7 +17,6 @@
 
 from nose.plugins.attrib import attr
 from nose.tools import raises
-import unittest2 as unittest
 
 from tempest import exceptions
 from tempest.common.utils.data_utils import rand_name
@@ -29,7 +28,7 @@
     @classmethod
     def setUpClass(cls):
         super(VolumesNegativeTest, cls).setUpClass()
-        cls.client = cls.volumes_client
+        cls.client = cls.volumes_extensions_client
 
     @attr(type='negative')
     def test_volume_get_nonexistant_volume_id(self):
diff --git a/tempest/tests/volume/__init__.py b/tempest/tests/volume/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/volume/__init__.py
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
new file mode 100644
index 0000000..41f08fe
--- /dev/null
+++ b/tempest/tests/volume/base.py
@@ -0,0 +1,149 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import logging
+import time
+import nose
+
+import unittest2 as unittest
+
+from tempest import config
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name
+from tempest.services.identity.json.admin_client import AdminClient
+from tempest import exceptions
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseVolumeTest(unittest.TestCase):
+
+    """Base test case class for all Cinder API tests"""
+
+    @classmethod
+    def setUpClass(cls):
+        cls.config = config.TempestConfig()
+        cls.isolated_creds = []
+
+        if cls.config.compute.allow_tenant_isolation:
+            creds = cls._get_isolated_creds()
+            username, tenant_name, password = creds
+            os = openstack.Manager(username=username,
+                                   password=password,
+                                   tenant_name=tenant_name)
+        else:
+            os = openstack.Manager()
+
+        cls.os = os
+        cls.volumes_client = os.volumes_client
+        cls.build_interval = cls.config.volume.build_interval
+        cls.build_timeout = cls.config.volume.build_timeout
+        cls.volumes = {}
+
+        skip_msg = ("%s skipped as Cinder endpoint is not available" %
+                                                            cls.__name__)
+        try:
+            cls.volumes_client.keystone_auth(cls.os.username,
+                                             cls.os.password,
+                                             cls.os.auth_url,
+                                             cls.volumes_client.service,
+                                             cls.os.tenant_name)
+        except exceptions.EndpointNotFound:
+            raise nose.SkipTest(skip_msg)
+
+    @classmethod
+    def _get_identity_admin_client(cls):
+        """
+        Returns an instance of the Identity Admin API client
+        """
+        client_args = (cls.config,
+                       cls.config.identity_admin.username,
+                       cls.config.identity_admin.password,
+                       cls.config.identity.auth_url)
+        tenant_name = cls.config.identity_admin.tenant_name
+        admin_client = AdminClient(*client_args, tenant_name=tenant_name)
+        return admin_client
+
+    @classmethod
+    def _get_isolated_creds(cls):
+        """
+        Creates a new set of user/tenant/password credentials for a
+        **regular** user of the Volume API so that a test case can
+        operate in an isolated tenant container.
+        """
+        admin_client = cls._get_identity_admin_client()
+        rand_name_root = cls.__name__
+        if cls.isolated_creds:
+            # Main user already created. Create the alt one...
+            rand_name_root += '-alt'
+        username = rand_name_root + "-user"
+        email = rand_name_root + "@example.com"
+        tenant_name = rand_name_root + "-tenant"
+        tenant_desc = tenant_name + "-desc"
+        password = "pass"
+
+        resp, tenant = admin_client.create_tenant(name=tenant_name,
+                                                  description=tenant_desc)
+        resp, user = admin_client.create_user(username,
+                                              password,
+                                              tenant['id'],
+                                              email)
+        # Store the complete creds (including UUID ids...) for later
+        # but return just the username, tenant_name, password tuple
+        # that the various clients will use.
+        cls.isolated_creds.append((user, tenant))
+
+        return username, tenant_name, password
+
+    @classmethod
+    def clear_isolated_creds(cls):
+        if not cls.isolated_creds:
+            pass
+        admin_client = cls._get_identity_admin_client()
+
+        for user, tenant in cls.isolated_creds:
+            admin_client.delete_user(user['id'])
+            admin_client.delete_tenant(tenant['id'])
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.clear_isolated_creds()
+
+    def create_volume(self, size=1, metadata={}):
+        """Wrapper utility that returns a test volume"""
+        display_name = rand_name(self.__class__.__name__ + "-volume")
+        resp, volume = self.volumes_client.create_volume(size=size,
+                                                    display_name=display_name,
+                                                    metdata=metadata)
+        self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+        self.volumes.append(volume)
+        return volume
+
+    def wait_for(self, condition):
+        """Repeatedly calls condition() until a timeout"""
+        start_time = int(time.time())
+        while True:
+            try:
+                condition()
+            except:
+                pass
+            else:
+                return
+            if int(time.time()) - start_time >= self.build_timeout:
+                condition()
+                return
+            time.sleep(self.build_interval)
diff --git a/tempest/tests/volume/test_volumes_get.py b/tempest/tests/volume/test_volumes_get.py
new file mode 100644
index 0000000..4305c67
--- /dev/null
+++ b/tempest/tests/volume/test_volumes_get.py
@@ -0,0 +1,99 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from nose.plugins.attrib import attr
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.volume.base import BaseVolumeTest
+
+
+class VolumesGetTest(BaseVolumeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesGetTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+
+    @attr(type='smoke')
+    def test_volume_create_get_delete(self):
+        """Create a volume, Get it's details and Delete the volume"""
+        try:
+            volume = {}
+            v_name = rand_name('Volume-')
+            metadata = {'Type': 'work'}
+            #Create a volume
+            resp, volume = self.client.create_volume(size=1,
+                                                     display_name=v_name,
+                                                     metadata=metadata)
+            self.assertEqual(200, resp.status)
+            self.assertTrue('id' in volume)
+            self.assertTrue('display_name' in volume)
+            self.assertEqual(volume['display_name'], v_name,
+            "The created volume name is not equal to the requested name")
+            self.assertTrue(volume['id'] is not None,
+            "Field volume id is empty or not found.")
+            self.client.wait_for_volume_status(volume['id'], 'available')
+            # Get Volume information
+            resp, fetched_volume = self.client.get_volume(volume['id'])
+            self.assertEqual(200, resp.status)
+            self.assertEqual(v_name,
+                             fetched_volume['display_name'],
+                             'The fetched Volume is different '
+                             'from the created Volume')
+            self.assertEqual(volume['id'],
+                             fetched_volume['id'],
+                             'The fetched Volume is different '
+                             'from the created Volume')
+            self.assertEqual(metadata,
+                             fetched_volume['metadata'],
+                             'The fetched Volume is different '
+                             'from the created Volume')
+        except:
+            self.fail("Could not create a volume")
+        finally:
+            if volume:
+                # Delete the Volume if it was created
+                resp, _ = self.client.delete_volume(volume['id'])
+                self.assertEqual(202, resp.status)
+                self.client.wait_for_resource_deletion(volume['id'])
+
+    @attr(type='positive')
+    def test_volume_get_metadata_none(self):
+        """Create a volume without passing metadata, get details, and delete"""
+        try:
+            volume = {}
+            v_name = rand_name('Volume-')
+            # Create a volume without metadata
+            resp, volume = self.client.create_volume(size=1,
+                                                     display_name=v_name,
+                                                     metadata={})
+            self.assertEqual(200, resp.status)
+            self.assertTrue('id' in volume)
+            self.assertTrue('display_name' in volume)
+            self.client.wait_for_volume_status(volume['id'], 'available')
+            #GET Volume
+            resp, fetched_volume = self.client.get_volume(volume['id'])
+            self.assertEqual(200, resp.status)
+            self.assertEqual(fetched_volume['metadata'], {})
+        except:
+            self.fail("Could not get volume metadata")
+        finally:
+            if volume:
+                # Delete the Volume if it was created
+                resp, _ = self.client.delete_volume(volume['id'])
+                self.assertEqual(202, resp.status)
+                self.client.wait_for_resource_deletion(volume['id'])
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
new file mode 100644
index 0000000..24055af
--- /dev/null
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import nose
+from nose.plugins.attrib import attr
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.volume.base import BaseVolumeTest
+
+
+class VolumesListTest(BaseVolumeTest):
+
+    """
+    This test creates a number of 1G volumes. To run successfully,
+    ensure that the backing file for the volume group that Nova uses
+    has space for at least 3 1G volumes!
+    If you are running a Devstack environment, ensure that the
+    VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesListTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+
+        # Create 3 test volumes
+        cls.volume_list = []
+        cls.volume_id_list = []
+        for i in range(3):
+            v_name = rand_name('volume')
+            metadata = {'Type': 'work'}
+            try:
+                resp, volume = cls.client.create_volume(size=1,
+                                                        display_name=v_name,
+                                                        metadata=metadata)
+                cls.client.wait_for_volume_status(volume['id'],
+                                                   'available')
+                resp, volume = cls.client.get_volume(volume['id'])
+                cls.volume_list.append(volume)
+                cls.volume_id_list.append(volume['id'])
+            except:
+                if cls.volume_list:
+                    # We could not create all the volumes, though we were able
+                    # to create *some* of the volumes. This is typically
+                    # because the backing file size of the volume group is
+                    # too small. So, here, we clean up whatever we did manage
+                    # to create and raise a SkipTest
+                    for volume in cls.volume_id_list:
+                        cls.client.delete_volume(volume)
+                    msg = ("Failed to create ALL necessary volumes to run "
+                           "test. This typically means that the backing file "
+                           "size of the nova-volumes group is too small to "
+                           "create the 3 volumes needed by this test case")
+                    raise nose.SkipTest(msg)
+                raise
+
+    @classmethod
+    def tearDownClass(cls):
+        # Delete the created volumes
+        for volume in cls.volume_id_list:
+            resp, _ = cls.client.delete_volume(volume)
+            cls.client.wait_for_resource_deletion(volume)
+        super(VolumesListTest, cls).tearDownClass()
+
+    @attr(type='smoke')
+    def test_volume_list(self):
+        """Get a list of Volumes"""
+        # Fetch all volumes
+        resp, fetched_list = self.client.list_volumes()
+        self.assertEqual(200, resp.status)
+        # Now check if all the volumes created in setup are in fetched list
+        missing_vols = [v for v in self.volume_list if v not in fetched_list]
+        self.assertFalse(missing_vols,
+                         "Failed to find volume %s in fetched list"
+                         % ', '.join(m_vol['display_name']
+                                        for m_vol in missing_vols))
+
+    @attr(type='smoke')
+    def test_volume_list_with_details(self):
+        """Get a list of Volumes with details"""
+        # Fetch all Volumes
+        resp, fetched_list = self.client.list_volumes_with_detail()
+        self.assertEqual(200, resp.status)
+        # Verify that all the volumes are returned
+        missing_vols = [v for v in self.volume_list if v not in fetched_list]
+        self.assertFalse(missing_vols,
+                         "Failed to find volume %s in fetched list"
+                         % ', '.join(m_vol['display_name']
+                                        for m_vol in missing_vols))
diff --git a/tempest/tests/volume/test_volumes_negative.py b/tempest/tests/volume/test_volumes_negative.py
new file mode 100644
index 0000000..63b209e
--- /dev/null
+++ b/tempest/tests/volume/test_volumes_negative.py
@@ -0,0 +1,133 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from nose.plugins.attrib import attr
+from nose.tools import raises
+
+from tempest import exceptions
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.volume.base import BaseVolumeTest
+
+
+class VolumesNegativeTest(BaseVolumeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesNegativeTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_volume_get_nonexistant_volume_id(self):
+        """Should not be able to get a nonexistant volume"""
+        #Creating a nonexistant volume id
+        volume_id_list = []
+        resp, volumes = self.client.list_volumes()
+        for i in range(len(volumes)):
+            volume_id_list.append(volumes[i]['id'])
+        while True:
+            non_exist_id = rand_name('999')
+            if non_exist_id not in volume_id_list:
+                break
+        #Trying to Get a non existant volume
+        resp, volume = self.client.get_volume(non_exist_id)
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_volume_delete_nonexistant_volume_id(self):
+        """Should not be able to delete a nonexistant Volume"""
+        # Creating nonexistant volume id
+        volume_id_list = []
+        resp, volumes = self.client.list_volumes()
+        for i in range(len(volumes)):
+            volume_id_list.append(volumes[i]['id'])
+        while True:
+            non_exist_id = '12345678-abcd-4321-abcd-123456789098'
+            if non_exist_id not in volume_id_list:
+                break
+        # Try to Delete a non existant volume
+        resp, body = self.client.delete_volume(non_exist_id)
+
+    @raises(exceptions.BadRequest)
+    @attr(type='negative')
+    def test_create_volume_with_invalid_size(self):
+        """
+        Should not be able to create volume with invalid size
+        in request
+        """
+        v_name = rand_name('Volume-')
+        metadata = {'Type': 'work'}
+        resp, volume = self.client.create_volume(size='#$%',
+                                                 display_name=v_name,
+                                                 metadata=metadata)
+
+    @raises(exceptions.BadRequest)
+    @attr(type='negative')
+    def test_create_volume_with_out_passing_size(self):
+        """
+        Should not be able to create volume without passing size
+        in request
+        """
+        v_name = rand_name('Volume-')
+        metadata = {'Type': 'work'}
+        resp, volume = self.client.create_volume(size='',
+                                                 display_name=v_name,
+                                                 metadata=metadata)
+
+    @raises(exceptions.BadRequest)
+    @attr(type='negative')
+    def test_create_volume_with_size_zero(self):
+        """
+        Should not be able to create volume with size zero
+        """
+        v_name = rand_name('Volume-')
+        metadata = {'Type': 'work'}
+        resp, volume = self.client.create_volume(size='0',
+                                                 display_name=v_name,
+                                                 metadata=metadata)
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_get_invalid_volume_id(self):
+        """
+        Should not be able to get volume with invalid id
+        """
+        resp, volume = self.client.get_volume('#$%%&^&^')
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_get_volume_without_passing_volume_id(self):
+        """
+        Should not be able to get volume when empty ID is passed
+        """
+        resp, volume = self.client.get_volume('')
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_delete_invalid_volume_id(self):
+        """
+        Should not be able to delete volume when invalid ID is passed
+        """
+        resp, volume = self.client.delete_volume('!@#$%^&*()')
+
+    @raises(exceptions.NotFound)
+    @attr(type='negative')
+    def test_delete_volume_without_passing_volume_id(self):
+        """
+        Should not be able to delete volume when empty ID is passed
+        """
+        resp, volume = self.client.delete_volume('')