Merge "List Domains Test Case-V3"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 3147859..282e455 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -12,6 +12,8 @@
 disable_ssl_certificate_validation = False
 # URL for where to find the OpenStack Identity API endpoint (Keystone)
 uri = http://127.0.0.1:5000/v2.0/
+# URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
+uri_v3 = http://127.0.0.1:5000/v3/
 # Should typically be left as keystone unless you have a non-Keystone
 # authentication API service
 strategy = keystone
@@ -63,6 +65,10 @@
 flavor_ref = 1
 flavor_ref_alt = 2
 
+# User names used to authenticate to an instance for a given image.
+image_ssh_user = root
+image_alt_ssh_user = root
+
 # Number of seconds to wait while looping to check the status of an
 # instance that is building.
 build_interval = 10
@@ -312,3 +318,19 @@
 # Name of existing keypair to launch servers with. The default is not to specify
 # any key, which will generate a keypair for each test class
 #keypair_name = heat_key
+
+[scenario]
+# Directory containing image files
+img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec
+
+# AMI image file name
+ami_img_file = cirros-0.3.1-x86_64-blank.img
+
+# ARI image file name
+ari_img_file = cirros-0.3.1-x86_64-initrd
+
+# AKI image file name
+aki_img_file = cirros-0.3.1-x86_64-vmlinuz
+
+# ssh username for the image file
+ssh_user = cirros
diff --git a/tools/pip-requires b/requirements.txt
similarity index 100%
rename from tools/pip-requires
rename to requirements.txt
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index acdfb74..b66bd7e 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -43,7 +43,7 @@
                     filter(lambda y: y['service'] == 'compute', hosts_all))
         cls.host = hosts[0]
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_create_delete(self):
         # Create and delete an aggregate.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -56,7 +56,7 @@
         self.assertEquals(200, resp.status)
         self.client.wait_for_resource_deletion(aggregate['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_create_delete_with_az(self):
         # Create and delete an aggregate.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -70,7 +70,7 @@
         self.assertEquals(200, resp.status)
         self.client.wait_for_resource_deletion(aggregate['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_create_verify_entry_in_list(self):
         # Create an aggregate and ensure it is listed.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -83,7 +83,7 @@
                       map(lambda x: (x['id'], x['availability_zone']),
                           aggregates))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_create_get_details(self):
         # Create an aggregate and ensure its details are returned.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -144,7 +144,7 @@
         self.assertRaises(exceptions.NotFound,
                           self.client.get_aggregate, -1)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_add_remove_host(self):
         # Add an host to the given aggregate and remove.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -165,7 +165,7 @@
                           body['availability_zone'])
         self.assertNotIn(self.host, body['hosts'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_add_host_list(self):
         # Add an host to the given aggregate and list.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -182,7 +182,7 @@
         self.assertEquals(None, agg['availability_zone'])
         self.assertIn(self.host, agg['hosts'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_add_host_get_details(self):
         # Add an host to the given aggregate and get details.
         aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -196,7 +196,7 @@
         self.assertEquals(None, body['availability_zone'])
         self.assertIn(self.host, body['hosts'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_aggregate_add_host_create_server_with_az(self):
         # Add an host to the given aggregate and create a server.
         aggregate_name = rand_name(self.aggregate_name_prefix)
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index ab2f2d9..8a56b89 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -34,14 +34,14 @@
         cls.client = cls.os_adm.availability_zone_client
         cls.non_adm_client = cls.availability_zone_client
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_availability_zone_list(self):
         # List of availability zone
         resp, availability_zone = self.client.get_availability_zone_list()
         self.assertEqual(200, resp.status)
         self.assertTrue(len(availability_zone) > 0)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_availability_zone_list_detail(self):
         # List of availability zones and available services
         resp, availability_zone = \
@@ -49,7 +49,7 @@
         self.assertEqual(200, resp.status)
         self.assertTrue(len(availability_zone) > 0)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_availability_zone_list_with_non_admin_user(self):
         # List of availability zone with non admin user
         resp, availability_zone = \
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 8424709..f201cf7 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -51,7 +51,7 @@
 class FixedIPsTestJson(FixedIPsBase):
     _interface = 'json'
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_fixed_ip_details(self):
         resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
         self.assertEqual(fixed_ip['address'], self.ip)
@@ -61,13 +61,13 @@
         self.assertRaises(exceptions.Unauthorized,
                           self.non_admin_client.get_fixed_ip_details, self.ip)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_set_reserve(self):
         body = {"reserve": "None"}
         resp, body = self.client.reserve_fixed_ip(self.ip, body)
         self.assertEqual(resp.status, 202)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_set_unreserve(self):
         body = {"unreserve": "None"}
         resp, body = self.client.reserve_fixed_ip(self.ip, body)
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 47097ed..6db20f9 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -53,7 +53,7 @@
         self.assertEqual(resp.status, 202)
         self.client.wait_for_resource_deletion(flavor_id)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_create_flavor(self):
         # Create a flavor and ensure it is listed
         # This operation requires the user to have 'admin' role
@@ -92,7 +92,7 @@
         self.assertEqual(resp.status, 200)
         self.assertEqual(flavor['name'], flavor_name)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     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
@@ -193,7 +193,7 @@
                 flag = True
         self.assertTrue(flag)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_flavor_not_public_verify_entry_not_in_list_details(self):
         #Create a flavor with os-flavor-access:is_public false should not
         #be present in list_details.
@@ -241,7 +241,7 @@
                 flag = True
         self.assertTrue(flag)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_is_public_string_variations(self):
         flavor_id_not_public = rand_int_id(start=1000)
         flavor_name_not_public = rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index f4b969d..63d5025 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -50,7 +50,7 @@
         cls.vcpus = 1
         cls.disk = 10
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_flavor_access_add_remove(self):
         #Test to add and remove flavor access to a given tenant.
         flavor_name = rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index db7928a..78dac21 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -34,7 +34,7 @@
         cls.client = cls.os_adm.services_client
         cls.non_admin_client = cls.services_client
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_services(self):
         # List Compute services
         resp, services = self.client.list_services()
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index 9c4c4b9..ce05899 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -51,7 +51,7 @@
         # Returns formatted datetime
         return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
 
-    @attr('positive')
+    @attr(type='gate')
     def test_list_usage_all_tenants(self):
         # Get usage for all tenants
         params = {'start': self.start,
@@ -61,7 +61,7 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(len(tenant_usage), 8)
 
-    @attr('positive')
+    @attr(type='gate')
     def test_get_usage_tenant(self):
         # Get usage for a specific tenant
         params = {'start': self.start,
@@ -72,7 +72,7 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(len(tenant_usage), 8)
 
-    @attr('positive')
+    @attr(type='gate')
     def test_get_usage_tenant_with_non_admin_user(self):
         # Get usage for a specific tenant with non admin user
         params = {'start': self.start,
@@ -83,7 +83,7 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(len(tenant_usage), 8)
 
-    @attr('negative')
+    @attr(type=['negative', 'gate'])
     def test_get_usage_tenant_with_empty_tenant_id(self):
         # Get usage for a specific tenant empty
         params = {'start': self.start,
@@ -92,7 +92,7 @@
                           self.adm_client.get_tenant_usage,
                           '', params)
 
-    @attr('negative')
+    @attr(type=['negative', 'gate'])
     def test_get_usage_tenant_with_invalid_date(self):
         # Get usage for tenant with invalid date
         params = {'start': self.end,
@@ -101,7 +101,7 @@
                           self.adm_client.get_tenant_usage,
                           self.tenant_id, params)
 
-    @attr('negative')
+    @attr(type=['negative', 'gate'])
     def test_list_usage_all_tenants_with_non_admin_user(self):
         # Get usage for all tenants with non admin user
         params = {'start': self.start,
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index b19b9d9..9883c00 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -73,6 +73,8 @@
         cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt
         cls.servers = []
 
+        cls.servers_client_v3_auth = os.servers_client_v3_auth
+
     @classmethod
     def _get_identity_admin_client(cls):
         """
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index a60cbe9..27526eb 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -56,21 +56,21 @@
         self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
                           999)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_limit_results(self):
         # Only the expected number of flavors should be returned
         params = {'limit': 1}
         resp, flavors = self.client.list_flavors(params)
         self.assertEqual(1, len(flavors))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_detailed_limit_results(self):
         # Only the expected number of flavors (detailed) should be returned
         params = {'limit': 1}
         resp, flavors = self.client.list_flavors_with_detail(params)
         self.assertEqual(1, len(flavors))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_using_marker(self):
         # The list of flavors should start from the provided marker
         resp, flavors = self.client.list_flavors()
@@ -81,7 +81,7 @@
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
                          'The list of flavors did not start after the marker.')
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_detailed_using_marker(self):
         # The list of flavors should start from the provided marker
         resp, flavors = self.client.list_flavors_with_detail()
@@ -92,7 +92,7 @@
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
                          'The list of flavors did not start after the marker.')
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_detailed_filter_by_min_disk(self):
         # The detailed list of flavors should be filtered by disk space
         resp, flavors = self.client.list_flavors_with_detail()
@@ -103,7 +103,7 @@
         resp, flavors = self.client.list_flavors_with_detail(params)
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_detailed_filter_by_min_ram(self):
         # The detailed list of flavors should be filtered by RAM
         resp, flavors = self.client.list_flavors_with_detail()
@@ -114,7 +114,7 @@
         resp, flavors = self.client.list_flavors_with_detail(params)
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_filter_by_min_disk(self):
         # The list of flavors should be filtered by disk space
         resp, flavors = self.client.list_flavors_with_detail()
@@ -125,7 +125,7 @@
         resp, flavors = self.client.list_flavors(params)
         self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_flavors_filter_by_min_ram(self):
         # The list of flavors should be filtered by RAM
         resp, flavors = self.client.list_flavors_with_detail()
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index c2e6bd0..0d7f26d 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -56,7 +56,7 @@
         resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
         super(FloatingIPsTestJSON, cls).tearDownClass()
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_allocate_floating_ip(self):
         # Positive test:Allocation of a new floating IP to a project
         # should be successful
@@ -81,7 +81,7 @@
                           self.client.create_floating_ip,
                           "non_exist_pool")
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_delete_floating_ip(self):
         # Positive test:Deletion of valid floating IP from project
         # should be successful
@@ -96,7 +96,7 @@
         # Check it was really deleted.
         self.client.wait_for_resource_deletion(floating_ip_body['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_associate_disassociate_floating_ip(self):
         # Positive test:Associate and disassociate the provided floating IP
         # to a specific server should be successful
@@ -138,7 +138,7 @@
                           self.client.disassociate_floating_ip_from_server,
                           "0.0.0.0", self.server_id)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_associate_already_associated_floating_ip(self):
         # positive test:Association of an already associated floating IP
         # to specific server should change the association of the Floating IP
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index d77b0a5..3e1aa82 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -42,7 +42,7 @@
             cls.client.delete_floating_ip(cls.floating_ip_id[i])
         super(FloatingIPDetailsTestJSON, cls).tearDownClass()
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_floating_ips(self):
         # Positive test:Should return the list of floating IPs
         resp, body = self.client.list_floating_ips()
@@ -53,7 +53,7 @@
         for i in range(3):
             self.assertTrue(self.floating_ip[i] in floating_ips)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_floating_ip_details(self):
         # Positive test:Should be able to GET the details of floatingIP
         #Creating a floating IP for which details are to be checked
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 471a75a..9db28ad 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -84,7 +84,7 @@
         self.assertRaises(exceptions.NotFound, self.client.get_image,
                           "nonexistingimageid")
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_status(self):
         # The list of images should contain only images with the
         # provided status
@@ -95,7 +95,7 @@
         self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
         self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_name(self):
         # List of all images should contain the expected images filtered
         # by name
@@ -106,7 +106,7 @@
         self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
         self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_server_id(self):
         # The images should contain images filtered by server id
         params = {'server': self.server1['id']}
@@ -118,7 +118,7 @@
         self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
         self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_server_ref(self):
         # The list of servers should be filtered by server ref
         server_links = self.server2['links']
@@ -135,7 +135,7 @@
             self.assertTrue(any([i for i in images
                                  if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_type(self):
         # The list of servers should be filtered by image type
         params = {'type': 'snapshot'}
@@ -146,7 +146,7 @@
         self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
         self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_limit_results(self):
         # Verify only the expected number of results are returned
         params = {'limit': '1'}
@@ -155,7 +155,7 @@
         #ref: Question #224349
         self.assertEqual(1, len([x for x in images if 'id' in x]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_filter_by_changes_since(self):
         # Verify only updated images are returned in the detailed list
 
@@ -166,7 +166,7 @@
         found = any([i for i in images if i['id'] == self.image3_id])
         self.assertTrue(found)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_filter_by_status(self):
         # Detailed list of all images should only contain images
         # with the provided status
@@ -177,7 +177,7 @@
         self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
         self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_filter_by_name(self):
         # Detailed list of all images should contain the expected
         # images filtered by name
@@ -188,7 +188,7 @@
         self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
         self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_limit_results(self):
         # Verify only the expected number of results (with full details)
         # are returned
@@ -196,7 +196,7 @@
         resp, images = self.client.list_images_with_detail(params)
         self.assertEqual(1, len(images))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_filter_by_server_ref(self):
         # Detailed list of servers should be filtered by server ref
         server_links = self.server2['links']
@@ -213,7 +213,7 @@
             self.assertTrue(any([i for i in images
                                  if i['id'] == self.image3_id]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_filter_by_type(self):
         # The detailed list of servers should be filtered by image type
         params = {'type': 'snapshot'}
@@ -225,7 +225,7 @@
         self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
         self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_images_with_detail_filter_by_changes_since(self):
         # Verify an update image is returned
 
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 4c0398e..6abca3f 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -29,7 +29,7 @@
         super(KeyPairsTestJSON, cls).setUpClass()
         cls.client = cls.keypairs_client
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_keypairs_create_list_delete(self):
         # Keypairs created should be available in the response list
         #Create 3 keypairs
@@ -63,7 +63,7 @@
             resp, _ = self.client.delete_keypair(keypair['name'])
             self.assertEqual(202, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_keypair_create_delete(self):
         # Keypair should be created, verified and deleted
         k_name = rand_name('keypair-')
@@ -79,7 +79,7 @@
         resp, _ = self.client.delete_keypair(k_name)
         self.assertEqual(202, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_keypair_detail(self):
         # Keypair should be created, Got details by name and deleted
         k_name = rand_name('keypair-')
@@ -102,7 +102,7 @@
             resp, _ = self.client.delete_keypair(k_name)
             self.assertEqual(202, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_keypair_create_with_pub_key(self):
         # Keypair should be created with a given public key
         k_name = rand_name('keypair-')
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 15af0f2..6a32b64 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -29,7 +29,7 @@
         super(SecurityGroupRulesTestJSON, cls).setUpClass()
         cls.client = cls.security_groups_client
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_group_rules_create(self):
         # Positive test: Creation of Security Group rule
         # should be successfull
@@ -52,7 +52,7 @@
         self.addCleanup(self.client.delete_security_group_rule, rule['id'])
         self.assertEqual(200, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_group_rules_create_with_optional_arguments(self):
         # Positive test: Creation of Security Group rule
         # with optional arguments
@@ -189,7 +189,7 @@
                           self.client.delete_security_group_rule,
                           rand_name('999'))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_group_rules_list(self):
         # Positive test: Created Security Group rules should be
         # in the list of all rules
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 5f3a37e..f960ca4 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -33,7 +33,7 @@
         resp, _ = self.client.delete_security_group(securitygroup_id)
         self.assertEqual(202, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_groups_create_list_delete(self):
         # Positive test:Should return the list of Security Groups
         #Create 3 Security Groups
@@ -61,7 +61,7 @@
 
     #TODO(afazekas): scheduled for delete,
     #test_security_group_create_get_delete covers it
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_group_create_delete(self):
         # Security Group should be created, verified and deleted
         s_name = rand_name('securitygroup-')
@@ -80,7 +80,7 @@
                          "The created Security Group name is "
                          "not equal to the requested name")
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_security_group_create_get_delete(self):
         # Security Group should be created, fetched and deleted
         s_name = rand_name('securitygroup-')
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index f605485..e5fee4d 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -92,14 +92,14 @@
         self.assertTrue(found)
 
     @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_can_log_into_created_server(self):
         # Check that the user can authenticate with the generated password
         linux_client = RemoteClient(self.server, self.ssh_user, self.password)
         self.assertTrue(linux_client.can_authenticate())
 
     @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_verify_created_server_vcpus(self):
         # Verify that the number of vcpus reported by the instance matches
         # the amount stated by the flavor
@@ -108,7 +108,7 @@
         self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
 
     @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_host_name_is_same_as_server_name(self):
         # Verify the instance host name is the same as the server name
         linux_client = RemoteClient(self.server, self.ssh_user, self.password)
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index a0ed009..e9385b5 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -33,7 +33,7 @@
         super(ServerDiskConfigTestJSON, cls).setUpClass()
         cls.client = cls.os.servers_client
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_rebuild_server_with_manual_disk_config(self):
         # A server should be rebuilt using the manual disk config option
         resp, server = self.create_server(disk_config='AUTO',
@@ -57,7 +57,7 @@
         #Delete the server
         resp, body = self.client.delete_server(server['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_rebuild_server_with_auto_disk_config(self):
         # A server should be rebuilt using the auto disk config option
         resp, server = self.create_server(disk_config='MANUAL',
@@ -82,7 +82,7 @@
         resp, body = self.client.delete_server(server['id'])
 
     @testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_resize_server_from_manual_to_auto(self):
         # A server should be resized from manual to auto disk config
         resp, server = self.create_server(disk_config='MANUAL',
@@ -102,7 +102,7 @@
         resp, body = self.client.delete_server(server['id'])
 
     @testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_resize_server_from_auto_to_manual(self):
         # A server should be resized from auto to manual disk config
         resp, server = self.create_server(disk_config='AUTO',
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index e5da0a2..f13e51e 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -31,7 +31,7 @@
         cls.request_id = resp['x-compute-request-id']
         cls.server_id = server['id']
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_instance_actions(self):
         # List actions of the provided server
         resp, body = self.client.reboot(self.server_id, 'HARD')
@@ -43,7 +43,7 @@
         self.assertTrue(any([i for i in body if i['action'] == 'create']))
         self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_instance_action(self):
         # Get the action details of the provided server
         resp, body = self.client.get_instance_action(self.server_id,
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index dbebf5e..31b44f7 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -84,7 +84,7 @@
         super(ListServerFiltersTestJSON, cls).tearDownClass()
 
     @utils.skip_unless_attr('multiple_images', 'Only one image found')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filter_by_image(self):
         # Filter the list of servers by image
         params = {'image': self.image_ref}
@@ -95,7 +95,7 @@
         self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filter_by_flavor(self):
         # Filter the list of servers by flavor
         params = {'flavor': self.flavor_ref_alt}
@@ -106,7 +106,7 @@
         self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filter_by_server_name(self):
         # Filter the list of servers by server name
         params = {'name': self.s1_name}
@@ -117,7 +117,7 @@
         self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
         self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filter_by_server_status(self):
         # Filter the list of servers by server status
         params = {'status': 'active'}
@@ -128,7 +128,7 @@
         self.assertIn(self.s2['id'], map(lambda x: x['id'], servers))
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filter_by_limit(self):
         # Verify only the expected number of servers are returned
         params = {'limit': 1}
@@ -137,7 +137,7 @@
         self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
 
     @utils.skip_unless_attr('multiple_images', 'Only one image found')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_detailed_filter_by_image(self):
         # Filter the detailed list of servers by image
         params = {'image': self.image_ref}
@@ -148,7 +148,7 @@
         self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_detailed_filter_by_flavor(self):
         # Filter the detailed list of servers by flavor
         params = {'flavor': self.flavor_ref_alt}
@@ -159,7 +159,7 @@
         self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_detailed_filter_by_server_name(self):
         # Filter the detailed list of servers by server name
         params = {'name': self.s1_name}
@@ -170,7 +170,7 @@
         self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
         self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_detailed_filter_by_server_status(self):
         # Filter the detailed list of servers by server status
         params = {'status': 'active'}
@@ -182,7 +182,7 @@
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
         self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filtered_by_name_wildcard(self):
         # List all servers that contains 'server' in name
         params = {'name': 'server'}
@@ -205,7 +205,7 @@
         self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
 
     @testtools.skip('Until Bug #1170718 is resolved.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filtered_by_ip(self):
         # Filter servers by ip
         # Here should be listed 1 server
@@ -218,7 +218,7 @@
         self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
         self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_filtered_by_ip_regex(self):
         # Filter servers by regex ip
         # List all servers filtered by part of ip address.
@@ -232,7 +232,7 @@
         self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
         self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_servers_detailed_limit_results(self):
         # Verify only the expected number of detailed results are returned
         params = {'limit': 1}
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index a705dd3..63bb86d 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -60,7 +60,7 @@
 
         return resp, body
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_multiple_create(self):
         resp, body = self._create_multiple_servers(wait_until='ACTIVE',
                                                    min_count=1,
@@ -103,7 +103,7 @@
                           min_count=min_count,
                           max_count=max_count)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_multiple_create_with_reservation_return(self):
         resp, body = self._create_multiple_servers(wait_until='ACTIVE',
                                                    min_count=1,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 228dc45..9f97f4f 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -167,7 +167,7 @@
         self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
 
     @testtools.skipIf(not resize_available, 'Resize not available.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_resize_server_revert(self):
         # The server's RAM and disk space should return to its original
         # values after a resize is reverted
@@ -217,7 +217,7 @@
                           personality=personality,
                           adminPass='rebuild')
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_console_output(self):
         # Positive test:Should be able to GET the console output
         # for a given server_id and number of lines
@@ -239,7 +239,7 @@
                           '!@#$%^&*()', 10)
 
     @testtools.skip('Until tempest Bug #1014683 is fixed.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_console_output_server_id_in_reboot_status(self):
         # Positive test:Should be able to GET the console output
         # for a given server_id in reboot status
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index 4744bf5..a3ec423 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -46,7 +46,7 @@
         self.assertRaises(exceptions.OverLimit, self.create_server,
                           personality=personality)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_can_create_server_with_max_number_personality_files(self):
         # Server should be created successfully if maximum allowed number of
         # files is injected into the server during creation.
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 7e0ac85..8225a4c 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -169,7 +169,7 @@
                           self.server_id,
                           self.volume_to_detach['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_rescued_vm_associate_dissociate_floating_ip(self):
         # Rescue the server
         self.servers_client.rescue_server(
@@ -189,7 +189,7 @@
                                                         self.server_id)
         self.assertEqual(202, resp.status)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_rescued_vm_add_remove_security_group(self):
         # Rescue the server
         self.servers_client.rescue_server(
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 57baae6..3ff2538 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -32,7 +32,7 @@
         self.clear_servers()
         super(ServersTestJSON, self).tearDown()
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_create_server_with_admin_password(self):
         # If an admin password is provided on server creation, the server's
         # root password should be set to that password.
@@ -60,7 +60,7 @@
         name2 = server['name']
         self.assertEqual(name1, name2)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_create_specify_keypair(self):
         # Specify a keypair while creating a server
 
@@ -73,7 +73,7 @@
         resp, server = self.client.get_server(server['id'])
         self.assertEqual(key_name, server['key_name'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_update_server_name(self):
         # The server name should be changed to the the provided value
         resp, server = self.create_server(wait_until='ACTIVE')
@@ -88,7 +88,7 @@
         resp, server = self.client.get_server(server['id'])
         self.assertEqual('newname', server['name'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_update_access_server_address(self):
         # The server's access addresses should reflect the provided values
         resp, server = self.create_server(wait_until='ACTIVE')
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 47d6169..3119643 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -33,7 +33,7 @@
         resp, server = cls.create_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_virtual_interfaces(self):
         # Positive test:Should be able to GET the virtual interfaces list
         # for a given server_id
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 4893e3d..291c8e4 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -23,7 +23,7 @@
 class ExtensionsTestJSON(base.BaseComputeTest):
     _interface = 'json'
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_extensions(self):
         # List of all extensions
         resp, extensions = self.extensions_client.list_extensions()
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 5d45251..b507e03 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -70,7 +70,7 @@
         self.attached = True
 
     @testtools.skipIf(not run_ssh, 'SSH required for this test')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
         # the volume remains attached.
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index 7d3c075..a6302e6 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -75,7 +75,7 @@
                 #Checking if the deleted Volume still exists
                 self.client.wait_for_resource_deletion(volume['id'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_volume_get_metadata_none(self):
         # CREATE, GET empty metadata dict
         try:
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index bbfadba..bcc49aa 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -33,7 +33,7 @@
     alt_tenant = rand_name('test_tenant_')
     alt_description = rand_name('desc_')
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_create_user(self):
         # Create a user
         self.data.setup_test_tenant()
@@ -125,7 +125,7 @@
         # Unset the token to allow further tests to generate a new token
         self.client.clear_auth()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_delete_user(self):
         # Delete a user
         self.data.setup_test_tenant()
@@ -150,7 +150,7 @@
         self.assertRaises(exceptions.NotFound, self.client.delete_user,
                           'junk12345123')
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_user_authentication(self):
         # Valid user's token is authenticated
         self.data.setup_test_user()
@@ -225,7 +225,7 @@
         self.assertEqual('200', resp['status'])
         self.client.clear_auth()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_get_users(self):
         # Get a list of users and find the test user
         self.data.setup_test_user()
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 78c09e0..1f45f92 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -54,7 +54,7 @@
         cls.subnet = cls.create_subnet(cls.network)
         cls.cidr = cls.subnet['cidr']
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_create_delete_network_subnet(self):
         # Creates a network
         name = rand_name('network-')
@@ -83,7 +83,7 @@
         resp, body = self.client.delete_network(network['id'])
         self.assertEqual('204', resp['status'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_show_network(self):
         # Verifies the details of a network
         resp, body = self.client.show_network(self.network['id'])
@@ -92,7 +92,7 @@
         self.assertEqual(self.network['id'], network['id'])
         self.assertEqual(self.name, network['name'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_networks(self):
         # Verify the network exists in the list of all networks
         resp, body = self.client.list_networks()
@@ -100,7 +100,7 @@
         found = any(n for n in networks if n['id'] == self.network['id'])
         self.assertTrue(found)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_show_subnet(self):
         # Verifies the details of a subnet
         resp, body = self.client.show_subnet(self.subnet['id'])
@@ -109,7 +109,7 @@
         self.assertEqual(self.subnet['id'], subnet['id'])
         self.assertEqual(self.cidr, subnet['cidr'])
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_list_subnets(self):
         # Verify the subnet exists in the list of all subnets
         resp, body = self.client.list_subnets()
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index a1c1c06..ea8637c 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -52,7 +52,7 @@
             cls.delete_containers(cls.containers, client[0], client[1])
 
     @testtools.skip('Until Bug #1093743 is resolved.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_container_synchronization(self):
         # container to container synchronization
         # to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 72a1d51..f7d2f88 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -299,7 +299,7 @@
                           metadata=self.custom_headers)
 
     @testtools.skip('Until Bug #1097137 is resolved.')
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_get_object_using_temp_url(self):
         # access object using temporary URL within expiration time
 
@@ -337,7 +337,7 @@
                 resp, _ = self.account_client.list_account_metadata()
                 self.assertNotIn('x-account-meta-temp-url-key', resp)
 
-    @attr(type=['positive', 'gate'])
+    @attr(type='gate')
     def test_object_upload_in_segments(self):
         # create object
         object_name = rand_name(name='LObject')
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 2b2b1a1..e278f59 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -109,7 +109,7 @@
 
         super(VolumeMultiBackendTest, cls).tearDownClass()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_multi_backend_enabled(self):
         # this test checks that multi backend is enabled for at least the
         # computes where the volumes created in setUp were made
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 74a62cb..4131d3e 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -38,7 +38,7 @@
                                                                auth_url,
                                                                adm_tenant)
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_type_list(self):
         # List Volume types.
         try:
@@ -48,7 +48,7 @@
         except Exception:
             self.fail("Could not list volume types")
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_create_get_delete_volume_with_volume_type_and_extra_specs(self):
         # Create/get/delete volume with volume_type and extra spec.
         try:
@@ -100,7 +100,7 @@
                 resp, _ = self.client.delete_volume_type(body['id'])
                 self.assertEqual(202, resp.status)
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_type_create_delete(self):
         # Create/Delete volume type.
         try:
@@ -123,7 +123,7 @@
         except Exception:
             self.fail("Could not create a volume_type")
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_type_create_get(self):
         # Create/get volume type.
         try:
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 20c5cc4..417f296 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -34,7 +34,7 @@
         cls.client.delete_volume_type(cls.volume_type['id'])
         super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_type_extra_specs_list(self):
         # List Volume types extra specs.
         try:
@@ -53,7 +53,7 @@
         except Exception:
             self.fail("Could not list volume types extra specs")
 
-    @attr(type=['gate'])
+    @attr(type='gate')
     def test_volume_type_extra_specs_update(self):
         # Update volume type extra specs
         try:
@@ -77,7 +77,7 @@
         except Exception:
             self.fail("Couldnt update volume type extra spec")
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_type_extra_spec_create_get_delete(self):
         # Create/Get/Delete volume type extra spec.
         try:
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 41fd930..cd5ab34 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -52,7 +52,7 @@
 
         super(VolumesActionsTest, cls).tearDownClass()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_attach_detach_volume_to_instance(self):
         # Volume is attached and detached successfully from an instance
         try:
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 0148183..68ab745 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -105,11 +105,11 @@
                 self.assertEqual(202, resp.status)
                 self.client.wait_for_resource_deletion(volume['id'])
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_create_get_delete(self):
         self._volume_create_get_delete(image_ref=None)
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_from_image(self):
         self._volume_create_get_delete(image_ref=self.config.compute.image_ref)
 
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 3202662..5d5fd7e 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -76,7 +76,7 @@
             cls.client.wait_for_resource_deletion(volid)
         super(VolumesListTest, cls).tearDownClass()
 
-    @attr(type=['smoke'])
+    @attr(type='smoke')
     def test_volume_list(self):
         # Get a list of Volumes
         # Fetch all volumes
diff --git a/tempest/clients.py b/tempest/clients.py
index d78ce61..e85809f 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -264,14 +264,26 @@
             raise exceptions.InvalidConfiguration(msg)
 
         self.auth_url = self.config.identity.uri
+        self.auth_url_v3 = self.config.identity.uri_v3
 
         if self.config.identity.strategy == 'keystone':
             client_args = (self.config, self.username, self.password,
                            self.auth_url, self.tenant_name)
+
+            if self.auth_url_v3:
+                auth_version = 'v3'
+                client_args_v3_auth = (self.config, self.username,
+                                       self.password, self.auth_url_v3,
+                                       self.tenant_name, auth_version)
+            else:
+                client_args_v3_auth = None
+
         else:
             client_args = (self.config, self.username, self.password,
                            self.auth_url)
 
+            client_args_v3_auth = None
+
         try:
             self.servers_client = SERVERS_CLIENTS[interface](*client_args)
             self.limits_client = LIMITS_CLIENTS[interface](*client_args)
@@ -305,6 +317,13 @@
             self.tenant_usages_client = \
                 TENANT_USAGES_CLIENT[interface](*client_args)
             self.policy_client = POLICY_CLIENT[interface](*client_args)
+
+            if client_args_v3_auth:
+                self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
+                    *client_args_v3_auth)
+            else:
+                self.servers_client_v3_auth = None
+
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index d81af83..baa3c03 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -1,6 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
 # Copyright 2012 OpenStack, LLC
+# Copyright 2013 IBM Corp.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -36,12 +37,14 @@
     TYPE = "json"
     LOG = logging.getLogger(__name__)
 
-    def __init__(self, config, user, password, auth_url, tenant_name=None):
+    def __init__(self, config, user, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         self.config = config
         self.user = user
         self.password = password
         self.auth_url = auth_url
         self.tenant_name = tenant_name
+        self.auth_version = auth_version
 
         self.service = None
         self.token = None
@@ -70,11 +73,16 @@
         """
 
         if self.strategy == 'keystone':
-            self.token, self.base_url = self.keystone_auth(self.user,
-                                                           self.password,
-                                                           self.auth_url,
-                                                           self.service,
-                                                           self.tenant_name)
+
+            if self.auth_version == 'v3':
+                auth_func = self.identity_auth_v3
+            else:
+                auth_func = self.keystone_auth
+
+            self.token, self.base_url = (
+                auth_func(self.user, self.password, self.auth_url,
+                          self.service, self.tenant_name))
+
         else:
             self.token, self.base_url = self.basic_auth(self.user,
                                                         self.password,
@@ -116,7 +124,7 @@
 
     def keystone_auth(self, user, password, auth_url, service, tenant_name):
         """
-        Provides authentication via Keystone.
+        Provides authentication via Keystone using v2 identity API.
         """
 
         # Normalize URI to ensure /tokens is in it.
@@ -170,6 +178,90 @@
         raise exceptions.IdentityError('Unexpected status code {0}'.format(
             resp.status))
 
+    def identity_auth_v3(self, user, password, auth_url, service,
+                         project_name, domain_id='default'):
+        """Provides authentication using Identity API v3."""
+
+        req_url = auth_url.rstrip('/') + '/auth/tokens'
+
+        creds = {
+            "auth": {
+                "identity": {
+                    "methods": ["password"],
+                    "password": {
+                        "user": {
+                            "name": user, "password": password,
+                            "domain": {"id": domain_id}
+                        }
+                    }
+                },
+                "scope": {
+                    "project": {
+                        "domain": {"id": domain_id},
+                        "name": project_name
+                    }
+                }
+            }
+        }
+
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(creds)
+        resp, body = self.http_obj.request(req_url, 'POST',
+                                           headers=headers, body=body)
+
+        if resp.status == 201:
+            try:
+                token = resp['x-subject-token']
+            except Exception:
+                self.LOG.exception("Failed to obtain token using V3"
+                                   " authentication (auth URL is '%s')" %
+                                   req_url)
+                raise
+
+            catalog = json.loads(body)['token']['catalog']
+
+            mgmt_url = None
+            for service_info in catalog:
+                if service_info['type'] != service:
+                    continue  # this isn't the entry for us.
+
+                endpoints = service_info['endpoints']
+
+                # Look for an endpoint in the region if configured.
+                if service in self.region:
+                    region = self.region[service]
+
+                    for ep in endpoints:
+                        if ep['region'] != region:
+                            continue
+
+                        mgmt_url = ep['url']
+                        # FIXME(blk-u): this isn't handling endpoint type
+                        # (public, internal, admin).
+                        break
+
+                if not mgmt_url:
+                    # Didn't find endpoint for region, use the first.
+
+                    ep = endpoints[0]
+                    mgmt_url = ep['url']
+                    # FIXME(blk-u): this isn't handling endpoint type
+                    # (public, internal, admin).
+
+                break
+
+            return token, mgmt_url
+
+        elif resp.status == 401:
+            raise exceptions.AuthenticationFailure(user=user,
+                                                   password=password)
+        else:
+            self.LOG.error("Failed to obtain token using V3 authentication"
+                           " (auth URL is '%s'), the response status is %s" %
+                           (req_url, resp.status))
+            raise exceptions.AuthenticationFailure(user=user,
+                                                   password=password)
+
     def post(self, url, body, headers):
         return self.request('POST', url, headers, body)
 
diff --git a/tempest/config.py b/tempest/config.py
index 8f3e574..89a3614 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -37,7 +37,9 @@
                 help="Set to True if using self-signed SSL certificates."),
     cfg.StrOpt('uri',
                default=None,
-               help="Full URI of the OpenStack Identity API (Keystone)"),
+               help="Full URI of the OpenStack Identity API (Keystone), v2"),
+    cfg.StrOpt('uri_v3',
+               help='Full URI of the OpenStack Identity API (Keystone), v3'),
     cfg.StrOpt('strategy',
                default='keystone',
                help="Which auth method does the environment use? "
@@ -118,6 +120,13 @@
     cfg.IntOpt('flavor_ref_alt',
                default=2,
                help='Valid secondary flavor to be used in tests.'),
+    cfg.StrOpt('image_ssh_user',
+               default="root",
+               help="User name used to authenticate to an instance."),
+    cfg.StrOpt('image_alt_ssh_user',
+               default="root",
+               help="User name used to authenticate to an instance using "
+                    "the alternate image."),
     cfg.BoolOpt('resize_available',
                 default=False,
                 help="Does the test environment support resizing?"),
@@ -486,6 +495,34 @@
         conf.register_opt(opt, group='stress')
 
 
+scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
+
+ScenarioGroup = [
+    cfg.StrOpt('img_dir',
+               default='/opt/stack/new/devstack/files/images/'
+               'cirros-0.3.1-x86_64-uec',
+               help='Directory containing image files'),
+    cfg.StrOpt('ami_img_file',
+               default='cirros-0.3.1-x86_64-blank.img',
+               help='AMI image file name'),
+    cfg.StrOpt('ari_img_file',
+               default='cirros-0.3.1-x86_64-initrd',
+               help='ARI image file name'),
+    cfg.StrOpt('aki_img_file',
+               default='cirros-0.3.1-x86_64-vmlinuz',
+               help='AKI image file name'),
+    cfg.StrOpt('ssh_user',
+               default='cirros',
+               help='ssh username for the image file')
+]
+
+
+def register_scenario_opts(conf):
+    conf.register_group(scenario_group)
+    for opt in ScenarioGroup:
+        conf.register_opt(opt, group='scenario')
+
+
 @singleton
 class TempestConfig:
     """Provides OpenStack configuration information."""
@@ -535,6 +572,7 @@
         register_boto_opts(cfg.CONF)
         register_compute_admin_opts(cfg.CONF)
         register_stress_opts(cfg.CONF)
+        register_scenario_opts(cfg.CONF)
         self.compute = cfg.CONF.compute
         self.whitebox = cfg.CONF.whitebox
         self.identity = cfg.CONF.identity
@@ -546,6 +584,7 @@
         self.boto = cfg.CONF.boto
         self.compute_admin = cfg.CONF['compute-admin']
         self.stress = cfg.CONF.stress
+        self.scenario = cfg.CONF.scenario
         if not self.compute_admin.username:
             self.compute_admin.username = self.identity.admin_username
             self.compute_admin.password = self.identity.admin_password
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a358f20..b62e8bb 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -20,6 +20,7 @@
 import subprocess
 
 # Default client libs
+import cinderclient.client
 import glanceclient
 import keystoneclient.v2_0.client
 import netaddr
@@ -33,6 +34,7 @@
     pass
 
 from tempest.api.network import common as net_common
+from tempest.common import ssh
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 import tempest.manager
@@ -49,6 +51,7 @@
     """
 
     NOVACLIENT_VERSION = '2'
+    CINDERCLIENT_VERSION = '1'
 
     def __init__(self):
         super(OfficialClientManager, self).__init__()
@@ -56,11 +59,13 @@
         self.image_client = self._get_image_client()
         self.identity_client = self._get_identity_client()
         self.network_client = self._get_network_client()
+        self.volume_client = self._get_volume_client()
         self.client_attr_names = [
             'compute_client',
             'image_client',
             'identity_client',
             'network_client',
+            'volume_client'
         ]
 
     def _get_compute_client(self, username=None, password=None,
@@ -103,6 +108,22 @@
         return glanceclient.Client('1', endpoint=endpoint, token=token,
                                    insecure=dscv)
 
+    def _get_volume_client(self, username=None, password=None,
+                           tenant_name=None):
+        if not username:
+            username = self.config.identity.username
+        if not password:
+            password = self.config.identity.password
+        if not tenant_name:
+            tenant_name = self.config.identity.tenant_name
+
+        auth_url = self.config.identity.uri
+        return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
+                                          username,
+                                          password,
+                                          tenant_name,
+                                          auth_url)
+
     def _get_identity_client(self, username=None, password=None,
                              tenant_name=None):
         # This identity client is not intended to check the security
@@ -263,6 +284,11 @@
             self.fail("SecurityGroup object not successfully created.")
 
         # Add rules to the security group
+
+        # These rules are intended to permit inbound ssh and icmp
+        # traffic from all sources, so no group_id is provided.
+        # Setting a group_id would only permit traffic from ports
+        # belonging to the same security group.
         rulesets = [
             {
                 # ssh
@@ -270,7 +296,6 @@
                 'from_port': 22,
                 'to_port': 22,
                 'cidr': '0.0.0.0/0',
-                'group_id': secgroup.id
             },
             {
                 # ping
@@ -278,7 +303,6 @@
                 'from_port': -1,
                 'to_port': -1,
                 'cidr': '0.0.0.0/0',
-                'group_id': secgroup.id
             }
         ]
         for ruleset in rulesets:
@@ -420,3 +444,22 @@
 
         # TODO(mnewby) Allow configuration of execution and sleep duration.
         return tempest.test.call_until_true(ping, 20, 1)
+
+    def _is_reachable_via_ssh(self, ip_address, username, private_key,
+                              timeout=120):
+        ssh_client = ssh.Client(ip_address, username,
+                                pkey=private_key,
+                                timeout=timeout)
+        return ssh_client.test_connection_auth()
+
+    def _check_vm_connectivity(self, ip_address, username, private_key,
+                               timeout=120):
+        self.assertTrue(self._ping_ip_address(ip_address),
+                        "Timed out waiting for %s to become "
+                        "reachable" % ip_address)
+        self.assertTrue(self._is_reachable_via_ssh(ip_address,
+                                                   username,
+                                                   private_key,
+                                                   timeout=timeout),
+                        'Auth failure in connecting to %s@%s via ssh' %
+                        (username, ip_address))
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
new file mode 100644
index 0000000..a55bbb2
--- /dev/null
+++ b/tempest/scenario/test_minimum_basic.py
@@ -0,0 +1,208 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import logging
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestMinimumBasicScenario(manager.OfficialClientTest):
+
+    """
+    This is a basic minimum scenario test.
+
+    This test below:
+    * across the multiple components
+    * as a regular user
+    * with and without optional parameters
+    * check command outputs
+
+    """
+
+    def _wait_for_server_status(self, status):
+        server_id = self.server.id
+        self.status_timeout(
+            self.compute_client.servers, server_id, status)
+
+    def _wait_for_volume_status(self, status):
+        volume_id = self.volume.id
+        self.status_timeout(
+            self.volume_client.volumes, volume_id, status)
+
+    def _image_create(self, name, fmt, path, properties={}):
+        name = rand_name('%s-' % name)
+        image_file = open(path, 'rb')
+        self.addCleanup(image_file.close)
+        params = {
+            'name': name,
+            'container_format': fmt,
+            'disk_format': fmt,
+            'is_public': 'True',
+        }
+        params.update(properties)
+        image = self.image_client.images.create(**params)
+        self.addCleanup(self.image_client.images.delete, image)
+        self.assertEqual("queued", image.status)
+        image.update(data=image_file)
+        return image.id
+
+    def glance_image_create(self):
+        aki_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.aki_img_file
+        ari_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ari_img_file
+        ami_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ami_img_file
+        LOG.debug("paths: ami: %s, ari: %s, aki: %s"
+                  % (ami_img_path, ari_img_path, aki_img_path))
+        kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path)
+        ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path)
+        properties = {
+            'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id}
+        }
+        self.image = self._image_create('scenario-ami', 'ami',
+                                        path=ami_img_path,
+                                        properties=properties)
+
+    def nova_keypair_add(self):
+        name = rand_name('scenario-keypair-')
+
+        self.keypair = self.compute_client.keypairs.create(name=name)
+        self.addCleanup(self.compute_client.keypairs.delete, self.keypair)
+        self.assertEqual(name, self.keypair.name)
+
+    def nova_boot(self):
+        name = rand_name('scenario-server-')
+        client = self.compute_client
+        flavor_id = self.config.compute.flavor_ref
+        self.server = client.servers.create(name=name, image=self.image,
+                                            flavor=flavor_id,
+                                            key_name=self.keypair.name)
+        self.addCleanup(self.compute_client.servers.delete, self.server)
+        self.assertEqual(name, self.server.name)
+        self._wait_for_server_status('ACTIVE')
+
+    def nova_list(self):
+        servers = self.compute_client.servers.list()
+        LOG.debug("server_list:%s" % servers)
+        self.assertTrue(self.server in servers)
+
+    def nova_show(self):
+        got_server = self.compute_client.servers.get(self.server)
+        LOG.debug("got server:%s" % got_server)
+        self.assertEqual(self.server, got_server)
+
+    def cinder_create(self):
+        name = rand_name('scenario-volume-')
+        LOG.debug("volume display-name:%s" % name)
+        self.volume = self.volume_client.volumes.create(size=1,
+                                                        display_name=name)
+        LOG.debug("volume created:%s" % self.volume.display_name)
+        self._wait_for_volume_status('available')
+
+        self.addCleanup(self.volume_client.volumes.delete, self.volume)
+        self.assertEqual(name, self.volume.display_name)
+
+    def cinder_list(self):
+        volumes = self.volume_client.volumes.list()
+        self.assertTrue(self.volume in volumes)
+
+    def cinder_show(self):
+        volume = self.volume_client.volumes.get(self.volume.id)
+        self.assertEqual(self.volume, volume)
+
+    def nova_volume_attach(self):
+        attach_volume_client = self.compute_client.volumes.create_server_volume
+        volume = attach_volume_client(self.server.id,
+                                      self.volume.id,
+                                      '/dev/vdb')
+        self.assertEqual(self.volume.id, volume.id)
+        self._wait_for_volume_status('in-use')
+
+    def nova_reboot(self):
+        self.server.reboot()
+        self._wait_for_server_status('ACTIVE')
+
+    def nova_floating_ip_create(self):
+        self.floating_ip = self.compute_client.floating_ips.create()
+        self.addCleanup(self.floating_ip.delete)
+
+    def nova_floating_ip_add(self):
+        self.server.add_floating_ip(self.floating_ip)
+
+    def nova_security_group_rule_create(self):
+        sgs = self.compute_client.security_groups.list()
+        for sg in sgs:
+            if sg.name == 'default':
+                secgroup = sg
+
+        ruleset = {
+            # ssh
+            'ip_protocol': 'tcp',
+            'from_port': 22,
+            'to_port': 22,
+            'cidr': '0.0.0.0/0',
+            'group_id': None
+        }
+        sg_rule = self.compute_client.security_group_rules.create(secgroup.id,
+                                                                  **ruleset)
+        self.addCleanup(self.compute_client.security_group_rules.delete,
+                        sg_rule.id)
+
+    def ssh_to_server(self):
+        username = self.config.scenario.ssh_user
+        self.linux_client = RemoteClient(self.floating_ip.ip,
+                                         username,
+                                         pkey=self.keypair.private_key)
+
+    def check_partitions(self):
+        partitions = self.linux_client.get_partitions()
+        self.assertEqual(1, partitions.count('vdb'))
+
+    def nova_volume_detach(self):
+        detach_volume_client = self.compute_client.volumes.delete_server_volume
+        detach_volume_client(self.server.id, self.volume.id)
+        self._wait_for_volume_status('available')
+
+        volume = self.volume_client.volumes.get(self.volume.id)
+        self.assertEqual('available', volume.status)
+
+    def test_minimum_basic_scenario(self):
+        self.glance_image_create()
+        self.nova_keypair_add()
+        self.nova_boot()
+        self.nova_list()
+        self.nova_show()
+        self.cinder_create()
+        self.cinder_list()
+        self.cinder_show()
+        self.nova_volume_attach()
+        self.cinder_show()
+        self.nova_reboot()
+
+        self.nova_floating_ip_create()
+        self.nova_floating_ip_add()
+        self.nova_security_group_rule_create()
+        self.ssh_to_server()
+        self.check_partitions()
+
+        self.nova_volume_detach()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 5ccfd52..b94caaa 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -31,10 +31,15 @@
 
      * For a freshly-booted VM with an IP address ("port") on a given network:
 
-       - the Tempest host can ping the IP address.  This implies that
-         the VM has been assigned the correct IP address and has
+       - the Tempest host can ping the IP address.  This implies, but
+         does not guarantee (see the ssh check that follows), that the
+         VM has been assigned the correct IP address and has
          connectivity to the Tempest host.
 
+       - the Tempest host can perform key-based authentication to an
+         ssh server hosted at the IP address.  This check guarantees
+         that the IP address is associated with the target VM.
+
        #TODO(mnewby) - Need to implement the following:
        - the Tempest host can ssh into the VM via the IP address and
          successfully execute the following:
@@ -214,12 +219,15 @@
             raise self.skipTest(msg)
         if not self.servers:
             raise self.skipTest("No VM's have been created")
+        # The target login is assumed to have been configured for
+        # key-based authentication by cloud-init.
+        ssh_login = self.config.compute.image_ssh_user
+        private_key = self.keypairs[self.tenant_id].private_key
         for server in self.servers:
             for net_name, ip_addresses in server.networks.iteritems():
                 for ip_address in ip_addresses:
-                    self.assertTrue(self._ping_ip_address(ip_address),
-                                    "Timed out waiting for %s's ip to become "
-                                    "reachable" % server.name)
+                    self._check_vm_connectivity(ip_address, ssh_login,
+                                                private_key)
 
     @attr(type='smoke')
     def test_007_assign_floating_ips(self):
@@ -237,9 +245,11 @@
     def test_008_check_public_network_connectivity(self):
         if not self.floating_ips:
             raise self.skipTest('No floating ips have been allocated.')
+        # The target login is assumed to have been configured for
+        # key-based authentication by cloud-init.
+        ssh_login = self.config.compute.image_ssh_user
+        private_key = self.keypairs[self.tenant_id].private_key
         for server, floating_ips in self.floating_ips.iteritems():
             for floating_ip in floating_ips:
                 ip_address = floating_ip.floating_ip_address
-                self.assertTrue(self._ping_ip_address(ip_address),
-                                "Timed out waiting for %s's ip to become "
-                                "reachable" % server.name)
+                self._check_vm_connectivity(ip_address, ssh_login, private_key)
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 3569b50..d4822da 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -26,9 +26,11 @@
 
 class ServersClientJSON(RestClient):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         super(ServersClientJSON, self).__init__(config, username, password,
-                                                auth_url, tenant_name)
+                                                auth_url, tenant_name,
+                                                auth_version=auth_version)
         self.service = self.config.compute.catalog_type
 
     def create_server(self, name, image_ref, flavor_ref, **kwargs):
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 6d811a5..1ec4df0 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -111,9 +111,11 @@
 
 class ServersClientXML(RestClientXML):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         super(ServersClientXML, self).__init__(config, username, password,
-                                               auth_url, tenant_name)
+                                               auth_url, tenant_name,
+                                               auth_version=auth_version)
         self.service = self.config.compute.catalog_type
 
     def _parse_key_value(self, node):
diff --git a/tempest/tests/compute/test_auth_token.py b/tempest/tests/compute/test_auth_token.py
new file mode 100644
index 0000000..ca319a1
--- /dev/null
+++ b/tempest/tests/compute/test_auth_token.py
@@ -0,0 +1,52 @@
+# Copyright 2013 IBM Corp
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+
+
+import testtools
+
+import tempest.config as config
+from tempest.tests.compute import base
+
+
+class AuthTokenTestJSON(base.BaseComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(AuthTokenTestJSON, cls).setUpClass()
+
+        cls.servers_v2 = cls.os.servers_client
+        cls.servers_v3 = cls.os.servers_client_v3_auth
+
+    def test_v2_token(self):
+        # Can get a token using v2 of the identity API and use that to perform
+        # an operation on the compute service.
+
+        # Doesn't matter which compute API is used,
+        # picking list_servers because it's easy.
+        self.servers_v2.list_servers()
+
+    @testtools.skipIf(not config.TempestConfig().identity.uri_v3,
+                      'v3 auth client not configured')
+    def test_v3_token(self):
+        # Can get a token using v3 of the identity API and use that to perform
+        # an operation on the compute service.
+
+        # Doesn't matter which compute API is used,
+        # picking list_servers because it's easy.
+        self.servers_v3.list_servers()
+
+
+class AuthTokenTestXML(AuthTokenTestJSON):
+    _interface = 'xml'
diff --git a/tempest/whitebox/test_images_whitebox.py b/tempest/whitebox/test_images_whitebox.py
index 8a23e8f..096d3bf 100644
--- a/tempest/whitebox/test_images_whitebox.py
+++ b/tempest/whitebox/test_images_whitebox.py
@@ -18,11 +18,9 @@
 from tempest.api.compute import base
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
-from tempest.test import attr
 from tempest.whitebox import manager
 
 
-@attr(type='whitebox')
 class ImagesWhiteboxTest(manager.ComputeWhiteboxTest, base.BaseComputeTest):
     _interface = 'json'
 
diff --git a/tempest/whitebox/test_servers_whitebox.py b/tempest/whitebox/test_servers_whitebox.py
index 5db0dc6..cd3c026 100644
--- a/tempest/whitebox/test_servers_whitebox.py
+++ b/tempest/whitebox/test_servers_whitebox.py
@@ -17,11 +17,9 @@
 
 from tempest.api.identity.base import BaseIdentityAdminTest
 from tempest import exceptions
-from tempest.test import attr
 from tempest.whitebox import manager
 
 
-@attr(type='whitebox')
 class ServersWhiteboxTest(manager.ComputeWhiteboxTest):
     _interface = 'json'
 
diff --git a/tools/test-requires b/test-requirements.txt
similarity index 100%
rename from tools/test-requires
rename to test-requirements.txt
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 5d4b290..1664b35 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -62,8 +62,8 @@
 def main(argv):
     root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
     venv = os.path.join(root, '.venv')
-    pip_requires = os.path.join(root, 'tools', 'pip-requires')
-    test_requires = os.path.join(root, 'tools', 'test-requires')
+    pip_requires = os.path.join(root, 'requirements.txt')
+    test_requires = os.path.join(root, 'test-requirements.txt')
     py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
     project = 'Tempest'
     install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
diff --git a/tox.ini b/tox.ini
index 924e844..e31e43d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -51,13 +51,13 @@
 
 [testenv:venv]
 commands = {posargs}
-deps = -r{toxinidir}/tools/pip-requires
-       -r{toxinidir}/tools/test-requires
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
 
 [testenv:pep8]
 commands = flake8
-deps = -r{toxinidir}/tools/pip-requires
-       -r{toxinidir}/tools/test-requires
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
 
 [hacking]
 local-check-factory = tempest.hacking.checks.factory