Merge "Adding test_server_sequence_suspend_resume"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index a55df76..fd8cbb5 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -91,7 +91,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/source/index.rst b/doc/source/index.rst
index e37e250..1ca7344 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,6 +3,7 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
+===================================
Tempest Testing Project
===================================
@@ -14,10 +15,15 @@
overview
-Field Guide:
+-------------------------------
+Field Guides
+-------------------------------
+Tempest contains tests of many different types, the field guides
+attempt to explain these in a way that makes it easy to understand
+where your test contributions should go.
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
field_guide/index
field_guide/api
@@ -28,6 +34,7 @@
field_guide/whitebox
+==================
Indices and tables
==================
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/run_tests.sh b/run_tests.sh
index d5b2494..366564e 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -166,11 +166,14 @@
fi
run_tests
+retval=$?
if [ $nova_coverage -eq 1 ]; then
run_coverage_report
fi
if [ -z "$noseargs" ]; then
- run_pep8
+ run_pep8
fi
+
+exit $retval
diff --git a/tempest/README.rst b/tempest/README.rst
index d506bc6..fa29fe2 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -1,5 +1,6 @@
-Tempest Field Guide
-========
+============
+Tempest Field Guide Overview
+============
Tempest is designed to be useful for a large number of different
environments. This includes being useful for gating commits to
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_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index c7f0b23..4163245 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -131,7 +131,7 @@
# Verify the image was deleted correctly
resp, body = self.client.delete_image(image_id)
self.assertEqual('204', resp['status'])
- self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+ self.client.wait_for_resource_deletion(image_id)
@testtools.skipUnless(compute.MULTI_USER,
'Need multiple users for this test.')
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_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 0f35ee5..db9bdc1 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
from tempest.api import compute
from tempest.api.compute import base
@@ -170,7 +171,8 @@
@attr(type='gate')
def test_list_servers_by_changes_since(self):
# Servers are listed by specifying changes-since date
- changes_since = {'changes-since': '2011-01-01T12:34:00Z'}
+ since = datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
+ changes_since = {'changes-since': since.isoformat()}
resp, body = self.client.list_servers(changes_since)
self.assertEqual('200', resp['status'])
# changes-since returns all instances, including deleted.
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_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5f53080..bbe489c 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -236,7 +236,11 @@
# Create a server with a nonexistent security group
security_groups = [{'name': 'does_not_exist'}]
- self.assertRaises(exceptions.BadRequest,
+ if self.config.network.quantum_available:
+ expected_exception = exceptions.NotFound
+ else:
+ expected_exception = exceptions.BadRequest
+ self.assertRaises(expected_exception,
self.create_server,
security_groups=security_groups)
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/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
new file mode 100644
index 0000000..8d019fe
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class DomainsTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def _delete_domain(self, domain_id):
+ # It is necessary to disable the domian before deleting,
+ # or else it would result in unauthorized error
+ _, body = self.v3_client.update_domain(domain_id, enabled=False)
+ resp, _ = self.v3_client.delete_domain(domain_id)
+ self.assertEqual(204, resp.status)
+
+ @attr(type='smoke')
+ def test_list_domains(self):
+ #Test to list domains
+ domain_ids = list()
+ fetched_ids = list()
+ for _ in range(3):
+ _, domain = self.v3_client.create_domain(
+ rand_name('domain-'), description=rand_name('domain-desc-'))
+ # Delete the domian at the end of this method
+ self.addCleanup(self._delete_domain, domain['id'])
+ domain_ids.append(domain['id'])
+ # List and Verify Domains
+ resp, body = self.v3_client.list_domains()
+ self.assertEqual(resp['status'], '200')
+ for d in body:
+ fetched_ids.append(d['id'])
+ missing_doms = [d for d in domain_ids if d not in fetched_ids]
+ self.assertEqual(0, len(missing_doms))
+
+
+class DomainsTestXML(DomainsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_policies.py b/tempest/api/identity/admin/v3/test_policies.py
new file mode 100644
index 0000000..799b081
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_policies.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class PoliciesTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def _delete_policy(self, policy_id):
+ resp, _ = self.policy_client.delete_policy(policy_id)
+ self.assertEqual(204, resp.status)
+
+ @attr(type='smoke')
+ def test_list_policies(self):
+ #Test to list policies
+ policy_ids = list()
+ fetched_ids = list()
+ for _ in range(3):
+ blob = rand_name('BlobName-')
+ policy_type = rand_name('PolicyType-')
+ resp, policy = self.policy_client.create_policy(blob,
+ policy_type)
+ # Delete the Policy at the end of this method
+ self.addCleanup(self._delete_policy, policy['id'])
+ policy_ids.append(policy['id'])
+ # List and Verify Policies
+ resp, body = self.policy_client.list_policies()
+ self.assertEqual(resp['status'], '200')
+ for p in body:
+ fetched_ids.append(p['id'])
+ missing_pols = [p for p in policy_ids if p not in fetched_ids]
+ self.assertEqual(0, len(missing_pols))
+
+ @attr(type='smoke')
+ def test_create_update_delete_policy(self):
+ #Test to update policy
+ blob = rand_name('BlobName-')
+ policy_type = rand_name('PolicyType-')
+ resp, policy = self.policy_client.create_policy(blob, policy_type)
+ self.addCleanup(self._delete_policy, policy['id'])
+ self.assertIn('id', policy)
+ self.assertIn('type', policy)
+ self.assertIn('blob', policy)
+ self.assertIsNotNone(policy['id'])
+ self.assertEqual(blob, policy['blob'])
+ self.assertEqual(policy_type, policy['type'])
+ resp, fetched_policy = self.policy_client.get_policy(policy['id'])
+ self.assertEqual(resp['status'], '200')
+ #Update policy
+ update_type = rand_name('UpdatedPolicyType-')
+ resp, data = self.policy_client.update_policy(
+ policy['id'], type=update_type)
+ self.assertTrue('type' in data)
+ #Assertion for updated value with fetched value
+ resp, fetched_policy = self.policy_client.get_policy(policy['id'])
+ self.assertIn('id', fetched_policy)
+ self.assertIn('blob', fetched_policy)
+ self.assertIn('type', fetched_policy)
+ self.assertEqual(fetched_policy['id'], policy['id'])
+ self.assertEqual(fetched_policy['blob'], policy['blob'])
+ self.assertEqual(update_type, fetched_policy['type'])
+
+
+class PoliciesTestXML(PoliciesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 6980425..db55509 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -31,6 +31,7 @@
cls.endpoints_client = os.endpoints_client
cls.v3_client = os.identity_v3_client
cls.service_client = os.service_client
+ cls.policy_client = os.policy_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
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_account_services.py b/tempest/api/object_storage/test_account_services.py
index d7b87d1..b40774e 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -53,27 +53,25 @@
self.assertIn('x-account-bytes-used', resp)
@attr(type='smoke')
- def test_create_account_metadata(self):
+ def test_create_and_delete_account_metadata(self):
+ header = 'test-account-meta'
+ data = 'Meta!'
# add metadata to account
- metadata = {'test-account-meta': 'Meta!'}
- resp, _ = \
- self.account_client.create_account_metadata(metadata=metadata)
+ resp, _ = self.account_client.create_account_metadata(
+ metadata={header: data})
self.assertEqual(resp['status'], '204')
- resp, metadata = self.account_client.list_account_metadata()
- self.assertIn('x-account-meta-test-account-meta', resp)
- self.assertEqual(resp['x-account-meta-test-account-meta'], 'Meta!')
+ resp, _ = self.account_client.list_account_metadata()
+ self.assertIn('x-account-meta-' + header, resp)
+ self.assertEqual(resp['x-account-meta-' + header], data)
- @attr(type='smoke')
- def test_delete_account_metadata(self):
# delete metadata from account
- metadata = ['test-account-meta']
resp, _ = \
- self.account_client.delete_account_metadata(metadata=metadata)
+ self.account_client.delete_account_metadata(metadata=[header])
self.assertEqual(resp['status'], '204')
- resp, metadata = self.account_client.list_account_metadata()
- self.assertNotIn('x-account-meta-test-account-meta', resp)
+ resp, _ = self.account_client.list_account_metadata()
+ self.assertNotIn('x-account-meta-' + header, resp)
@attr(type=['negative', 'gate'])
def test_list_containers_with_non_authorized_user(self):
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..2b8feef 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -298,8 +298,7 @@
self.container_name, object_name,
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 +336,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/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 067f58c..45d519b 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -107,3 +107,14 @@
def test_admin_bashcompletion(self):
self.keystone('bash-completion')
+
+ # Optional arguments:
+
+ def test_admin_version(self):
+ self.keystone('', flags='--version')
+
+ def test_admin_debug_list(self):
+ self.keystone('catalog', flags='--debug')
+
+ def test_admin_timeout(self):
+ self.keystone('catalog', flags='--timeout 15')
diff --git a/tempest/clients.py b/tempest/clients.py
index 91d54eb..e85809f 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -72,11 +72,13 @@
EndPointClientJSON
from tempest.services.identity.v3.json.identity_client import \
IdentityV3ClientJSON
+from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
from tempest.services.identity.v3.json.service_client import \
ServiceClientJSON
from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
from tempest.services.identity.v3.xml.identity_client import \
IdentityV3ClientXML
+from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
from tempest.services.identity.v3.xml.service_client import \
ServiceClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
@@ -224,6 +226,11 @@
"xml": TenantUsagesClientXML,
}
+POLICY_CLIENT = {
+ "json": PolicyClientJSON,
+ "xml": PolicyClientXML,
+}
+
class Manager(object):
@@ -257,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)
@@ -297,6 +316,14 @@
self.services_client = SERVICES_CLIENT[interface](*client_args)
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/images_client.py b/tempest/services/compute/json/images_client.py
index 376dafc..b13d0f1 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -150,3 +150,10 @@
resp, body = self.delete("images/%s/metadata/%s" %
(str(image_id), key))
return resp, body
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_image(id)
+ except exceptions.NotFound:
+ return True
+ return False
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/images_client.py b/tempest/services/compute/xml/images_client.py
index c7e337b..cc13aa1 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -226,3 +226,10 @@
"""Deletes a single image metadata key/value pair."""
return self.delete("images/%s/metadata/%s" % (str(image_id), key),
self.headers)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_image(id)
+ except exceptions.NotFound:
+ return True
+ return False
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/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 014df1e..adbdc83 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -160,3 +160,51 @@
(project_id, user_id, role_id), None,
self.headers)
return resp, body
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.post('domains', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['domain']
+
+ def delete_domain(self, domain_id):
+ """Delete a domain."""
+ resp, body = self.delete('domains/%s' % str(domain_id))
+ return resp, body
+
+ def list_domains(self):
+ """List Domains."""
+ resp, body = self.get('domains')
+ body = json.loads(body)
+ return resp, body['domains']
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ resp, body = self.get_domain(domain_id)
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.patch('domains/%s' % domain_id, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['domain']
+
+ def get_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id)
+ body = json.loads(body)
+ return resp, body['domain']
diff --git a/tempest/services/identity/v3/json/policy_client.py b/tempest/services/identity/v3/json/policy_client.py
new file mode 100644
index 0000000..27404c4
--- /dev/null
+++ b/tempest/services/identity/v3/json/policy_client.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class PolicyClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(PolicyClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(PolicyClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_policy(self, blob, type):
+ """Creates a Policy."""
+ post_body = {
+ "blob": blob,
+ "type": type
+ }
+ post_body = json.dumps({'policy': post_body})
+ resp, body = self.post('policies', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def list_policies(self):
+ """Lists the policies."""
+ resp, body = self.get('policies')
+ body = json.loads(body)
+ return resp, body['policies']
+
+ def get_policy(self, policy_id):
+ """Lists out the given policy."""
+ url = 'policies/%s' % policy_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def update_policy(self, policy_id, **kwargs):
+ """Updates a policy."""
+ resp, body = self.get_policy(policy_id)
+ type = kwargs.get('type')
+ post_body = {
+ 'type': type
+ }
+ post_body = json.dumps({'policy': post_body})
+ url = 'policies/%s' % policy_id
+ resp, body = self.patch(url, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def delete_policy(self, policy_id):
+ """Deletes the policy."""
+ url = "policies/%s" % policy_id
+ return self.delete(url)
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 92151dd..708ee28 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -44,6 +44,14 @@
array.append(xml_to_json(child))
return array
+ def _parse_domains(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "domain":
+ array.append(xml_to_json(child))
+ return array
+
def _parse_array(self, node):
array = []
for child in node.getchildren():
@@ -185,3 +193,51 @@
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), '', self.headers)
return resp, body
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = Element("domain",
+ xmlns=XMLNS,
+ name=name,
+ description=description,
+ enabled=str(en).lower())
+ resp, body = self.post('domains', str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_domains(self):
+ """Get the list of domains."""
+ resp, body = self.get("domains", self.headers)
+ body = self._parse_domains(etree.fromstring(body))
+ return resp, body
+
+ def delete_domain(self, domain_id):
+ """Delete a domain."""
+ resp, body = self.delete('domains/%s' % domain_id, self.headers)
+ return resp, body
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ resp, body = self.get_domain(domain_id)
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = Element("domain",
+ xmlns=XMLNS,
+ name=name,
+ description=description,
+ enabled=str(en).lower())
+ resp, body = self.patch('domains/%s' % domain_id,
+ str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/identity/v3/xml/policy_client.py b/tempest/services/identity/v3/xml/policy_client.py
new file mode 100644
index 0000000..c3f6d99
--- /dev/null
+++ b/tempest/services/identity/v3/xml/policy_client.py
@@ -0,0 +1,97 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from urlparse import urlparse
+
+import httplib2
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class PolicyClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(PolicyClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "policy":
+ array.append(xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ json = xml_to_json(body)
+ return json
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class RestClient."""
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(PolicyClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_policy(self, blob, type):
+ """Creates a Policy."""
+ create_policy = Element("policy", xmlns=XMLNS, blob=blob, type=type)
+ resp, body = self.post('policies', str(Document(create_policy)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_policies(self):
+ """Lists the policies."""
+ resp, body = self.get('policies', self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_policy(self, policy_id):
+ """Lists out the given policy."""
+ url = 'policies/%s' % policy_id
+ resp, body = self.get(url, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_policy(self, policy_id, **kwargs):
+ """Updates a policy."""
+ resp, body = self.get_policy(policy_id)
+ type = kwargs.get('type')
+ update_policy = Element("policy", xmlns=XMLNS, type=type)
+ url = 'policies/%s' % policy_id
+ resp, body = self.patch(url, str(Document(update_policy)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_policy(self, policy_id):
+ """Deletes the policy."""
+ url = "policies/%s" % policy_id
+ return self.delete(url)
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 2c431ed..2fcdf2e 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -1,4 +1,4 @@
-Quanta Research Cambridge OpenStack Stress Test System
+Tempest Field Guide to Stress Tests
======================================================
Nova is a distributed, asynchronous system that is prone to race condition
@@ -10,7 +10,7 @@
Environment
------------
-This particular framework assumes your working Nova cluster understands Nova
+This particular framework assumes your working Nova cluster understands Nova
API 2.0. The stress tests can read the logs from the cluster. To enable this
you have to provide the hostname to call 'nova-manage' and
the private key and user name for ssh to the cluster in the
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/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index 2c0d8ae..0f836d0 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -72,21 +72,22 @@
retrieved_image = self.images_client.get_image(image["image_id"])
self.assertTrue(retrieved_image.name == image["name"])
self.assertTrue(retrieved_image.id == image["image_id"])
- if retrieved_image.state != "available":
+ state = retrieved_image.state
+ if state != "available":
def _state():
retr = self.images_client.get_image(image["image_id"])
return retr.state
state = state_wait(_state, "available")
self.assertEqual("available", state)
self.images_client.deregister_image(image["image_id"])
- #TODO(afazekas): double deregister ?
+ self.assertNotIn(image["image_id"], str(
+ self.images_client.get_all_images()))
self.cancelResourceCleanUp(image["cleanUp"])
- @testtools.skip("Skipped until the Bug #1074904 is resolved")
def test_register_get_deregister_aki_image(self):
# Register and deregister aki image
image = {"name": rand_name("aki-name-"),
- "location": self.bucket_name + "/" + self.ari_manifest,
+ "location": self.bucket_name + "/" + self.aki_manifest,
"type": "aki"}
image["image_id"] = self.images_client.register_image(
name=image["name"],
@@ -102,9 +103,8 @@
if retrieved_image.state != "available":
self.assertImageStateWait(retrieved_image, "available")
self.images_client.deregister_image(image["image_id"])
- #TODO(afazekas): verify deregister in a better way
- retrieved_image = self.images_client.get_image(image["image_id"])
- self.assertIn(retrieved_image.state, self.valid_image_state)
+ self.assertNotIn(image["image_id"], str(
+ self.images_client.get_all_images()))
self.cancelResourceCleanUp(image["cleanUp"])
@testtools.skip("Skipped until the Bug #1074908 and #1074904 is resolved")
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/tools/skip_tracker.py b/tools/skip_tracker.py
index a4cf394..c7b0033 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -28,7 +28,7 @@
from launchpadlib import launchpad
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-TESTDIR = os.path.join(BASEDIR, 'tempest', 'tests')
+TESTDIR = os.path.join(BASEDIR, 'tempest')
LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
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