Merge "Adding description for testcases - volume part5"
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/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml b/releasenotes/notes/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml
new file mode 100644
index 0000000..26fe01a
--- /dev/null
+++ b/releasenotes/notes/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - Add a new config option can_migrate_between_any_hosts in the
+ compute-feature-enabled section, which can be set to False for environment
+ with non homogeneous compute nodes, so that it can select a destination
+ host for migrating automatically, otherwise the testcase may fail
+ unexpectedly. e.g., if source host is with CPU "E5-2699 v4" and the
+ selected target host is with CPU "E5-2670 v3", the live-migration will
+ fail because of the downgrade issue.
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index a845c72..941315e 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -71,6 +71,10 @@
def _live_migrate(self, server_id, target_host, state,
volume_backed=False):
+ # If target_host is None, check whether source host is different with
+ # the new host after migration.
+ if target_host is None:
+ source_host = self.get_host_for_server(server_id)
self._migrate_server_to(server_id, target_host, volume_backed)
waiters.wait_for_server_status(self.servers_client, server_id, state)
migration_list = (self.admin_migration_client.list_migrations()
@@ -82,8 +86,12 @@
if (live_migration['instance_uuid'] == server_id):
msg += "\n%s" % live_migration
msg += "]"
- self.assertEqual(target_host, self.get_host_for_server(server_id),
- msg)
+ if target_host is None:
+ self.assertNotEqual(source_host,
+ self.get_host_for_server(server_id), msg)
+ else:
+ self.assertEqual(target_host, self.get_host_for_server(server_id),
+ msg)
class LiveMigrationTest(LiveMigrationTestBase):
@@ -105,7 +113,11 @@
server_id = self.create_test_server(wait_until="ACTIVE",
volume_backed=volume_backed)['id']
source_host = self.get_host_for_server(server_id)
- destination_host = self.get_host_other_than(server_id)
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ # not to specify a host so that the scheduler will pick one
+ destination_host = None
+ else:
+ destination_host = self.get_host_other_than(server_id)
if state == 'PAUSED':
self.admin_servers_client.pause_server(server_id)
@@ -123,11 +135,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):
@@ -155,7 +173,11 @@
"""Test live migrating a server with volume attached"""
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
- target_host = self.get_host_other_than(server_id)
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ # not to specify a host so that the scheduler will pick one
+ target_host = None
+ else:
+ target_host = self.get_host_other_than(server_id)
volume = self.create_volume()
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
index 0e6c016..5917931 100644
--- a/tempest/api/compute/certificates/test_certificates.py
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -21,6 +21,7 @@
class CertificatesV2TestJSON(base.BaseV2ComputeTest):
+ """Test Certificates API"""
@classmethod
def skip_checks(cls):
@@ -30,10 +31,10 @@
@decorators.idempotent_id('c070a441-b08e-447e-a733-905909535b1b')
def test_create_root_certificate(self):
- # create certificates
+ """Test creating root certificate"""
self.certificates_client.create_certificate()
@decorators.idempotent_id('3ac273d0-92d2-4632-bdfc-afbc21d4606c')
def test_get_root_certificate(self):
- # get the root certificate
+ """Test getting root certificate details"""
self.certificates_client.show_certificate('root')
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 9257458..e99e218 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -25,6 +25,7 @@
class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips API with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -46,8 +47,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6e0f059b-e4dd-48fb-8207-06e3bba5b074')
def test_allocate_floating_ip_from_nonexistent_pool(self):
- # Negative test:Allocation of a new floating IP from a nonexistent_pool
- # to a project should fail
+ """Test allocating floating ip from non existent pool should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.create_floating_ip,
pool="non_exist_pool")
@@ -55,15 +55,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ae1c55a8-552b-44d4-bfb6-2a115a15d0ba')
def test_delete_nonexistent_floating_ip(self):
- # Negative test:Deletion of a nonexistent floating IP
- # from project should fail
-
+ """Test deleting non existent floating ip should fail"""
# Deleting the non existent floating IP
self.assertRaises(lib_exc.NotFound, self.client.delete_floating_ip,
self.non_exist_id)
class FloatingIPsAssociationNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips API with compute microversion less than 2.44"""
max_microversion = '2.43'
@@ -76,8 +75,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('595fa616-1a71-4670-9614-46564ac49a4c')
def test_associate_nonexistent_floating_ip(self):
- # Negative test:Association of a non existent floating IP
- # to specific server should fail
+ """Test associating non existent floating ip to server should fail"""
# Associating non existent floating IP
self.assertRaises(lib_exc.NotFound,
self.client.associate_floating_ip_to_server,
@@ -86,7 +84,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0a081a66-e568-4e6b-aa62-9587a876dca8')
def test_dissociate_nonexistent_floating_ip(self):
- # Negative test:Dissociation of a non existent floating IP should fail
+ """Test dissociating non existent floating ip should fail"""
# Dissociating non existent floating IP
self.assertRaises(lib_exc.NotFound,
self.client.disassociate_floating_ip_from_server,
@@ -95,7 +93,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('804b4fcb-bbf5-412f-925d-896672b61eb3')
def test_associate_ip_to_server_without_passing_floating_ip(self):
- # Negative test:Association of empty floating IP to specific server
+ """Test associating empty floating ip to server should fail"""
# should raise NotFound or BadRequest(In case of Nova V2.1) exception.
self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest),
self.client.associate_floating_ip_to_server,
@@ -106,10 +104,13 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_ip_to_server_with_floating_ip(self):
- # The VM have one port
- # Associate floating IP A to the VM
- # Associate floating IP B which is from same pool with floating IP A
- # to the VM, should raise BadRequest exception
+ """Test associating floating ip to server already with floating ip
+
+ 1. The VM have one port
+ 2. Associate floating IP A to the VM
+ 3. Associate floating IP B which is from same pool with floating IP A
+ to the VM, should raise BadRequest exception
+ """
body = self.client.create_floating_ip(
pool=CONF.network.public_network_id)['floating_ip']
self.addCleanup(self.client.delete_floating_ip, body['id'])
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 944f798..6bfee95 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -21,6 +21,7 @@
class FloatingIPDetailsTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ip details with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -37,7 +38,7 @@
@decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
def test_list_floating_ips(self):
- # Positive test:Should return the list of floating IPs
+ """Test listing floating ips"""
body = self.client.list_floating_ips()['floating_ips']
floating_ips = body
self.assertNotEmpty(floating_ips,
@@ -47,7 +48,7 @@
@decorators.idempotent_id('eef497e0-8ff7-43c8-85ef-558440574f84')
def test_get_floating_ip_details(self):
- # Positive test:Should be able to GET the details of floatingIP
+ """Test getting floating ip details"""
# Creating a floating IP for which details are to be checked
body = self.client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
@@ -68,7 +69,7 @@
@decorators.idempotent_id('df389fc8-56f5-43cc-b290-20eda39854d3')
def test_list_floating_ip_pools(self):
- # Positive test:Should return the list of floating IP Pools
+ """Test listing floating ip pools"""
floating_ip_pools = self.pools_client.list_floating_ip_pools()
self.assertNotEmpty(floating_ip_pools['floating_ip_pools'],
"Expected floating IP Pools. Got zero.")
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index d69248c..aa0320d 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -23,14 +23,18 @@
class FloatingIPDetailsNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Negative tests of floating ip detail
+
+ Negative tests of floating ip detail with compute microversion less
+ than 2.36.
+ """
max_microversion = '2.35'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7ab18834-4a4b-4f28-a2c5-440579866695')
def test_get_nonexistent_floating_ip_details(self):
- # Negative test:Should not be able to GET the details
- # of non-existent floating IP
+ """Test getting non existent floating ip should fail"""
# Creating a non-existent floatingIP id
if CONF.service_available.neutron:
non_exist_id = data_utils.rand_uuid()
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 66abb21..8df2e84 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -19,11 +19,16 @@
class KeyPairsV2TestJSON(base.BaseKeypairTest):
+ """Test keypairs API with compute microversion less than 2.2"""
+
max_microversion = '2.1'
@decorators.idempotent_id('1d1dbedb-d7a0-432a-9d09-83f543c3c19b')
def test_keypairs_create_list_delete(self):
- # Keypairs created should be available in the response list
+ """Test create/list/delete keypairs
+
+ Keypairs created should be available in the response list
+ """
# Create 3 keypairs
key_list = list()
for _ in range(3):
@@ -48,7 +53,7 @@
@decorators.idempotent_id('6c1d3123-4519-4742-9194-622cb1714b7d')
def test_keypair_create_delete(self):
- # Keypair should be created, verified and deleted
+ """Test create/delete keypair"""
k_name = data_utils.rand_name('keypair')
keypair = self.create_keypair(k_name)
key_name = keypair['name']
@@ -58,7 +63,7 @@
@decorators.idempotent_id('a4233d5d-52d8-47cc-9a25-e1864527e3df')
def test_get_keypair_detail(self):
- # Keypair should be created, Got details by name and deleted
+ """Test getting keypair detail by keypair name"""
k_name = data_utils.rand_name('keypair')
self.create_keypair(k_name)
keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
@@ -68,7 +73,7 @@
@decorators.idempotent_id('39c90c6a-304a-49dd-95ec-2366129def05')
def test_keypair_create_with_pub_key(self):
- # Keypair should be created with a given public key
+ """Test creating keypair with a given public key"""
k_name = data_utils.rand_name('keypair')
pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs"
"Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd"
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 81635ca..40bea3f 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -21,10 +21,12 @@
class KeyPairsNegativeTestJSON(base.BaseKeypairTest):
+ """Negative tests of keypairs API"""
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('29cca892-46ae-4d48-bc32-8fe7e731eb81')
def test_keypair_create_with_invalid_pub_key(self):
- # Keypair should not be created with a non RSA public key
+ """Test keypair should not be created with a non RSA public key"""
pub_key = "ssh-rsa JUNK nova@ubuntu"
self.assertRaises(lib_exc.BadRequest,
self.create_keypair, pub_key=pub_key)
@@ -32,7 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7cc32e47-4c42-489d-9623-c5e2cb5a2fa5')
def test_keypair_delete_nonexistent_key(self):
- # Non-existent key deletion should throw a proper error
+ """Test non-existent key deletion should throw a proper error"""
k_name = data_utils.rand_name("keypair-non-existent")
self.assertRaises(lib_exc.NotFound,
self.keypairs_client.delete_keypair,
@@ -41,7 +43,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dade320e-69ca-42a9-ba4a-345300f127e0')
def test_create_keypair_with_empty_public_key(self):
- # Keypair should not be created with an empty public key
+ """Test keypair should not be created with an empty public key"""
pub_key = ' '
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
pub_key=pub_key)
@@ -49,7 +51,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fc100c19-2926-4b9c-8fdc-d0589ee2f9ff')
def test_create_keypair_when_public_key_bits_exceeds_maximum(self):
- # Keypair should not be created when public key bits are too long
+ """Test keypair should not be created when public key are too long"""
pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
pub_key=pub_key)
@@ -57,7 +59,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0359a7f1-f002-4682-8073-0c91e4011b7c')
def test_create_keypair_with_duplicate_name(self):
- # Keypairs with duplicate names should not be created
+ """Test keypairs with duplicate names should not be created"""
k_name = data_utils.rand_name('keypair')
self.keypairs_client.create_keypair(name=k_name)
# Now try the same keyname to create another key
@@ -68,14 +70,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1398abe1-4a84-45fb-9294-89f514daff00')
def test_create_keypair_with_empty_name_string(self):
- # Keypairs with name being an empty string should not be created
+ """Test keypairs with empty name should not be created"""
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
'')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3faa916f-779f-4103-aca7-dc3538eee1b7')
def test_create_keypair_with_long_keynames(self):
- # Keypairs with name longer than 255 chars should not be created
+ """Test keypairs with name longer than 255 should not be created"""
k_name = 'keypair-'.ljust(260, '0')
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
@@ -83,7 +85,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
def test_create_keypair_invalid_name(self):
- # Keypairs with name being an invalid name should not be created
+ """Test keypairs with an invalid name should not be created"""
k_name = r'key_/.\@:'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
diff --git a/tempest/api/compute/keypairs/test_keypairs_v22.py b/tempest/api/compute/keypairs/test_keypairs_v22.py
index 1aff262..e229c37 100644
--- a/tempest/api/compute/keypairs/test_keypairs_v22.py
+++ b/tempest/api/compute/keypairs/test_keypairs_v22.py
@@ -18,6 +18,8 @@
class KeyPairsV22TestJSON(test_keypairs.KeyPairsV2TestJSON):
+ """Test keypairs API with compute microversion greater than 2.1"""
+
min_microversion = '2.2'
max_microversion = 'latest'
@@ -43,9 +45,11 @@
@decorators.idempotent_id('8726fa85-7f98-4b20-af9e-f710a4f3391c')
def test_keypairsv22_create_list_show(self):
+ """Test create/list/show keypair"""
self._test_keypairs_create_list_show()
@decorators.idempotent_id('89d59d43-f735-441a-abcf-0601727f47b6')
def test_keypairsv22_create_list_show_with_type(self):
+ """Test create/list/show keypair with keypair type"""
keypair_type = 'x509'
self._test_keypairs_create_list_show(keypair_type=keypair_type)
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 4c99ea6..59848f6 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -18,6 +18,10 @@
class SecurityGroupRulesTestJSON(base.BaseSecurityGroupsTest):
+ """Test security group rules API
+
+ Test security group rules API with compute microversion less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -55,8 +59,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('850795d7-d4d3-4e55-b527-a774c0123d3a')
def test_security_group_rules_create(self):
- # Positive test: Creation of Security Group rule
- # should be successful
+ """Test creating security group rules"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
securitygroup_id = security_group['id']
@@ -72,10 +75,7 @@
@decorators.idempotent_id('7a01873e-3c38-4f30-80be-31a043cfe2fd')
def test_security_group_rules_create_with_optional_cidr(self):
- # Positive test: Creation of Security Group rule
- # with optional argument cidr
- # should be successful
-
+ """Test creating security group rules with optional field cidr"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
parent_group_id = security_group['id']
@@ -94,10 +94,7 @@
@decorators.idempotent_id('7f5d2899-7705-4d4b-8458-4505188ffab6')
def test_security_group_rules_create_with_optional_group_id(self):
- # Positive test: Creation of Security Group rule
- # with optional argument group_id
- # should be successful
-
+ """Test creating security group rules with optional field group id"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
parent_group_id = security_group['id']
@@ -122,8 +119,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('a6154130-5a55-4850-8be4-5e9e796dbf17')
def test_security_group_rules_list(self):
- # Positive test: Created Security Group rules should be
- # in the list of all rules
+ """Test listing security group rules"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
securitygroup_id = security_group['id']
@@ -159,7 +155,7 @@
@decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf')
def test_security_group_rules_delete_when_peer_group_deleted(self):
- # Positive test:rule will delete when peer group deleting
+ """Test security group rule gets deleted when peer group is deleted"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
sg1_id = security_group['id']
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 8283aae..3d000ca 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -20,6 +20,11 @@
class SecurityGroupRulesNegativeTestJSON(base.BaseSecurityGroupsTest):
+ """Negative tests of security group rules API
+
+ Negative tests of security group rules API with compute microversion
+ less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -29,8 +34,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1d507e98-7951-469b-82c3-23f1e6b8c254')
def test_create_security_group_rule_with_non_existent_id(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with non existent Parent group id
+ """Test creating security group rule with non existent parent group
+
+ Negative test: Creation of security group rule should fail
+ with non existent parent group id.
+ """
# Adding rules to the non existent Security Group id
parent_group_id = self.generate_random_security_group_id()
ip_protocol = 'tcp'
@@ -45,8 +53,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2244d7e4-adb7-4ecb-9930-2d77e123ce4f')
def test_create_security_group_rule_with_invalid_id(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with Parent group id which is not integer
+ """Test creating security group rule with invalid parent group id
+
+ Negative test: Creation of security group rule should fail
+ with parent group id which is not integer.
+ """
# Adding rules to the non int Security Group id
parent_group_id = data_utils.rand_name('non_int_id')
ip_protocol = 'tcp'
@@ -61,7 +72,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8bd56d02-3ffa-4d67-9933-b6b9a01d6089')
def test_create_security_group_rule_duplicate(self):
- # Negative test: Create Security Group rule duplicate should fail
+ """Test creating duplicate security group rule should fail"""
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -85,8 +96,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('84c81249-9f6e-439c-9bbf-cbb0d2cddbdf')
def test_create_security_group_rule_with_invalid_ip_protocol(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid ip_protocol
+ """Test creating security group rule with invalid ip protocol
+
+ Negative test: Creation of security group rule should fail
+ with invalid ip_protocol.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -104,8 +118,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('12bbc875-1045-4f7a-be46-751277baedb9')
def test_create_security_group_rule_with_invalid_from_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid from_port
+ """Test creating security group rule with invalid from_port
+
+ Negative test: Creation of security group rule should fail
+ with invalid from_port.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -122,8 +139,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ff88804d-144f-45d1-bf59-dd155838a43a')
def test_create_security_group_rule_with_invalid_to_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid to_port
+ """Test creating security group rule with invalid to_port
+
+ Negative test: Creation of security group rule should fail
+ with invalid to_port.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -140,8 +160,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('00296fa9-0576-496a-ae15-fbab843189e0')
def test_create_security_group_rule_with_invalid_port_range(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid port range.
+ """Test creating security group rule with invalid port range
+
+ Negative test: Creation of security group rule should fail
+ with invalid port range.
+ """
# Creating a Security Group to add rule to it.
sg = self.create_security_group()
# Adding a rule to the created Security Group
@@ -158,8 +181,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('56fddcca-dbb8-4494-a0db-96e9f869527c')
def test_delete_security_group_rule_with_non_existent_id(self):
- # Negative test: Deletion of Security Group rule should be FAIL
- # with non existent id
+ """Test deleting non existent security group rule should fail"""
non_existent_rule_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound,
self.rules_client.delete_security_group_rule,
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 62d5bea..671a779 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -21,6 +21,7 @@
class SecurityGroupsTestJSON(base.BaseSecurityGroupsTest):
+ """Test security groups API with compute microversion less than 2.36"""
@classmethod
def setup_clients(cls):
@@ -30,7 +31,10 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('eb2b087d-633d-4d0d-a7bd-9e6ba35b32de')
def test_security_groups_create_list_delete(self):
- # Positive test:Should return the list of Security Groups
+ """Test create/list/delete security groups
+
+ Positive test: Should return the list of security groups.
+ """
# Create 3 Security Groups
security_group_list = []
for _ in range(3):
@@ -60,9 +64,11 @@
@decorators.idempotent_id('ecc0da4a-2117-48af-91af-993cca39a615')
def test_security_group_create_get_delete(self):
- # Security Group should be created, fetched and deleted
- # with char space between name along with
- # leading and trailing spaces
+ """Test create/get/delete security group
+
+ Security group should be created, fetched and deleted
+ with char space between name along with leading and trailing spaces.
+ """
s_name = ' %s ' % data_utils.rand_name('securitygroup ')
securitygroup = self.create_security_group(name=s_name)
securitygroup_name = securitygroup['name']
@@ -80,8 +86,11 @@
@decorators.idempotent_id('fe4abc0d-83f5-4c50-ad11-57a1127297a2')
def test_server_security_groups(self):
- # Checks that security groups may be added and linked to a server
- # and not deleted if the server is active.
+ """Test adding security groups to a server
+
+ Checks that security groups may be added and linked to a server
+ and not deleted if the server is active.
+ """
# Create a couple security groups that we will use
# for the server resource this test creates
sg = self.create_security_group()
@@ -121,7 +130,7 @@
@decorators.idempotent_id('7d4e1d3c-3209-4d6d-b020-986304ebad1f')
def test_update_security_groups(self):
- # Update security group name and description
+ """Test updating security group name and description"""
# Create a security group
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
@@ -139,6 +148,11 @@
@decorators.idempotent_id('79517d60-535a-438f-af3d-e6feab1cbea7')
def test_list_security_groups_by_server(self):
+ """Test listing security groups by server
+
+ Create security groups and add them to a server, then list security
+ groups by server, the added security groups should be in the list.
+ """
# Create a couple security groups that we will use
# for the server resource this test creates
sg = self.create_security_group()
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 9c44bb2..4607112 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -25,6 +25,11 @@
class SecurityGroupsNegativeTestJSON(base.BaseSecurityGroupsTest):
+ """Negative tests of security groups API
+
+ Negative tests of security groups API with compute microversion
+ less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -34,8 +39,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('673eaec1-9b3e-48ed-bdf1-2786c1b9661c')
def test_security_group_get_nonexistent_group(self):
- # Negative test:Should not be able to GET the details
- # of non-existent Security Group
+ """Test getting non existent security group details should fail"""
non_exist_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound, self.client.show_security_group,
non_exist_id)
@@ -45,8 +49,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1759c3cb-b0fc-44b7-86ce-c99236be911d')
def test_security_group_create_with_invalid_group_name(self):
- # Negative test: Security Group should not be created with group name
- # as an empty string/with white spaces/chars more than 255
+ """Test creating security group with invalid group name should fail
+
+ Negative test: Security group should not be created with group name
+ as an empty string, or group name with white spaces, or group name
+ with chars more than 255.
+ """
s_description = data_utils.rand_name('description')
# Create Security Group with empty string as group name
self.assertRaises(lib_exc.BadRequest,
@@ -67,9 +75,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('777b6f14-aca9-4758-9e84-38783cfa58bc')
def test_security_group_create_with_invalid_group_description(self):
- # Negative test: Security Group should not be created with description
- # longer than 255 chars. Empty description is allowed by the API
- # reference, however.
+ """Test creating security group with invalid group description
+
+ Negative test: Security group should not be created with description
+ longer than 255 chars. Empty description is allowed by the API
+ reference, however.
+ """
s_name = data_utils.rand_name('securitygroup')
# Create Security Group with group description longer than 255 chars
s_description = 'description-'.ljust(260, '0')
@@ -82,8 +93,7 @@
"Neutron allows duplicate names for security groups")
@decorators.attr(type=['negative'])
def test_security_group_create_with_duplicate_name(self):
- # Negative test:Security Group with duplicate name should not
- # be created
+ """Test creating security group with duplicate name should fail"""
s_name = data_utils.rand_name('securitygroup')
s_description = data_utils.rand_name('description')
self.create_security_group(name=s_name, description=s_description)
@@ -95,7 +105,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('36a1629f-c6da-4a26-b8b8-55e7e5d5cd58')
def test_delete_the_default_security_group(self):
- # Negative test:Deletion of the "default" Security Group should Fail
+ """Test deleting "default" security group should fail"""
default_security_group_id = None
body = self.client.list_security_groups()['security_groups']
for i in range(len(body)):
@@ -110,7 +120,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6727c00b-214c-4f9e-9a52-017ac3e98411')
def test_delete_nonexistent_security_group(self):
- # Negative test:Deletion of a non-existent Security Group should fail
+ """Test deleting non existent security group should fail"""
non_exist_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound,
self.client.delete_security_group, non_exist_id)
@@ -118,8 +128,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1438f330-8fa4-4aeb-8a94-37c250106d7f')
def test_delete_security_group_without_passing_id(self):
- # Negative test:Deletion of a Security Group with out passing ID
- # should Fail
+ """Test deleting security group passing empty group id should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.delete_security_group, '')
@@ -128,7 +137,7 @@
"Neutron does not check the security group ID")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_id(self):
- # Update security_group with invalid sg_id should fail
+ """Test updating security group with invalid group id should fail"""
s_name = data_utils.rand_name('sg')
s_description = data_utils.rand_name('description')
# Create a non int sg_id
@@ -142,7 +151,7 @@
"Neutron does not check the security group name")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_name(self):
- # Update security_group with invalid sg_name should fail
+ """Test updating security group to invalid group name should fail"""
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
# Update Security Group with group name longer than 255 chars
@@ -156,7 +165,7 @@
"Neutron does not check the security group description")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_des(self):
- # Update security_group with invalid sg_des should fail
+ """Test updating security group to invalid description should fail"""
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
# Update Security Group with group description longer than 255 chars
@@ -168,7 +177,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('27edee9c-873d-4da6-a68a-3c256efebe8f')
def test_update_non_existent_security_group(self):
- # Update a non-existent Security Group should Fail
+ """Test updating a non existent security group should fail"""
non_exist_id = self.generate_random_security_group_id()
s_name = data_utils.rand_name('sg')
s_description = data_utils.rand_name('description')
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index c1af6c7..0601bbe 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -99,6 +99,7 @@
class AttachInterfacesTestJSON(AttachInterfacesTestBase):
+ """Test attaching interfaces"""
def wait_for_port_detach(self, port_id):
"""Waits for the port's device_id to be unset.
@@ -230,6 +231,7 @@
@decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051')
@utils.services('network')
def test_create_list_show_delete_interfaces_by_network_port(self):
+ """Test create/list/show/delete interfaces by network port"""
server, ifs, _ = self._create_server_get_interfaces()
interface_count = len(ifs)
self.assertGreater(interface_count, 0)
@@ -262,6 +264,7 @@
@decorators.idempotent_id('d290c06c-f5b3-11e7-8ec8-002293781009')
@utils.services('network')
def test_create_list_show_delete_interfaces_by_fixed_ip(self):
+ """Test create/list/show/delete interfaces by fixed ip"""
# NOTE(zhufl) By default only project that is admin or network owner
# or project with role advsvc is authorised to create interfaces with
# fixed-ip, so if we don't create network for each project, do not
@@ -290,7 +293,7 @@
@decorators.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
def test_reassign_port_between_servers(self):
- """Tests the following:
+ """Tests reassigning port between servers
1. Create a port in Neutron.
2. Create two servers in Nova.
@@ -343,12 +346,15 @@
class AttachInterfacesUnderV243Test(AttachInterfacesTestBase):
+ """Test attaching interfaces with compute microversion less than 2.44"""
+
max_microversion = '2.43'
@decorators.attr(type='smoke')
@decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9')
@utils.services('network')
def test_add_remove_fixed_ip(self):
+ """Test adding and removing fixed ip from server"""
# NOTE(zhufl) By default only project that is admin or network owner
# or project with role advsvc is authorised to add interfaces with
# fixed-ip, so if we don't create network for each project, do not
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 8879369..a7e2187 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -103,6 +103,7 @@
class TaggedBootDevicesTest(DeviceTaggingBase):
+ """Test tagged boot device with compute microversion equals 2.32"""
min_microversion = '2.32'
# NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
@@ -149,6 +150,16 @@
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@utils.services('network', 'volume', 'image')
def test_tagged_boot_devices(self):
+ """Test tagged boot devices
+
+ 1. Create volumes
+ 2. Create networks
+ 3. Create subnets
+ 4. Create ports
+ 5. Create server, specifying tags for items in networks and
+ block_device_mapping_v2,
+ 6. Verify tagged devices are in server via metadata service
+ """
# Create volumes
# The create_volume methods waits for the volumes to be available and
# the base class will clean them up on tearDown.
@@ -300,11 +311,14 @@
class TaggedBootDevicesTest_v242(TaggedBootDevicesTest):
+ """Test tagged boot devices with compute microversion greater than 2.41"""
+
min_microversion = '2.42'
max_microversion = 'latest'
class TaggedAttachmentsTest(DeviceTaggingBase):
+ """Test tagged attachments with compute microversion greater than 2.48"""
min_microversion = '2.49'
max_microversion = 'latest'
@@ -342,6 +356,16 @@
@decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c')
@utils.services('network', 'volume', 'image')
def test_tagged_attachment(self):
+ """Test tagged attachment
+
+ 1. Create network
+ 2. Create subnet
+ 3. Create volume
+ 4. Create server
+ 5. Attach tagged networks and volume
+ 6. Verify tagged devices are in server via metadata service
+ 7. Detach tagged networks and volume
+ """
# Create network
net = self.networks_client.create_network(
name=data_utils.rand_name(
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 00837eb..5ab592a 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -19,6 +19,8 @@
class InstanceActionsTestJSON(base.BaseV2ComputeTest):
+ """Test instance actions API"""
+
create_default_network = True
@classmethod
@@ -34,7 +36,7 @@
@decorators.idempotent_id('77ca5cc5-9990-45e0-ab98-1de8fead201a')
def test_list_instance_actions(self):
- # List actions of the provided server
+ """Test listing actions of the provided server"""
self.client.reboot_server(self.server['id'], type='HARD')
waiters.wait_for_server_status(self.client,
self.server['id'], 'ACTIVE')
@@ -47,7 +49,7 @@
@decorators.idempotent_id('aacc71ca-1d70-4aa5-bbf6-0ff71470e43c')
def test_get_instance_action(self):
- # Get the action details of the provided server
+ """Test getting the action details of the provided server"""
body = self.client.show_instance_action(
self.server['id'], self.request_id)['instanceAction']
self.assertEqual(self.server['id'], body['instance_uuid'])
@@ -55,6 +57,8 @@
class InstanceActionsV221TestJSON(base.BaseV2ComputeTest):
+ """Test instance actions with compute microversion greater than 2.20"""
+
create_default_network = True
min_microversion = '2.21'
@@ -67,8 +71,11 @@
@decorators.idempotent_id('0a0f85d4-10fa-41f6-bf80-a54fb4aa2ae1')
def test_get_list_deleted_instance_actions(self):
+ """Test listing actions for deleted instance
- # List actions of the deleted server
+ Listing actions for deleted instance should succeed and the returned
+ actions should contain 'create' and 'delete'.
+ """
server = self.create_test_server(wait_until='ACTIVE')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
index 4b5a2c3..dd2bf06 100644
--- a/tempest/api/compute/servers/test_instance_actions_negative.py
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -20,6 +20,8 @@
class InstanceActionsNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of instance actions"""
+
create_default_network = True
@classmethod
@@ -35,7 +37,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('67e1fce6-7ec2-45c6-92d4-0a8f1a632910')
def test_list_instance_actions_non_existent_server(self):
- # List actions of the non-existent server id
+ """Test listing actions for non existent instance should fail"""
non_existent_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.list_instance_actions,
@@ -44,6 +46,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0269f40a-6f18-456c-b336-c03623c897f1')
def test_get_instance_action_invalid_request(self):
- # Get the action details of the provided server with invalid request
+ """Test getting instance action with invalid request_id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.show_instance_action,
self.server['id'], '999')
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_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 1247494..5445113 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -53,9 +53,11 @@
class ServerRescueTestJSON(ServerRescueTestBase):
+ """Test server rescue"""
@decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
def test_rescue_unrescue_instance(self):
+ """Test rescue/unrescue server"""
password = data_utils.rand_password()
server = self.create_test_server(adminPass=password,
wait_until='ACTIVE')
@@ -68,6 +70,7 @@
class ServerRescueTestJSONUnderV235(ServerRescueTestBase):
+ """Test server rescue with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -81,7 +84,7 @@
@testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
"Floating ips are not available")
def test_rescued_vm_associate_dissociate_floating_ip(self):
- # Association of floating IP to a rescued vm
+ """Test associate/dissociate floating ip for rescued server"""
floating_ip_body = self.floating_ips_client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(self.floating_ips_client.delete_floating_ip,
@@ -96,6 +99,7 @@
@decorators.idempotent_id('affca41f-7195-492d-8065-e09eee245404')
def test_rescued_vm_add_remove_security_group(self):
+ """Test add/remove security group to for rescued server"""
# Add Security group
sg = self.create_security_group()
self.servers_client.add_security_group(self.rescued_server_id,
@@ -154,33 +158,43 @@
class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
+ """Test rescuing server specifying type of device for the rescue disk"""
@decorators.idempotent_id('947004c3-e8ef-47d9-9f00-97b74f9eaf96')
def test_stable_device_rescue_cdrom_ide(self):
+ """Test rescuing server with cdrom and ide as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='cdrom', hw_rescue_bus='ide')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('16865750-1417-4854-bcf7-496e6753c01e')
def test_stable_device_rescue_disk_virtio(self):
+ """Test rescuing server with disk and virtio as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('12340157-6306-4745-bdda-cfa019908b48')
def test_stable_device_rescue_disk_scsi(self):
+ """Test rescuing server with disk and scsi as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='scsi')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('647d04cf-ad35-4956-89ab-b05c5c16f30c')
def test_stable_device_rescue_disk_usb(self):
+ """Test rescuing server with disk and usb as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='usb')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('a3772b42-00bf-4310-a90b-1cc6fd3e7eab')
def test_stable_device_rescue_disk_virtio_with_volume_attached(self):
+ """Test rescuing server with volume attached
+
+ Attach a volume to the server and then rescue the server with disk
+ and virtio as the rescue disk.
+ """
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio')
server = self.servers_client.show_server(server_id)['server']
@@ -192,12 +206,22 @@
class ServerBootFromVolumeStableRescueTest(BaseServerStableDeviceRescueTest):
+ """Test rescuing server specifying type of device for the rescue disk
+
+ Test rescuing server specifying type of device for the rescue disk with
+ compute microversion greater than 2.86.
+ """
min_microversion = '2.87'
@decorators.attr(type='slow')
@decorators.idempotent_id('48f123cb-922a-4065-8db6-b9a9074a556b')
def test_stable_device_rescue_bfv_blank_volume(self):
+ """Test rescuing server with blank volume as block_device_mapping_v2
+
+ Create a server with block_device_mapping_v2 with blank volume,
+ then rescue the server with disk and virtio as the rescue disk.
+ """
block_device_mapping_v2 = [{
"boot_index": "0",
"source_type": "blank",
@@ -211,6 +235,11 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('e4636333-c928-40fc-98b7-70a23eef4224')
def test_stable_device_rescue_bfv_image_volume(self):
+ """Test rescuing server with blank volume as block_device_mapping_v2
+
+ Create a server with block_device_mapping_v2 with image volume,
+ then rescue the server with disk and virtio as the rescue disk.
+ """
block_device_mapping_v2 = [{
"boot_index": "0",
"source_type": "image",
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 12e7fea..3318876 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -27,10 +27,11 @@
class ExtensionsTest(base.BaseV2ComputeTest):
+ """Tests Compute Extensions API"""
@decorators.idempotent_id('3bb27738-b759-4e0d-a5fa-37d7a6df07d1')
def test_list_extensions(self):
- # List of all extensions
+ """Test listing compute extensions"""
if not CONF.compute_feature_enabled.api_extensions:
raise self.skipException('There are not any extensions configured')
extensions = self.extensions_client.list_extensions()['extensions']
@@ -50,6 +51,6 @@
@decorators.idempotent_id('05762f39-bdfa-4cdb-9b46-b78f8e78e2fd')
@utils.requires_ext(extension='os-consoles', service='compute')
def test_get_extension(self):
- # get the specified extensions
+ """Test getting specified compute extension details"""
extension = self.extensions_client.show_extension('os-consoles')
self.assertEqual('os-consoles', extension['extension']['alias'])
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
index 76131e2..97c26e4 100644
--- a/tempest/api/compute/test_networks.py
+++ b/tempest/api/compute/test_networks.py
@@ -20,6 +20,7 @@
class ComputeNetworksTest(base.BaseV2ComputeTest):
+ """Test compute networks API with compute microversion less than 2.36"""
max_microversion = '2.35'
@classmethod
@@ -35,5 +36,6 @@
@decorators.idempotent_id('3fe07175-312e-49a5-a623-5f52eeada4c2')
def test_list_networks(self):
+ """Test listing networks using compute networks API"""
networks = self.client.list_networks()['networks']
self.assertNotEmpty(networks, "No networks found.")
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index f4eada0..17f4b80 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -18,6 +18,8 @@
class ComputeTenantNetworksTest(base.BaseV2ComputeTest):
+ """Test compute tenant networks API with microversion less than 2.36"""
+
max_microversion = '2.35'
@classmethod
@@ -34,8 +36,11 @@
@decorators.idempotent_id('edfea98e-bbe3-4c7a-9739-87b986baff26')
@utils.services('network')
def test_list_show_tenant_networks(self):
- # Fetch all networks that are visible to the tenant: this may include
- # shared and external networks
+ """Test list/show tenant networks
+
+ Fetch all networks that are visible to the tenant: this may include
+ shared and external networks.
+ """
tenant_networks = [
n['id'] for n in self.client.list_tenant_networks()['networks']
]
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 9a506af..516f599 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -21,6 +21,8 @@
class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
+ """Negative tests of volume attaching"""
+
create_default_network = True
@classmethod
@@ -34,6 +36,7 @@
@decorators.related_bug('1630783', status_code=500)
@decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
def test_delete_attached_volume(self):
+ """Test deleting attachemd volume should fail"""
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
self.attach_volume(server, volume)
@@ -44,10 +47,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c')
def test_attach_attached_volume_to_same_server(self):
- # Test attaching the same volume to the same instance once
- # it's already attached. The nova/cinder validation for this differs
- # depending on whether or not cinder v3.27 is being used to attach
- # the volume to the instance.
+ """Test attaching attached volume to same server should fail
+
+ Test attaching the same volume to the same instance once
+ it's already attached. The nova/cinder validation for this differs
+ depending on whether or not cinder v3.27 is being used to attach
+ the volume to the instance.
+ """
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
@@ -59,6 +65,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce')
def test_attach_attached_volume_to_different_server(self):
+ """Test attaching attached volume to different server should fail"""
server1 = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
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_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/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
index 51f2857..5f9f29f 100644
--- a/tempest/api/network/admin/test_ports.py
+++ b/tempest/api/network/admin/test_ports.py
@@ -48,6 +48,8 @@
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -63,6 +65,8 @@
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -82,6 +86,8 @@
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -110,6 +116,8 @@
name=data_utils.rand_name(self.__class__.__name__),
network_id=self.network['id'])
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
body = self.admin_ports_client.show_port(port['id'])
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index c6d049a..479578d 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -77,6 +77,8 @@
name=data_utils.rand_name(self.__class__.__name__))
port = body['port']
# Schedule port deletion with verification upon test completion
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(self._delete_port, port['id'])
self.assertTrue(port['admin_state_up'])
# Verify port update
@@ -99,6 +101,10 @@
created_ports = body['ports']
port1 = created_ports[0]
port2 = created_ports[1]
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port1['id'])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port2['id'])
self.addCleanup(self._delete_port, port1['id'])
self.addCleanup(self._delete_port, port2['id'])
self.assertEqual(port1['network_id'], network1['id'])
@@ -126,6 +132,8 @@
body = self.ports_client.create_port(
network_id=net_id,
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -183,11 +191,15 @@
port_1 = self.ports_client.create_port(
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_1['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_1['port']['id'])
port_2 = self.ports_client.create_port(
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_2['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_2['port']['id'])
# List ports filtered by fixed_ips
@@ -241,6 +253,8 @@
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__),
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_1['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_1['port']['id'])
fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_2}]
@@ -248,6 +262,8 @@
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__),
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_2['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_2['port']['id'])
@@ -307,6 +323,8 @@
# Add router interface to port created above
self.routers_client.add_router_interface(router['id'],
port_id=port['port']['id'])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.routers_client.remove_router_interface,
router['id'], port_id=port['port']['id'])
@@ -343,6 +361,8 @@
# Create a port with multiple IP addresses
port = self.create_port(network,
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port['id'])
self.assertEqual(2, len(port['fixed_ips']))
@@ -386,6 +406,8 @@
"admin_state_up": True,
"fixed_ips": fixed_ip_1}
body = self.ports_client.create_port(**post_body)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -456,6 +478,8 @@
network_id=self.network['id'],
mac_address=free_mac_address,
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -474,6 +498,8 @@
network = self._create_network()
self._create_subnet(network)
port = self.create_port(network, security_groups=[])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port['id'])
self.assertIsNotNone(port['security_groups'])
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 80f790f..687fe57 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -21,6 +21,7 @@
class BulkTest(base.BaseObjectTest):
+ """Test bulk operation of archived file"""
def setUp(self):
super(BulkTest, self).setUp()
@@ -70,7 +71,7 @@
@decorators.idempotent_id('a407de51-1983-47cc-9f14-47c2b059413c')
@utils.requires_ext(extension='bulk_upload', service='object')
def test_extract_archive(self):
- # Test bulk operation of file upload with an archived file
+ """Test bulk operation of file upload with an archived file"""
filepath, container_name, object_name = self._create_archive()
resp = self._upload_archive(filepath)
self.containers.append(container_name)
@@ -95,7 +96,7 @@
@decorators.idempotent_id('c075e682-0d2a-43b2-808d-4116200d736d')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
@@ -110,7 +111,7 @@
@decorators.idempotent_id('dbea2bcb-efbb-4674-ac8a-a5a0e33d1d79')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete_by_POST(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files by HTTP POST"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 48f42ec..6854bbe 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -22,6 +22,7 @@
class AccountQuotasTest(base.BaseObjectTest):
+ """Test account quotas"""
credentials = [['operator', CONF.object_storage.operator_role],
['reseller', CONF.object_storage.reseller_admin_role]]
@@ -79,6 +80,7 @@
@decorators.idempotent_id('a22ef352-a342-4587-8f47-3bbdb5b039c4')
@utils.requires_ext(extension='account_quotas', service='object')
def test_upload_valid_object(self):
+ """Test uploading valid object"""
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index 3e664d7..8d2a501 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -21,6 +21,7 @@
class AccountNegativeTest(base.BaseObjectTest):
+ """Negative tests of account"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -33,7 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('070e6aca-6152-4867-868d-1118d68fb38c')
def test_list_containers_with_non_authorized_user(self):
- # list containers using non-authorized user
+ """Test listing containers using non-authorized user"""
test_auth_provider = self.os_operator.auth_provider
# Get auth for the test user
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index e9ca0b1..c8731fe 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -22,6 +22,7 @@
class ObjectTestACLs(base.BaseObjectTest):
+ """Test object ACLs"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -36,7 +37,7 @@
@decorators.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6')
def test_read_object_with_rights(self):
- # attempt to read object using authorized user
+ """Test reading object using authorized user"""
# update X-Container-Read metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
@@ -64,7 +65,7 @@
@decorators.idempotent_id('aa58bfa5-40d9-4bc3-82b4-d07f4a9e392a')
def test_write_object_with_rights(self):
- # attempt to write object using authorized user
+ """Test writing object using authorized user"""
# update X-Container-Write metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 90b24b4..73d7f27 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -22,6 +22,7 @@
class ObjectACLsNegativeTest(base.BaseObjectTest):
+ """Negative tests of object ACLs"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -48,6 +49,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af587587-0c24-4e15-9822-8352ce711013')
def test_write_object_without_using_creds(self):
+ """Test writing object without using credentials"""
# trying to create object with empty headers
# X-Auth-Token is not provided
object_name = data_utils.rand_name(name='Object')
@@ -62,6 +64,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af85af0b-a025-4e72-a90e-121babf55720')
def test_delete_object_without_using_creds(self):
+ """Test deleting object without using credentials"""
# create object
object_name = data_utils.rand_name(name='Object')
self.object_client.create_object(self.container_name, object_name,
@@ -79,7 +82,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('63d84e37-55a6-42e2-9e5f-276e60e26a00')
def test_write_object_with_non_authorized_user(self):
- # attempt to upload another file using non-authorized user
+ """Test writing object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
# trying to create object with non-authorized user
@@ -94,7 +97,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('abf63359-be52-4feb-87dd-447689fc77fd')
def test_read_object_with_non_authorized_user(self):
- # attempt to read object using non-authorized user
+ """Test reading object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
@@ -112,7 +115,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7343ac3d-cfed-4198-9bb0-00149741a492')
def test_delete_object_with_non_authorized_user(self):
- # attempt to delete object using non-authorized user
+ """Test deleting object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
@@ -130,7 +133,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ed01334-01e9-41ea-87ea-e6f465582823')
def test_read_object_without_rights(self):
- # attempt to read object using non-authorized user
+ """Test reading object without rights"""
# update X-Container-Read metadata ACL
cont_headers = {'X-Container-Read': 'badtenant:baduser'}
resp_meta, _ = (
@@ -155,7 +158,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a3a585a7-d8cf-4b65-a1a0-edc2b1204f85')
def test_write_object_without_rights(self):
- # attempt to write object using non-authorized user
+ """Test writing object without rights"""
# update X-Container-Write metadata ACL
cont_headers = {'X-Container-Write': 'badtenant:baduser'}
resp_meta, _ = (
@@ -177,7 +180,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8ba512ad-aa6e-444e-b882-2906a0ea2052')
def test_write_object_without_write_rights(self):
- # attempt to write object using non-authorized user
+ """Test writing object without write rights"""
# update X-Container-Read and X-Container-Write metadata ACL
tenant_name = self.os_operator.credentials.tenant_name
username = self.os_operator.credentials.username
@@ -203,7 +206,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b4e366f8-f185-47ab-b789-df4416f9ecdb')
def test_delete_object_without_write_rights(self):
- # attempt to delete object using non-authorized user
+ """Test deleting object without write rights"""
# update X-Container-Read and X-Container-Write metadata ACL
tenant_name = self.os_operator.credentials.tenant_name
username = self.os_operator.credentials.username
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index cdc420e..7ad6f6f 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -19,6 +19,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test containers"""
+
def tearDown(self):
self.delete_containers()
super(ContainerTest, self).tearDown()
@@ -26,6 +28,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
def test_create_container(self):
+ """Test creating container"""
container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -33,7 +36,7 @@
@decorators.idempotent_id('49f866ed-d6af-4395-93e7-4187eb56d322')
def test_create_container_overwrite(self):
- # overwrite container with the same name
+ """Test overwriting container with the same name"""
container_name = data_utils.rand_name(name='TestContainer')
self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -43,7 +46,7 @@
@decorators.idempotent_id('c2ac4d59-d0f5-40d5-ba19-0635056d48cd')
def test_create_container_with_metadata_key(self):
- # create container with the blank value of metadata
+ """Test creating container with the blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': ''}
resp, _ = self.container_client.update_container(
@@ -60,7 +63,7 @@
@decorators.idempotent_id('e1e8df32-7b22-44e1-aa08-ccfd8d446b58')
def test_create_container_with_metadata_value(self):
- # create container with metadata value
+ """Test creating container with metadata value"""
container_name = data_utils.rand_name(name='TestContainer')
# metadata name using underscores should be converted to hyphens
@@ -79,7 +82,7 @@
@decorators.idempotent_id('24d16451-1c0c-4e4f-b59c-9840a3aba40e')
def test_create_container_with_remove_metadata_key(self):
- # create container with the blank value of remove metadata
+ """Test creating container with the blank value of remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -97,7 +100,7 @@
@decorators.idempotent_id('8a21ebad-a5c7-4e29-b428-384edc8cd156')
def test_create_container_with_remove_metadata_value(self):
- # create container with remove metadata
+ """Test creating container with remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -114,6 +117,7 @@
@decorators.idempotent_id('95d3a249-b702-4082-a2c4-14bb860cf06a')
def test_delete_container(self):
+ """Test deleting container"""
# create a container
container_name = self.create_container()
# delete container, success asserted within
@@ -123,7 +127,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('312ff6bd-5290-497f-bda1-7c5fec6697ab')
def test_list_container_contents(self):
- # get container contents list
+ """Test getting container contents list"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -134,7 +138,7 @@
@decorators.idempotent_id('4646ac2d-9bfb-4c7d-a3c5-0f527402b3df')
def test_list_container_contents_with_no_object(self):
- # get empty container contents list
+ """Test getting empty container contents list"""
container_name = self.create_container()
resp, object_list = self.container_client.list_container_objects(
@@ -144,7 +148,7 @@
@decorators.idempotent_id('fe323a32-57b9-4704-a996-2e68f83b09bc')
def test_list_container_contents_with_delimiter(self):
- # get container contents list using delimiter param
+ """Test getting container contents list using delimiter param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject/')
self.create_object(container_name, object_name)
@@ -158,7 +162,7 @@
@decorators.idempotent_id('55b4fa5c-e12e-4ca9-8fcf-a79afe118522')
def test_list_container_contents_with_end_marker(self):
- # get container contents list using end_marker param
+ """Test getting container contents list using end_marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -171,7 +175,7 @@
@decorators.idempotent_id('196f5034-6ab0-4032-9da9-a937bbb9fba9')
def test_list_container_contents_with_format_json(self):
- # get container contents list using format_json param
+ """Test getting container contents list using format_json param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -190,7 +194,7 @@
@decorators.idempotent_id('655a53ca-4d15-408c-a377-f4c6dbd0a1fa')
def test_list_container_contents_with_format_xml(self):
- # get container contents list using format_xml param
+ """Test getting container contents list using format_xml param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -214,7 +218,7 @@
@decorators.idempotent_id('297ec38b-2b61-4ff4-bcd1-7fa055e97b61')
def test_list_container_contents_with_limit(self):
- # get container contents list using limit param
+ """Test getting container contents list using limit param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -227,7 +231,7 @@
@decorators.idempotent_id('c31ddc63-2a58-4f6b-b25c-94d2937e6867')
def test_list_container_contents_with_marker(self):
- # get container contents list using marker param
+ """Test getting container contents list using marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -240,7 +244,7 @@
@decorators.idempotent_id('58ca6cc9-6af0-408d-aaec-2a6a7b2f0df9')
def test_list_container_contents_with_path(self):
- # get container contents list using path param
+ """Test getting container contents list using path param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject')
object_name = 'Swift/' + object_name
@@ -255,7 +259,7 @@
@decorators.idempotent_id('77e742c7-caf2-4ec9-8aa4-f7d509a3344c')
def test_list_container_contents_with_prefix(self):
- # get container contents list using prefix param
+ """Test getting container contents list using prefix param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -270,7 +274,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('96e68f0e-19ec-4aa2-86f3-adc6a45e14dd')
def test_list_container_metadata(self):
- # List container metadata
+ """Test listing container metadata"""
container_name = self.create_container()
metadata = {'name': 'Pictures'}
@@ -286,7 +290,7 @@
@decorators.idempotent_id('a2faf936-6b13-4f8d-92a2-c2278355821e')
def test_list_no_container_metadata(self):
- # HEAD container without metadata
+ """Test listing container without metadata"""
container_name = self.create_container()
resp, _ = self.container_client.list_container_metadata(
@@ -296,7 +300,10 @@
@decorators.idempotent_id('cf19bc0b-7e16-4a5a-aaed-cb0c2fe8deef')
def test_update_container_metadata_with_create_and_delete_metadata(self):
- # Send one request of adding and deleting metadata
+ """Test updating container with adding and deleting metadata
+
+ Send one request of adding and deleting metadata.
+ """
container_name = data_utils.rand_name(name='TestContainer')
metadata_1 = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata_1)
@@ -319,7 +326,7 @@
@decorators.idempotent_id('2ae5f295-4bf1-4e04-bfad-21e54b62cec5')
def test_update_container_metadata_with_create_metadata(self):
- # update container metadata using add metadata
+ """Test updating container metadata using add metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': 'Meta1'}
@@ -337,7 +344,7 @@
@decorators.idempotent_id('3a5ce7d4-6e4b-47d0-9d87-7cd42c325094')
def test_update_container_metadata_with_delete_metadata(self):
- # update container metadata using delete metadata
+ """Test updating container metadata using delete metadata"""
container_name = data_utils.rand_name(name='TestContainer')
metadata = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata)
@@ -355,7 +362,7 @@
@decorators.idempotent_id('31f40a5f-6a52-4314-8794-cd89baed3040')
def test_update_container_metadata_with_create_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': ''}
@@ -371,7 +378,7 @@
@decorators.idempotent_id('a2e36378-6f1f-43f4-840a-ffd9cfd61914')
def test_update_container_metadata_with_delete_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **headers)
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index b8c83b7..31c33db 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -25,6 +25,7 @@
class ContainerNegativeTest(base.BaseObjectTest):
+ """Negative tests of containers"""
@classmethod
def resource_setup(cls):
@@ -41,7 +42,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_name_exceeds_max_length(self):
- # Attempts to create a container name that is longer than max
+ """Test creating container with name longer than max"""
max_length = self.constraints['max_container_name_length']
# create a container with long name
container_name = data_utils.arbitrary_string(size=max_length + 1)
@@ -58,8 +59,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_name_exceeds_max_length(self):
- # Attempts to create container with metadata name
- # that is longer than max.
+ """Test creating container with metadata name longer than max"""
max_length = self.constraints['max_meta_name_length']
container_name = data_utils.rand_name(name='TestContainer')
metadata_name = 'X-Container-Meta-' + data_utils.arbitrary_string(
@@ -77,8 +77,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_value_exceeds_max_length(self):
- # Attempts to create container with metadata value
- # that is longer than max.
+ """Test creating container with metadata value longer than max"""
max_length = self.constraints['max_meta_value_length']
container_name = data_utils.rand_name(name='TestContainer')
metadata_value = data_utils.arbitrary_string(size=max_length + 1)
@@ -95,8 +94,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_exceeds_overall_metadata_count(self):
- # Attempts to create container with metadata that exceeds the
- # default count
+ """Test creating container with metadata exceeding default count"""
max_count = self.constraints['max_meta_count']
container_name = data_utils.rand_name(name='TestContainer')
metadata = {}
@@ -113,8 +111,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('1a95ab2e-b712-4a98-8a4d-8ce21b7557d6')
def test_get_metadata_headers_with_invalid_container_name(self):
- # Attempts to retrieve metadata headers with an invalid
- # container name.
+ """Test getting metadata headers with invalid container name"""
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_metadata,
'invalid_container_name')
@@ -122,7 +119,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('125a24fa-90a7-4cfc-b604-44e49d788390')
def test_update_metadata_with_nonexistent_container_name(self):
- # Attempts to update metadata using a nonexistent container name.
+ """Test updating metadata using a nonexistent container name"""
metadata = {'animal': 'penguin'}
self.assertRaises(
@@ -133,7 +130,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba')
def test_delete_with_nonexistent_container_name(self):
- # Attempts to delete metadata using a nonexistent container name.
+ """Test deleting metadata using a non existent container name"""
metadata = {'animal': 'penguin'}
self.assertRaises(
@@ -144,8 +141,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5')
def test_list_all_container_objects_with_nonexistent_container(self):
- # Attempts to get a listing of all objects on a container
- # that doesn't exist.
+ """Test getting a list of all objects on a non existent container"""
params = {'limit': 9999, 'format': 'json'}
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_objects,
@@ -154,8 +150,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e')
def test_list_all_container_objects_on_deleted_container(self):
- # Attempts to get a listing of all objects on a container
- # that was deleted.
+ """Test getting a list of all objects on a deleted container"""
container_name = self.create_container()
# delete container
resp, _ = self.container_client.delete_container(container_name)
@@ -168,6 +163,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('42da116e-1e8c-4c96-9e06-2f13884ed2b1')
def test_delete_non_empty_container(self):
+ """Test deleting a container with object in it"""
# create a container and an object within it
# attempt to delete a container that isn't empty.
container_name = self.create_container()
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 1243b83..ef98ed8 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -21,6 +21,7 @@
class StaticWebTest(base.BaseObjectTest):
+ """Test static web"""
@classmethod
def resource_setup(cls):
@@ -47,6 +48,7 @@
@decorators.idempotent_id('c1f055ab-621d-4a6a-831f-846fcb578b8b')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_index(self):
+ """Test web index"""
headers = {'web-index': self.object_name}
self.container_client.create_update_or_delete_container_metadata(
@@ -79,6 +81,7 @@
@decorators.idempotent_id('941814cf-db9e-4b21-8112-2b6d0af10ee5')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_listing(self):
+ """Test web listing"""
headers = {'web-listings': 'true'}
self.container_client.create_update_or_delete_container_metadata(
@@ -111,6 +114,7 @@
@decorators.idempotent_id('bc37ec94-43c8-4990-842e-0e5e02fc8926')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_listing_css(self):
+ """Test web listing css"""
headers = {'web-listings': 'true',
'web-listings-css': 'listings.css'}
@@ -134,6 +138,7 @@
@decorators.idempotent_id('f18b4bef-212e-45e7-b3ca-59af3a465f82')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_error(self):
+ """Test web error"""
headers = {'web-listings': 'true',
'web-error': self.object_name}
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index 8e9e406..f5e2443 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -19,12 +19,14 @@
class HealthcheckTest(base.BaseObjectTest):
+ """Test healthcheck"""
def setUp(self):
super(HealthcheckTest, self).setUp()
@decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337')
def test_get_healthcheck(self):
+ """Test getting healthcheck"""
url = self.account_client._get_base_version_url() + "healthcheck"
resp, body = self.account_client.raw_request(url, "GET")
self.account_client._error_checker(resp, body)
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index cd834bf..d857d3b 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -25,6 +25,7 @@
class ObjectFormPostTest(base.BaseObjectTest):
+ """Test object post with form"""
metadata = {}
containers = []
@@ -110,6 +111,7 @@
@decorators.idempotent_id('80fac02b-6e54-4f7b-be0d-a965b5cbef76')
@utils.requires_ext(extension='formpost', service='object')
def test_post_object_using_form(self):
+ """Test posting object using form"""
body, content_type = self.get_multipart_form()
headers = {'Content-Type': content_type,
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index df6a0fd..0499eef 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -26,6 +26,7 @@
class ObjectFormPostNegativeTest(base.BaseObjectTest):
+ """Negative tests of object post with form"""
metadata = {}
containers = []
@@ -112,6 +113,7 @@
@utils.requires_ext(extension='formpost', service='object')
@decorators.attr(type=['negative'])
def test_post_object_using_form_expired(self):
+ """Test posting object using expired form"""
body, content_type = self.get_multipart_form(expires=1)
time.sleep(2)
@@ -129,6 +131,7 @@
@utils.requires_ext(extension='formpost', service='object')
@decorators.attr(type=['negative'])
def test_post_object_using_form_invalid_signature(self):
+ """Test posting object using form with invalid signature"""
self.key = "Wrong"
body, content_type = self.get_multipart_form()
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index b99f93a..29354b6 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -25,6 +25,7 @@
class ObjectTempUrlTest(base.BaseObjectTest):
+ """Test object temp url"""
@classmethod
def resource_setup(cls):
@@ -90,6 +91,7 @@
@decorators.idempotent_id('f91c96d4-1230-4bba-8eb9-84476d18d991')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url(self):
+ """Test getting object using temp url"""
expires = self._get_expiry_date()
# get a temp URL for the created object
@@ -109,6 +111,7 @@
@decorators.idempotent_id('671f9583-86bd-4128-a034-be282a68c5d8')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url_key_2(self):
+ """Test getting object using metadata 'Temp-URL-Key-2'"""
key2 = 'Meta2-'
metadata = {'Temp-URL-Key-2': key2}
self.account_client.create_update_or_delete_account_metadata(
@@ -134,6 +137,7 @@
@decorators.idempotent_id('9b08dade-3571-4152-8a4f-a4f2a873a735')
@utils.requires_ext(extension='tempurl', service='object')
def test_put_object_using_temp_url(self):
+ """Test putting object using temp url"""
new_data = data_utils.random_bytes(size=len(self.object_name))
expires = self._get_expiry_date()
@@ -160,6 +164,7 @@
@decorators.idempotent_id('249a0111-5ad3-4534-86a7-1993d55f9185')
@utils.requires_ext(extension='tempurl', service='object')
def test_head_object_using_temp_url(self):
+ """Test HEAD operation of object using temp url"""
expires = self._get_expiry_date()
# get a temp URL for the created object
@@ -174,6 +179,7 @@
@decorators.idempotent_id('9d9cfd90-708b-465d-802c-e4a8090b823d')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url_with_inline_query_parameter(self):
+ """Test getting object using temp url with inline query parameter"""
expires = self._get_expiry_date()
# get a temp URL for the created object
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index 17ae6c1..bbb4827 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -26,6 +26,7 @@
class ObjectTempUrlNegativeTest(base.BaseObjectTest):
+ """Negative tests of object temp url"""
metadata = {}
containers = []
@@ -96,7 +97,7 @@
@decorators.idempotent_id('5a583aca-c804-41ba-9d9a-e7be132bdf0b')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_after_expiration_time(self):
-
+ """Test getting object after expiration time"""
expires = self._get_expiry_date(1)
# get a temp URL for the created object
url = self._get_temp_url(self.container_name,
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 75111b6..b64b172 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,6 +24,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test versioned container"""
+
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
self.assertHeaders(resp, 'Container', 'HEAD')
@@ -39,6 +41,15 @@
not CONF.object_storage_feature_enabled.object_versioning,
'Object-versioning is disabled')
def test_versioned_container(self):
+ """Test versioned container
+
+ 1. create container1
+ 2. create container2, with container1 as 'X-versions-Location' header
+ 3. create object1 in container1
+ 4. create 2nd version of object1
+ 5. delete object version 2
+ 6. delete object version 1
+ """
# create container
vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, _ = self.container_client.update_container(vers_container_name)
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 293af81..1d12a73 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -39,12 +39,14 @@
@decorators.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d')
def test_list_services(self):
+ """Test listing volume services"""
services = (self.admin_volume_services_client.list_services()
['services'])
self.assertNotEmpty(services)
@decorators.idempotent_id('63a3e1ca-37ee-4983-826d-83276a370d25')
def test_get_service_by_service_binary_name(self):
+ """Test getting volume service by binary name"""
services = (self.admin_volume_services_client.list_services(
binary=self.binary_name)['services'])
self.assertNotEmpty(services)
@@ -53,6 +55,7 @@
@decorators.idempotent_id('178710e4-7596-4e08-9333-745cb8bc4f8d')
def test_get_service_by_host_name(self):
+ """Test getting volume service by service host name"""
services_on_host = [service for service in self.services if
_get_host(service['host']) == self.host_name]
@@ -69,6 +72,7 @@
@decorators.idempotent_id('67ec6902-f91d-4dec-91fa-338523208bbc')
def test_get_service_by_volume_host_name(self):
+ """Test getting volume service by volume host name"""
volume_id = self.create_volume()['id']
volume = self.admin_volume_client.show_volume(volume_id)['volume']
hostname = _get_host(volume['os-vol-host-attr:host'])
@@ -83,7 +87,7 @@
@decorators.idempotent_id('ffa6167c-4497-4944-a464-226bbdb53908')
def test_get_service_by_service_and_host_name(self):
-
+ """Test getting volume service by binary name and host name"""
services = (self.admin_volume_services_client.list_services(
host=self.host_name, binary=self.binary_name))['services']
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index b64face..55ec428 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -24,11 +24,13 @@
class VolumeTypesAccessTest(base.BaseVolumeAdminTest):
+ """Test volume type access"""
credentials = ['primary', 'alt', 'admin']
@decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
def test_volume_type_access_add(self):
+ """Test adding volume type access for non-admin project"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
@@ -52,6 +54,7 @@
@decorators.idempotent_id('5220eb28-a435-43ce-baaf-ed46f0e95159')
def test_volume_type_access_list(self):
+ """Test listing volume type access"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 730acdf..852aa93 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -19,6 +19,7 @@
class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
+ """Test volume type extra specs"""
@classmethod
def resource_setup(cls):
@@ -27,7 +28,7 @@
@decorators.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
def test_volume_type_extra_specs_list(self):
- # List Volume types extra specs.
+ """Test listing volume type extra specs"""
extra_specs = {"spec1": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -40,7 +41,7 @@
@decorators.idempotent_id('0806db36-b4a0-47a1-b6f3-c2e7f194d017')
def test_volume_type_extra_specs_update(self):
- # Update volume type extra specs
+ """Test updating volume type extra specs"""
extra_specs = {"spec2": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -74,7 +75,7 @@
@decorators.idempotent_id('d4772798-601f-408a-b2a5-29e8a59d1220')
def test_volume_type_extra_spec_create_get_delete(self):
- # Create/Get/Delete volume type extra spec.
+ """Test Create/Get/Delete volume type extra specs"""
spec_key = "spec3"
extra_specs = {spec_key: "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
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/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 45060d0..bd4b3fa 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -67,11 +67,8 @@
# Export Backup
export_backup = (self.admin_backups_client.export_backup(backup['id'])
['backup-record'])
- self.assertIn('backup_service', export_backup)
- self.assertIn('backup_url', export_backup)
self.assertTrue(export_backup['backup_service'].startswith(
'cinder.backup.drivers'))
- self.assertIsNotNone(export_backup['backup_url'])
# NOTE(geguileo): Backups are imported with the same backup id
# (important for incremental backups among other things), so we cannot
@@ -92,7 +89,6 @@
# deletions will delete data from the backup back-end because they
# were both pointing to the same backend data.
self.addCleanup(self._delete_backup, new_id)
- self.assertIn("id", import_backup)
self.assertEqual(new_id, import_backup['id'])
waiters.wait_for_volume_resource_status(self.admin_backups_client,
import_backup['id'],
diff --git a/tempest/api/volume/admin/test_volumes_list.py b/tempest/api/volume/admin/test_volumes_list.py
index 6ce4a85..c3229f0 100644
--- a/tempest/api/volume/admin/test_volumes_list.py
+++ b/tempest/api/volume/admin/test_volumes_list.py
@@ -24,6 +24,7 @@
class VolumesListAdminTestJSON(base.BaseVolumeAdminTest):
+ """Test listing volumes with admin privilege"""
@classmethod
def resource_setup(cls):
@@ -41,7 +42,7 @@
@decorators.idempotent_id('5866286f-3290-4cfd-a414-088aa6cdc469')
def test_volume_list_param_tenant(self):
- # Test to list volumes from single tenant
+ """Test admin can list volumes belonging to specified project"""
# Create a volume in admin tenant
adm_vol = self.admin_volume_client.create_volume(
size=CONF.volume.volume_size)['volume']
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 0b6ee38..39369be 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -22,7 +22,7 @@
@decorators.idempotent_id('01f1ae88-eba9-4c6b-a011-6f7ace06b725')
def test_get_availability_zone_list(self):
- # List of availability zone
+ """Test listing volume available zones"""
availability_zone = (
self.availability_zone_client.list_availability_zones()
['availabilityZoneInfo'])
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index 39ce00c..acd9ca2 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -26,10 +26,11 @@
class ExtensionsTestJSON(base.BaseVolumeTest):
+ """Test volume extensions"""
@decorators.idempotent_id('94607eb0-43a5-47ca-82aa-736b41bd2e2c')
def test_list_extensions(self):
- # List of all extensions
+ """Test listing volume extensions"""
extensions = (self.volumes_extension_client.list_extensions()
['extensions'])
if not CONF.volume_feature_enabled.api_extensions:
diff --git a/tempest/api/volume/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
index 53b3acc..8f9bbd2 100644
--- a/tempest/api/volume/test_image_metadata.py
+++ b/tempest/api/volume/test_image_metadata.py
@@ -24,6 +24,7 @@
class VolumesImageMetadata(base.BaseVolumeTest):
+ """Test volume image metadata"""
@classmethod
def skip_checks(cls):
@@ -41,6 +42,7 @@
@decorators.idempotent_id('03efff0b-5c75-4822-8f10-8789ac15b13e')
@utils.services('image')
def test_update_show_delete_image_metadata(self):
+ """Test update/show/delete volume's image metadata"""
# Update image metadata
image_metadata = {'image_id': '5137a025-3c5f-43c1-bc64-5f41270040a5',
'image_name': 'image',
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index e6fe25d..ee1b5e5 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -23,6 +23,8 @@
class SnapshotMetadataTestJSON(base.BaseVolumeTest):
+ """Test snapshot metadata"""
+
@classmethod
def skip_checks(cls):
super(SnapshotMetadataTestJSON, cls).skip_checks()
@@ -45,6 +47,7 @@
@decorators.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c')
def test_crud_snapshot_metadata(self):
+ """Test create/get/update/delete snapshot metadata"""
# Create metadata for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
@@ -82,7 +85,7 @@
@decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
def test_update_show_snapshot_metadata_item(self):
- # Update metadata item for the snapshot
+ """Test update/show snapshot metadata item"""
metadata = {"key1": "value1",
"key2": "value2",
"key3": "value3"}
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 9edffc6..5b50bfa 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -25,6 +25,8 @@
class VolumesActionsTest(base.BaseVolumeTest):
+ """Test volume actions"""
+
create_default_network = True
@classmethod
@@ -38,6 +40,7 @@
@decorators.attr(type='smoke')
@utils.services('compute')
def test_attach_detach_volume_to_instance(self):
+ """Test attaching and detaching volume to instance"""
# Create a server
server = self.create_server()
# Volume is attached and detached successfully from an instance
@@ -53,7 +56,7 @@
@decorators.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
def test_volume_bootable(self):
- # Verify that a volume bootable flag is retrieved
+ """Test setting and retrieving bootable flag of a volume"""
for bool_bootable in [True, False]:
self.volumes_client.set_bootable_volume(self.volume['id'],
bootable=bool_bootable)
@@ -69,6 +72,11 @@
@decorators.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371')
@utils.services('compute')
def test_get_volume_attachment(self):
+ """Test getting volume attachments
+
+ Attach a volume to a server, and then retrieve volume's attachments
+ info.
+ """
# Create a server
server = self.create_server()
# Verify that a volume's attachment information is retrieved
@@ -84,7 +92,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' %
@@ -97,6 +104,7 @@
@decorators.idempotent_id('d8f1ca95-3d5b-44a3-b8ca-909691c9532d')
@utils.services('image')
def test_volume_upload(self):
+ """Test uploading volume to create an image"""
# NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
@@ -119,6 +127,7 @@
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
+ """Test reserving and unreserving volume"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# To get the volume info
@@ -132,6 +141,7 @@
@decorators.idempotent_id('fff74e1e-5bd3-4b33-9ea9-24c103bc3f59')
def test_volume_readonly_update(self):
+ """Test updating and retrieve volume's readonly flag"""
for readonly in [True, False]:
# Update volume readonly
self.volumes_client.update_volume_readonly(self.volume['id'],
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index dc6dc2d..2e78114 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -91,11 +91,7 @@
self.assertEqual('container', backup['container'])
# Get all backups with detail
- backups = self.backups_client.list_backups(
- detail=True)['backups']
- for backup_info in backups:
- self.assertIn('created_at', backup_info)
- self.assertIn('links', backup_info)
+ backups = self.backups_client.list_backups(detail=True)['backups']
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
@@ -196,7 +192,6 @@
backup['id'], **update_kwargs)['backup']
self.assertEqual(backup['id'], update_backup['id'])
self.assertEqual(update_kwargs['name'], update_backup['name'])
- self.assertIn('links', update_backup)
# Assert response body for show_backup method
retrieved_backup = self.backups_client.show_backup(
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_negative.py b/tempest/api/volume/test_volumes_negative.py
index 866bd87..76c22f0 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -28,6 +28,7 @@
class VolumesNegativeTest(base.BaseVolumeTest):
+ """Negative tests of volumes"""
@classmethod
def resource_setup(cls):
@@ -58,50 +59,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f131c586-9448-44a4-a8b0-54ca838aa43e')
def test_volume_get_nonexistent_volume_id(self):
- # Should not be able to get a non-existent volume
+ """Test getting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29')
def test_volume_delete_nonexistent_volume_id(self):
- # Should not be able to delete a non-existent Volume
+ """Test deleting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1ed83a8a-682d-4dfb-a30e-ee63ffd6c049')
def test_create_volume_with_invalid_size(self):
- # Should not be able to create volume with invalid size in request
+ """Test creating volume with invalid size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='#$%')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9387686f-334f-4d31-a439-33494b9e2683')
def test_create_volume_without_passing_size(self):
- # Should not be able to create volume without passing size
- # in request
+ """Test creating volume with empty size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41331caa-eaf4-4001-869d-bc18c1869360')
def test_create_volume_with_size_zero(self):
- # Should not be able to create volume with size zero
+ """Test creating volume with zero size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='0')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8b472729-9eba-446e-a83b-916bdb34bef7')
def test_create_volume_with_size_negative(self):
- # Should not be able to create volume with size negative
+ """Test creating volume with negative size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='-1')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('10254ed8-3849-454e-862e-3ab8e6aa01d2')
def test_create_volume_with_nonexistent_volume_type(self):
- # Should not be able to create volume with non-existent volume type
+ """Test creating volume with non existent volume type should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
volume_type=data_utils.rand_uuid())
@@ -109,7 +109,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9')
def test_create_volume_with_nonexistent_snapshot_id(self):
- # Should not be able to create volume with non-existent snapshot
+ """Test creating volume with non existent snapshot should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
snapshot_id=data_utils.rand_uuid())
@@ -117,7 +117,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344')
def test_create_volume_with_nonexistent_source_volid(self):
- # Should not be able to create volume with non-existent source volume
+ """Test creating volume with non existent source volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
source_volid=data_utils.rand_uuid())
@@ -125,46 +125,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
def test_update_volume_with_nonexistent_volume_id(self):
+ """Test updating non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
def test_update_volume_with_invalid_volume_id(self):
+ """Test updating volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
def test_update_volume_with_empty_volume_id(self):
+ """Test updating volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('30799cfd-7ee4-446c-b66c-45b383ed211b')
def test_get_invalid_volume_id(self):
- # Should not be able to get volume with invalid id
+ """Test getting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c6c3db06-29ad-4e91-beb0-2ab195fe49e3')
def test_get_volume_without_passing_volume_id(self):
- # Should not be able to get volume when empty ID is passed
+ """Test getting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.show_volume, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1f035827-7c32-4019-9240-b4ec2dbd9dfd')
def test_delete_invalid_volume_id(self):
- # Should not be able to delete volume when invalid ID is passed
+ """Test deleting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('441a1550-5d44-4b30-af0f-a6d402f52026')
def test_delete_volume_without_passing_volume_id(self):
- # Should not be able to delete volume when empty ID is passed
+ """Test deleting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.delete_volume, '')
@@ -172,6 +175,7 @@
@decorators.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@utils.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
+ """Test attaching non existent volume to server should fail"""
server = self.create_server()
self.assertRaises(lib_exc.NotFound,
@@ -183,6 +187,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a')
def test_detach_volumes_with_invalid_volume_id(self):
+ """Test detaching volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.detach_volume,
'xxx')
@@ -190,7 +195,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e0c75c74-ee34-41a9-9288-2a2051452854')
def test_volume_extend_with_size_smaller_than_original_size(self):
- # Extend volume with smaller size than original size.
+ """Test extending volume with decreasing size should fail"""
extend_size = 0
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -199,7 +204,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5d0b480d-e833-439f-8a5a-96ad2ed6f22f')
def test_volume_extend_with_non_number_size(self):
- # Extend volume when size is non number.
+ """Test extending volume with non-integer size should fail"""
extend_size = 'abc'
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -208,7 +213,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('355218f1-8991-400a-a6bb-971239287d92')
def test_volume_extend_with_None_size(self):
- # Extend volume with None size.
+ """Test extending volume with none size should fail"""
extend_size = None
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -217,7 +222,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb')
def test_volume_extend_with_nonexistent_volume_id(self):
- # Extend volume size when volume is nonexistent.
+ """Test extending non existent volume should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
data_utils.rand_uuid(), new_size=extend_size)
@@ -225,7 +230,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
def test_volume_extend_without_passing_volume_id(self):
- # Extend volume size when passing volume id is None.
+ """Test extending volume without passing volume id should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
None, new_size=extend_size)
@@ -233,6 +238,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2')
def test_reserve_volume_with_nonexistent_volume_id(self):
+ """Test reserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.reserve_volume,
data_utils.rand_uuid())
@@ -240,6 +246,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c')
def test_unreserve_volume_with_nonexistent_volume_id(self):
+ """Test unreserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.unreserve_volume,
data_utils.rand_uuid())
@@ -247,6 +254,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
def test_reserve_volume_with_negative_volume_status(self):
+ """Test reserving already reserved volume should fail"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
@@ -259,6 +267,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f')
def test_list_volumes_with_nonexistent_name(self):
+ """Test listing volumes with non existent name should get nothing"""
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = self.volumes_client.list_volumes(
@@ -268,6 +277,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359')
def test_list_volumes_detail_with_nonexistent_name(self):
+ """Test listing volume details with non existent name
+
+ Listing volume details with non existent name should get nothing.
+ """
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = \
@@ -278,6 +291,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c')
def test_list_volumes_with_invalid_status(self):
+ """Test listing volumes with invalid status should get nothing"""
params = {'status': 'null'}
fetched_volume = self.volumes_client.list_volumes(
params=params)['volumes']
@@ -286,6 +300,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ba94b27b-be3f-496c-a00e-0283b373fa75')
def test_list_volumes_detail_with_invalid_status(self):
+ """Test listing volume details with invalid status
+
+ Listing volume details with invalid status should get nothing
+ """
params = {'status': 'null'}
fetched_volume = \
self.volumes_client.list_volumes(detail=True,
@@ -296,6 +314,7 @@
@decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f')
@utils.services('image')
def test_create_volume_from_image_with_decreasing_size(self):
+ """Test creating volume from image with decreasing size should fail"""
# Create image
image = self.create_image()
@@ -311,6 +330,7 @@
@decorators.idempotent_id('d15e7f35-2cfc-48c8-9418-c8223a89bcbb')
@utils.services('image')
def test_create_volume_from_deactivated_image(self):
+ """Test creating volume from deactivated image should fail"""
# Create image
image = self.create_image()
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/config.py b/tempest/config.py
index eca2023..989b8ef 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -481,6 +481,12 @@
'MIN_LIBVIRT_VERSION is >= 1.2.17 on all '
'branches from stable/rocky and will be '
'removed in a future release.'),
+ cfg.BoolOpt('can_migrate_between_any_hosts',
+ default=True,
+ help="Does the test environment support migrating between "
+ "any hosts? In environments with non-homogeneous compute "
+ "nodes you can set this to False so that it will select "
+ "destination host for migrating automatically"),
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
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/backups.py b/tempest/lib/api_schema/response/volume/backups.py
new file mode 100644
index 0000000..9e85f5f
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/backups.py
@@ -0,0 +1,229 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+common_show_backup = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'object_count': {'type': 'integer'},
+ 'container': {'type': ['string', 'null']},
+ 'description': {'type': ['string', 'null']},
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': parameter_types.date_time,
+ 'updated_at': parameter_types.date_time_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'has_dependent_backups': {'type': 'boolean'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'fail_reason': {'type': ['string', 'null']},
+ 'size': {'type': 'integer'},
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'is_incremental': {'type': 'boolean'},
+ 'data_timestamp': parameter_types.date_time_or_null,
+ 'snapshot_id': {'type': ['string', 'null']},
+ # TODO(zhufl): os-backup-project-attr:project_id is added
+ # in 3.18, we should move it to the 3.18 schema file when
+ # microversion is supported in volume interfaces.
+ 'os-backup-project-attr:project_id': {
+ 'type': 'string', 'format': 'uuid'},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ # TODO(zhufl): user_id is added in 3.56, we should move it
+ # to the 3.56 schema file when microversion is supported
+ # in volume interfaces.
+ 'user_id': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'object_count', 'fail_reason', 'links',
+ 'created_at', 'updated_at', 'name', 'volume_id', 'size', 'id',
+ 'data_timestamp']
+}
+
+create_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+update_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'},
+ 'metadata': {'^.+$': {'type': 'string'}}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+restore_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'restore': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_id', 'volume_id', 'volume_name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['restore']
+ }
+}
+
+delete_backup = {'status_code': [202]}
+
+show_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': common_show_backup
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+list_backups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ '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']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+list_backups_detail = copy.deepcopy(common_show_backup)
+# 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_backups_detail['properties'].update({'count': {'type': 'integer'}})
+list_backups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ 'type': 'array',
+ 'items': list_backups_detail
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+export_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup-record': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_service': {'type': 'string'},
+ 'backup_url': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_service', 'backup_url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup-record']
+ }
+}
+
+import_backup = {
+ 'status_code': [201],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+reset_backup_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/groups.py b/tempest/lib/api_schema/response/volume/groups.py
new file mode 100644
index 0000000..cb31269
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/groups.py
@@ -0,0 +1,164 @@
+# 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 = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+delete_group = {'status_code': [202]}
+
+show_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should move it
+ # to the 3.25 schema file when microversion is supported
+ # in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'replication_status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+list_groups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+list_groups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should
+ # move it to the 3.25 schema file when microversion
+ # is supported in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+create_group_from_source = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+update_group = {'status_code': [202]}
+reset_group_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/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 8ac1d38..71fed02 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -11,7 +11,6 @@
# under the License.
import functools
-import re
import sys
import netaddr
@@ -134,9 +133,8 @@
This method will not unmount the config drive, so unmount_config_drive
must be used for cleanup.
"""
- cmd_blkid = 'blkid | grep -i config-2'
- result = self.exec_command(cmd_blkid)
- dev_name = re.match('([^:]+)', result).group()
+ cmd_blkid = 'blkid -L config-2 -o device'
+ dev_name = self.exec_command(cmd_blkid).strip()
try:
self.exec_command('sudo mount %s /mnt' % dev_name)
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 970471e..1df45fa 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_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 backups 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({'backup': kwargs})
resp, body = self.post('backups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def update_backup(self, backup_id, **kwargs):
@@ -47,7 +48,7 @@
put_body = json.dumps({'backup': kwargs})
resp, body = self.put('backups/%s' % backup_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def restore_backup(self, backup_id, **kwargs):
@@ -60,13 +61,13 @@
post_body = json.dumps({'restore': kwargs})
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.restore_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % backup_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
@@ -74,7 +75,7 @@
url = "backups/%s" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def list_backups(self, detail=False, **params):
@@ -86,13 +87,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail
"""
url = "backups"
+ list_backups_schema = schema.list_backups_no_detail
if detail:
url += "/detail"
+ list_backups_schema = schema.list_backups_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_backups_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def export_backup(self, backup_id):
@@ -100,7 +103,7 @@
url = "backups/%s/export_record" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.export_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def import_backup(self, **kwargs):
@@ -113,14 +116,14 @@
post_body = json.dumps({'backup-record': kwargs})
resp, body = self.post("backups/import_record", post_body)
body = json.loads(body)
- self.expected_success(201, resp.status)
+ self.validate_response(schema.import_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_backup_status(self, backup_id, status):
"""Reset the specified backup's status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('backups/%s/action' % backup_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_backup_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index ffae232..3d8523d 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_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 groups 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
@@ -23,6 +24,7 @@
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
+ api_version = 'v3'
def create_group(self, **kwargs):
"""Creates a group.
@@ -35,7 +37,7 @@
post_body = json.dumps({'group': kwargs})
resp, body = self.post('groups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id, delete_volumes=True):
@@ -49,7 +51,7 @@
post_body = json.dumps({'delete': post_body})
resp, body = self.post('groups/%s/action' % group_id,
post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
@@ -62,7 +64,7 @@
url = "groups/%s" % str(group_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group, resp, body)
return rest_client.ResponseBody(resp, body)
def list_groups(self, detail=False, **params):
@@ -74,13 +76,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/#list-groups-with-details
"""
url = "groups"
+ schema_list_groups = schema.list_groups_no_detail
if detail:
url += "/detail"
+ schema_list_groups = schema.list_groups_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(schema_list_groups, resp, body)
return rest_client.ResponseBody(resp, body)
def create_group_from_source(self, **kwargs):
@@ -93,7 +97,7 @@
post_body = json.dumps({'create-from-src': kwargs})
resp, body = self.post('groups/action', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_from_source, resp, body)
return rest_client.ResponseBody(resp, body)
def update_group(self, group_id, **kwargs):
@@ -105,7 +109,7 @@
"""
put_body = json.dumps({'group': kwargs})
resp, body = self.put('groups/%s' % group_id, put_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_group, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_group_status(self, group_id, status_to_set):
@@ -116,7 +120,7 @@
"""
post_body = json.dumps({'reset_status': {'status': status_to_set}})
resp, body = self.post('groups/%s/action' % group_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_group_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 9c6729b..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):
@@ -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/manager.py b/tempest/manager.py
index e3174d4..b485ef2 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -33,7 +33,7 @@
def __init__(self, credentials, scope='project'):
msg = ("tempest.manager.Manager is not a stable interface and as such "
- "it should not imported directly. It will be removed as "
+ "it should not be imported directly. It will be removed as "
"soon as the client manager becomes available in tempest.lib.")
LOG.warning(msg)
dscv = CONF.identity.disable_ssl_certificate_validation
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a80b77d..ff860d5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -91,10 +91,32 @@
volume_microversion=self.volume_request_microversion,
placement_microversion=self.placement_request_microversion))
+ def setup_compute_client(cls):
+ """Compute and Compute security groups client"""
+ cls.compute_images_client = cls.os_primary.compute_images_client
+ cls.keypairs_client = cls.os_primary.keypairs_client
+ cls.compute_security_groups_client = (
+ cls.os_primary.compute_security_groups_client)
+ cls.compute_security_group_rules_client = (
+ cls.os_primary.compute_security_group_rules_client)
+ cls.servers_client = cls.os_primary.servers_client
+ cls.interface_client = cls.os_primary.interfaces_client
+
+ def setup_network_client(cls):
+ """Neutron network client"""
+ cls.networks_client = cls.os_primary.networks_client
+ cls.ports_client = cls.os_primary.ports_client
+ cls.routers_client = cls.os_primary.routers_client
+ cls.subnets_client = cls.os_primary.subnets_client
+ cls.floating_ips_client = cls.os_primary.floating_ips_client
+ cls.security_groups_client = cls.os_primary.security_groups_client
+ cls.security_group_rules_client = (
+ cls.os_primary.security_group_rules_client)
+
@classmethod
def setup_clients(cls):
+ """This setup the service clients for the tests"""
super(ScenarioTest, cls).setup_clients()
- # Clients (in alphabetical order)
cls.flavors_client = cls.os_primary.flavors_client
cls.compute_floating_ips_client = (
cls.os_primary.compute_floating_ips_client)
@@ -108,37 +130,20 @@
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
- # Compute image client
- cls.compute_images_client = cls.os_primary.compute_images_client
- cls.keypairs_client = cls.os_primary.keypairs_client
- # Nova security groups client
- cls.compute_security_groups_client = (
- cls.os_primary.compute_security_groups_client)
- cls.compute_security_group_rules_client = (
- cls.os_primary.compute_security_group_rules_client)
- cls.servers_client = cls.os_primary.servers_client
- cls.interface_client = cls.os_primary.interfaces_client
- # Neutron network client
- cls.networks_client = cls.os_primary.networks_client
- cls.ports_client = cls.os_primary.ports_client
- cls.routers_client = cls.os_primary.routers_client
- cls.subnets_client = cls.os_primary.subnets_client
- cls.floating_ips_client = cls.os_primary.floating_ips_client
- cls.security_groups_client = cls.os_primary.security_groups_client
- cls.security_group_rules_client = (
- cls.os_primary.security_group_rules_client)
- # Use the latest available volume clients
+
+ cls.setup_compute_client(cls)
+ cls.setup_network_client(cls)
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
cls.snapshots_client = cls.os_primary.snapshots_client_latest
cls.backups_client = cls.os_primary.backups_client_latest
# ## Test functions library
- #
# The create_[resource] functions only return body and discard the
# resp part which is not used in scenario tests
def create_port(self, network_id, client=None, **kwargs):
+ """Creates port"""
if not client:
client = self.ports_client
name = data_utils.rand_name(self.__class__.__name__)
@@ -156,6 +161,13 @@
return port
def create_keypair(self, client=None):
+ """Creates keypair
+
+ Keypair is a public key of OpenSSH key pair used for accessing
+ and create servers
+ Keypair can also be created by a private key for the same purpose
+ Here, the keys are randomly generated[public/private]
+ """
if not client:
client = self.keypairs_client
name = data_utils.rand_name(self.__class__.__name__)
@@ -295,6 +307,13 @@
def create_volume(self, size=None, name=None, snapshot_id=None,
imageRef=None, volume_type=None):
+ """Creates volume
+
+ This wrapper utility creates volume and waits for volume to be
+ in 'available' state.
+ This method returns the volume's full representation by GET request.
+ """
+
if size is None:
size = CONF.volume.volume_size
if imageRef:
@@ -334,6 +353,11 @@
def create_backup(self, volume_id, name=None, description=None,
force=False, snapshot_id=None, incremental=False,
container=None):
+ """Creates backup
+
+ This wrapper utility creates backup and waits for backup to be
+ in 'available' state.
+ """
name = name or data_utils.rand_name(
self.__class__.__name__ + "-backup")
@@ -351,6 +375,12 @@
return backup
def restore_backup(self, backup_id):
+ """Restore backup
+
+ This wrapper utility restores backup and waits for backup to be
+ in 'available' state.
+ """
+
restore = self.backups_client.restore_backup(backup_id)['restore']
self.addCleanup(self.volumes_client.delete_volume,
restore['volume_id'])
@@ -362,8 +392,31 @@
self.assertEqual(backup_id, restore['backup_id'])
return restore
+ def rebuild_server(self, server_id, image=None,
+ preserve_ephemeral=False, wait=True,
+ rebuild_kwargs=None):
+ if image is None:
+ image = CONF.compute.image_ref
+ rebuild_kwargs = rebuild_kwargs or {}
+ LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
+ server_id, image, preserve_ephemeral)
+ self.servers_client.rebuild_server(
+ server_id=server_id,
+ image_ref=image,
+ preserve_ephemeral=preserve_ephemeral,
+ **rebuild_kwargs)
+ if wait:
+ waiters.wait_for_server_status(self.servers_client,
+ server_id, 'ACTIVE')
+
def create_volume_snapshot(self, volume_id, name=None, description=None,
metadata=None, force=False):
+ """Creates volume
+
+ This wrapper utility creates volume snapshot and waits for backup
+ to be in 'available' state.
+ """
+
name = name or data_utils.rand_name(
self.__class__.__name__ + '-snapshot')
snapshot = self.snapshots_client.create_snapshot(
@@ -372,6 +425,7 @@
display_name=name,
description=description,
metadata=metadata)['snapshot']
+
self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
snapshot['id'])
self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
@@ -401,6 +455,23 @@
admin_volume_type_client.delete_volume_type(volume_type['id'])
def create_volume_type(self, client=None, name=None, backend_name=None):
+ """Creates volume type
+
+ In a multiple-storage back-end configuration,
+ each back end has a name (volume_backend_name).
+ The name of the back end is declared as an extra-specification
+ of a volume type (such as, volume_backend_name=LVM).
+ When a volume is created, the scheduler chooses an
+ appropriate back end to handle the request, according
+ to the volume type specified by the user.
+ The scheduler uses volume types to explicitly create volumes on
+ specific back ends.
+
+ Before using volume type, a volume type has to be declared
+ to Block Storage. In addition to that, an extra-specification
+ has to be created to link the volume type to a back end name.
+ """
+
if not client:
client = self.os_admin.volume_types_client_latest
if not name:
@@ -416,6 +487,7 @@
volume_type = client.create_volume_type(
name=randomized_name, extra_specs=extra_specs)['volume_type']
+ self.assertIn('id', volume_type)
self.addCleanup(self._cleanup_volume_type, volume_type)
return volume_type
@@ -456,7 +528,7 @@
return rules
def _create_security_group(self):
- # Create security group
+ """Create security group and add rules to security group"""
sg_name = data_utils.rand_name(self.__class__.__name__)
sg_desc = sg_name + " description"
secgroup = self.compute_security_groups_client.create_security_group(
@@ -470,7 +542,6 @@
# Add rules to the security group
self._create_loginable_secgroup_rule(secgroup['id'])
-
return secgroup
def get_remote_client(self, ip_address, username=None, private_key=None,
@@ -502,36 +573,7 @@
linux_client.validate_authentication()
return linux_client
- def _image_create(self, name, fmt, path,
- disk_format=None, properties=None):
- if properties is None:
- properties = {}
- name = data_utils.rand_name('%s-' % name)
- params = {
- 'name': name,
- 'container_format': fmt,
- 'disk_format': disk_format or fmt,
- }
- if CONF.image_feature_enabled.api_v1:
- params['is_public'] = 'False'
- params['properties'] = properties
- params = {'headers': common_image.image_meta_to_headers(**params)}
- else:
- params['visibility'] = 'private'
- # Additional properties are flattened out in the v2 API.
- params.update(properties)
- body = self.image_client.create_image(**params)
- image = body['image'] if 'image' in body else body
- self.addCleanup(self.image_client.delete_image, image['id'])
- self.assertEqual("queued", image['status'])
- with open(path, 'rb') as image_file:
- if CONF.image_feature_enabled.api_v1:
- self.image_client.update_image(image['id'], data=image_file)
- else:
- self.image_client.store_image_file(image['id'], image_file)
- return image['id']
-
- def glance_image_create(self):
+ def image_create(self, name='scenario-img'):
img_path = CONF.scenario.img_file
if not os.path.exists(img_path):
# TODO(kopecmartin): replace LOG.warning for rasing
@@ -553,16 +595,38 @@
"properties: %s",
img_path, img_container_format, img_disk_format,
img_properties)
- image = self._image_create('scenario-img',
- img_container_format,
- img_path,
- disk_format=img_disk_format,
- properties=img_properties)
- LOG.debug("image:%s", image)
-
- return image
+ if img_properties is None:
+ img_properties = {}
+ name = data_utils.rand_name('%s-' % name)
+ params = {
+ 'name': name,
+ 'container_format': img_container_format,
+ 'disk_format': img_disk_format or img_container_format,
+ }
+ if CONF.image_feature_enabled.api_v1:
+ params['is_public'] = 'False'
+ if img_properties:
+ params['properties'] = img_properties
+ params = {'headers': common_image.image_meta_to_headers(**params)}
+ else:
+ params['visibility'] = 'private'
+ # Additional properties are flattened out in the v2 API.
+ if img_properties:
+ params.update(img_properties)
+ body = self.image_client.create_image(**params)
+ image = body['image'] if 'image' in body else body
+ self.addCleanup(self.image_client.delete_image, image['id'])
+ self.assertEqual("queued", image['status'])
+ with open(img_path, 'rb') as image_file:
+ if CONF.image_feature_enabled.api_v1:
+ self.image_client.update_image(image['id'], data=image_file)
+ else:
+ self.image_client.store_image_file(image['id'], image_file)
+ LOG.debug("image:%s", image['id'])
+ return image['id']
def _log_console_output(self, servers=None, client=None):
+ """Console log output"""
if not CONF.compute_feature_enabled.console_output:
LOG.debug('Console output not supported, cannot log')
return
@@ -581,11 +645,12 @@
"for the console log", server['id'])
def _log_net_info(self, exc):
- # network debug is called as part of ssh init
+ """network debug is called as part of ssh init"""
if not isinstance(exc, lib_exc.SSHTimeout):
LOG.debug('Network information on a devstack host')
def create_server_snapshot(self, server, name=None):
+ """Creates server snapshot"""
# Glance client
_image_client = self.image_client
# Compute client
@@ -603,7 +668,7 @@
_image_client.delete_image, image_id)
if CONF.image_feature_enabled.api_v1:
- # In glance v1 the additional properties are stored in the headers.
+ # In glance v1 the additional properties are stored in the headers
resp = _image_client.check_image(image_id)
snapshot_image = common_image.get_image_meta_from_headers(resp)
image_props = snapshot_image.get('properties', {})
@@ -633,22 +698,34 @@
return snapshot_image
def nova_volume_attach(self, server, volume_to_attach):
+ """Compute volume attach
+
+ This utility attaches volume from compute and waits for the
+ volume status to be 'in-use' state.
+ """
volume = self.servers_client.attach_volume(
server['id'], volumeId=volume_to_attach['id'])['volumeAttachment']
self.assertEqual(volume_to_attach['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
-
# Return the updated volume after the attachment
return self.volumes_client.show_volume(volume['id'])['volume']
def nova_volume_detach(self, server, volume):
+ """Compute volume detach
+
+ This utility detaches volume from compute and check whether the
+ volume status is 'available' state, and if not, an exception
+ will be thrown.
+ """
self.servers_client.detach_volume(server['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
def ping_ip_address(self, ip_address, should_succeed=True,
ping_timeout=None, mtu=None, server=None):
+ """ping ip address"""
timeout = ping_timeout or CONF.validation.ping_timeout
cmd = ['ping', '-c1', '-w1']
@@ -709,6 +786,7 @@
:raises: AssertError if the result of the connectivity check does
not match the value of the should_connect param
"""
+
LOG.debug('checking network connections to IP %s with user: %s',
ip_address, username)
if should_connect:
@@ -732,7 +810,7 @@
LOG.exception(extra_msg)
raise
- def create_floating_ip(self, thing, pool_name=None):
+ def create_floating_ip(self, server, pool_name=None):
"""Create a floating IP and associates to a server on Nova"""
if not pool_name:
@@ -743,11 +821,17 @@
self.compute_floating_ips_client.delete_floating_ip,
floating_ip['id'])
self.compute_floating_ips_client.associate_floating_ip_to_server(
- floating_ip['ip'], thing['id'])
+ floating_ip['ip'], server['id'])
return floating_ip
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
private_key=None, server=None):
+ """Creates timestamp
+
+ This wrapper utility does ssh, creates timestamp and returns the
+ created timestamp.
+ """
+
ssh_client = self.get_remote_client(ip_address,
private_key=private_key,
server=server)
@@ -765,6 +849,11 @@
def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
private_key=None, server=None):
+ """Returns timestamp
+
+ This wrapper utility does ssh and returns the timestamp.
+ """
+
ssh_client = self.get_remote_client(ip_address,
private_key=private_key,
server=server)
@@ -782,6 +871,7 @@
Based on the configuration we're in, return a correct ip
address for validating that a guest is up.
"""
+
if CONF.validation.connect_method == 'floating':
# The tests calling this method don't have a floating IP
# and can't make use of the validation resources. So the
@@ -807,6 +897,8 @@
@classmethod
def get_host_for_server(cls, server_id):
+ """Gets host of server"""
+
server_details = cls.os_admin.servers_client.show_server(server_id)
return server_details['server']['OS-EXT-SRV-ATTR:host']
@@ -825,6 +917,12 @@
security_group=None,
delete_on_termination=False,
name=None):
+ """Boot instance from resource
+
+ This wrapper utility boots instance from resource with block device
+ mapping with source info passed in arguments
+ """
+
create_kwargs = dict()
if keypair:
create_kwargs['key_name'] = keypair['name']
@@ -841,6 +939,7 @@
return self.create_server(image_id='', **create_kwargs)
def create_volume_from_image(self):
+ """Create volume from image"""
img_uuid = CONF.compute.image_ref
vol_name = data_utils.rand_name(
self.__class__.__name__ + '-volume-origin')
@@ -896,8 +995,17 @@
namestart='subnet-smoke', **kwargs):
"""Create a subnet for the given network
+ This utility creates subnet for the given network
within the cidr block configured for tenant networks.
+
+ :param **kwargs:
+ See extra parameters below
+
+ :Keyword Arguments:
+
+ * *ip_version = ip version of the given network,
"""
+
if not subnets_client:
subnets_client = self.subnets_client
@@ -1000,22 +1108,23 @@
"Unable to get network by name: %s" % network_name)
return net[0]
- def create_floating_ip(self, thing, external_network_id=None,
+ def create_floating_ip(self, server, external_network_id=None,
port_id=None, client=None):
"""Create a floating IP and associates to a resource/port on Neutron"""
+
if not external_network_id:
external_network_id = CONF.network.public_network_id
if not client:
client = self.floating_ips_client
if not port_id:
- port_id, ip4 = self._get_server_port_id_and_ip4(thing)
+ port_id, ip4 = self._get_server_port_id_and_ip4(server)
else:
ip4 = None
kwargs = {
'floating_network_id': external_network_id,
'port_id': port_id,
- 'tenant_id': thing.get('project_id') or thing['tenant_id'],
+ 'tenant_id': server.get('project_id') or server['tenant_id'],
'fixed_ip_address': ip4,
}
if CONF.network.subnet_id:
@@ -1035,6 +1144,7 @@
:param status: target status
:raises: AssertionError if status doesn't match
"""
+
floatingip_id = floating_ip['id']
def refresh():
@@ -1061,6 +1171,7 @@
private_key,
should_connect=True,
servers_for_debug=None):
+ """Checks tenant network connectivity"""
if not CONF.network.project_networks_reachable:
msg = 'Tenant networks not configured to be reachable.'
LOG.info(msg)
@@ -1093,6 +1204,7 @@
:returns: True, if the connection succeeded and it was expected to
succeed. False otherwise.
"""
+
method_name = '%s_check' % protocol
connectivity_checker = getattr(source, method_name)
@@ -1156,6 +1268,7 @@
:param project_id: secgroup will be created in this project
:returns: the created security group
"""
+
if client is None:
client = self.security_groups_client
if not project_id:
@@ -1197,6 +1310,7 @@
port_range_max: 22
}
"""
+
if sec_group_rules_client is None:
sec_group_rules_client = self.security_group_rules_client
if security_groups_client is None:
@@ -1287,6 +1401,7 @@
network has, a tenant router will be created and returned that
routes traffic to the public network.
"""
+
if not client:
client = self.routers_client
if not project_id:
@@ -1327,6 +1442,7 @@
'provider:segmentation_id': '42'}
:returns: network, subnet, router
"""
+
if CONF.network.shared_physical_network:
# NOTE(Shrews): This exception is for environments where tenant
# credential isolation is available, but network separation is
@@ -1383,6 +1499,7 @@
def create_encryption_type(self, client=None, type_id=None, provider=None,
key_size=None, cipher=None,
control_location=None):
+ """Creates an encryption type for volume"""
if not client:
client = self.admin_encryption_types_client
if not type_id:
@@ -1396,6 +1513,7 @@
def create_encrypted_volume(self, encryption_provider, volume_type,
key_size=256, cipher='aes-xts-plain64',
control_location='front-end'):
+ """Creates an encrypted volume"""
volume_type = self.create_volume_type(name=volume_type)
self.create_encryption_type(type_id=volume_type['id'],
provider=encryption_provider,
@@ -1436,11 +1554,12 @@
cls.object_client = cls.os_operator.object_client
def get_swift_stat(self):
- """get swift status for our user account."""
+ """Get swift status for our user account."""
self.account_client.list_account_containers()
LOG.debug('Swift status information obtained successfully')
def create_container(self, container_name=None):
+ """Creates container"""
name = container_name or data_utils.rand_name(
'swift-scenario-container')
self.container_client.update_container(name)
@@ -1453,10 +1572,12 @@
return name
def delete_container(self, container_name):
+ """Deletes container"""
self.container_client.delete_container(container_name)
LOG.debug('Container %s deleted', container_name)
def upload_object_to_container(self, container_name, obj_name=None):
+ """Uploads object to container"""
obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
obj_data = data_utils.random_bytes()
self.object_client.create_object(container_name, obj_name, obj_data)
@@ -1467,6 +1588,7 @@
return obj_name, obj_data
def delete_object(self, container_name, filename):
+ """Deletes object"""
self.object_client.delete_object(container_name, filename)
self.list_and_check_container_objects(container_name,
not_present_obj=[filename])
@@ -1474,8 +1596,13 @@
def list_and_check_container_objects(self, container_name,
present_obj=None,
not_present_obj=None):
- # List objects for a given container and assert which are present and
- # which are not.
+ """List and verify objects for a given container
+
+ This utility lists objects for a given container
+ and asserts which are present and
+ which are not
+ """
+
if present_obj is None:
present_obj = []
if not_present_obj is None:
@@ -1490,5 +1617,6 @@
self.assertNotIn(obj, object_list)
def download_and_verify(self, container_name, obj_name, expected_data):
+ """Asserts the object and expected data to verify if they are same"""
_, obj = self.object_client.get_object(container_name, obj_name)
self.assertEqual(obj, expected_data)
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 008d1ae..fc93a5e 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -44,7 +44,7 @@
raise cls.skipException('Encrypted volume attach is not supported')
def launch_instance(self):
- image = self.glance_image_create()
+ image = self.image_create()
keypair = self.create_keypair()
return self.create_server(image_id=image, key_name=keypair['name'])
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 4cd860d..fe42583 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -106,7 +106,7 @@
@decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
@utils.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
- image = self.glance_image_create()
+ image = self.image_create()
keypair = self.create_keypair()
server = self.create_server(image_id=image, key_name=keypair['name'])
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 8de6614..14f24c7 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -130,7 +130,7 @@
key_name=self.keypair['name'],
security_groups=[{'name': self.sec_grp['name']}],
networks=[{'uuid': n['id']} for n in networks])
- fip = self.create_floating_ip(thing=srv)
+ fip = self.create_floating_ip(server=srv)
ips = self.define_server_ips(srv=srv)
ssh = self.get_remote_client(
ip_address=fip['floating_ip_address'],
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_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index 97e1132..ca7918a 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -45,6 +45,8 @@
"availability_zone": "az1",
"container": "volumebackups",
"created_at": "2013-04-02T10:35:27.000000",
+ "updated_at": "2013-04-02T10:39:27.000000",
+ "data_timestamp": "2013-04-02T10:35:27.000000",
"description": None,
"fail_reason": None,
"id": "2ef47aee-8844-490c-804d-2a8efe561c65",
@@ -64,7 +66,6 @@
"user_id": "515ba0dd59f84f25a6a084a45d8d93b2",
"size": 1,
"status": "available",
- "updated_at": "2013-04-02T10:35:27.000000",
"volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
"is_incremental": True,
"has_dependent_backups": False
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"]
- }
}
}
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 530ce5e..618c388 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -41,7 +41,7 @@
'x/intel-nfv-ci-tests', # https://review.opendev.org/#/c/634640/
'openstack/networking-generic-switch',
# https://review.opendev.org/#/c/634846/
- 'openstack/networking-l2gw-tempest-plugin',
+ 'x/networking-l2gw-tempest-plugin',
# https://review.opendev.org/#/c/635093/
'openstack/networking-midonet', # https://review.opendev.org/#/c/635096/
'x/networking-plumgrid', # https://review.opendev.org/#/c/635096/