Merge "Fixed the LP bug 993754. Ensure that the server created in the test is destroyed in finally: block of the test."
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 226fa30..aa101d3 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -3,6 +3,10 @@
 # test clients use when authenticating with different user/tenant
 # combinations
 
+# The type of endpoint for a Identity service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "identity"
+catalog_type = identity
 # Set to True if your test environment's Keystone authentication service should
 # be accessed over HTTPS
 use_ssl = False
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index e9741bf..fd2f684 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -132,12 +132,15 @@
             if mgmt_url == None:
                 raise exceptions.EndpointNotFound(service)
 
-            #TODO (dwalleck): This is a horrible stopgap.
-            #Need to join strings more cleanly
-            temp = mgmt_url.rsplit('/')
-            service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/'
-            management_url = service_url + tenant_id
-            return token, management_url
+            if mgmt_url.endswith(tenant_id):
+                return token, mgmt_url
+            else:
+                #TODO (dwalleck): This is a horrible stopgap.
+                #Need to join strings more cleanly
+                temp = mgmt_url.rsplit('/')
+                service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/'
+                management_url = service_url + tenant_id
+                return token, management_url
         elif resp.status == 401:
             raise exceptions.AuthenticationFailure(user=user,
                                                    password=password)
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 2d540f4..752bc10 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -37,3 +37,10 @@
         url += urllib.urlencode(params)
 
     return url
+
+
+def parse_image_id(image_ref):
+    """Return the image id from a given image ref"""
+    temp = image_ref.rsplit('/')
+    #Return the last item, which is the image id
+    return temp[len(temp) - 1]
diff --git a/tempest/config.py b/tempest/config.py
index fcfeee5..d4a0c03 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -45,6 +45,11 @@
     SECTION_NAME = "identity"
 
     @property
+    def catalog_type(self):
+        """Catalog type of the Identity service."""
+        return self.get("catalog_type", 'identity')
+
+    @property
     def host(self):
         """Host IP for making Identity API requests."""
         return self.get("host", "127.0.0.1")
diff --git a/tempest/openstack.py b/tempest/openstack.py
index 491f385..3e19ba5 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -30,6 +30,7 @@
 from tempest.services.nova.json.floating_ips_client import FloatingIPsClient
 from tempest.services.nova.json.keypairs_client import KeyPairsClient
 from tempest.services.nova.json.volumes_client import VolumesClient
+from tempest.services.identity.json.admin_client import AdminClient
 
 LOG = logging.getLogger(__name__)
 
@@ -79,6 +80,7 @@
         self.security_groups_client = SecurityGroupsClient(*client_args)
         self.floating_ips_client = FloatingIPsClient(*client_args)
         self.volumes_client = VolumesClient(*client_args)
+        self.admin_client = AdminClient(*client_args)
 
 
 class AltManager(Manager):
