Merge "Use of single interface for creating image"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index ded713d..2a3edf4 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -204,6 +204,3 @@
('index', 'doc-tempest.tex', u'Tempest Testing Project',
u'OpenStack Foundation', 'manual'),
]
-
-# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
-latex_use_xindy = False
diff --git a/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml
new file mode 100644
index 0000000..d2a644e
--- /dev/null
+++ b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml
@@ -0,0 +1,10 @@
+---
+fixes:
+ - |
+ Fixed bug #1890060. tempest subunit_describe_calls --verbose not working with Cliff CLI.
+ The subunit_describe_calls --verbose argument was a boolean and worked in the non Cliff CLI
+ which is now deprecated, but does not work with cliff since --verbase is a standard cliff
+ argument which is an int. Since the tool is in lib directory we cannot change the interface,
+ so we add a new argument -a --all-stdout that will allow cliff CLI to support the
+ feature in subunnit_describe_calls to print request and response headers and bodies
+ to stdout.
\ No newline at end of file
diff --git a/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml b/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml
new file mode 100644
index 0000000..bbb1901
--- /dev/null
+++ b/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml
@@ -0,0 +1,10 @@
+---
+fixes:
+ - |
+ is_resource_deleted method of v3 volumes_client might have returned
+ a KeyError exception due to an incorrect accessing of a volume id
+ in the case the volume was in error_deleting state.
+ incorrect code - volume['id']
+ correct code - volume['volume']['id']
+ More details about the issue can be found at
+ https://bugs.launchpad.net/tempest/+bug/1887980
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index 2d7e1a7..786c7f0 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -63,7 +63,7 @@
@decorators.idempotent_id('2c8f145f-8012-4cb8-ac7e-95a587f0e4ab')
@utils.services('network')
def test_create_list_delete_floating_ips_bulk(self):
- # Create, List and delete the Floating IPs Bulk
+ """Creating, listing and deleting the Floating IPs Bulk"""
pool = 'test_pool'
# NOTE(GMann): Reserving the IP range but those are not attached
# anywhere. Using the below mentioned interface which is not ever
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 31fe2b5..30f3388 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -18,7 +18,7 @@
class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests hosts API using admin privileges."""
+ """Tests nova hosts API using admin privileges."""
max_microversion = '2.42'
@@ -29,13 +29,13 @@
@decorators.idempotent_id('9bfaf98d-e2cb-44b0-a07e-2558b2821e4f')
def test_list_hosts(self):
- # Listing hosts.
+ """Listing nova hosts"""
hosts = self.client.list_hosts()['hosts']
self.assertGreaterEqual(len(hosts), 2, str(hosts))
@decorators.idempotent_id('5dc06f5b-d887-47a2-bb2a-67762ef3c6de')
def test_list_hosts_with_zone(self):
- # Listing hosts with specified availability zone
+ """Listing nova hosts with specified availability zone"""
self.useFixture(fixtures.LockFixture('availability_zone'))
hosts = self.client.list_hosts()['hosts']
host = hosts[0]
@@ -45,23 +45,27 @@
@decorators.idempotent_id('9af3c171-fbf4-4150-a624-22109733c2a6')
def test_list_hosts_with_a_blank_zone(self):
- # Listing hosts with blank availability zone.
- # If send the request with a blank zone, the request will be successful
- # and it will return all the hosts list
+ """Listing nova hosts with blank availability zone
+
+ If send the request with a blank zone, the request will be successful
+ and it will return all the hosts list
+ """
hosts = self.client.list_hosts(zone='')['hosts']
self.assertNotEmpty(hosts)
@decorators.idempotent_id('c6ddbadb-c94e-4500-b12f-8ffc43843ff8')
def test_list_hosts_with_nonexistent_zone(self):
- # Listing hosts with not existing availability zone.
- # If send the request with a nonexistent zone, the request will be
- # successful and no hosts will be returned
+ """Listing nova hosts with not existing availability zone.
+
+ If send the request with a nonexistent zone, the request will be
+ successful and no hosts will be returned
+ """
hosts = self.client.list_hosts(zone='xxx')['hosts']
self.assertEmpty(hosts)
@decorators.idempotent_id('38adbb12-aee2-4498-8aec-329c72423aa4')
def test_show_host_detail(self):
- # Showing host details.
+ """Showing nova host details"""
hosts = self.client.list_hosts()['hosts']
hosts = [host for host in hosts if host['service'] == 'compute']
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index e8733c8..e9436bc 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -39,21 +39,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dd032027-0210-4d9c-860e-69b1b8deed5f')
def test_list_hosts_with_non_admin_user(self):
- # Non admin user is not allowed to list hosts.
+ """Non admin user is not allowed to list hosts"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.list_hosts)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e75b0a1a-041f-47a1-8b4a-b72a6ff36d3f')
def test_show_host_detail_with_nonexistent_hostname(self):
- # Showing host detail with not existing hostname should fail.
+ """Showing host detail with not existing hostname should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.show_host, 'nonexistent_hostname')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('19ebe09c-bfd4-4b7c-81a2-e2e0710f59cc')
def test_show_host_detail_with_non_admin_user(self):
- # Non admin user is not allowed to show host details.
+ """Non admin user is not allowed to show host details"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.show_host,
self.hostname)
@@ -61,7 +61,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e40c72b1-0239-4ed6-ba21-81a184df1f7c')
def test_update_host_with_non_admin_user(self):
- # Non admin user is not allowed to update host.
+ """Non admin user is not allowed to update host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.update_host,
self.hostname,
@@ -71,8 +71,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fbe2bf3e-3246-4a95-a59f-94e4e298ec77')
def test_update_host_with_invalid_status(self):
- # Updating host to invalid status should fail,
- # 'status' can only be 'enable' or 'disable'.
+ """Updating host to invalid status should fail
+
+ 'status' can only be 'enable' or 'disable'.
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname,
@@ -82,8 +84,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ab1e230e-5e22-41a9-8699-82b9947915d4')
def test_update_host_with_invalid_maintenance_mode(self):
- # Updating host to invalid maintenance mode should fail,
- # 'maintenance_mode' can only be 'enable' or 'disable'.
+ """Updating host to invalid maintenance mode should fail
+
+ 'maintenance_mode' can only be 'enable' or 'disable'.
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname,
@@ -93,8 +97,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0cd85f75-6992-4a4a-b1bd-d11e37fd0eee')
def test_update_host_without_param(self):
- # Updating host without param should fail,
- # 'status' or 'maintenance_mode' is needed for host update.
+ """Updating host without param should fail
+
+ 'status' or 'maintenance_mode' is needed for host update
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname)
@@ -102,7 +108,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('23c92146-2100-4d68-b2d6-c7ade970c9c1')
def test_update_nonexistent_host(self):
- # Updating not existing host should fail.
+ """Updating not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.update_host,
'nonexistent_hostname',
@@ -112,7 +118,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0d981ac3-4320-4898-b674-82b61fbb60e4')
def test_startup_nonexistent_host(self):
- # Starting up not existing host should fail.
+ """Starting up not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.startup_host,
'nonexistent_hostname')
@@ -120,7 +126,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f4ebb7e-b2ae-4e5b-a38f-0fd1bb0ddfca')
def test_startup_host_with_non_admin_user(self):
- # Non admin user is not allowed to startup host.
+ """Non admin user is not allowed to startup host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.startup_host,
self.hostname)
@@ -128,7 +134,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9e637444-29cf-4244-88c8-831ae82c31b6')
def test_shutdown_nonexistent_host(self):
- # Shutting down not existing host should fail.
+ """Shutting down not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.shutdown_host,
'nonexistent_hostname')
@@ -136,7 +142,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a803529c-7e3f-4d3c-a7d6-8e1c203d27f6')
def test_shutdown_host_with_non_admin_user(self):
- # Non admin user is not allowed to shutdown host.
+ """Non admin user is not allowed to shutdown host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.shutdown_host,
self.hostname)
@@ -144,7 +150,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f86bfd7b-0b13-4849-ae29-0322e83ee58b')
def test_reboot_nonexistent_host(self):
- # Rebooting not existing host should fail.
+ """Rebooting not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.reboot_host,
'nonexistent_hostname')
@@ -152,7 +158,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('02d79bb9-eb57-4612-abf6-2cb38897d2f8')
def test_reboot_host_with_non_admin_user(self):
- # Non admin user is not allowed to reboot host.
+ """Non admin user is not allowed to reboot host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reboot_host,
self.hostname)
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index e45aac5..347193d 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -36,19 +36,19 @@
@decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
def test_get_hypervisor_list(self):
- # List of hypervisor and available hypervisors hostname
+ """List of hypervisor and available hypervisors hostname"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('1e7fdac2-b672-4ad1-97a4-bad0e3030118')
def test_get_hypervisor_list_details(self):
- # Display the details of the all hypervisor
+ """Display the details of the all hypervisor"""
hypers = self.client.list_hypervisors(detail=True)['hypervisors']
self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('94ff9eae-a183-428e-9cdb-79fde71211cc')
def test_get_hypervisor_show_details(self):
- # Display the details of the specified hypervisor
+ """Display the details of the specified hypervisor"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@@ -59,14 +59,14 @@
@decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
def test_get_hypervisor_stats(self):
- # Verify the stats of the all hypervisor
+ """Verify the stats of the all hypervisor"""
stats = (self.client.show_hypervisor_statistics()
['hypervisor_statistics'])
self.assertNotEmpty(stats)
@decorators.idempotent_id('91a50d7d-1c2b-4f24-b55a-a1fe20efca70')
def test_get_hypervisor_uptime(self):
- # Verify that GET shows the specified hypervisor uptime
+ """Verify that GET shows the specified hypervisor uptime"""
hypers = self._list_hypervisors()
# Ironic will register each baremetal node as a 'hypervisor',
@@ -106,10 +106,13 @@
class HypervisorAdminV228Test(HypervisorAdminTestBase):
+ """Tests Hypervisors API higher than 2.27 that require admin privileges"""
+
min_microversion = '2.28'
@decorators.idempotent_id('d46bab64-0fbe-4eb8-9133-e6ee56188cc5')
def test_get_list_hypervisor_details(self):
+ """Test listing and showing hypervisor details"""
# NOTE(zhufl): This test tests the hypervisor APIs response schema
# for 2.28 microversion. No specific assert or behaviour verification
# is needed.
@@ -119,11 +122,13 @@
class HypervisorAdminUnderV252Test(HypervisorAdminTestBase):
+ """Tests Hypervisors API below 2.53 that require admin privileges"""
+
max_microversion = '2.52'
@decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
def test_get_hypervisor_show_servers(self):
- # Show instances about the specific hypervisors
+ """Test showing instances about the specific hypervisors"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@@ -134,7 +139,7 @@
@decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
def test_search_hypervisor(self):
- # Searching for hypervisors by its name.
+ """Test searching for hypervisors by its name"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
hypers = self.client.search_hypervisor(
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 723b93c..9aaffd9 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -40,8 +40,9 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
def test_show_nonexistent_hypervisor(self):
- # Showing not existing hypervisor should fail.
+ """Test showing non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
+
self.assertRaises(
lib_exc.NotFound,
self.client.show_hypervisor,
@@ -50,7 +51,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('51e663d0-6b89-4817-a465-20aca0667d03')
def test_show_hypervisor_with_non_admin_user(self):
- # Non admin user is not allowed to show hypervisor.
+ """Test showing hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -62,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849')
def test_get_hypervisor_stats_with_non_admin_user(self):
- # Non admin user is not allowed to get hypervisor stats.
+ """Test getting hypervisor stats by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.show_hypervisor_statistics)
@@ -70,7 +71,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f60aa680-9a3a-4c7d-90e1-fae3a4891303')
def test_get_nonexistent_hypervisor_uptime(self):
- # Getting uptime of not existing hypervisor should fail.
+ """Test showing uptime of non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
@@ -81,7 +82,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6c3461f9-c04c-4e2a-bebb-71dc9cb47df2')
def test_get_hypervisor_uptime_with_non_admin_user(self):
- # Non admin user is not allowed to get hypervisor uptime.
+ """Test showing uptime of hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -93,7 +94,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('51b3d536-9b14-409c-9bce-c6f7c794994e')
def test_get_hypervisor_list_with_non_admin_user(self):
- # List of hypervisor and available services with non admin user
+ """Test listing hypervisors by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.list_hypervisors)
@@ -101,19 +102,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dc02db05-e801-4c5f-bc8e-d915290ab345')
def test_get_hypervisor_list_details_with_non_admin_user(self):
- # Non admin user is not allowed to list hypervisor details.
+ """Test listing hypervisor details by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.list_hypervisors, detail=True)
class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase):
+ """Tests Hypervisors API below ver 2.53 that require admin privileges"""
+
max_microversion = '2.52'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
def test_show_servers_with_non_admin_user(self):
- # Non admin user is not allowed to show servers on hypervisor.
+ """Test showing hypervisor servers by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -125,7 +128,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
def test_show_servers_with_nonexistent_hypervisor(self):
- # Showing servers on not existing hypervisor should fail.
+ """Test showing servers on non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
@@ -136,7 +139,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
def test_search_hypervisor_with_non_admin_user(self):
- # Non admin user is not allowed to search hypervisor.
+ """Test searching hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -148,7 +151,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
def test_search_nonexistent_hypervisor(self):
- # Searching not existing hypervisor should fail.
+ """Test searching non existent hypervisor should fail"""
self.assertRaises(
lib_exc.NotFound,
self.client.search_hypervisor,
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
index 40ed532..3068127 100644
--- a/tempest/api/compute/admin/test_keypairs_v210.py
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -19,6 +19,8 @@
class KeyPairsV210TestJSON(base.BaseKeypairTest):
+ """Tests KeyPairs API with microversion higher than 2.9"""
+
credentials = ['primary', 'admin']
min_microversion = '2.10'
@@ -48,6 +50,13 @@
@decorators.idempotent_id('3c8484af-cfb3-48f6-b8ba-d5d58bbf3eac')
def test_admin_manage_keypairs_for_other_users(self):
+ """Test admin managing keypairs for other users
+
+ First admin creates a keypair for an other user, then admin lists
+ keypairs filtered by that user, and keypairs created for that user
+ should appear in the result and keypairs not created for that user
+ should not appear in the result.
+ """
user_id = self.non_admin_client.user_id
key_list = self._create_and_check_keypairs(user_id)
first_keyname = key_list[0]['name']
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index a845c72..064949f 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -123,11 +123,17 @@
self._live_migrate(server_id, source_host, state, volume_backed)
@decorators.idempotent_id('1dce86b8-eb04-4c03-a9d8-9c1dc3ee0c7b')
+ @testtools.skipUnless(CONF.compute_feature_enabled.
+ block_migration_for_live_migration,
+ 'Block Live migration not available')
def test_live_block_migration(self):
"""Test live migrating an active server"""
self._test_live_migration()
@decorators.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
+ @testtools.skipUnless(CONF.compute_feature_enabled.
+ block_migration_for_live_migration,
+ 'Block Live migration not available')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_live_block_migration_paused(self):
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index f90ff92..04dbc2d 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -53,10 +53,12 @@
class QuotasAdminNegativeTest(QuotasAdminNegativeTestBase):
+ """Negative tests of nova quotas"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('733abfe8-166e-47bb-8363-23dbd7ff3476')
def test_update_quota_normal_user(self):
+ """Test updating nova quota by normal user should fail"""
self.assertRaises(lib_exc.Forbidden,
self.client.update_quota_set,
self.demo_tenant_id,
@@ -67,7 +69,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('91058876-9947-4807-9f22-f6eb17140d9b')
def test_create_server_when_cpu_quota_is_full(self):
- # Disallow server creation when tenant's vcpu quota is full
+ """Disallow server creation when tenant's vcpu quota is full"""
self._update_quota('cores', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
@@ -75,7 +77,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6fdd7012-584d-4327-a61c-49122e0d5864')
def test_create_server_when_memory_quota_is_full(self):
- # Disallow server creation when tenant's memory quota is full
+ """Disallow server creation when tenant's memory quota is full"""
self._update_quota('ram', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
@@ -83,13 +85,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7c6be468-0274-449a-81c3-ac1c32ee0161')
def test_create_server_when_instances_quota_is_full(self):
- # Once instances quota limit is reached, disallow server creation
+ """Once instances quota limit is reached, disallow server creation"""
self._update_quota('instances', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
class QuotasSecurityGroupAdminNegativeTest(QuotasAdminNegativeTestBase):
+ """Negative tests of nova security group quota"""
+
max_microversion = '2.35'
@decorators.skip_because(bug="1186354",
@@ -98,7 +102,7 @@
@decorators.idempotent_id('7c6c8f3b-2bf6-4918-b240-57b136a66aa0')
@utils.services('network')
def test_security_groups_exceed_limit(self):
- # Negative test: Creation Security Groups over limit should FAIL
+ """Negative test: Creation Security Groups over limit should FAIL"""
# Set the quota to number of used security groups
sg_quota = self.limits_client.show_limits()['limits']['absolute'][
'totalSecurityGroupsUsed']
@@ -117,7 +121,7 @@
@decorators.idempotent_id('6e9f436d-f1ed-4f8e-a493-7275dfaa4b4d')
@utils.services('network')
def test_security_groups_rules_exceed_limit(self):
- # Negative test: Creation of Security Group Rules should FAIL
+ """Negative test: Creation of Security Group Rules should FAIL"""
# when we reach limit maxSecurityGroupRules
self._update_quota('security_group_rules', 0)
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index bf846e5..24518a8 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -19,7 +19,10 @@
class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Services API. List and Enable/Disable require admin privileges."""
+ """Tests Nova Services API.
+
+ List and Enable/Disable require admin privileges.
+ """
@classmethod
def setup_clients(cls):
@@ -28,13 +31,13 @@
@decorators.idempotent_id('5be41ef4-53d1-41cc-8839-5c2a48a1b283')
def test_list_services(self):
- # Listing nova services
+ """Listing nova services"""
services = self.client.list_services()['services']
self.assertNotEmpty(services)
@decorators.idempotent_id('f345b1ec-bc6e-4c38-a527-3ca2bc00bef5')
def test_get_service_by_service_binary_name(self):
- # Listing nova services by binary name.
+ """Listing nova services by binary name"""
binary_name = 'nova-compute'
services = self.client.list_services(binary=binary_name)['services']
self.assertNotEmpty(services)
@@ -43,7 +46,7 @@
@decorators.idempotent_id('affb42d5-5b4b-43c8-8b0b-6dca054abcca')
def test_get_service_by_host_name(self):
- # Listing nova services by host name.
+ """Listing nova services by host name"""
services = self.client.list_services()['services']
host_name = services[0]['host']
services_on_host = [service for service in services if
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index 20294e9..58861a1 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -18,11 +18,12 @@
class FlavorsV2TestJSON(base.BaseV2ComputeTest):
+ """Tests Flavors"""
@decorators.attr(type='smoke')
@decorators.idempotent_id('e36c0eaa-dff5-4082-ad1f-3f9a80aa3f59')
def test_list_flavors(self):
- # List of all flavors should contain the expected flavor
+ """List of all flavors should contain the expected flavor"""
flavors = self.flavors_client.list_flavors()['flavors']
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'],
@@ -31,7 +32,7 @@
@decorators.idempotent_id('6e85fde4-b3cd-4137-ab72-ed5f418e8c24')
def test_list_flavors_with_detail(self):
- # Detailed list of all flavors should contain the expected flavor
+ """Detailed list of all flavors should contain the expected flavor"""
flavors = self.flavors_client.list_flavors(detail=True)['flavors']
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
self.assertIn(flavor, flavors)
@@ -39,20 +40,20 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('1f12046b-753d-40d2-abb6-d8eb8b30cb2f')
def test_get_flavor(self):
- # The expected flavor details should be returned
+ """The expected flavor details should be returned"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
self.assertEqual(self.flavor_ref, flavor['id'])
@decorators.idempotent_id('8d7691b3-6ed4-411a-abc9-2839a765adab')
def test_list_flavors_limit_results(self):
- # Only the expected number of flavors should be returned
+ """Only the expected number of flavors should be returned"""
params = {'limit': 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
self.assertEqual(1, len(flavors))
@decorators.idempotent_id('b26f6327-2886-467a-82be-cef7a27709cb')
def test_list_flavors_detailed_limit_results(self):
- # Only the expected number of flavors (detailed) should be returned
+ """Only the expected number of flavors(detailed) should be returned"""
params = {'limit': 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
@@ -60,7 +61,7 @@
@decorators.idempotent_id('e800f879-9828-4bd0-8eae-4f17189951fb')
def test_list_flavors_using_marker(self):
- # The list of flavors should start from the provided marker
+ """The list of flavors should start from the provided marker"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -71,7 +72,7 @@
@decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107')
def test_list_flavors_detailed_using_marker(self):
- # The list of flavors should start from the provided marker
+ """The list of flavors should start from the provided marker"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -83,7 +84,7 @@
@decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79')
def test_list_flavors_detailed_filter_by_min_disk(self):
- # The detailed list of flavors should be filtered by disk space
+ """The detailed list of flavors should be filtered by disk space"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -94,7 +95,7 @@
@decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292')
def test_list_flavors_detailed_filter_by_min_ram(self):
- # The detailed list of flavors should be filtered by RAM
+ """The detailed list of flavors should be filtered by RAM"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -105,7 +106,7 @@
@decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4')
def test_list_flavors_filter_by_min_disk(self):
- # The list of flavors should be filtered by disk space
+ """The list of flavors should be filtered by disk space"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -115,7 +116,7 @@
@decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa')
def test_list_flavors_filter_by_min_ram(self):
- # The list of flavors should be filtered by RAM
+ """The list of flavors should be filtered by RAM"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index ef33685..91ce1f9 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -25,6 +25,8 @@
class ImagesTestJSON(base.BaseV2ComputeTest):
+ """Test server images"""
+
create_default_network = True
@classmethod
@@ -48,6 +50,7 @@
@decorators.idempotent_id('aa06b52b-2db5-4807-b218-9441f75d74e3')
def test_delete_saving_image(self):
+ """Test deleting server image while it is in 'SAVING' state"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
# wait for server active to avoid conflict when deleting server
@@ -74,6 +77,7 @@
@decorators.idempotent_id('aaacd1d0-55a2-4ce8-818a-b5439df8adc9')
def test_create_image_from_stopped_server(self):
+ """Test creating server image from stopped server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.stop_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
@@ -91,6 +95,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_create_image_from_paused_server(self):
+ """Test creating server image from paused server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.pause_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
@@ -109,6 +114,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
'Suspend is not available.')
def test_create_image_from_suspended_server(self):
+ """Test creating server image from suspended server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.suspend_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 2400348..5ff2a6a 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -42,11 +42,12 @@
class ImagesNegativeTestJSON(ImagesNegativeTestBase):
+ """Negative tests of server image"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6cd5a89d-5b47-46a7-93bc-3916f0d84973')
def test_create_image_from_deleted_server(self):
- # An image should not be created if the server instance is removed
+ """Check server image should not be created if the server is removed"""
server = self.create_test_server(wait_until='ACTIVE')
# Delete server before trying to create image
@@ -61,7 +62,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('82c5b0c4-9dbd-463c-872b-20c4755aae7f')
def test_create_image_from_invalid_server(self):
- # An image should not be created with invalid server id
+ """Check server image should not be created with invalid server id"""
# Create a new image with invalid server id
meta = {'image_type': 'test'}
self.assertRaises(lib_exc.NotFound, self.create_image_from_server,
@@ -70,7 +71,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ec176029-73dc-4037-8d72-2e4ff60cf538')
def test_create_image_specify_uuid_35_characters_or_less(self):
- # Return an error if Image ID passed is 35 characters or less
+ """Check server image should not be created for invalid server id
+
+ Return an error if server id passed is 35 characters or less
+ """
snapshot_name = data_utils.rand_name('test-snap')
test_uuid = ('a' * 35)
self.assertRaises(lib_exc.NotFound, self.client.create_image,
@@ -79,7 +83,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('36741560-510e-4cc2-8641-55fe4dfb2437')
def test_create_image_specify_uuid_37_characters_or_more(self):
- # Return an error if Image ID passed is 37 characters or more
+ """Check server image should not be created for invalid server id
+
+ Return an error if sever id passed is 37 characters or more
+ """
snapshot_name = data_utils.rand_name('test-snap')
test_uuid = ('a' * 37)
self.assertRaises(lib_exc.NotFound, self.client.create_image,
@@ -87,20 +94,23 @@
class ImagesDeleteNegativeTestJSON(ImagesNegativeTestBase):
+ """Negative tests of server image
+
+ Negative tests of server image with compute microversion less than 2.36.
+ """
max_microversion = '2.35'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('381acb65-785a-4942-94ce-d8f8c84f1f0f')
def test_delete_image_with_invalid_image_id(self):
- # An image should not be deleted with invalid image id
+ """Check an image should not be deleted with invalid image id"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('137aef61-39f7-44a1-8ddf-0adf82511701')
def test_delete_non_existent_image(self):
- # Return an error while trying to delete a non-existent image
-
+ """Check trying to delete a non-existent image should fail"""
non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
non_existent_image_id)
@@ -108,13 +118,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e6e41425-af5c-4fe6-a4b5-7b7b963ffda5')
def test_delete_image_blank_id(self):
- # Return an error while trying to delete an image with blank Id
+ """Check trying to delete an image with blank id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('924540c3-f1f1-444c-8f58-718958b6724e')
def test_delete_image_non_hex_string_id(self):
- # Return an error while trying to delete an image with non hex id
+ """Check trying to delete an image with non hex id should fail"""
invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
invalid_image_id)
@@ -122,13 +132,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('68e2c175-bd26-4407-ac0f-4ea9ce2139ea')
def test_delete_image_negative_image_id(self):
- # Return an error while trying to delete an image with negative id
+ """Check trying to delete an image with negative id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b340030d-82cd-4066-a314-c72fb7c59277')
def test_delete_image_with_id_over_character_limit(self):
- # Return an error while trying to delete image with id over limit
+ """Check trying to delete image with id over limit should fail"""
invalid_image_id = data_utils.rand_uuid() + "1"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
invalid_image_id)
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index cbb65bb..4dc23a7 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -21,6 +21,8 @@
class ListImagesTestJSON(base.BaseV2ComputeTest):
+ """Test listing server images with compute microversion less than 2.36"""
+
max_microversion = '2.35'
@classmethod
@@ -37,20 +39,26 @@
@decorators.idempotent_id('490d0898-e12a-463f-aef0-c50156b9f789')
def test_get_image(self):
- # Returns the correct details for a single image
+ """Test getting the correct details for a single server image"""
image = self.client.show_image(self.image_ref)['image']
self.assertEqual(self.image_ref, image['id'])
@decorators.idempotent_id('fd51b7f4-d4a3-4331-9885-866658112a6f')
def test_list_images(self):
- # The list of all images should contain the image
+ """Test listing server images
+
+ The list of all images should contain the image
+ """
images = self.client.list_images()['images']
found = [i for i in images if i['id'] == self.image_ref]
self.assertNotEmpty(found)
@decorators.idempotent_id('9f94cb6b-7f10-48c5-b911-a0b84d7d4cd6')
def test_list_images_with_detail(self):
- # Detailed list of all images should contain the expected images
+ """Test listing server images with detail
+
+ Detailed list of all images should contain the expected images
+ """
images = self.client.list_images(detail=True)['images']
found = [i for i in images if i['id'] == self.image_ref]
self.assertNotEmpty(found)
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 8c2202e..c729069 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -18,6 +18,11 @@
class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
+ """Test compute absolute limits
+
+ Test compute absolute limits with compute microversion less than 2.57
+ """
+
max_microversion = '2.56'
@classmethod
@@ -27,12 +32,17 @@
@decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b')
def test_absLimits_get(self):
+ """Test getting nova absolute limits"""
# To check if all limits are present in the response (will be checked
# by schema)
self.client.show_limits()
class AbsoluteLimitsV257TestJSON(base.BaseV2ComputeTest):
+ """Test compute absolute limits
+
+ Test compute absolute limits with compute microversion greater than 2.56
+ """
min_microversion = '2.57'
max_microversion = 'latest'
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index 500638a..de6a9b9 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -20,6 +20,7 @@
class AbsoluteLimitsNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of nova absolute limits"""
def setUp(self):
# NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
@@ -34,7 +35,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
def test_max_metadata_exceed_limit(self):
- # We should not create vm with metadata over maxServerMeta limit
+ """Test creating server with metadata over limit should fail
+
+ We should not create server with metadata over maxServerMeta limit
+ """
# Get max limit value
limits = self.client.show_limits()['limits']
max_meta = limits['absolute']['maxServerMeta']
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index a7db88a..ee25a22 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -26,6 +26,7 @@
class DeleteServersTestJSON(base.BaseV2ComputeTest):
+ """Test deleting servers in various states"""
create_default_network = True
# NOTE: Server creations of each test class should be under 10
@@ -38,21 +39,21 @@
@decorators.idempotent_id('9e6e0c87-3352-42f7-9faf-5d6210dbd159')
def test_delete_server_while_in_building_state(self):
- # Delete a server while it's VM state is Building
+ """Test deleting a server while it's VM state is Building"""
server = self.create_test_server(wait_until='BUILD')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
@decorators.idempotent_id('925fdfb4-5b13-47ea-ac8a-c36ae6fddb05')
def test_delete_active_server(self):
- # Delete a server while it's VM state is Active
+ """Test deleting a server while it's VM state is Active"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
@decorators.idempotent_id('546d368c-bb6c-4645-979a-83ed16f3a6be')
def test_delete_server_while_in_shutoff_state(self):
- # Delete a server while it's VM state is Shutoff
+ """Test deleting a server while it's VM state is Shutoff"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.stop_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF')
@@ -63,7 +64,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_delete_server_while_in_pause_state(self):
- # Delete a server while it's VM state is Pause
+ """Test deleting a server while it's VM state is Pause"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.pause_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'PAUSED')
@@ -74,7 +75,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
'Suspend is not available.')
def test_delete_server_while_in_suspended_state(self):
- # Delete a server while it's VM state is Suspended
+ """Test deleting a server while it's VM state is Suspended"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.suspend_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SUSPENDED')
@@ -85,7 +86,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
def test_delete_server_while_in_shelved_state(self):
- # Delete a server while it's VM state is Shelved
+ """Test deleting a server while it's VM state is Shelved"""
server = self.create_test_server(wait_until='ACTIVE')
compute.shelve_server(self.client, server['id'])
@@ -96,7 +97,7 @@
@testtools.skipIf(not CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_delete_server_while_in_verify_resize_state(self):
- # Delete a server while it's VM state is VERIFY_RESIZE
+ """Test deleting a server while it's VM state is VERIFY_RESIZE"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.resize_server(server['id'], self.flavor_ref_alt)
waiters.wait_for_server_status(self.client, server['id'],
@@ -107,7 +108,7 @@
@decorators.idempotent_id('d0f3f0d6-d9b6-4a32-8da4-23015dcab23c')
@utils.services('volume')
def test_delete_server_while_in_attached_volume(self):
- # Delete a server while a volume is attached to it
+ """Test deleting a server while a volume is attached to it"""
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 7f62c64..990dd52 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -296,21 +296,32 @@
for ip in ip_list:
self.assertNotIn(ip_list[ip], map(lambda x: x['id'], servers))
- @decorators.skip_because(bug="1540645")
@decorators.idempotent_id('a905e287-c35e-42f2-b132-d02b09f3654a')
def test_list_servers_filtered_by_ip_regex(self):
"""Filter the list of servers by part of server ip address"""
- # Here should be listed all servers
if not self.fixed_network_name:
msg = 'fixed_network_name needs to be configured to run this test'
raise self.skipException(msg)
- self.s1 = self.client.show_server(self.s1['id'])['server']
- addr_spec = self.s1['addresses'][self.fixed_network_name][0]
- ip = addr_spec['addr'][0:-3]
+ # query addresses of the 3 servers
+ addrs = []
+ for s in [self.s1, self.s2, self.s3]:
+ s_show = self.client.show_server(s['id'])['server']
+ addr_spec = s_show['addresses'][self.fixed_network_name][0]
+ addrs.append(addr_spec['addr'])
+ # find common part of the 3 ip addresses
+ prefix = ''
+ addrs_len = [len(a) for a in addrs]
+ addrs_len.sort()
+ # iterate over the smallest length of an ip
+ for i in range(addrs_len[0]):
+ if not addrs[0][i] == addrs[1][i] == addrs[2][i]:
+ break
+ prefix += addrs[0][i]
+
if addr_spec['version'] == 4:
- params = {'ip': ip}
+ params = {'ip': prefix}
else:
- params = {'ip6': ip}
+ params = {'ip6': prefix}
# capture all servers in case something goes wrong
all_servers = self.client.list_servers(detail=True)
body = self.client.list_servers(**params)
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 4b5efaa..4c0d021 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -82,18 +82,18 @@
@decorators.idempotent_id('5dc57eda-35b7-4af7-9e5f-3c2be3d2d68b')
def test_create_delete_server_group_with_affinity_policy(self):
- # Create and Delete the server-group with affinity policy
+ """Test Create/Delete the server-group with affinity policy"""
self._create_delete_server_group(self.policy)
@decorators.idempotent_id('3645a102-372f-4140-afad-13698d850d23')
def test_create_delete_server_group_with_anti_affinity_policy(self):
- # Create and Delete the server-group with anti-affinity policy
+ """Test Create/Delete the server-group with anti-affinity policy"""
policy = ['anti-affinity']
self._create_delete_server_group(policy)
@decorators.idempotent_id('154dc5a4-a2fe-44b5-b99e-f15806a4a113')
def test_create_delete_multiple_server_groups_with_same_name_policy(self):
- # Create and Delete the server-groups with same name and same policy
+ """Test Create/Delete the server-groups with same name and policy"""
server_groups = []
server_group_name = data_utils.rand_name('server-group')
for _ in range(0, 2):
@@ -108,14 +108,14 @@
@decorators.idempotent_id('b3545034-dd78-48f0-bdc2-a4adfa6d0ead')
def test_show_server_group(self):
- # Get the server-group
+ """Test getting the server-group detail"""
body = self.client.show_server_group(
self.created_server_group['id'])['server_group']
self.assertEqual(self.created_server_group, body)
@decorators.idempotent_id('d4874179-27b4-4d7d-80e4-6c560cdfe321')
def test_list_server_groups(self):
- # List the server-group
+ """Test listing the server-groups"""
body = self.client.list_server_groups()['server_groups']
self.assertIn(self.created_server_group, body)
@@ -124,7 +124,7 @@
compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
'ServerGroupAffinityFilter is not available.')
def test_create_server_with_scheduler_hint_group(self):
- # Create a server with the scheduler hint "group".
+ """Test creating a server with the scheduler hint 'group'"""
hints = {'group': self.created_server_group['id']}
server = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 7fa30b0..6676358 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -30,6 +30,8 @@
class ServersNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of servers"""
+
create_default_network = True
def setUp(self):
@@ -66,8 +68,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dbbfd247-c40c-449e-8f6c-d2aa7c7da7cf')
def test_server_name_blank(self):
- # Create a server with name parameter empty
-
+ """Creating a server with name parameter empty should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
name='')
@@ -77,8 +78,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.personality,
'Nova personality feature disabled')
def test_personality_file_contents_not_encoded(self):
- # Use an unencoded file when creating a server with personality
-
+ """Using an unencoded injected file to create server should fail"""
file_contents = 'This is a test file.'
person = [{'path': '/etc/testfile.txt',
'contents': file_contents}]
@@ -90,8 +90,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fcba1052-0a50-4cf3-b1ac-fae241edf02f')
def test_create_with_invalid_image(self):
- # Create a server with an unknown image
-
+ """Creating a server with an unknown image should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
image_id=-1)
@@ -99,8 +98,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('18f5227f-d155-4429-807c-ccb103887537')
def test_create_with_invalid_flavor(self):
- # Create a server with an unknown flavor
-
+ """Creating a server with an unknown flavor should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
flavor=-1,)
@@ -108,8 +106,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7f70a4d1-608f-4794-9e56-cb182765972c')
def test_invalid_access_ip_v4_address(self):
- # An access IPv4 address must match a valid address pattern
+ """Creating a server with invalid ipv4 ip address should fail
+ An access IPv4 address must match a valid address pattern
+ """
IPv4 = '1.1.1.1.1.1'
self.assertRaises(lib_exc.BadRequest,
self.create_test_server, accessIPv4=IPv4)
@@ -117,8 +117,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5226dd80-1e9c-4d8a-b5f9-b26ca4763fd0')
def test_invalid_ip_v6_address(self):
- # An access IPv6 address must match a valid address pattern
+ """Creating a server with invalid ipv6 ip address should fail
+ An access IPv6 address must match a valid address pattern
+ """
IPv6 = 'notvalid'
self.assertRaises(lib_exc.BadRequest,
@@ -129,7 +131,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_nonexistent_server(self):
- # Resize a non-existent server
+ """Resizing a non-existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.resize_server,
@@ -140,7 +142,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_with_non_existent_flavor(self):
- # Resize a server with non-existent flavor
+ """Resizing a server with non existent flavor should fail"""
nonexistent_flavor = data_utils.rand_uuid()
self.assertRaises(lib_exc.BadRequest, self.client.resize_server,
self.server_id, flavor_ref=nonexistent_flavor)
@@ -150,14 +152,14 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_with_null_flavor(self):
- # Resize a server with null flavor
+ """Resizing a server with null flavor should fail"""
self.assertRaises(lib_exc.BadRequest, self.client.resize_server,
self.server_id, flavor_ref="")
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d4c023a0-9c55-4747-9dd5-413b820143c7')
def test_reboot_non_existent_server(self):
- # Reboot a non existent server
+ """Rebooting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
nonexistent_server, type='SOFT')
@@ -167,7 +169,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_pause_paused_server(self):
- # Pause a paused server.
+ """Pausing a paused server should fail"""
self.client.pause_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED')
self.assertRaises(lib_exc.Conflict,
@@ -178,7 +180,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('98fa0458-1485-440f-873b-fe7f0d714930')
def test_rebuild_deleted_server(self):
- # Rebuild a deleted server
+ """Rebuilding a deleted server should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
self.deleted_server_id, self.image_ref)
@@ -187,14 +189,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
def test_reboot_deleted_server(self):
- # Reboot a deleted server
+ """Rebooting a deleted server should fail"""
self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
self.deleted_server_id, type='SOFT')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d86141a7-906e-4731-b187-d64a2ea61422')
def test_rebuild_non_existent_server(self):
- # Rebuild a non existent server
+ """Rebuilding a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
@@ -204,6 +206,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fd57f159-68d6-4c2a-902b-03070828a87e')
def test_create_numeric_server_name(self):
+ """Creating a server with numeric server name should fail"""
server_name = 12345
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -212,8 +215,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c3e0fb12-07fc-4d76-a22e-37409887afe8')
def test_create_server_name_length_exceeds_256(self):
- # Create a server with name length exceeding 255 characters
+ """Creating a server with name length exceeding limit should fail
+ Create a server with name length exceeding 255 characters, an error is
+ returned.
+ """
server_name = 'a' * 256
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -224,6 +230,11 @@
@utils.services('volume')
@decorators.idempotent_id('12146ac1-d7df-4928-ad25-b1f99e5286cd')
def test_create_server_invalid_bdm_in_2nd_dict(self):
+ """Creating a server with invalid block_device_mapping_v2 should fail
+
+ Create a server with invalid block_device_mapping_v2, an error is
+ returned.
+ """
volume = self.create_volume()
bdm_1st = {"source_type": "image",
"delete_on_termination": True,
@@ -243,10 +254,9 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4e72dc2d-44c5-4336-9667-f7972e95c402')
def test_create_with_invalid_network_uuid(self):
+ """Creating a server with invalid network uuid should fail"""
# Pass invalid network uuid while creating a server
-
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
-
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
networks=networks)
@@ -254,8 +264,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7a2efc39-530c-47de-b875-2dd01c8d39bd')
def test_create_with_non_existent_keypair(self):
+ """Creating a server with non-existent keypair should fail"""
# Pass a non-existent keypair while creating a server
-
key_name = data_utils.rand_name('key')
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -264,8 +274,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fc74810-0bd2-4cd7-8244-4f33a9db865a')
def test_create_server_metadata_exceeds_length_limit(self):
+ """Creating a server with metadata longer than limit should fail """
# Pass really long metadata while creating a server
-
metadata = {'a': 'b' * 260}
self.assertRaises((lib_exc.BadRequest, lib_exc.OverLimit),
self.create_test_server,
@@ -274,8 +284,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aa8eed43-e2cb-4ebf-930b-da14f6a21d81')
def test_update_name_of_non_existent_server(self):
- # Update name of a non-existent server
-
+ """Updating name of a non-existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
new_name = data_utils.rand_name(
self.__class__.__name__ + '-server') + '_updated'
@@ -286,18 +295,19 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('38204696-17c6-44da-9590-40f87fb5a899')
def test_update_server_set_empty_name(self):
- # Update name of the server to an empty string
-
+ """Updating name of the server to an empty string should fail"""
new_name = ''
-
self.assertRaises(lib_exc.BadRequest, self.client.update_server,
self.server_id, name=new_name)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c8e244c-dada-4590-9944-749c455b431f')
def test_update_server_name_length_exceeds_256(self):
- # Update name of server exceed the name length limit
+ """Updating name of server exceeding the name length limit should fail
+ Update name of server exceeding the name length limit, an error is
+ returned.
+ """
new_name = 'a' * 256
self.assertRaises(lib_exc.BadRequest,
self.client.update_server,
@@ -307,8 +317,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1041b4e6-514b-4855-96a5-e974b60870a3')
def test_delete_non_existent_server(self):
- # Delete a non existent server
-
+ """Deleting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
nonexistent_server)
@@ -316,23 +325,24 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('75f79124-277c-45e6-a373-a1d6803f4cc4')
def test_delete_server_pass_negative_id(self):
- # Pass an invalid string parameter to delete server
-
+ """Passing an invalid string parameter to delete server should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_server, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f4d7279b-5fd2-4bf2-9ba4-ae35df0d18c5')
def test_delete_server_pass_id_exceeding_length_limit(self):
- # Pass a server ID that exceeds length limit to delete server
+ """Deleting server with a server ID exceeding length limit should fail
+ Pass a server ID that exceeds length limit to delete server, an error
+ is returned.
+ """
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
sys.maxsize + 1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c5fa6041-80cd-483b-aa6d-4e45f19d093c')
def test_create_with_nonexistent_security_group(self):
- # Create a server with a nonexistent security group
-
+ """Creating a server with a nonexistent security group should fail"""
security_groups = [{'name': 'does_not_exist'}]
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -341,7 +351,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3436b02f-1b1e-4f03-881e-c6a602327439')
def test_get_non_existent_server(self):
- # Get a non existent server details
+ """Getting a non existent server details should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.show_server,
nonexistent_server)
@@ -349,7 +359,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a31460a9-49e1-42aa-82ee-06e0bb7c2d03')
def test_stop_non_existent_server(self):
- # Stop a non existent server
+ """Stopping a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.servers_client.stop_server,
nonexistent_server)
@@ -359,7 +369,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_pause_non_existent_server(self):
- # pause a non existent server
+ """Pausing a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.pause_server,
nonexistent_server)
@@ -369,7 +379,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_unpause_non_existent_server(self):
- # unpause a non existent server
+ """Unpausing a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.unpause_server,
nonexistent_server)
@@ -379,7 +389,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_unpause_server_invalid_state(self):
- # unpause an active server.
+ """Unpausing an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.unpause_server,
self.server_id)
@@ -389,7 +399,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_suspend_non_existent_server(self):
- # suspend a non existent server
+ """Suspending a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.suspend_server,
nonexistent_server)
@@ -399,7 +409,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_suspend_server_invalid_state(self):
- # suspend a suspended server.
+ """Suspending a suspended server should fail"""
self.client.suspend_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id,
'SUSPENDED')
@@ -413,7 +423,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_resume_non_existent_server(self):
- # resume a non existent server
+ """Resuming a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.resume_server,
nonexistent_server)
@@ -423,7 +433,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_resume_server_invalid_state(self):
- # resume an active server.
+ """Resuming an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.resume_server,
self.server_id)
@@ -431,7 +441,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7dd919e7-413f-4198-bebb-35e2a01b13e9')
def test_get_console_output_of_non_existent_server(self):
- # get the console output for a non existent server
+ """Getting the console output for a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.get_console_output,
@@ -440,7 +450,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6f47992b-5144-4250-9f8b-f00aa33950f3')
def test_force_delete_nonexistent_server_id(self):
- # force-delete a non existent server
+ """Force-deleting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.force_delete_server,
@@ -449,7 +459,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c6d38cc-fcfb-437a-85b9-7b788af8bf01')
def test_restore_nonexistent_server_id(self):
- # restore-delete a non existent server
+ """Restore-deleting a non existent server should fail
+
+ We can restore a soft deleted server, but can't restore a non
+ existent server.
+ """
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.restore_soft_deleted_server,
@@ -458,7 +472,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fcadfab-bd6a-4753-8db7-4a51e51aade9')
def test_restore_server_invalid_state(self):
- # we can only restore-delete a server in 'soft-delete' state
+ """Restore-deleting a server not in 'soft-delete' state should fail
+
+ We can restore a soft deleted server, but can't restore a server that
+ is not in 'soft-delete' state.
+ """
self.assertRaises(lib_exc.Conflict,
self.client.restore_soft_deleted_server,
self.server_id)
@@ -468,7 +486,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_shelve_non_existent_server(self):
- # shelve a non existent server
+ """Shelving a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.shelve_server,
nonexistent_server)
@@ -478,7 +496,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_shelve_shelved_server(self):
- # shelve a shelved server.
+ """Shelving a shelved server should fail"""
compute.shelve_server(self.client, self.server_id)
def _unshelve_server():
@@ -508,7 +526,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_unshelve_non_existent_server(self):
- # unshelve a non existent server
+ """Unshelving a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.unshelve_server,
nonexistent_server)
@@ -518,7 +536,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_unshelve_server_invalid_state(self):
- # unshelve an active server.
+ """Unshelving an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.unshelve_server,
self.server_id)
@@ -527,7 +545,7 @@
@decorators.idempotent_id('74085be3-a370-4ca2-bc51-2d0e10e0f573')
@utils.services('volume', 'image')
def test_create_server_from_non_bootable_volume(self):
- # Create a volume
+ """Creating a server from a non bootable volume should fail"""
volume = self.create_volume()
# Update volume bootable status to false
@@ -555,6 +573,8 @@
class ServersNegativeTestMultiTenantJSON(base.BaseV2ComputeTest):
+ """Negative tests of servers for multiple projects"""
+
create_default_network = True
credentials = ['primary', 'alt']
@@ -581,8 +601,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('543d84c1-dd2e-4c6d-8cb2-b9da0efaa384')
def test_update_server_of_another_tenant(self):
- # Update name of a server that belongs to another tenant
+ """Updating server that belongs to another project should fail
+ Update name of a server that belongs to another project, an error is
+ returned.
+ """
new_name = self.server_id + '_new'
self.assertRaises(lib_exc.NotFound,
self.alt_client.update_server, self.server_id,
@@ -591,7 +614,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c75009d-3eea-423e-bea3-61b09fd25f9c')
def test_delete_a_server_of_another_tenant(self):
- # Delete a server that belongs to another tenant
+ """Deleting a server that belongs to another project should fail"""
self.assertRaises(lib_exc.NotFound,
self.alt_client.delete_server,
self.server_id)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 97813a5..d85e4f7 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -59,13 +59,17 @@
class AttachVolumeTestJSON(BaseAttachVolumeTest):
+ """Test attaching volume to server"""
@decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
# This test is conditionally marked slow if SSH validation is enabled.
@decorators.attr(type='slow', condition=CONF.validation.run_validation)
def test_attach_detach_volume(self):
- # Stop and Start a server with an attached volume, ensuring that
- # the volume remains attached.
+ """Test attaching and detaching volume from server
+
+ Stop and Start a server with an attached volume, ensuring that
+ the volume remains attached.
+ """
server, validation_resources = self._create_server()
# NOTE(andreaf) Create one remote client used throughout the test.
@@ -125,6 +129,13 @@
@decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
def test_list_get_volume_attachments(self):
+ """Test listing and getting volume attachments
+
+ First we attach one volume to the server, check listing and getting
+ the volume attachment of the server. Then we attach another volume to
+ the server, check listing and getting the volume attachments of the
+ server. Finally we detach the volumes from the server one by one.
+ """
# List volume attachment of the server
server, validation_resources = self._create_server()
volume_1st = self.create_volume()
@@ -244,8 +255,12 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
def test_attach_volume_shelved_or_offload_server(self):
- # Create server, count number of volumes on it, shelve
- # server and attach pre-created volume to shelved server
+ """Test attaching volume to shelved server
+
+ Create server, count number of volumes on it, shelve
+ server and attach pre-created volume to shelved server, then
+ unshelve the server and check that attached volume exists.
+ """
server, validation_resources = self._create_server()
volume = self.create_volume()
num_vol = self._count_volumes(server, validation_resources)
@@ -271,8 +286,12 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
def test_detach_volume_shelved_or_offload_server(self):
- # Count number of volumes on instance, shelve
- # server and attach pre-created volume to shelved server
+ """Test detaching volume from shelved server
+
+ Count number of volumes on server, shelve server and attach
+ pre-created volume to shelved server, then detach the volume, unshelve
+ the instance and check that we have the expected number of volume(s).
+ """
server, validation_resources = self._create_server()
volume = self.create_volume()
num_vol = self._count_volumes(server, validation_resources)
@@ -291,6 +310,12 @@
class AttachVolumeMultiAttachTest(BaseAttachVolumeTest):
+ """Test attaching one volume to multiple servers
+
+ Test attaching one volume to multiple servers with compute
+ microversion greater than 2.59.
+ """
+
min_microversion = '2.60'
max_microversion = 'latest'
@@ -367,6 +392,12 @@
@decorators.idempotent_id('8d5853f7-56e7-4988-9b0c-48cea3c7049a')
def test_list_get_volume_attachments_multiattach(self):
+ """Test listing and getting multiattached volume attachments
+
+ Attach a single volume to two servers, list attachments from the
+ volume and make sure the server uuids are in the list, then detach
+ the volume from servers one by one.
+ """
# Attach a single volume to two servers.
servers, volume, attachments = self._create_and_multiattach()
@@ -448,7 +479,10 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_resize_server_with_multiattached_volume(self):
- # Attach a single volume to multiple servers, then resize the servers
+ """Test resizing servers with multiattached volume
+
+ Attach a single volume to multiple servers, then resize the servers
+ """
servers, volume, _ = self._create_and_multiattach()
for server in servers:
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index f3b7494..3c71ba9 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -20,6 +20,7 @@
class RolesNegativeTestJSON(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone roles via v2 API"""
def _get_role_params(self):
user = self.setup_test_user()
@@ -30,14 +31,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d5d5f1df-f8ca-4de0-b2ef-259c1cc67025')
def test_list_roles_by_unauthorized_user(self):
- # Non-administrator user should not be able to list roles
+ """Test Non-admin user should not be able to list roles via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_roles_client.list_roles)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('11a3c7da-df6c-40c2-abc2-badd682edf9f')
def test_list_roles_request_without_token(self):
- # Request to list roles without a valid token should fail
+ """Test listing roles without a valid token via v2 API should fail"""
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized, self.roles_client.list_roles)
@@ -46,14 +47,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c0b89e56-accc-4c73-85f8-9c0f866104c1')
def test_role_create_blank_name(self):
- # Should not be able to create a role with a blank name
+ """Test creating a role with a blank name via v2 API is not allowed"""
self.assertRaises(lib_exc.BadRequest, self.roles_client.create_role,
name='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('585c8998-a8a4-4641-a5dd-abef7a8ced00')
def test_create_role_by_unauthorized_user(self):
- # Non-administrator user should not be able to create role
+ """Test non-admin user should not be able to create role via v2 API"""
role_name = data_utils.rand_name(name='role')
self.assertRaises(lib_exc.Forbidden,
self.non_admin_roles_client.create_role,
@@ -62,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a7edd17a-e34a-4aab-8bb7-fa6f498645b8')
def test_create_role_request_without_token(self):
- # Request to create role without a valid token should fail
+ """Test creating role without a valid token via v2 API should fail"""
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
role_name = data_utils.rand_name(name='role')
@@ -73,7 +74,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c0cde2c8-81c1-4bb0-8fe2-cf615a3547a8')
def test_role_create_duplicate(self):
- # Role names should be unique
+ """Test role names should be unique via v2 API"""
role_name = data_utils.rand_name(name='role-dup')
body = self.roles_client.create_role(name=role_name)['role']
role1_id = body.get('id')
@@ -84,7 +85,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('15347635-b5b1-4a87-a280-deb2bd6d865e')
def test_delete_role_by_unauthorized_user(self):
- # Non-administrator user should not be able to delete role
+ """Test non-admin user should not be able to delete role via v2 API"""
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
self.addCleanup(self.roles_client.delete_role, body['id'])
@@ -95,7 +96,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('44b60b20-70de-4dac-beaf-a3fc2650a16b')
def test_delete_role_request_without_token(self):
- # Request to delete role without a valid token should fail
+ """Test deleting role without a valid token via v2 API should fail"""
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
self.addCleanup(self.roles_client.delete_role, body['id'])
@@ -110,7 +111,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('38373691-8551-453a-b074-4260ad8298ef')
def test_delete_role_non_existent(self):
- # Attempt to delete a non existent role should fail
+ """Test deleting a non existent role via v2 API should fail"""
non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role,
non_existent_role)
@@ -118,8 +119,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('391df5cf-3ec3-46c9-bbe5-5cb58dd4dc41')
def test_assign_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # assign a role to user
+ """Test non-admin user assigning a role to user via v2 API
+
+ Non-admin user should not be authorized to assign a role to user via
+ v2 API.
+ """
(user, tenant, role) = self._get_role_params()
self.assertRaises(
lib_exc.Forbidden,
@@ -129,7 +133,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f0d2683c-5603-4aee-95d7-21420e87cfd8')
def test_assign_user_role_request_without_token(self):
- # Request to assign a role to a user without a valid token
+ """Test assigning a role to a user without a valid token via v2 API
+
+ Assigning a role to a user without a valid token via v2 API should
+ fail.
+ """
(user, tenant, role) = self._get_role_params()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -142,7 +150,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('99b297f6-2b5d-47c7-97a9-8b6bb4f91042')
def test_assign_user_role_for_non_existent_role(self):
- # Attempt to assign a non existent role to user should fail
+ """Test assigning a non existent role to user via v2 API
+
+ Assigning a non existent role to user via v2 API should fail.
+ """
(user, tenant, _) = self._get_role_params()
non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
@@ -152,7 +163,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b2285aaa-9e76-4704-93a9-7a8acd0a6c8f')
def test_assign_user_role_for_non_existent_tenant(self):
- # Attempt to assign a role on a non existent tenant should fail
+ """Test assigning a role on a non existent tenant via v2 API
+
+ Assigning a role on a non existent tenant via v2 API should fail.
+ """
(user, _, role) = self._get_role_params()
non_existent_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
@@ -162,7 +176,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c3132cd-c4c8-4402-b5ea-71eb44e97793')
def test_assign_duplicate_user_role(self):
- # Duplicate user role should not get assigned
+ """Test duplicate user role should not get assigned via v2 API"""
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -174,8 +188,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d0537987-0977-448f-a435-904c15de7298')
def test_remove_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # remove a user's role
+ """Test non-admin user removing a user's role via v2 API
+
+ Non-admin user should not be authorized to remove a user's role via
+ v2 API
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -188,7 +205,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('cac81cf4-c1d2-47dc-90d3-f2b7eb572286')
def test_remove_user_role_request_without_token(self):
- # Request to remove a user's role without a valid token
+ """Test removing a user's role without a valid token via v2 API
+
+ Removing a user's role without a valid token via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -203,7 +223,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ab32d759-cd16-41f1-a86e-44405fa9f6d2')
def test_remove_user_role_non_existent_role(self):
- # Attempt to delete a non existent role from a user should fail
+ """Test deleting a non existent role from a user via v2 API
+
+ Deleting a non existent role from a user via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -216,7 +239,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('67a679ec-03dd-4551-bbfc-d1c93284f023')
def test_remove_user_role_non_existent_tenant(self):
- # Attempt to remove a role from a non existent tenant should fail
+ """Test removing a role from a non existent tenant via v2 API
+
+ Removing a role from a non existent tenant via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -229,8 +255,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7391ab4c-06f3-477a-a64a-c8e55ce89837')
def test_list_user_roles_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to list
- # a user's roles
+ """Test non-admin user listing a user's roles via v2 API
+
+ Non-admin user should not be authorized to list a user's roles via v2
+ API.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -243,7 +272,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('682adfb2-fd5f-4b0a-a9ca-322e9bebb907')
def test_list_user_roles_request_without_token(self):
- # Request to list user's roles without a valid token should fail
+ """Test listing user's roles without a valid token via v2 API
+
+ Listing user's roles without a valid token via v2 API should fail
+ """
(user, tenant, _) = self._get_role_params()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/v2/test_tenant_negative.py b/tempest/api/identity/admin/v2/test_tenant_negative.py
index 49bb949..792dad9 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -20,18 +20,22 @@
class TenantsNegativeTestJSON(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone tenants via v2 API"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ca9bb202-63dd-4240-8a07-8ef9c19c04bb')
def test_list_tenants_by_unauthorized_user(self):
- # Non-administrator user should not be able to list tenants
+ """Test Non-admin should not be able to list tenants via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.list_tenants)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('df33926c-1c96-4d8d-a762-79cc6b0c3cf4')
def test_list_tenant_request_without_token(self):
- # Request to list tenants without a valid token should fail
+ """Test listing tenants without a valid token via v2 API
+
+ Listing tenants without a valid token via v2 API should fail.
+ """
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized,
@@ -41,7 +45,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('162ba316-f18b-4987-8c0c-fd9140cd63ed')
def test_tenant_delete_by_unauthorized_user(self):
- # Non-administrator user should not be able to delete a tenant
+ """Test non-admin should not be able to delete a tenant via v2 API"""
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.delete_tenant,
@@ -50,7 +54,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e450db62-2e9d-418f-893a-54772d6386b1')
def test_tenant_delete_request_without_token(self):
- # Request to delete a tenant without a valid token should fail
+ """Test deleting a tenant without a valid token via v2 API
+
+ Deleting a tenant without a valid token via v2 API should fail.
+ """
tenant = self.setup_test_tenant()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -62,14 +69,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c9a2aed-6e3c-467a-8f5c-89da9d1b516b')
def test_delete_non_existent_tenant(self):
- # Attempt to delete a non existent tenant should fail
+ """Test deleting a non existent tenant via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.tenants_client.delete_tenant,
data_utils.rand_uuid_hex())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af16f44b-a849-46cb-9f13-a751c388f739')
def test_tenant_create_duplicate(self):
- # Tenant names should be unique
+ """Test tenant names should be unique via v2 API"""
tenant_name = data_utils.rand_name(name='tenant')
self.setup_test_tenant(name=tenant_name)
self.assertRaises(lib_exc.Conflict, self.tenants_client.create_tenant,
@@ -78,7 +85,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d26b278a-6389-4702-8d6e-5980d80137e0')
def test_create_tenant_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to create a tenant
+ """Test non-admin user creating a tenant via v2 API
+
+ Non-admin user should not be authorized to create a tenant via v2 API.
+ """
tenant_name = data_utils.rand_name(name='tenant')
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.create_tenant,
@@ -87,7 +97,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a3ee9d7e-6920-4dd5-9321-d4b2b7f0a638')
def test_create_tenant_request_without_token(self):
- # Create tenant request without a token should not be authorized
+ """Test creating tenant without a token via v2 API is not allowed"""
tenant_name = data_utils.rand_name(name='tenant')
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -99,7 +109,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5a2e4ca9-b0c0-486c-9c48-64a94fba2395')
def test_create_tenant_with_empty_name(self):
- # Tenant name should not be empty
+ """Test tenant name should not be empty via v2 API"""
self.assertRaises(lib_exc.BadRequest,
self.tenants_client.create_tenant,
name='')
@@ -107,7 +117,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2ff18d1e-dfe3-4359-9dc3-abf582c196b9')
def test_create_tenants_name_length_over_64(self):
- # Tenant name length should not be greater than 64 characters
+ """Test tenant name length should not exceed 64 via v2 API"""
tenant_name = 'a' * 65
self.assertRaises(lib_exc.BadRequest,
self.tenants_client.create_tenant,
@@ -116,14 +126,17 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bd20dc2a-9557-4db7-b755-f48d952ad706')
def test_update_non_existent_tenant(self):
- # Attempt to update a non existent tenant should fail
+ """Test updating a non existent tenant via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.tenants_client.update_tenant,
data_utils.rand_uuid_hex())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41704dc5-c5f7-4f79-abfa-76e6fedc570b')
def test_tenant_update_by_unauthorized_user(self):
- # Non-administrator user should not be able to update a tenant
+ """Test non-admin user updating a tenant via v2 API
+
+ Non-admin user should not be able to update a tenant via v2 API
+ """
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.update_tenant,
@@ -132,7 +145,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7a421573-72c7-4c22-a98e-ce539219c657')
def test_tenant_update_request_without_token(self):
- # Request to update a tenant without a valid token should fail
+ """Test updating a tenant without a valid token via v2 API
+
+ Updating a tenant without a valid token via v2 API should fail
+ """
tenant = self.setup_test_tenant()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index 6ce1a8b..5d89f9d 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -23,9 +23,11 @@
class TokensTestJSON(base.BaseIdentityV2AdminTest):
+ """Test keystone tokens via v2 API"""
@decorators.idempotent_id('453ad4d5-e486-4b2f-be72-cffc8149e586')
def test_create_check_get_delete_token(self):
+ """Test getting create/check/get/delete token for user via v2 API"""
# get a token by username and password
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
@@ -59,7 +61,7 @@
@decorators.idempotent_id('25ba82ee-8a32-4ceb-8f50-8b8c71e8765e')
def test_rescope_token(self):
- """An unscoped token can be requested
+ """Test an unscoped token can be requested via v2 API
That token can be used to request a scoped token.
"""
@@ -112,6 +114,7 @@
@decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31')
def test_list_endpoints_for_token(self):
+ """Test listing endpoints for token via v2 API"""
tempest_services = ['keystone', 'nova', 'neutron', 'swift', 'cinder',
'neutron']
# get a token for the user
diff --git a/tempest/api/identity/admin/v2/test_tokens_negative.py b/tempest/api/identity/admin/v2/test_tokens_negative.py
index eb3e365..f2e41ff 100644
--- a/tempest/api/identity/admin/v2/test_tokens_negative.py
+++ b/tempest/api/identity/admin/v2/test_tokens_negative.py
@@ -19,12 +19,17 @@
class TokensAdminTestNegative(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone tokens via v2 API"""
credentials = ['primary', 'admin', 'alt']
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a0a0a600-4292-4364-99c5-922c834fdf05')
def test_check_token_existence_negative(self):
+ """Test checking other tenant's token existence via v2 API
+
+ Checking other tenant's token existence via v2 API should fail.
+ """
creds = self.os_primary.credentials
creds_alt = self.os_alt.credentials
username = creds.username
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 0d98af5..57a321a 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -23,6 +23,7 @@
class UsersTestJSON(base.BaseIdentityV2AdminTest):
+ """Test keystone users via v2 API"""
@classmethod
def resource_setup(cls):
@@ -33,14 +34,14 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2d55a71e-da1d-4b43-9c03-d269fd93d905')
def test_create_user(self):
- # Create a user
+ """Test creating a user via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(name=self.alt_user, tenantId=tenant['id'])
self.assertEqual(self.alt_user, user['name'])
@decorators.idempotent_id('89d9fdb8-15c2-4304-a429-48715d0af33d')
def test_create_user_with_enabled(self):
- # Create a user with enabled : False
+ """Test creating a user with enabled : False via v2 API"""
tenant = self.setup_test_tenant()
name = data_utils.rand_name('test_user')
user = self.create_test_user(name=name,
@@ -53,7 +54,7 @@
@decorators.idempotent_id('39d05857-e8a5-4ed4-ba83-0b52d3ab97ee')
def test_update_user(self):
- # Test case to check if updating of user attributes is successful.
+ """Test updating user attributes via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(tenantId=tenant['id'])
@@ -75,14 +76,14 @@
@decorators.idempotent_id('29ed26f4-a74e-4425-9a85-fdb49fa269d2')
def test_delete_user(self):
- # Delete a user
+ """Test deleting a user via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(tenantId=tenant['id'])
self.users_client.delete_user(user['id'])
@decorators.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1')
def test_user_authentication(self):
- # Valid user's token is authenticated
+ """Test that valid user's token is authenticated via v2 API"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -97,6 +98,7 @@
@decorators.idempotent_id('5d1fa498-4c2d-4732-a8fe-2b054598cfdd')
def test_authentication_request_without_token(self):
+ """Test authentication request without token via v2 API"""
# Request for token authentication with a valid token in header
password = data_utils.rand_password()
user = self.setup_test_user(password)
@@ -116,7 +118,10 @@
@decorators.idempotent_id('a149c02e-e5e0-4b89-809e-7e8faf33ccda')
def test_get_users(self):
- # Get a list of users and find the test user
+ """Test getting users via v2 API
+
+ Get a list of users and find the test user
+ """
user = self.setup_test_user()
users = self.users_client.list_users()['users']
self.assertThat([u['name'] for u in users],
@@ -125,7 +130,7 @@
@decorators.idempotent_id('6e317209-383a-4bed-9f10-075b7c82c79a')
def test_list_users_for_tenant(self):
- # Return a list of all users for a tenant
+ """Test returning a list of all users for a tenant via v2 API"""
tenant = self.setup_test_tenant()
user_ids = list()
fetched_user_ids = list()
@@ -147,7 +152,7 @@
@decorators.idempotent_id('a8b54974-40e1-41c0-b812-50fc90827971')
def test_list_users_with_roles_for_tenant(self):
- # Return list of users on tenant when roles are assigned to users
+ """Test listing users on tenant with roles assigned via v2 API"""
user = self.setup_test_user()
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
role = self.setup_test_role()
@@ -175,7 +180,7 @@
@decorators.idempotent_id('1aeb25ac-6ec5-4d8b-97cb-7ac3567a989f')
def test_update_user_password(self):
- # Test case to check if updating of user password is successful.
+ """Test updating of user password via v2 API"""
user = self.setup_test_user()
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
# Updating the user with new password
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index c8c0151..63e456e 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -20,6 +20,8 @@
class RegionsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test regions"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -44,6 +46,7 @@
@decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
def test_create_update_get_delete_region(self):
+ """Test creating, updating, getting and updating region"""
# Create region
r_description = data_utils.rand_name('description')
region = self.client.create_region(
@@ -81,7 +84,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2c12c5b5-efcf-4aa5-90c5-bff1ab0cdbe2')
def test_create_region_with_specific_id(self):
- # Create a region with a specific id
+ """Test creating region with specific id"""
r_region_id = data_utils.rand_uuid()
r_description = data_utils.rand_name('description')
region = self.client.create_region(
@@ -93,7 +96,7 @@
@decorators.idempotent_id('d180bf99-544a-445c-ad0d-0c0d27663796')
def test_list_regions(self):
- # Get a list of regions
+ """Test getting a list of regions"""
fetched_regions = self.client.list_regions()['regions']
missing_regions =\
[e for e in self.setup_regions if e not in fetched_regions]
@@ -104,6 +107,7 @@
@decorators.idempotent_id('2d1057cb-bbde-413a-acdf-e2d265284542')
def test_list_regions_filter_by_parent_region_id(self):
+ """Test listing regions filtered by parent region id"""
# Add a sub-region to one of the existing test regions
r_description = data_utils.rand_name('description')
region = self.client.create_region(
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 5ba4c9f..dd7d5af 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,6 +25,8 @@
class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test roles"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -75,6 +77,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
def test_role_create_update_show_list(self):
+ """Test creating, updating, showing and listing a role"""
r_name = data_utils.rand_name('Role')
role = self.roles_client.create_role(name=r_name)['role']
self.addCleanup(self.roles_client.delete_role, role['id'])
@@ -101,6 +104,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_user_on_project(self):
+ """Test granting, listing, revoking role to user on project"""
self.roles_client.create_user_role_on_project(self.project['id'],
self.user_body['id'],
self.role['id'])
@@ -122,6 +126,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_user_on_domain(self):
+ """Test granting, listing, revoking role to user on domain"""
self.roles_client.create_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@@ -142,6 +147,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_group_on_project(self):
+ """Test granting, listing, revoking role to group on project"""
# Grant role to group on project
self.roles_client.create_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
@@ -175,6 +181,7 @@
@decorators.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
def test_grant_list_revoke_role_to_group_on_domain(self):
+ """Test granting, listing, revoking role to group on domain"""
self.roles_client.create_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
@@ -192,6 +199,7 @@
@decorators.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
def test_list_roles(self):
+ """Test listing roles"""
# Return a list of all roles
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.roles]
@@ -215,6 +223,7 @@
@decorators.idempotent_id('c90c316c-d706-4728-bcba-eb1912081b69')
def test_implied_roles_create_check_show_delete(self):
+ """Test creating, checking, showing and deleting implied roles"""
prior_role_id = self.roles[0]['id']
implies_role_id = self.roles[1]['id']
@@ -248,6 +257,7 @@
@decorators.idempotent_id('dc6f5959-b74d-4e30-a9e5-a8255494ff00')
def test_roles_hierarchy(self):
+ """Test creating implied role and listing role inferences rules"""
# Create inference rule from "roles[0]" to "role[1]"
self._create_implied_role(
self.roles[0]['id'], self.roles[1]['id'])
@@ -280,6 +290,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_assignments_for_implied_roles_create_delete(self):
+ """Test assignments when implied roles are created and deleted"""
# Create a grant using "roles[0]"
self.roles_client.create_user_role_on_project(
self.project['id'], self.user_body['id'], self.roles[0]['id'])
@@ -321,6 +332,7 @@
@decorators.idempotent_id('d92a41d2-5501-497a-84bb-6e294330e8f8')
def test_domain_roles_create_delete(self):
+ """Test creating, listing and deleting domain roles"""
domain_role = self.roles_client.create_role(
name=data_utils.rand_name('domain_role'),
domain_id=self.domain['id'])['role']
@@ -341,6 +353,7 @@
@decorators.idempotent_id('eb1e1c24-1bc4-4d47-9748-e127a1852c82')
def test_implied_domain_roles(self):
+ """Test creating implied roles when roles are in domains"""
# Create two roles in the same domain
domain_role1 = self.setup_test_role(domain_id=self.domain['id'])
domain_role2 = self.setup_test_role(domain_id=self.domain['id'])
@@ -373,6 +386,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_assignments_for_domain_roles(self):
+ """Test assignments for domain roles"""
domain_role = self.setup_test_role(domain_id=self.domain['id'])
# Create a grant using "domain_role"
@@ -395,6 +409,7 @@
@decorators.idempotent_id('3748c316-c18f-4b08-997b-c60567bc6235')
def test_list_all_implied_roles(self):
+ """Test listing all implied roles"""
# Create inference rule from "roles[0]" to "roles[1]"
self._create_implied_role(
self.roles[0]['id'], self.roles[1]['id'])
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 5f1b58d..f3a7471 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -24,6 +24,7 @@
class TokensV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test tokens"""
credentials = ['primary', 'admin', 'alt']
@@ -123,6 +124,7 @@
@decorators.idempotent_id('08ed85ce-2ba8-4864-b442-bcc61f16ae89')
def test_get_available_project_scopes(self):
+ """Test getting available project scopes"""
manager_project_id = self.os_primary.credentials.project_id
admin_user_id = self.os_admin.credentials.user_id
admin_role_id = self.get_role_by_name(CONF.identity.admin_role)['id']
@@ -152,10 +154,13 @@
@decorators.idempotent_id('ec5ecb05-af64-4c04-ac86-4d9f6f12f185')
def test_get_available_domain_scopes(self):
- # Test for verifying that listing domain scopes for a user works if
- # the user has a domain role or belongs to a group that has a domain
- # role. For this test, admin client is used to add roles to alt user,
- # which performs API calls, to avoid 401 Unauthorized errors.
+ """Test getting available domain scopes
+
+ To verify that listing domain scopes for a user works if
+ the user has a domain role or belongs to a group that has a domain
+ role. For this test, admin client is used to add roles to alt user,
+ which performs API calls, to avoid 401 Unauthorized errors.
+ """
alt_user_id = self.os_alt.credentials.user_id
def _create_user_domain_role_for_alt_user():
diff --git a/tempest/api/identity/v2/test_api_discovery.py b/tempest/api/identity/v2/test_api_discovery.py
index 5b9d38c..afda104 100644
--- a/tempest/api/identity/v2/test_api_discovery.py
+++ b/tempest/api/identity/v2/test_api_discovery.py
@@ -18,11 +18,12 @@
class TestApiDiscovery(base.BaseIdentityV2Test):
- """Tests for API discovery features."""
+ """Tests for identity v2 API discovery features."""
@decorators.attr(type='smoke')
@decorators.idempotent_id('ea889a68-a15f-4166-bfb1-c12456eae853')
def test_api_version_resources(self):
+ """Test showing identity v2 api version resources"""
descr = self.non_admin_client.show_api_description()['version']
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -34,6 +35,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('007a0be0-78fe-4fdb-bbee-e9216cc17bb2')
def test_api_media_types(self):
+ """Test showing identity v2 api version media type"""
descr = self.non_admin_client.show_api_description()['version']
# Get MIME type bases and descriptions
media_types = [(media_type['base'], media_type['type']) for
@@ -49,6 +51,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('77fd6be0-8801-48e6-b9bf-38cdd2f253ec')
def test_api_version_statuses(self):
+ """Test showing identity v2 api version status"""
descr = self.non_admin_client.show_api_description()['version']
status = descr['status'].lower()
supported_statuses = ['current', 'stable', 'experimental',
diff --git a/tempest/api/identity/v2/test_extension.py b/tempest/api/identity/v2/test_extension.py
index c538c14..13555bd 100644
--- a/tempest/api/identity/v2/test_extension.py
+++ b/tempest/api/identity/v2/test_extension.py
@@ -18,10 +18,11 @@
class ExtensionTestJSON(base.BaseIdentityV2Test):
+ """Test extensions in identity v2 API"""
@decorators.idempotent_id('85f3f661-f54c-4d48-b563-72ae952b9383')
def test_list_extensions(self):
- # List all the extensions
+ """List all the identity extensions via v2 API"""
body = self.non_admin_client.list_extensions()['extensions']['values']
self.assertNotEmpty(body)
keys = ['name', 'updated', 'alias', 'links',
diff --git a/tempest/api/identity/v2/test_tenants.py b/tempest/api/identity/v2/test_tenants.py
index b2a6d13..1752b65 100644
--- a/tempest/api/identity/v2/test_tenants.py
+++ b/tempest/api/identity/v2/test_tenants.py
@@ -19,11 +19,13 @@
class IdentityTenantsTest(base.BaseIdentityV2Test):
+ """Test listing tenants in identity v2 API"""
credentials = ['primary', 'alt']
@decorators.idempotent_id('ecae2459-243d-4ba1-ad02-65f15dc82b78')
def test_list_tenants_returns_only_authorized_tenants(self):
+ """Test listing tenants only returns authorized tenants via v2 API"""
alt_tenant_name = self.os_alt.credentials.tenant_name
resp = self.non_admin_tenants_client.list_tenants()
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index 64b81c2..a928ad9 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -20,10 +20,11 @@
class TokensTest(base.BaseIdentityV2Test):
+ """Test tokens in identity v2 API"""
@decorators.idempotent_id('65ae3b78-91ff-467b-a705-f6678863b8ec')
def test_create_token(self):
-
+ """Test creating token for user via v2 API"""
token_client = self.non_admin_token_client
# get a token for the user
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 2eea860..a63b45c 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -28,6 +28,7 @@
class IdentityUsersTest(base.BaseIdentityV2Test):
+ """Test user password in identity v2 API"""
@classmethod
def resource_setup(cls):
@@ -85,6 +86,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_user_update_own_password(self):
+ """test updating user's own password via v2 API"""
old_pass = self.creds.password
old_token = self.non_admin_users_client.token
new_pass = data_utils.rand_password()
diff --git a/tempest/api/identity/v3/test_api_discovery.py b/tempest/api/identity/v3/test_api_discovery.py
index e87d1cd..ebb96fd 100644
--- a/tempest/api/identity/v3/test_api_discovery.py
+++ b/tempest/api/identity/v3/test_api_discovery.py
@@ -22,10 +22,11 @@
class TestApiDiscovery(base.BaseIdentityV3Test):
- """Tests for API discovery features."""
+ """Tests for identity API discovery features."""
@decorators.idempotent_id('79aec9ae-710f-4c54-a4fc-3aa25b4feac3')
def test_identity_v3_existence(self):
+ """Test that identity v3 version should exist"""
versions = self.non_admin_versions_client.list_versions()
found = any(
"v3" in version.get('id')
@@ -35,9 +36,12 @@
@decorators.idempotent_id('721f480f-35b6-46c7-846e-047e6acea0dc')
@decorators.attr(type='smoke')
def test_list_api_versions(self):
- # NOTE: Actually this API doesn't depend on v3 API at all, because
- # the API operation is "GET /" without v3's endpoint. The reason of
- # this test path is just v3 API is CURRENT on Keystone side.
+ """Test listing identity api versions
+
+ NOTE: Actually this API doesn't depend on v3 API at all, because
+ the API operation is "GET /" without v3's endpoint. The reason of
+ this test path is just v3 API is CURRENT on Keystone side.
+ """
versions = self.non_admin_versions_client.list_versions()
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -49,6 +53,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('b9232f5e-d9e5-4d97-b96c-28d3db4de1bd')
def test_api_version_resources(self):
+ """Test showing identity v3 api version resources"""
descr = self.non_admin_client.show_api_description()['version']
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -60,6 +65,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('657c1970-4722-4189-8831-7325f3bc4265')
def test_api_media_types(self):
+ """Test showing identity v3 api version media type"""
descr = self.non_admin_client.show_api_description()['version']
# Get MIME type bases and descriptions
media_types = [(media_type['base'], media_type['type']) for
@@ -75,6 +81,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('8879a470-abfb-47bb-bb8d-5a7fd279ad1e')
def test_api_version_statuses(self):
+ """Test showing identity v3 api version status"""
descr = self.non_admin_client.show_api_description()['version']
status = descr['status'].lower()
supported_statuses = ['current', 'stable', 'experimental',
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index 1cee902..ef1bbdf 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -23,6 +23,7 @@
class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
+ """Test application credentials"""
def _list_app_creds(self, name=None):
kwargs = dict(user_id=self.user_id)
@@ -33,6 +34,7 @@
@decorators.idempotent_id('8080c75c-eddc-4786-941a-c2da7039ae61')
def test_create_application_credential(self):
+ """Test creating application credential"""
app_cred = self.create_application_credential()
# Check that the secret appears in the create response
@@ -55,6 +57,7 @@
@decorators.idempotent_id('852daf0c-42b5-4239-8466-d193d0543ed3')
def test_create_application_credential_expires(self):
+ """Test creating application credential with expire time"""
expires_at = timeutils.utcnow() + datetime.timedelta(hours=1)
app_cred = self.create_application_credential(expires_at=expires_at)
@@ -64,6 +67,7 @@
@decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
def test_list_application_credentials(self):
+ """Test listing application credentials"""
self.create_application_credential()
self.create_application_credential()
@@ -72,6 +76,7 @@
@decorators.idempotent_id('9bb5e5cc-5250-493a-8869-8b665f6aa5f6')
def test_query_application_credentials(self):
+ """Test listing application credentials filtered by name"""
self.create_application_credential()
app_cred_two = self.create_application_credential()
app_cred_two_name = app_cred_two['name']
diff --git a/tempest/api/identity/v3/test_domains.py b/tempest/api/identity/v3/test_domains.py
index 9f132dd..bb62ea6 100644
--- a/tempest/api/identity/v3/test_domains.py
+++ b/tempest/api/identity/v3/test_domains.py
@@ -21,6 +21,7 @@
class DefaultDomainTestJSON(base.BaseIdentityV3Test):
+ """Test identity default domains"""
@classmethod
def setup_clients(cls):
@@ -35,5 +36,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
def test_default_domain_exists(self):
+ """Test showing default domain"""
domain = self.domains_client.show_domain(self.domain_id)['domain']
self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/v3/test_projects.py b/tempest/api/identity/v3/test_projects.py
index bbb4013..338b57b 100644
--- a/tempest/api/identity/v3/test_projects.py
+++ b/tempest/api/identity/v3/test_projects.py
@@ -19,11 +19,13 @@
class IdentityV3ProjectsTest(base.BaseIdentityV3Test):
+ """Test identity projects"""
credentials = ['primary', 'alt']
@decorators.idempotent_id('86128d46-e170-4644-866a-cc487f699e1d')
def test_list_projects_returns_only_authorized_projects(self):
+ """Test listing projects only returns authorized projects"""
alt_project_name = self.os_alt.credentials.project_name
resp = self.non_admin_users_client.list_user_projects(
self.os_primary.credentials.user_id)
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index cb05f39..b201285 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -24,9 +24,11 @@
class TokensV3Test(base.BaseIdentityV3Test):
+ """Test identity tokens"""
@decorators.idempotent_id('a9512ac3-3909-48a4-b395-11f438e16260')
def test_validate_token(self):
+ """Test validating token for user"""
creds = self.os_primary.credentials
user_id = creds.user_id
username = creds.username
@@ -69,7 +71,7 @@
@decorators.idempotent_id('6f8e4436-fc96-4282-8122-e41df57197a9')
def test_create_token(self):
-
+ """Test creating token for user"""
creds = self.os_primary.credentials
user_id = creds.user_id
username = creds.username
@@ -120,9 +122,12 @@
@decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
def test_token_auth_creation_existence_deletion(self):
- # Tests basic token auth functionality in a way that is compatible with
- # pre-provisioned credentials. The default user is used for token
- # authentication.
+ """Test auth/check existence/delete token for user
+
+ Tests basic token auth functionality in a way that is compatible with
+ pre-provisioned credentials. The default user is used for token
+ authentication.
+ """
# Valid user's token is authenticated
user = self.os_primary.credentials
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index d4e7612..6425ea9 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -28,6 +28,7 @@
class IdentityV3UsersTest(base.BaseIdentityV3Test):
+ """Test identity user password"""
@classmethod
def resource_setup(cls):
@@ -82,6 +83,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_user_update_own_password(self):
+ """Test updating user's own password"""
old_pass = self.creds.password
old_token = self.non_admin_client.token
new_pass = data_utils.rand_password()
@@ -111,6 +113,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_password_history_check_self_service_api(self):
+ """Test checking password changing history"""
old_pass = self.creds.password
new_pass1 = data_utils.rand_password()
new_pass2 = data_utils.rand_password()
@@ -141,6 +144,7 @@
'Security compliance not available.')
@decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
def test_user_account_lockout(self):
+ """Test locking out user account after failure attempts"""
if (CONF.identity.user_lockout_failure_attempts <= 0 or
CONF.identity.user_lockout_duration <= 0):
raise self.skipException(
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 3e72b34..c1a7211 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -40,19 +40,16 @@
"%s skipped as image import is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
- def test_image_import(self):
- """Here we test these functionalities
+ @classmethod
+ def resource_setup(cls):
+ super(ImportImagesTest, cls).resource_setup()
+ cls.available_import_methods = cls.client.info_import()[
+ 'import-methods']['value']
+ if not cls.available_import_methods:
+ raise cls.skipException('Server does not support '
+ 'any import method')
- Create image, stage image data, import image and verify
- that import succeeded.
- """
-
- body = self.client.info_import()
- if 'glance-direct' not in body['import-methods']['value']:
- raise self.skipException('Server does not support '
- 'glance-direct import method')
-
+ def _create_image(self):
# Create image
uuid = '00000000-1111-2222-3333-444455556666'
image_name = data_utils.rand_name('image')
@@ -69,21 +66,50 @@
self.assertEqual('private', image['visibility'])
self.assertIn('status', image)
self.assertEqual('queued', image['status'])
+ return image
+ @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
+ def test_image_glance_direct_import(self):
+ """Test 'glance-direct' import functionalities
+
+ Create image, stage image data, import image and verify
+ that import succeeded.
+ """
+ if 'glance-direct' not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ 'glance-direct import method')
+ image = self._create_image()
# Stage image data
file_content = data_utils.random_bytes()
image_file = six.BytesIO(file_content)
self.client.stage_image_file(image['id'], image_file)
+ # Check image status is 'uploading'
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('uploading', body['status'])
+ # import image from staging to backend
+ self.client.image_import(image['id'], method='glance-direct')
+ self.client.wait_for_resource_activation(image['id'])
+ @decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62')
+ def test_image_web_download_import(self):
+ """Test 'web-download' import functionalities
+
+ Create image, import image and verify that import
+ succeeded.
+ """
+ if 'web-download' not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ 'web-download import method')
+ image = self._create_image()
# Now try to get image details
body = self.client.show_image(image['id'])
self.assertEqual(image['id'], body['id'])
- self.assertEqual(image_name, body['name'])
- self.assertEqual(uuid, body['ramdisk_id'])
- self.assertEqual('uploading', body['status'])
-
- # import image from staging to backend
- self.client.image_import(image['id'])
+ self.assertEqual('queued', body['status'])
+ # import image from web to backend
+ image_uri = CONF.image.http_image
+ self.client.image_import(image['id'], method='web-download',
+ image_uri=image_uri)
self.client.wait_for_resource_activation(image['id'])
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 33e503f..ecddfba 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -108,5 +108,4 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
volume_id, 'available')
vol_info = self.volumes_client.show_volume(volume_id)['volume']
- self.assertIn('attachments', vol_info)
self.assertEmpty(vol_info['attachments'])
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 9edffc6..e42ea40 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -84,7 +84,6 @@
self.volume['id'], 'available')
self.addCleanup(self.volumes_client.detach_volume, self.volume['id'])
volume = self.volumes_client.show_volume(self.volume['id'])['volume']
- self.assertIn('attachments', volume)
attachment = volume['attachments'][0]
self.assertEqual('/dev/%s' %
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index ade2deb..91728ab 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -37,11 +37,9 @@
kwargs['name'] = v_name
kwargs['metadata'] = metadata
volume = self.volumes_client.create_volume(**kwargs)['volume']
- self.assertIn('id', volume)
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
- self.assertIn('name', volume)
self.assertEqual(volume['name'], v_name,
"The created volume name is not equal "
"to the requested name")
@@ -101,7 +99,6 @@
'availability_zone': volume['availability_zone'],
'size': CONF.volume.volume_size}
new_volume = self.volumes_client.create_volume(**params)['volume']
- self.assertIn('id', new_volume)
self.addCleanup(self.delete_volume, self.volumes_client,
new_volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
@@ -153,7 +150,5 @@
@decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
def test_show_volume_summary(self):
"""Test showing volume summary"""
- volume_summary = \
- self.volumes_client.show_volume_summary()['volume-summary']
- for key in ['total_size', 'total_count']:
- self.assertIn(key, volume_summary)
+ # check response schema
+ self.volumes_client.show_volume_summary()
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 2345698..60f85a4 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -26,11 +26,14 @@
class VolumesListTestJSON(base.BaseVolumeTest):
- # NOTE: This test creates a number of 1G volumes. To run it successfully,
- # ensure that the backing file for the volume group that Cinder uses
- # has space for at least 3 1G volumes!
- # If you are running a Devstack environment, ensure that the
- # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
+ """Test listing volumes
+
+ NOTE: This test creates a number of 1G volumes. To run it successfully,
+ ensure that the backing file for the volume group that Cinder uses
+ has space for at least 3 1G volumes!
+ If you are running a Devstack environment, ensure that the
+ VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
+ """
VOLUME_FIELDS = ('id', 'name')
@@ -116,7 +119,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('0b6ddd39-b948-471f-8038-4787978747c4')
def test_volume_list(self):
- # Get a list of Volumes
+ """Test getting a list of volumes"""
# Fetch all volumes
fetched_list = self.volumes_client.list_volumes()['volumes']
self._assert_volumes_in(fetched_list, self.volume_list,
@@ -124,13 +127,14 @@
@decorators.idempotent_id('adcbb5a7-5ad8-4b61-bd10-5380e111a877')
def test_volume_list_with_details(self):
- # Get a list of Volumes with details
+ """Test getting a list of detailed volumes"""
# Fetch all Volumes
fetched_list = self.volumes_client.list_volumes(detail=True)['volumes']
self._assert_volumes_in(fetched_list, self.volume_list)
@decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
def test_volume_list_by_name(self):
+ """Test getting a list of volumes filtered by volume name"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
@@ -140,6 +144,7 @@
@decorators.idempotent_id('2de3a6d4-12aa-403b-a8f2-fdeb42a89623')
def test_volume_list_details_by_name(self):
+ """Test getting a list of detailed volumes filtered by volume name"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
@@ -149,6 +154,7 @@
@decorators.idempotent_id('39654e13-734c-4dab-95ce-7613bf8407ce')
def test_volumes_list_by_status(self):
+ """Test getting a list of volumes filtered by volume status"""
params = {'status': 'available'}
fetched_list = self.volumes_client.list_volumes(
params=params)['volumes']
@@ -158,6 +164,7 @@
@decorators.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_status(self):
+ """Test getting a list of detailed volumes filtered by status"""
params = {'status': 'available'}
fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
@@ -181,6 +188,7 @@
@decorators.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_bootable(self):
+ """Test getting a list of detailed volumes filtered by bootable"""
params = {'bootable': 'false'}
fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
@@ -190,6 +198,7 @@
@decorators.idempotent_id('c0cfa863-3020-40d7-b587-e35f597d5d87')
def test_volumes_list_by_availability_zone(self):
+ """Test getting a list of volumes filtered by availability zone"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
@@ -201,6 +210,7 @@
@decorators.idempotent_id('e1b80d13-94f0-4ba2-a40e-386af29f8db1')
def test_volumes_list_details_by_availability_zone(self):
+ """Test getting a list of detailed volumes by availability zone"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
@@ -212,19 +222,19 @@
@decorators.idempotent_id('b5ebea1b-0603-40a0-bb41-15fcd0a53214')
def test_volume_list_with_param_metadata(self):
- # Test to list volumes when metadata param is given
+ """Test listing volumes when metadata param is given"""
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('1ca92d3c-4a8e-4b43-93f5-e4c7fb3b291d')
def test_volume_list_with_detail_param_metadata(self):
- # Test to list volumes details when metadata param is given
+ """Test listing volumes details when metadata param is given"""
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params, with_detail=True)
@decorators.idempotent_id('777c87c1-2fc4-4883-8b8e-5c0b951d1ec8')
def test_volume_list_param_display_name_and_status(self):
- # Test to list volume when display name and status param is given
+ """Test listing volume when display name and status param is given"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name'],
'status': 'available'}
@@ -232,7 +242,7 @@
@decorators.idempotent_id('856ab8ca-6009-4c37-b691-be1065528ad4')
def test_volume_list_with_detail_param_display_name_and_status(self):
- # Test to list volume when name and status param is given
+ """Test listing volume when name and status param is given"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name'],
'status': 'available'}
@@ -240,7 +250,7 @@
@decorators.idempotent_id('2a7064eb-b9c3-429b-b888-33928fc5edd3')
def test_volume_list_details_with_multiple_params(self):
- # List volumes detail using combined condition
+ """Test listing volumes detail using combined filtering condition"""
def _list_details_with_multiple_params(limit=2,
status='available',
sort_dir='asc',
@@ -375,14 +385,29 @@
@decorators.idempotent_id('e9138a2c-f67b-4796-8efa-635c196d01de')
def test_volume_list_details_pagination(self):
+ """Test listing volumes with details by pagination
+
+ All volumes will be returned by multiple requests, and the number of
+ 'limit' volumes will be returned at a time.
+ """
self._test_pagination('volumes', ids=self.volume_id_list, detail=True)
@decorators.idempotent_id('af55e775-8e4b-4feb-8719-215c43b0238c')
def test_volume_list_pagination(self):
+ """Test listing volumes by pagination
+
+ All volumes will be returned by multiple requests, and the number of
+ 'limit' volumes will be returned at a time.
+ """
self._test_pagination('volumes', ids=self.volume_id_list, detail=False)
@decorators.idempotent_id('46eff077-100b-427f-914e-3db2abcdb7e2')
def test_volume_list_with_detail_param_marker(self):
+ """Test listing volumes with details from the specified marker
+
+ Choose a volume id from all volumes as a marker, list volumes with
+ that marker, only volumes with id greater than marker will be returned.
+ """
# Choosing a random volume from a list of volumes for 'marker'
# parameter
marker = random.choice(self.volume_id_list)
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index e029538..172fbaa 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -30,6 +30,8 @@
* ``--ports, -p``: (Optional) The path to a JSON file describing the ports
being used by different services
* ``--verbose, -v``: (Optional) Print Request and Response Headers and Body
+ data to stdout in the non cliff deprecated CLI
+* ``--all-stdout, -a``: (Optional) Print Request and Response Headers and Body
data to stdout
@@ -278,7 +280,7 @@
return url_parser
-def output(url_parser, output_file, verbose):
+def output(url_parser, output_file, all_stdout):
if output_file is not None:
with open(output_file, "w") as outfile:
outfile.write(json.dumps(url_parser.test_logs))
@@ -294,7 +296,7 @@
sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
item.get('status_code'), item.get('verb'),
item.get('service'), item.get('url')))
- if verbose:
+ if all_stdout:
sys.stdout.write('\t\t- request headers: {0}\n'.format(
item.get('request_headers')))
sys.stdout.write('\t\t- request body: {0}\n'.format(
@@ -313,7 +315,7 @@
"please use: 'tempest subunit-describe-calls'")
cl_args = ArgumentParser().parse_args()
parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports)
- output(parser, cl_args.output_file, cl_args.verbose)
+ output(parser, cl_args.output_file, cl_args.all_stdout)
def _parser_add_args(parser):
@@ -339,9 +341,23 @@
help="A JSON file describing the ports for each service."
)
- parser.add_argument(
- "-v", "--verbose", action='store_true', default=False,
- help="Add Request and Response header and body data to stdout."
+ group = parser.add_mutually_exclusive_group()
+ # the -v and --verbose command are for the old subunit-describe-calls
+ # main() CLI interface. It does not work with the new
+ # tempest subunit-describe-callss CLI. So when the main CLI approach is
+ # deleted this argument is not needed.
+ group.add_argument(
+ "-v", "--verbose", action='store_true', dest='all_stdout',
+ help='Add Request and Response header and body data to stdout print.'
+ ' NOTE: This argument deprecated and does not work with'
+ ' tempest subunit-describe-calls CLI.'
+ ' Use new option: "-a", "--all-stdout"'
+ )
+ group.add_argument(
+ "-a", "--all-stdout", action='store_true',
+ help="Add Request and Response header and body data to stdout print."
+ " Note: this argument work with the subunit-describe-calls and"
+ " tempest subunit-describe-calls CLI commands."
)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
index 28ed816..8aed37d 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -120,3 +120,10 @@
# 7: SUSPENDED
'enum': [0, 1, 3, 4, 6, 7]
}
+
+uuid_or_null = {
+ 'anyOf': [
+ {'type': 'string', 'format': 'uuid'},
+ {'type': 'null'}
+ ]
+}
diff --git a/tempest/lib/api_schema/response/volume/group_snapshots.py b/tempest/lib/api_schema/response/volume/group_snapshots.py
new file mode 100644
index 0000000..c75c3ba
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/group_snapshots.py
@@ -0,0 +1,106 @@
+# Copyright 2015 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.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_group_snapshot = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshot': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ 'group_type_id': {'type': 'string', 'format': 'uuid'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'group_type_id']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshot']
+ }
+}
+
+delete_group_snapshot = {'status_code': [202]}
+
+common_show_group_snapshot = {
+ 'type': 'object',
+ 'properties': {
+ 'created_at': parameter_types.date_time,
+ 'group_id': {'type': 'string', 'format': 'uuid'},
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'group_type_id': {'type': 'string', 'format': 'uuid'},
+ },
+ 'additionalProperties': False,
+ 'required': ['created_at', 'group_id', 'id', 'name',
+ 'status', 'description', 'group_type_id']
+}
+
+show_group_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshot': common_show_group_snapshot
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshot']
+ }
+}
+
+list_group_snapshots_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshots': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshots'],
+ }
+}
+
+list_group_snapshots_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshots': {
+ 'type': 'array',
+ 'items': common_show_group_snapshot
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshots'],
+ }
+}
+
+reset_group_snapshot_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
new file mode 100644
index 0000000..ffcf488
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -0,0 +1,368 @@
+# Copyright 2018 ZTE 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 copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+attachments = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'server_id': {'type': 'string', 'format': 'uuid'},
+ 'attachment_id': {'type': 'string', 'format': 'uuid'},
+ 'attached_at': parameter_types.date_time_or_null,
+ 'host_name': {'type': ['string', 'null']},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'device': {'type': ['string', 'null']},
+ 'id': {'type': 'string', 'format': 'uuid'}
+ },
+ 'additionalProperties': False,
+ 'required': ['server_id', 'attachment_id', 'host_name',
+ 'volume_id', 'device', 'id']
+ }
+}
+
+common_show_volume = {
+ 'type': 'object',
+ 'properties': {
+ 'migration_status': {'type': ['string', 'null']},
+ 'attachments': attachments,
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'os-vol-host-attr:host': {
+ 'type': ['string', 'null'], 'pattern': '.+@.+#.+'},
+ 'encrypted': {'type': 'boolean'},
+ 'updated_at': parameter_types.date_time_or_null,
+ 'replication_status': {'type': ['string', 'null']},
+ 'snapshot_id': parameter_types.uuid_or_null,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'size': {'type': 'integer'},
+ 'user_id': {'type': 'string', 'format': 'uuid'},
+ 'os-vol-tenant-attr:tenant_id': {'type': 'string',
+ 'format': 'uuid'},
+ 'os-vol-mig-status-attr:migstat': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'status': {'type': 'string'},
+ 'volume_image_metadata': {'type': ['object', 'null']},
+ 'description': {'type': ['string', 'null']},
+ 'multiattach': {'type': 'boolean'},
+ 'source_volid': parameter_types.uuid_or_null,
+ 'consistencygroup_id': parameter_types.uuid_or_null,
+ 'os-vol-mig-status-attr:name_id': parameter_types.uuid_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'bootable': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'volume_type': {'type': ['string', 'null']},
+ # TODO(zhufl): group_id is added in 3.13, we should move it to the
+ # 3.13 schema file when microversion is supported in volume interfaces
+ 'group_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): provider_id is added in 3.21, we should move it to the
+ # 3.21 schema file when microversion is supported in volume interfaces
+ 'provider_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): service_uuid and shared_targets are added in 3.48,
+ # we should move them to the 3.48 schema file when microversion
+ # is supported in volume interfaces.
+ 'service_uuid': parameter_types.uuid_or_null,
+ 'shared_targets': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['attachments', 'links', 'encrypted',
+ 'updated_at', 'replication_status', 'id',
+ 'size', 'user_id', 'availability_zone',
+ 'metadata', 'status', 'description',
+ 'multiattach', 'consistencygroup_id',
+ 'name', 'bootable', 'created_at',
+ 'volume_type', 'snapshot_id', 'source_volid']
+}
+
+list_volumes_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'links': parameter_types.links,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': ['string', 'null']},
+ # TODO(zhufl): count is added in 3.45, we should move
+ # it to the 3.45 schema file when microversion is
+ # supported in volume interfaces
+ # 'count': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['links', 'id', 'name']
+ }
+ },
+ 'volumes_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+show_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': common_show_volume
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+list_volumes_detail = copy.deepcopy(common_show_volume)
+# TODO(zhufl): count is added in 3.45, we should move it to the 3.45 schema
+# file when microversion is supported in volume interfaces
+# list_volumes_detail['properties'].update({'count': {'type': 'integer'}})
+list_volumes_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': list_volumes_detail
+ },
+ 'volumes_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+create_volume = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': {
+ 'type': 'object',
+ 'properties': {
+ 'migration_status': {'type': ['string', 'null']},
+ 'attachments': attachments,
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'encrypted': {'type': 'boolean'},
+ 'updated_at': parameter_types.date_time_or_null,
+ 'replication_status': {'type': ['string', 'null']},
+ 'snapshot_id': parameter_types.uuid_or_null,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'size': {'type': 'integer'},
+ 'user_id': {'type': 'string', 'format': 'uuid'},
+ 'metadata': {'type': 'object'},
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'multiattach': {'type': 'boolean'},
+ 'source_volid': parameter_types.uuid_or_null,
+ 'consistencygroup_id': parameter_types.uuid_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'bootable': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'volume_type': {'type': ['string', 'null']},
+ # TODO(zhufl): group_id is added in 3.13, we should move
+ # it to the 3.13 schema file when microversion is
+ # supported in volume interfaces.
+ 'group_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): provider_id is added in 3.21, we should
+ # move it to the 3.21 schema file when microversion is
+ # supported in volume interfaces
+ 'provider_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): service_uuid and shared_targets are added
+ # in 3.48, we should move them to the 3.48 schema file
+ # when microversion is supported in volume interfaces.
+ 'service_uuid': parameter_types.uuid_or_null,
+ 'shared_targets': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['attachments', 'links', 'encrypted',
+ 'updated_at', 'replication_status', 'id',
+ 'size', 'user_id', 'availability_zone',
+ 'metadata', 'status', 'description',
+ 'multiattach', 'consistencygroup_id',
+ 'name', 'bootable', 'created_at',
+ 'volume_type', 'snapshot_id', 'source_volid']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+update_volume = copy.deepcopy(create_volume)
+update_volume.update({'status_code': [200]})
+
+delete_volume = {'status_code': [202]}
+
+show_volume_summary = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume-summary': {
+ 'type': 'object',
+ 'properties': {
+ 'total_size': {'type': 'integer'},
+ 'total_count': {'type': 'integer'},
+ # TODO(zhufl): metadata is added in 3.36, we should move
+ # it to the 3.36 schema file when microversion is
+ # supported in volume interfaces
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['total_size', 'total_count']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume-summary']
+ }
+}
+
+# TODO(zhufl): This is under discussion, so will be merged in a seperate patch.
+# https://bugs.launchpad.net/cinder/+bug/1880566
+# upload_volume = {
+# 'status_code': [202],
+# 'response_body': {
+# 'type': 'object',
+# 'properties': {
+# 'os-volume_upload_image': {
+# 'type': 'object',
+# 'properties': {
+# 'status': {'type': 'string'},
+# 'image_name': {'type': 'string'},
+# 'disk_format': {'type': 'string'},
+# 'container_format': {'type': 'string'},
+# 'is_public': {'type': 'boolean'},
+# 'visibility': {'type': 'string'},
+# 'protected': {'type': 'boolean'},
+# 'updated_at': parameter_types.date_time_or_null,
+# 'image_id': {'type': 'string', 'format': 'uuid'},
+# 'display_description': {'type': ['string', 'null']},
+# 'id': {'type': 'string', 'format': 'uuid'},
+# 'size': {'type': 'integer'},
+# 'volume_type': {
+# 'type': ['object', 'null'],
+# 'properties': {
+# 'created_at': parameter_types.date_time,
+# 'deleted': {'type': 'boolean'},
+# 'deleted_at': parameter_types.date_time_or_null,
+# 'description': {'type': ['string', 'null']},
+# 'extra_specs': {
+# 'type': 'object',
+# 'patternProperties': {
+# '^.+$': {'type': 'string'}
+# }
+# },
+# 'id': {'type': 'string', 'format': 'uuid'},
+# 'is_public': {'type': 'boolean'},
+# 'name': {'type': ['string', 'null']},
+# 'qos_specs_id': parameter_types.uuid_or_null,
+# 'updated_at': parameter_types.date_time_or_null
+# },
+# }
+# },
+# 'additionalProperties': False,
+# 'required': ['status', 'image_name', 'updated_at',
+# 'image_id',
+# 'display_description', 'id', 'size',
+# 'volume_type', 'disk_format',
+# 'container_format']
+# }
+# },
+# 'additionalProperties': False,
+# 'required': ['os-volume_upload_image']
+# }
+# }
+
+attach_volume = {'status_code': [202]}
+set_bootable_volume = {'status_code': [200]}
+detach_volume = {'status_code': [202]}
+reserve_volume = {'status_code': [202]}
+unreserve_volume = {'status_code': [202]}
+extend_volume = {'status_code': [202]}
+reset_volume_status = {'status_code': [202]}
+update_volume_readonly = {'status_code': [202]}
+force_delete_volume = {'status_code': [202]}
+retype_volume = {'status_code': [202]}
+force_detach_volume = {'status_code': [202]}
+
+create_volume_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+show_volume_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+update_volume_metadata = copy.deepcopy(show_volume_metadata)
+
+show_volume_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+update_volume_metadata_item = copy.deepcopy(show_volume_metadata_item)
+delete_volume_metadata_item = {'status_code': [200]}
+
+update_volume_image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {'metadata': {'type': 'object'}},
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+delete_volume_image_metadata = {'status_code': [200]}
+show_volume_image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+unmanage_volume = {'status_code': [202]}
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index b9c5776..4713cce 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -198,7 +198,7 @@
def image_import(self, image_id, method='glance-direct',
all_stores_must_succeed=None, all_stores=True,
- stores=None):
+ stores=None, image_uri=None):
"""Import data from staging area to glance store.
For a full list of available parameters, please refer to the official
@@ -214,6 +214,7 @@
all available stores (incompatible with stores)
:param stores: A list of destination store names for the import. Must
be None if server does not support multistore.
+ :param image_uri: A URL to be used with the web-download method
"""
url = 'images/%s/import' % image_id
data = {
@@ -228,6 +229,8 @@
if all_stores_must_succeed is not None:
data['all_stores_must_succeed'] = all_stores_must_succeed
+ if image_uri:
+ data['method']['uri'] = image_uri
data = json.dumps(data)
headers = {'Content-Type': 'application/json'}
resp, _ = self.post(url, data, headers=headers)
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index e425a3f..4051c06 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import group_snapshots as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -34,7 +35,7 @@
post_body = json.dumps({'group_snapshot': kwargs})
resp, body = self.post('group_snapshots', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group_snapshot(self, group_snapshot_id):
@@ -44,7 +45,7 @@
https://docs.openstack.org/api-ref/block-storage/v3/#delete-group-snapshot
"""
resp, body = self.delete('group_snapshots/%s' % group_snapshot_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group_snapshot(self, group_snapshot_id):
@@ -56,7 +57,7 @@
url = "group_snapshots/%s" % str(group_snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def list_group_snapshots(self, detail=False, **params):
@@ -67,13 +68,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/#list-group-snapshots-with-details
"""
url = "group_snapshots"
+ list_group_snapshots = schema.list_group_snapshots_no_detail
if detail:
url += "/detail"
+ list_group_snapshots = schema.list_group_snapshots_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_group_snapshots, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_group_snapshot_status(self, group_snapshot_id, status_to_set):
@@ -85,7 +88,7 @@
post_body = json.dumps({'reset_status': {'status': status_to_set}})
resp, body = self.post('group_snapshots/%s/action' % group_snapshot_id,
post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_group_snapshot_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index aa6c867..4ac4112 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -54,8 +54,9 @@
"""
version_url = urljoin(self._get_base_version_url(), version + '/')
- resp, body = self.raw_request(version_url, 'GET',
- {'X-Auth-Token': self.token})
+ headers = self.get_headers()
+ headers['X-Auth-Token'] = self.token
+ resp, body = self.raw_request(version_url, 'GET', headers=headers)
self._error_checker(resp, body)
body = json.loads(body)
self.validate_response(schema.volume_api_version_details, resp, body)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 4fb6d2e..b8535d8 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -17,6 +17,7 @@
import six
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import volumes as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -55,14 +56,16 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes
"""
url = 'volumes'
+ list_schema = schema.list_volumes_no_detail
if detail:
+ list_schema = schema.list_volumes_with_detail
url += '/detail'
if params:
url += '?%s' % self._prepare_params(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def migrate_volume(self, volume_id, **kwargs):
@@ -83,7 +86,7 @@
url = "volumes/%s" % volume_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume(self, **kwargs):
@@ -96,7 +99,7 @@
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('volumes', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume(self, volume_id, **kwargs):
@@ -109,7 +112,7 @@
put_body = json.dumps({'volume': kwargs})
resp, body = self.put('volumes/%s' % volume_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume(self, volume_id, **params):
@@ -123,7 +126,7 @@
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.delete(url)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_summary(self, **params):
@@ -138,7 +141,7 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_summary, resp, body)
return rest_client.ResponseBody(resp, body)
def upload_volume(self, volume_id, **kwargs):
@@ -152,6 +155,10 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
body = json.loads(body)
+ # TODO(zhufl): This is under discussion, so will be merged
+ # in a seperate patch.
+ # https://bugs.launchpad.net/cinder/+bug/1880566
+ # self.validate_response(schema.upload_volume, resp, body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -165,7 +172,7 @@
post_body = json.dumps({'os-attach': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.attach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
@@ -178,7 +185,7 @@
post_body = json.dumps({'os-set_bootable': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.set_bootable_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def detach_volume(self, volume_id):
@@ -186,7 +193,7 @@
post_body = json.dumps({'os-detach': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def reserve_volume(self, volume_id):
@@ -194,7 +201,7 @@
post_body = json.dumps({'os-reserve': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reserve_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def unreserve_volume(self, volume_id):
@@ -202,7 +209,7 @@
post_body = json.dumps({'os-unreserve': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unreserve_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
@@ -219,7 +226,7 @@
if volume["volume"]["status"] == "error_deleting":
raise lib_exc.DeleteErrorException(
"Volume %s failed to delete and is in error_deleting status" %
- volume['id'])
+ volume['volume']['id'])
return False
@property
@@ -237,7 +244,7 @@
post_body = json.dumps({'os-extend': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.extend_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_volume_status(self, volume_id, **kwargs):
@@ -249,7 +256,7 @@
"""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_volume_status, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_readonly(self, volume_id, **kwargs):
@@ -262,14 +269,14 @@
post_body = json.dumps({'os-update_readonly_flag': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_volume_readonly, resp, body)
return rest_client.ResponseBody(resp, body)
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.force_delete_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
@@ -283,7 +290,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.post(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.create_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_metadata(self, volume_id):
@@ -291,7 +298,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
@@ -305,7 +312,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_metadata_item(self, volume_id, id):
@@ -313,7 +320,7 @@
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_metadata_item(self, volume_id, id, meta_item):
@@ -322,14 +329,14 @@
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.delete(url)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.delete_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
@@ -341,7 +348,7 @@
"""
post_body = json.dumps({'os-retype': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.retype_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def force_detach_volume(self, volume_id, **kwargs):
@@ -354,7 +361,7 @@
post_body = json.dumps({'os-force_detach': kwargs})
url = 'volumes/%s/action' % volume_id
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.force_detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_image_metadata(self, volume_id, **kwargs):
@@ -368,7 +375,7 @@
url = "volumes/%s/action" % (volume_id)
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_image_metadata(self, volume_id, key_name):
@@ -376,7 +383,7 @@
post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
url = "volumes/%s/action" % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.delete_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_image_metadata(self, volume_id):
@@ -385,7 +392,7 @@
url = "volumes/%s/action" % volume_id
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def unmanage_volume(self, volume_id):
@@ -397,5 +404,5 @@
"""
post_body = json.dumps({'os-unmanage': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unmanage_volume, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/cmd/sample_streams/calls.subunit b/tempest/tests/cmd/subunit_describe_calls_data/calls.subunit
similarity index 100%
rename from tempest/tests/cmd/sample_streams/calls.subunit
rename to tempest/tests/cmd/subunit_describe_calls_data/calls.subunit
Binary files differ
diff --git a/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json
new file mode 100644
index 0000000..53976ee
--- /dev/null
+++ b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json
@@ -0,0 +1,87 @@
+{"bar":[
+ {
+ "name":"AgentsAdminTestJSON:setUp",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\", \"agent_id\": 1}}",
+ "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_create_agent",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\", \"agent_id\": 2}}",
+ "response_headers":"{'status': '200', 'content-length': '195', 'x-compute-request-id': 'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:tearDown",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/1",
+ "verb":"DELETE"
+},
+ {
+ "name":"AgentsAdminTestJSON:_run_cleanups",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/2",
+ "verb":"DELETE"
+}], "foo":[
+ {
+ "name":"AgentsAdminTestJSON:setUp",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\", \"agent_id\": 3}}",
+ "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_delete_agent",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/3",
+ "verb":"DELETE"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_delete_agent",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agents\": []}",
+ "response_headers":"{'status': '200', 'content-length': '14', 'content-location': 'http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents', 'x-compute-request-id': 'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"GET"
+},
+ {
+ "name":"AgentsAdminTestJSON:tearDown",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_headers":"{'status': '404', 'content-length': '82', 'x-compute-request-id': 'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': 'application/json; charset=UTF-8'}",
+ "service":"Nova",
+ "status_code":"404",
+ "url":"v2.1/<id>/os-agents/3",
+ "verb":"DELETE"
+}]}
\ No newline at end of file
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index 7bf7315..fc44793 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -511,7 +511,8 @@
},
{
"id": "aa77asdf-1234",
- "name": "saved-volume"
+ "name": "saved-volume",
+ "links": [],
}
]
}
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index cb34ba6..4fed84a 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -14,220 +14,422 @@
# License for the specific language governing permissions and limitations
# under the License.
+import argparse
+from io import StringIO
import os
+import shutil
import subprocess
+import sys
import tempfile
+from unittest import mock
+from unittest.mock import patch
+
+from oslo_serialization import jsonutils as json
from tempest.cmd import subunit_describe_calls
from tempest.tests import base
-class TestSubunitDescribeCalls(base.TestCase):
- def test_return_code(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file,
- '-o', tempfile.mkstemp()[1]], stdin=subprocess.PIPE)
- p.communicate()
- self.assertEqual(0, p.returncode)
+class TestArgumentParser(base.TestCase):
+ def test_init(self):
+ test_object = subunit_describe_calls.ArgumentParser()
+ self.assertEqual("subunit-describe-calls", test_object.prog)
+ self.assertEqual(subunit_describe_calls.DESCRIPTION,
+ test_object.description)
- def test_verbose(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file,
- '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- stdout = p.communicate()
- self.assertEqual(0, p.returncode)
- self.assertIn(b'- request headers:', stdout[0])
- self.assertIn(b'- request body:', stdout[0])
- self.assertIn(b'- response headers:', stdout[0])
- self.assertIn(b'- response body:', stdout[0])
- def test_return_code_no_output(self):
- subunit_file = os.path.join(
+class TestUrlParser(base.TestCase):
+ services_custom_ports = {
+ "18776": "Block Storage",
+ "18774": "Nova",
+ "18773": "Nova-API",
+ "18386": "Sahara",
+ "35358": "Keystone",
+ "19292": "Glance",
+ "19696": "Neutron",
+ "16000": "Swift",
+ "18004": "Heat",
+ "18777": "Ceilometer",
+ "10080": "Horizon",
+ "18080": "Swift",
+ "1873": "rsync",
+ "13260": "iSCSI",
+ "13306": "MySQL",
+ "15672": "AMQP",
+ "18082": "murano"}
+
+ def setUp(self):
+ super(TestUrlParser, self).setUp()
+ self.test_object = subunit_describe_calls.UrlParser()
+
+ def test_get_service_default_ports(self):
+ base_url = "http://site.something.com:"
+ for port in self.test_object.services:
+ url = base_url + port + "/v2/action"
+ service = self.test_object.services[port]
+ self.assertEqual(service, self.test_object.get_service(url))
+
+ def test_get_service_custom_ports(self):
+ self.test_object = subunit_describe_calls.\
+ UrlParser(services=self.services_custom_ports)
+ base_url = "http://site.something.com:"
+ for port in self.services_custom_ports:
+ url = base_url + port + "/v2/action"
+ service = self.services_custom_ports[port]
+ self.assertEqual(service, self.test_object.get_service(url))
+
+ def test_get_service_port_not_found(self):
+ url = "https://site.somewhere.com:1234/v2/action"
+ self.assertEqual("Unknown", self.test_object.get_service(url))
+ self.assertEqual("Unknown", self.test_object.get_service(""))
+
+ def test_parse_details_none(self):
+ self.assertIsNone(self.test_object.parse_details(None))
+
+ def test_url_path_ports(self):
+ uuid_sample1 = "3715e0bb-b1b3-4291-aa13-2c86c3b9ec93"
+ uuid_sample2 = "2715e0bb-b1b4-4291-aa13-2c86c3b9ec88"
+
+ # test http url
+ host = "http://host.company.com"
+ url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \
+ uuid_sample2 + "/extra_specs"
+ self.assertEqual("v3/<uuid>/types/<uuid>/extra_specs",
+ self.test_object.url_path(url))
+ url = host + ":8774/v2.1/servers/" + uuid_sample1
+ self.assertEqual("v2.1/servers/<uuid>",
+ self.test_object.url_path(url))
+ # test https url
+ host = "https://host.company.com"
+ url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \
+ uuid_sample2 + "/extra_specs"
+ self.assertEqual("v3/<uuid>/types/<uuid>/extra_specs",
+ self.test_object.url_path(url))
+ url = host + ":8774/v2.1/servers/" + uuid_sample1
+ self.assertEqual("v2.1/servers/<uuid>",
+ self.test_object.url_path(url))
+
+ def test_url_path_no_match(self):
+ host_port = 'https://host.company.com:1234/'
+ url = 'v2/action/no/special/data'
+ self.assertEqual(url, self.test_object.url_path(host_port + url))
+ url = 'data'
+ self.assertEqual(url, self.test_object.url_path(url))
+
+
+class TestCliBase(base.TestCase):
+ """Base class for share code on all CLI sub-process testing"""
+
+ def setUp(self):
+ super(TestCliBase, self).setUp()
+ self._subunit_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
+ 'subunit_describe_calls_data', 'calls.subunit')
+
+ def _bytes_to_string(self, data):
+ if isinstance(data, (bytes, bytearray)):
+ data = str(data, 'utf-8')
+ return data
+
+ def _assert_cli_message(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn("Running subunit_describe_calls ...", data)
+
+ def _assert_deprecated_warning(self, stdout):
+ self.assertIn(
+ b"Use of: 'subunit-describe-calls' is deprecated, "
+ b"please use: 'tempest subunit-describe-calls'", stdout)
+
+ def _assert_expect_json(self, json_data):
+ expected_file_name = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'subunit_describe_calls_data', 'calls_subunit_expected.json')
+ with open(expected_file_name, "rb") as read_file:
+ expected_result = json.load(read_file)
+ self.assertDictEqual(expected_result, json_data)
+
+ def _assert_headers_and_bodies(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn('- request headers:', data)
+ self.assertIn('- request body:', data)
+ self.assertIn('- response headers:', data)
+ self.assertIn('- response body:', data)
+
+ def _assert_methods_details(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn('foo', data)
+ self.assertIn('- 200 POST request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 200 DELETE request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 200 GET request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 404 DELETE request for Nova to v2.1/<id>/',
+ data)
+
+ def _assert_mutual_exclusive_message(self, stderr):
+ self.assertIn(b"usage: subunit-describe-calls "
+ b"[-h] [-s [<subunit file>]]", stderr)
+ self.assertIn(b"[-n <non subunit name>] [-o <output file>]",
+ stderr)
+ self.assertIn(b"[-p <ports file>] [-v | -a]", stderr)
+ self.assertIn(
+ b"subunit-describe-calls: error: argument -v/--verbose: "
+ b"not allowed with argument -a/--all-stdout", stderr)
+
+ def _assert_no_headers_and_bodies(self, data):
+ data = self._bytes_to_string(data)
+ self.assertNotIn('- request headers:', data)
+ self.assertNotIn('- request body:', data)
+ self.assertNotIn('- response headers:', data)
+ self.assertNotIn('- response body:', data)
+
+
+class TestMainCli(TestCliBase):
+ """Test cases that use subunit_describe_calls module main interface
+
+ via subprocess calls to make sure the total user experience
+ is well defined and tested. This interface is deprecated.
+ Note: these test do not affect code coverage percentages.
+ """
+
+ def test_main_output_file(self):
+ temp_file = tempfile.mkstemp()[1]
p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- stdout = p.communicate()
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-o', temp_file], stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
self.assertEqual(0, p.returncode)
- self.assertIn(b'foo', stdout[0])
- self.assertIn(b'- 200 POST request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 200 DELETE request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 200 GET request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 404 DELETE request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertNotIn(b'- request headers:', stdout[0])
- self.assertNotIn(b'- request body:', stdout[0])
- self.assertNotIn(b'- response headers:', stdout[0])
- self.assertNotIn(b'- response body:', stdout[0])
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ with open(temp_file, 'r') as file:
+ data = json.loads(file.read())
+ self._assert_expect_json(data)
+
+ def test_main_verbose(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_main_all_stdout(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '--all-stdout'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_main(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+
+ def test_main_verbose_and_all_stdout(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-a', '-v'],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(2, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_mutual_exclusive_message(stderr)
+
+
+class TestCli(TestCliBase):
+ """Test cases that use tempest subunit_describe_calls cliff interface
+
+ via subprocess calls to make sure the total user experience
+ is well defined and tested.
+ Note: these test do not affect code coverage percentages.
+ """
+
+ def _assert_cliff_verbose(self, stdout):
+ self.assertIn(b'tempest initialize_app', stdout)
+ self.assertIn(b'prepare_to_run_command TempestSubunitDescribeCalls',
+ stdout)
+ self.assertIn(b'tempest clean_up TempestSubunitDescribeCalls',
+ stdout)
+
+ def test_run_all_stdout(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-a'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_run_verbose(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-v'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+ self._assert_cliff_verbose(stderr)
+
+ def test_run_min(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+
+ def test_run_verbose_all_stdout(self):
+ """Test Cliff -v argument
+
+ Since Cliff framework has a argument at the
+ abstract command level the -v or --verbose for
+ this command is not processed as a boolean.
+ So the use of verbose only exists for the
+ deprecated main CLI interface. When the
+ main is deleted this test would not be needed.
+ """
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-a', '-v'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_cliff_verbose(stderr)
+ self._assert_methods_details(stdout)
+
+
+class TestSubunitDescribeCalls(TestCliBase):
+ """Test cases use the subunit_describe_calls module interface
+
+ and effect code coverage reporting
+ """
+
+ def setUp(self):
+ super(TestSubunitDescribeCalls, self).setUp()
+ self.test_object = subunit_describe_calls.TempestSubunitDescribeCalls(
+ app=mock.Mock(),
+ app_args=mock.Mock(spec=argparse.Namespace))
def test_parse(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- parser = subunit_describe_calls.parse(
- open(subunit_file), "pythonlogging", None)
- expected_result = {
- 'bar': [{
- 'name': 'AgentsAdminTestJSON:setUp',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-424013832", "os": "linux"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-424013832", "os": "linux", '
- '"agent_id": 1}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'203', 'x-compute-request-id': "
- "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:test_create_agent',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "kvm", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86-252246646", "os": "win"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "kvm", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86-252246646", "os": "win", '
- '"agent_id": 2}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'195', 'x-compute-request-id': "
- "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:tearDown',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '',
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/1',
- 'verb': 'DELETE'}, {
- 'name': 'AgentsAdminTestJSON:_run_cleanups',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/2',
- 'verb': 'DELETE'}],
- 'foo': [{
- 'name': 'AgentsAdminTestJSON:setUp',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-948635295", "os": "linux"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-948635295", "os": "linux", '
- '"agent_id": 3}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'203', 'x-compute-request-id': "
- "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '',
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'}, {
- 'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agents": []}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'14', 'content-location': "
- "'http://23.253.76.97:8774/v2.1/"
- "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', "
- "'x-compute-request-id': "
- "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'GET'}, {
- 'name': 'AgentsAdminTestJSON:tearDown',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_headers': "{'status': '404', 'content-length': "
- "'82', 'x-compute-request-id': "
- "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': "
- "'application/json; charset=UTF-8'}",
- 'service': 'Nova',
- 'status_code': '404',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'}]}
+ with open(self._subunit_file, 'r') as read_file:
+ parser = subunit_describe_calls.parse(
+ read_file, "pythonlogging", None)
+ self._assert_expect_json(parser.test_logs)
- self.assertEqual(expected_result, parser.test_logs)
+ def test_get_description(self):
+ self.assertEqual(subunit_describe_calls.DESCRIPTION,
+ self.test_object.get_description())
+
+ def test_get_parser_default_min(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args([])
+ self.assertIsNone(parsed_args.output_file)
+ self.assertIsNone(parsed_args.ports)
+ self.assertFalse(parsed_args.all_stdout)
+ self.assertEqual(parsed_args.subunit, sys.stdin)
+
+ def test_get_parser_default_max(self):
+ temp_dir = tempfile.mkdtemp(prefix="parser")
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+ outfile_name = os.path.join(temp_dir, 'output.json')
+ open(outfile_name, 'a').close()
+ portfile_name = os.path.join(temp_dir, 'ports.json')
+ open(portfile_name, 'a').close()
+
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-a", "-o " + outfile_name,
+ "-p " + portfile_name])
+
+ self.assertIsNotNone(parsed_args.output_file)
+ self.assertIsNotNone(parsed_args.ports)
+ self.assertTrue(parsed_args.all_stdout)
+ self.assertEqual(parsed_args.subunit, sys.stdin)
+
+ def test_take_action_min(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-s" + self._subunit_file],)
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+
+ stdout_data = mock_stdout.getvalue()
+ self._assert_methods_details(stdout_data)
+ self._assert_no_headers_and_bodies(stdout_data)
+
+ def test_take_action_all_stdout(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-as" + self._subunit_file],)
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+
+ stdout_data = mock_stdout.getvalue()
+ self._assert_methods_details(stdout_data)
+ self._assert_headers_and_bodies(stdout_data)
+
+ def test_take_action_outfile_files(self):
+ temp_file = tempfile.mkstemp()[1]
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(
+ ["-as" + self._subunit_file, '-o', temp_file], )
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+ stdout_data = mock_stdout.getvalue()
+ self._assert_cli_message(stdout_data)
+ with open(temp_file, 'r') as file:
+ data = json.loads(file.read())
+ self._assert_expect_json(data)
+
+ def test_take_action_no_items(self):
+ temp_file = tempfile.mkstemp()[1]
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(
+ ["-as" + temp_file], )
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+ stdout_data = mock_stdout.getvalue()
+ self._assert_cli_message(stdout_data)
+
+ def test_take_action_exception(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-s" + self._subunit_file],)
+ with patch('sys.stderr', new=StringIO()) as mock_stderr:
+ with patch('tempest.cmd.subunit_describe_calls.entry_point') \
+ as mock_method:
+ mock_method.side_effect = OSError()
+ self.assertRaises(OSError, self.test_object.take_action,
+ parsed_args)
+ stderr_data = mock_stderr.getvalue()
+
+ self.assertIn("Traceback (most recent call last):", stderr_data)
+ self.assertIn("entry_point(parsed_args)", stderr_data)
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 130f90a..28ebca1 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -11,13 +11,56 @@
# under the License.
import importlib
+import os
+import sys
import tempfile
from unittest import mock
+import fixtures
+
from tempest.lib.cmd import check_uuid
from tempest.tests import base
+class TestCLInterface(base.TestCase):
+ CODE = "import unittest\n" \
+ "class TestClass(unittest.TestCase):\n" \
+ " def test_tests(self):\n" \
+ " pass"
+
+ def create_tests_file(self, directory):
+ with open(directory + "/__init__.py", "w"):
+ pass
+
+ tests_file = directory + "/tests.py"
+ with open(tests_file, "w") as fake_file:
+ fake_file.write(TestCLInterface.CODE)
+
+ return tests_file
+
+ def test_fix_argument_no(self):
+ temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
+ tests_file = self.create_tests_file(temp_dir.path)
+
+ sys.argv = [sys.argv[0]] + ["--package",
+ os.path.relpath(temp_dir.path)]
+
+ self.assertRaises(SystemExit, check_uuid.run)
+ with open(tests_file, "r") as f:
+ self.assertTrue(TestCLInterface.CODE == f.read())
+
+ def test_fix_argument_yes(self):
+ temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
+ tests_file = self.create_tests_file(temp_dir.path)
+
+ sys.argv = [sys.argv[0]] + ["--fix", "--package",
+ os.path.relpath(temp_dir.path)]
+
+ check_uuid.run()
+ with open(tests_file, "r") as f:
+ self.assertTrue(TestCLInterface.CODE != f.read())
+
+
class TestSourcePatcher(base.TestCase):
def test_add_patch(self):
patcher = check_uuid.SourcePatcher()
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index 56c1a35..6bd75d9 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -26,10 +26,6 @@
"volume-summary": {
"total_size": 4,
"total_count": 4,
- "metadata": {
- "key1": ["value1", "value2"],
- "key2": ["value2"]
- }
}
}