diff --git a/tempest/services/identity/__init__.py b/tempest/services/identity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/__init__.py
diff --git a/tempest/services/identity/json/__init__.py b/tempest/services/identity/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/json/__init__.py
diff --git a/tempest/services/identity/json/admin_client.py b/tempest/services/identity/json/admin_client.py
new file mode 100644
index 0000000..45a7985
--- /dev/null
+++ b/tempest/services/identity/json/admin_client.py
@@ -0,0 +1,44 @@
+from tempest.common.rest_client import RestClient
+import json
+
+
+class AdminClient(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AdminClient, self).__init__(config, username, password,
+                                                    auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def has_admin_extensions(self):
+        """
+        Returns True if the KSADM Admin Extensions are supported
+        False otherwise
+        """
+        if hasattr(self, '_has_admin_extensions'):
+            return self._has_admin_extensions
+        resp, body = self.list_roles()
+        self._has_admin_extensions = ('status' in resp and resp.status != 503)
+        return self._has_admin_extensions
+
+    def create_role(self, name):
+        """Create a role"""
+        post_body = {
+            'name': name,
+        }
+        post_body = json.dumps({'role': post_body})
+        resp, body = self.post('OS-KSADM/roles', post_body,
+                                      self.headers)
+        body = json.loads(body)
+        return resp, body['role']
+
+    def delete_role(self, role_id):
+        """Delete a role"""
+        resp, body = self.delete('OS-KSADM/roles/%s' % role_id)
+        return resp, body
+
+    def list_roles(self):
+        """Returns roles"""
+        resp, body = self.get('OS-KSADM/roles')
+        body = json.loads(body)
+        return resp, body['roles']
diff --git a/tempest/services/nova/json/flavors_client.py b/tempest/services/nova/json/flavors_client.py
index 84fa9ff..bd77484 100644
--- a/tempest/services/nova/json/flavors_client.py
+++ b/tempest/services/nova/json/flavors_client.py
@@ -39,3 +39,27 @@
         resp, body = self.get("flavors/%s" % str(flavor_id))
         body = json.loads(body)
         return resp, body['flavor']
+
+    def create_flavor(self, name, ram, vcpus, disk, ephemeral, flavor_id,
+                    swap, rxtx):
+        """Creates a new flavor or instance type"""
+        post_body = {
+                'name': name,
+                'ram': ram,
+                'vcpus': vcpus,
+                'disk': disk,
+                'OS-FLV-EXT-DATA:ephemeral': ephemeral,
+                'id': flavor_id,
+                'swap': swap,
+                'rxtx_factor': rxtx
+            }
+
+        post_body = json.dumps({'flavor': post_body})
+        resp, body = self.post('flavors', post_body, self.headers)
+
+        body = json.loads(body)
+        return resp, body['flavor']
+
+    def delete_flavor(self, flavor_id):
+        """Deletes the given flavor"""
+        return self.delete("flavors/%s" % str(flavor_id))
diff --git a/tempest/services/nova/json/servers_client.py b/tempest/services/nova/json/servers_client.py
index 08ac6df..53b2d6f 100644
--- a/tempest/services/nova/json/servers_client.py
+++ b/tempest/services/nova/json/servers_client.py
@@ -21,6 +21,7 @@
         flavor_ref (Required): The flavor used to build the server.
         Following optional keyword arguments are accepted:
         adminPass: Sets the initial root password.
+        key_name: Key name of keypair that was created earlier.
         meta: A dictionary of values to be used as metadata.
         personality: A list of dictionaries for files to be injected into
         the server.
@@ -40,7 +41,7 @@
             'flavorRef': flavor_ref
         }
 
-        for option in ['personality', 'adminPass',
+        for option in ['personality', 'adminPass', 'key_name',
                         'security_groups', 'networks', 'user_data',
                         'availability_zone', 'accessIPv4', 'accessIPv6',
                         'min_count', 'max_count', ('metadata', 'meta'),
diff --git a/tempest/tests/compute/admin/__init__.py b/tempest/tests/compute/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/compute/admin/__init__.py
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
new file mode 100644
index 0000000..377781b
--- /dev/null
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -0,0 +1,111 @@
+from nose.plugins.attrib import attr
+from nose import SkipTest
+import tempest.config
+from tempest import exceptions
+from tempest import openstack
+from tempest.tests.base_compute_test import BaseComputeTest
+
+
+class FlavorsAdminTest(BaseComputeTest):
+
+    """
+    Tests Flavors API Create and Delete that require admin privileges
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        cls.config = tempest.config.TempestConfig()
+        cls.admin_username = cls.config.compute_admin.username
+        cls.admin_password = cls.config.compute_admin.password
+        cls.admin_tenant = cls.config.compute_admin.tenant_name
+
+        if not(cls.admin_username and cls.admin_password and cls.admin_tenant):
+            raise SkipTest("Missing Admin credentials in configuration")
+        else:
+            cls.admin_os = openstack.AdminManager()
+            cls.admin_client = cls.admin_os.flavors_client
+            cls.flavor_name = 'test_flavor'
+            cls.ram = 512
+            cls.vcpus = 1
+            cls.disk = 10
+            cls.ephemeral = 10
+            cls.new_flavor_id = 1234
+            cls.swap = 1024
+            cls.rxtx = 1
+
+    @attr(type='positive')
+    def test_create_flavor(self):
+        """Create a flavor and ensure it is listed
+        This operation requires the user to have 'admin' role"""
+
+        #Create the flavor
+        resp, flavor = self.admin_client.create_flavor(self.flavor_name,
+                                                        self.ram, self.vcpus,
+                                                        self.disk,
+                                                        self.ephemeral,
+                                                        self.new_flavor_id,
+                                                        self.swap, self.rxtx)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(flavor['name'], self.flavor_name)
+        self.assertEqual(flavor['vcpus'], self.vcpus)
+        self.assertEqual(flavor['disk'], self.disk)
+        self.assertEqual(flavor['ram'], self.ram)
+        self.assertEqual(int(flavor['id']), self.new_flavor_id)
+        self.assertEqual(flavor['swap'], self.swap)
+        self.assertEqual(flavor['rxtx_factor'], self.rxtx)
+        self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'], self.ephemeral)
+
+        #Verify flavor is retrieved
+        resp, flavor = self.admin_client.get_flavor_details(self.new_flavor_id)
+        self.assertEqual(resp.status, 200)
+        self.assertEqual(flavor['name'], self.flavor_name)
+
+        #Delete the flavor
+        resp, body = self.admin_client.delete_flavor(flavor['id'])
+        self.assertEqual(resp.status, 202)
+
+    @attr(type='positive')
+    def test_create_flavor_verify_entry_in_list_details(self):
+        """Create a flavor and ensure it's details are listed
+        This operation requires the user to have 'admin' role"""
+
+        #Create the flavor
+        resp, flavor = self.admin_client.create_flavor(self.flavor_name,
+                                                        self.ram, self.vcpus,
+                                                        self.disk,
+                                                        self.ephemeral,
+                                                        self.new_flavor_id,
+                                                        self.swap, self.rxtx)
+        flag = False
+        #Verify flavor is retrieved
+        resp, flavors = self.admin_client.list_flavors_with_detail()
+        self.assertEqual(resp.status, 200)
+        for flavor in flavors:
+            if flavor['name'] == self.flavor_name:
+                flag = True
+        self.assertTrue(flag)
+
+        #Delete the flavor
+        resp, body = self.admin_client.delete_flavor(self.new_flavor_id)
+        self.assertEqual(resp.status, 202)
+
+    @attr(type='negative')
+    def test_get_flavor_details_for_deleted_flavor(self):
+        """Delete a flavor and ensure it is not listed"""
+
+        # Create a test flavor
+        resp, flavor = self.admin_client.create_flavor(self.flavor_name,
+                                                self.ram,
+                                                self.vcpus, self.disk,
+                                                self.ephemeral,
+                                                self.new_flavor_id,
+                                                self.swap, self.rxtx)
+        self.assertEquals(200, resp.status)
+
+        # Delete the flavor
+        resp, _ = self.admin_client.delete_flavor(self.new_flavor_id)
+        self.assertEqual(resp.status, 202)
+
+        # Get deleted flavor details
+        self.assertRaises(exceptions.NotFound,
+                self.admin_client.get_flavor_details, self.new_flavor_id)
diff --git a/tempest/tests/identity/__init__.py b/tempest/tests/identity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/identity/__init__.py
diff --git a/tempest/tests/identity/test_roles.py b/tempest/tests/identity/test_roles.py
new file mode 100644
index 0000000..9f51505
--- /dev/null
+++ b/tempest/tests/identity/test_roles.py
@@ -0,0 +1,84 @@
+import unittest2 as unittest
+
+import nose
+
+from tempest import openstack
+from tempest import exceptions
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests import utils
+
+
+class RolesTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.os = openstack.AdminManager()
+        cls.client = cls.os.admin_client
+        cls.config = cls.os.config
+
+        if not cls.client.has_admin_extensions():
+            raise nose.SkipTest("Admin extensions disabled")
+
+        cls.roles = []
+        for _ in xrange(5):
+            resp, body = cls.client.create_role(rand_name('role-'))
+            cls.roles.append(body['id'])
+
+    @classmethod
+    def tearDownClass(cls):
+        for role in cls.roles:
+            cls.client.delete_role(role)
+
+    def test_list_roles(self):
+        """Return a list of all roles"""
+        resp, body = self.client.list_roles()
+        found = [role for role in body if role['id'] in self.roles]
+        self.assertTrue(any(found))
+        self.assertEqual(len(found), len(self.roles))
+
+    def test_role_create_delete(self):
+        """Role should be created, verified, and deleted"""
+        role_name = rand_name('role-test-')
+        resp, body = self.client.create_role(role_name)
+        self.assertTrue('status' in resp)
+        self.assertTrue(resp['status'].startswith('2'))
+        self.assertEqual(role_name, body['name'])
+
+        resp, body = self.client.list_roles()
+        found = [role for role in body if role['name'] == role_name]
+        self.assertTrue(any(found))
+
+        resp, body = self.client.delete_role(found[0]['id'])
+        self.assertTrue('status' in resp)
+        self.assertTrue(resp['status'].startswith('2'))
+
+        resp, body = self.client.list_roles()
+        found = [role for role in body if role['name'] == role_name]
+        self.assertFalse(any(found))
+
+    def test_role_create_blank_name(self):
+        """Should not be able to create a role with a blank name"""
+        try:
+            resp, body = self.client.create_role('')
+        except exceptions.Duplicate:
+            self.fail('A role with a blank name already exists.')
+        self.assertTrue('status' in resp)
+        self.assertFalse(resp['status'].startswith('2'), 'Create role with '
+                         'empty name should fail')
+
+    def test_role_create_duplicate(self):
+        """Role names should be unique"""
+        role_name = rand_name('role-dup-')
+        resp, body = self.client.create_role(role_name)
+        role1_id = body.get('id')
+        self.assertTrue('status' in resp)
+        self.assertTrue(resp['status'].startswith('2'))
+
+        try:
+            resp, body = self.client.create_role(role_name)
+            # this should raise an exception
+            self.fail('Should not be able to create a duplicate role name.'
+                      ' %s' % role_name)
+        except exceptions.Duplicate:
+            pass
+        self.client.delete_role(role1_id)
diff --git a/tempest/tests/test_authorization.py b/tempest/tests/test_authorization.py
index 9f3c34d..e66446d 100644
--- a/tempest/tests/test_authorization.py
+++ b/tempest/tests/test_authorization.py
@@ -4,9 +4,7 @@
 from nose.tools import raises
 
 from tempest import openstack
-from tempest.services.nova.json.images_client import ImagesClient
-from tempest.services.nova.json.servers_client import ServersClient
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.data_utils import rand_name, parse_image_id
 from tempest import exceptions
 from tempest.tests import utils
 
@@ -53,7 +51,7 @@
 
                 name = rand_name('image')
                 resp, body = cls.client.create_image(server['id'], name)
-                image_id = cls._parse_image_id(resp['location'])
+                image_id = parse_image_id(resp['location'])
                 cls.images_client.wait_for_image_resp_code(image_id, 200)
                 cls.images_client.wait_for_image_status(image_id, 'ACTIVE')
                 resp, cls.image = cls.images_client.get_image(image_id)
@@ -162,9 +160,3 @@
         finally:
             # Reset the base_url...
             self.other_client.base_url = saved_base_url
-
-    @classmethod
-    def _parse_image_id(self, image_ref):
-        temp = image_ref.rsplit('/')
-        #Return the last item, which is the image id
-        return temp[len(temp) - 1]
diff --git a/tempest/tests/test_flavors.py b/tempest/tests/test_flavors.py
index 34aa68c..d5d598f 100644
--- a/tempest/tests/test_flavors.py
+++ b/tempest/tests/test_flavors.py
@@ -1,8 +1,5 @@
-import unittest2 as unittest
 from nose.plugins.attrib import attr
 from tempest import exceptions
-from tempest import openstack
-import tempest.config
 from base_compute_test import BaseComputeTest
 
 
@@ -11,13 +8,12 @@
     @classmethod
     def setUpClass(cls):
         cls.client = cls.flavors_client
-        cls.flavor_id = cls.flavor_ref
 
     @attr(type='smoke')
     def test_list_flavors(self):
         """List of all flavors should contain the expected flavor"""
         resp, flavors = self.client.list_flavors()
-        resp, flavor = self.client.get_flavor_details(self.flavor_id)
+        resp, flavor = self.client.get_flavor_details(self.flavor_ref)
         flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'],
                              'name': flavor['name']}
         self.assertTrue(flavor_min_detail in flavors)
@@ -26,14 +22,14 @@
     def test_list_flavors_with_detail(self):
         """Detailed list of all flavors should contain the expected flavor"""
         resp, flavors = self.client.list_flavors_with_detail()
-        resp, flavor = self.client.get_flavor_details(self.flavor_id)
+        resp, flavor = self.client.get_flavor_details(self.flavor_ref)
         self.assertTrue(flavor in flavors)
 
     @attr(type='smoke')
     def test_get_flavor(self):
         """The expected flavor details should be returned"""
-        resp, flavor = self.client.get_flavor_details(self.flavor_id)
-        self.assertEqual(self.flavor_id, str(flavor['id']))
+        resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+        self.assertEqual(self.flavor_ref, str(flavor['id']))
 
     @attr(type='negative')
     def test_get_non_existant_flavor(self):
@@ -120,3 +116,10 @@
         params = {'minRam': flavors[1]['ram']}
         resp, flavors = self.client.list_flavors(params)
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+
+    @attr(type='negative')
+    def test_get_flavor_details_for_invalid_flavor_id(self):
+        """Ensure 404 returned for non-existant flavor ID"""
+
+        self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
+                        9999)
diff --git a/tempest/tests/test_floating_ips_actions.py b/tempest/tests/test_floating_ips_actions.py
index a9222a4..563d0e3 100644
--- a/tempest/tests/test_floating_ips_actions.py
+++ b/tempest/tests/test_floating_ips_actions.py
@@ -48,16 +48,18 @@
         Positive test:Allocation of a new floating IP to a project
         should be succesfull
         """
-        resp, body = self.client.create_floating_ip()
-        self.assertEqual(200, resp.status)
-        floating_ip_id_allocated = body['id']
-        resp, floating_ip_details = \
+        try:
+            resp, body = self.client.create_floating_ip()
+            self.assertEqual(200, resp.status)
+            floating_ip_id_allocated = body['id']
+            resp, floating_ip_details = \
                 self.client.get_floating_ip_details(floating_ip_id_allocated)
-        #Checking if the details of allocated IP is in list of floating IP
-        resp, body = self.client.list_floating_ips()
-        self.assertTrue(floating_ip_details in body)
-        #Deleting the floating IP which is created in this method
-        self.client.delete_floating_ip(floating_ip_id_allocated)
+            #Checking if the details of allocated IP is in list of floating IP
+            resp, body = self.client.list_floating_ips()
+            self.assertTrue(floating_ip_details in body)
+        finally:
+            #Deleting the floating IP which is created in this method
+            self.client.delete_floating_ip(floating_ip_id_allocated)
 
     @attr(type='positive')
     def test_delete_floating_ip(self):
diff --git a/tempest/tests/test_images.py b/tempest/tests/test_images.py
index 716da65..33020d6 100644
--- a/tempest/tests/test_images.py
+++ b/tempest/tests/test_images.py
@@ -5,11 +5,7 @@
 from base_compute_test import BaseComputeTest
 import tempest.config
 from tempest import openstack
-
-
-def _parse_image_id(image_ref):
-    temp = image_ref.rsplit('images/')
-    return temp[1]
+from tempest.common.utils import data_utils
 
 
 class ImagesTest(BaseComputeTest):
@@ -38,7 +34,7 @@
         name = rand_name('image')
         meta = {'image_type': 'test'}
         resp, body = self.client.create_image(server['id'], name, meta)
-        image_id = _parse_image_id(resp['location'])
+        image_id = data_utils.parse_image_id(resp['location'])
         self.client.wait_for_image_resp_code(image_id, 200)
         self.client.wait_for_image_status(image_id, 'ACTIVE')
 
@@ -78,7 +74,7 @@
             pass
 
         else:
-            image_id = _parse_image_id(resp['location'])
+            image_id = data_utils.parse_image_id(resp['location'])
             self.client.wait_for_image_resp_code(image_id, 200)
             self.client.wait_for_image_status(image_id, 'ACTIVE')
             self.client.delete_image(image_id)
diff --git a/tempest/tests/test_list_images.py b/tempest/tests/test_list_images.py
index 9d2bb1b..810e933 100644
--- a/tempest/tests/test_list_images.py
+++ b/tempest/tests/test_list_images.py
@@ -1,14 +1,8 @@
 from nose.plugins.attrib import attr
 
 from tempest import exceptions
-from tempest import openstack
 from base_compute_test import BaseComputeTest
-from tempest.common.utils.data_utils import rand_name
-
-
-def _parse_image_id(image_ref):
-    temp = image_ref.rsplit('/')
-    return temp[len(temp) - 1]
+from tempest.common.utils.data_utils import rand_name, parse_image_id
 
 
 class ListImagesTest(BaseComputeTest):
@@ -31,7 +25,7 @@
         # Create images to be used in the filter tests
         image1_name = rand_name('image')
         resp, body = cls.client.create_image(cls.server1['id'], image1_name)
-        cls.image1_id = _parse_image_id(resp['location'])
+        cls.image1_id = parse_image_id(resp['location'])
         cls.client.wait_for_image_resp_code(cls.image1_id, 200)
         cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
         resp, cls.image1 = cls.client.get_image(cls.image1_id)
@@ -41,14 +35,14 @@
         # server will sometimes cause failures
         image3_name = rand_name('image')
         resp, body = cls.client.create_image(cls.server2['id'], image3_name)
-        cls.image3_id = _parse_image_id(resp['location'])
+        cls.image3_id = parse_image_id(resp['location'])
         cls.client.wait_for_image_resp_code(cls.image3_id, 200)
         cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
         resp, cls.image3 = cls.client.get_image(cls.image3_id)
 
         image2_name = rand_name('image')
         resp, body = cls.client.create_image(cls.server1['id'], image2_name)
-        cls.image2_id = _parse_image_id(resp['location'])
+        cls.image2_id = parse_image_id(resp['location'])
         cls.client.wait_for_image_resp_code(cls.image2_id, 200)
         cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
         resp, cls.image2 = cls.client.get_image(cls.image2_id)
diff --git a/tempest/tests/test_servers.py b/tempest/tests/test_servers.py
index 414d58a..b152b4c 100644
--- a/tempest/tests/test_servers.py
+++ b/tempest/tests/test_servers.py
@@ -54,58 +54,65 @@
         password should be set to that password.
         """
 
-        name = rand_name('server')
-        resp, server = self.client.create_server(name, self.image_ref,
+        try:
+            name = rand_name('server')
+            resp, server = self.client.create_server(name, self.image_ref,
                                                  self.flavor_ref,
                                                  adminPass='testpassword')
 
-        #Verify the password is set correctly in the response
-        self.assertEqual('testpassword', server['adminPass'])
+            #Verify the password is set correctly in the response
+            self.assertEqual('testpassword', server['adminPass'])
 
         #Teardown
-        self.client.delete_server(server['id'])
+        finally:
+            self.client.delete_server(server['id'])
 
     @attr(type='smoke')
     def test_update_server_name(self):
         """The server name should be changed to the the provided value"""
-        name = rand_name('server')
-        resp, server = self.client.create_server(name, self.image_ref,
+        try:
+            name = rand_name('server')
+            resp, server = self.client.create_server(name, self.image_ref,
                                                  self.flavor_ref)
-        self.client.wait_for_server_status(server['id'], 'ACTIVE')
+            self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-        #Update the server with a new name
-        resp, server = self.client.update_server(server['id'], name='newname')
-        self.assertEquals(200, resp.status)
-        self.client.wait_for_server_status(server['id'], 'ACTIVE')
+            #Update the server with a new name
+            resp, server = self.client.update_server(server['id'],
+                                                 name='newname')
+            self.assertEquals(200, resp.status)
+            self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-        #Verify the name of the server has changed
-        resp, server = self.client.get_server(server['id'])
-        self.assertEqual('newname', server['name'])
+            #Verify the name of the server has changed
+            resp, server = self.client.get_server(server['id'])
+            self.assertEqual('newname', server['name'])
 
         #Teardown
-        self.client.delete_server(server['id'])
+        finally:
+            self.client.delete_server(server['id'])
 
     @attr(type='smoke')
     def test_update_access_server_address(self):
         """
         The server's access addresses should reflect the provided values
         """
-        name = rand_name('server')
-        resp, server = self.client.create_server(name, self.image_ref,
+        try:
+            name = rand_name('server')
+            resp, server = self.client.create_server(name, self.image_ref,
                                                  self.flavor_ref)
-        self.client.wait_for_server_status(server['id'], 'ACTIVE')
+            self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-        #Update the IPv4 and IPv6 access addresses
-        resp, body = self.client.update_server(server['id'],
+            #Update the IPv4 and IPv6 access addresses
+            resp, body = self.client.update_server(server['id'],
                                                accessIPv4='1.1.1.1',
                                                accessIPv6='::babe:2.2.2.2')
-        self.assertEqual(200, resp.status)
-        self.client.wait_for_server_status(server['id'], 'ACTIVE')
+            self.assertEqual(200, resp.status)
+            self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-        #Verify the access addresses have been updated
-        resp, server = self.client.get_server(server['id'])
-        self.assertEqual('1.1.1.1', server['accessIPv4'])
-        self.assertEqual('::babe:2.2.2.2', server['accessIPv6'])
+            #Verify the access addresses have been updated
+            resp, server = self.client.get_server(server['id'])
+            self.assertEqual('1.1.1.1', server['accessIPv4'])
+            self.assertEqual('::babe:2.2.2.2', server['accessIPv6'])
 
         #Teardown
-        self.client.delete_server(server['id'])
+        finally:
+            self.client.delete_server(server['id'])
diff --git a/tempest/tests/test_volumes_list.py b/tempest/tests/test_volumes_list.py
index 01ce394..0a2e4e6 100644
--- a/tempest/tests/test_volumes_list.py
+++ b/tempest/tests/test_volumes_list.py
@@ -1,43 +1,87 @@
-from nose.plugins.attrib import attr
+# 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 unittest2 as unittest
+
+import nose
+
 from tempest import openstack
 from tempest.common.utils.data_utils import rand_name
 
 
 class VolumesTest(unittest.TestCase):
 
+    """
+    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
+    """
+
     @classmethod
     def setUpClass(cls):
         cls.os = openstack.Manager()
         cls.client = cls.os.volumes_client
-        #Create 3 Volumes
+        # Create 3 Volumes
         cls.volume_list = list()
         cls.volume_id_list = list()
         for i in range(3):
-            v_name = rand_name('Name-')
+            v_name = rand_name('volume')
             metadata = {'Type': 'work'}
-            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'])
+            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_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 i in range(3):
-            resp, _ = cls.client.delete_volume(cls.volume_id_list[i])
+        # Delete the created Volumes
+        for volume in cls.volume_list:
+            resp, _ = cls.client.delete_volume(volume['id'])
 
-    @attr(type='smoke')
     def test_volume_list(self):
         """Should return the list of Volumes"""
-        #Fetch all 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
+        # Now check if all the Volumes created in setup are in fetched list
         missing_volumes =\
         [v for v in self.volume_list if v not in fetched_list]
         self.assertFalse(missing_volumes,
@@ -45,7 +89,6 @@
                          % ', '.join(m_vol['displayName']
                                         for m_vol in missing_volumes))
 
-    @attr(type='smoke')
     def test_volume_list_with_details(self):
         """Should return the list of Volumes with details"""
         #Fetch all Volumes