Merge "Add cost time printing in wait_for_volume_resource_status"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 573b7d4..307eb07 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -338,4 +338,4 @@
* `3.3`_
- .. _3.3: https://docs.openstack.org/cinder/ocata/devref/api_microversion_history.html#id4
+ .. _3.3: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id4
diff --git a/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml b/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml
index 4abfe9e..ca42014 100644
--- a/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml
+++ b/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml
@@ -1,5 +1,7 @@
---
fixes:
- Add a missing return statement to the retype_volume API in the v2 volumes_client library.
- This changes the response body from None to an empty dictionary.
+ - |
+ Add a missing return statement to the retype_volume API in the v2
+ volumes_client library: Bug#1703997
+ This changes the response body from None to an empty dictionary.
diff --git a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
new file mode 100644
index 0000000..305e756
--- /dev/null
+++ b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
@@ -0,0 +1,15 @@
+---
+features:
+ - |
+ Add a new function called ``compare_version_header_to_response`` to
+ ``tempest.lib.common.api_version_utils``, which compares the API
+ micoversion in the response header to another microversion using the
+ comparators defined in
+ ``tempest.lib.common.api_version_request.APIVersionRequest``.
+
+ It is now possible to determine how to retrieve an attribute from a
+ response body of an API call, depending on the returned microversion.
+
+ Add a new exception type called ``InvalidParam`` to
+ ``tempest.lib.exceptions``, allowing the possibility of raising an
+ exception if an invalid parameter is passed to a library function.
diff --git a/releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml b/releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml
new file mode 100644
index 0000000..7ed3081
--- /dev/null
+++ b/releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new boolean config option ``domain_specific_drivers``
+ is added to the section ``identity-feature-enabled``.
+ This option must be enabled when testing an environment that
+ is configured to use domain-specific identity drivers.
diff --git a/releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml b/releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml
new file mode 100644
index 0000000..0787821
--- /dev/null
+++ b/releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml
@@ -0,0 +1,11 @@
+---
+prelude: >
+ This release marks the start of support for the Pike release in Tempest.
+other:
+ - OpenStack Releases supported after this release are **Pike**, **Ocata**,
+ and **Newton**.
+
+ The release under current development of this tag is Queens, meaning
+ that every Tempest commit is also tested against master during the Queens
+ cycle. However, this does not necessarily mean that using Tempest as of
+ this tag will work against a Queens (or future release) cloud.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index db01da0..df1de46 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ v17.0.0
v16.1.0
v16.0.0
v15.0.0
diff --git a/releasenotes/source/v17.0.0.rst b/releasenotes/source/v17.0.0.rst
new file mode 100644
index 0000000..3f50f11
--- /dev/null
+++ b/releasenotes/source/v17.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v17.0.0 Release Notes
+=====================
+
+.. release-notes:: 17.0.0 Release Notes
+ :version: 17.0.0
diff --git a/requirements.txt b/requirements.txt
index aaecaaa..36b9efa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@
testrepository>=0.0.18 # Apache-2.0/BSD
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
-oslo.log>=3.22.0 # Apache-2.0
+oslo.log>=3.30.0 # Apache-2.0
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
six>=1.9.0 # MIT
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index ea92563..0c80252 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -16,7 +16,6 @@
from tempest.api.compute import base
from tempest.common import compute
-from tempest.common import credentials_factory as credentials
from tempest.common import utils
from tempest import config
from tempest.lib.common.utils import test_utils
@@ -46,11 +45,6 @@
@classmethod
def skip_checks(cls):
super(AutoAllocateNetworkTest, cls).skip_checks()
- identity_version = cls.get_identity_version()
- if not credentials.is_admin_available(
- identity_version=identity_version):
- msg = "Missing Identity Admin API credentials in configuration."
- raise cls.skipException(msg)
if not CONF.service_available.neutron:
raise cls.skipException('Neutron is required')
if not utils.is_extension_enabled('auto-allocated-topology',
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 69c8a82..ebba73c 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -29,6 +29,8 @@
if CONF.service_available.neutron:
msg = ("%s skipped as neutron is available" % cls.__name__)
raise cls.skipException(msg)
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
@classmethod
def setup_clients(cls):
@@ -49,17 +51,14 @@
break
@decorators.idempotent_id('16b7d848-2f7c-4709-85a3-2dfb4576cc52')
- @utils.services('network')
def test_list_fixed_ip_details(self):
fixed_ip = self.client.show_fixed_ip(self.ip)
self.assertEqual(fixed_ip['fixed_ip']['address'], self.ip)
@decorators.idempotent_id('5485077b-7e46-4cec-b402-91dc3173433b')
- @utils.services('network')
def test_set_reserve(self):
self.client.reserve_fixed_ip(self.ip, reserve="None")
@decorators.idempotent_id('7476e322-b9ff-4710-bf82-49d51bac6e2e')
- @utils.services('network')
def test_set_unreserve(self):
self.client.reserve_fixed_ip(self.ip, unreserve="None")
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index c54c866..a5deb3c 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -29,6 +29,8 @@
if CONF.service_available.neutron:
msg = ("%s skipped as neutron is available" % cls.__name__)
raise cls.skipException(msg)
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
@classmethod
def setup_clients(cls):
@@ -51,14 +53,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f17f47d-daad-4adc-986e-12370c93e407')
- @utils.services('network')
def test_list_fixed_ip_details_with_non_admin_user(self):
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.show_fixed_ip, self.ip)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ce60042c-fa60-4836-8d43-1c8e3359dc47')
- @utils.services('network')
def test_set_reserve_with_non_admin_user(self):
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reserve_fixed_ip,
@@ -66,7 +66,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f1f7a35b-0390-48c5-9803-5f27461439db')
- @utils.services('network')
def test_set_unreserve_with_non_admin_user(self):
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reserve_fixed_ip,
@@ -74,7 +73,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f51cf464-7fc5-4352-bc3e-e75cfa2cb717')
- @utils.services('network')
def test_set_reserve_with_invalid_ip(self):
# NOTE(maurosr): since this exercises the same code snippet, we do it
# only for reserve action
@@ -87,7 +85,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fd26ef50-f135-4232-9d32-281aab3f9176')
- @utils.services('network')
def test_fixed_ip_with_invalid_action(self):
self.assertRaises(lib_exc.BadRequest,
self.client.reserve_fixed_ip,
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 4001e26..9ee0ac9 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -32,6 +32,8 @@
@classmethod
def skip_checks(cls):
super(FloatingIPsTestJSON, cls).skip_checks()
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@@ -62,7 +64,6 @@
super(FloatingIPsTestJSON, cls).resource_cleanup()
@decorators.idempotent_id('f7bfb946-297e-41b8-9e8c-aba8e9bb5194')
- @utils.services('network')
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
@@ -78,7 +79,6 @@
self.assertIn(floating_ip_details, body)
@decorators.idempotent_id('de45e989-b5ca-4a9b-916b-04a52e7bbb8b')
- @utils.services('network')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
# should be successful
@@ -93,7 +93,6 @@
self.client.wait_for_resource_deletion(floating_ip_body['id'])
@decorators.idempotent_id('307efa27-dc6f-48a0-8cd2-162ce3ef0b52')
- @utils.services('network')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_disassociate_floating_ip(self):
@@ -116,7 +115,6 @@
self.server_id)
@decorators.idempotent_id('6edef4b2-aaf1-4abc-bbe3-993e2561e0fe')
- @utils.services('network')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_already_associated_floating_ip(self):
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 3455a81..c3d7816 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
@@ -30,6 +30,8 @@
@classmethod
def skip_checks(cls):
super(FloatingIPsNegativeTestJSON, cls).skip_checks()
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@@ -58,7 +60,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6e0f059b-e4dd-48fb-8207-06e3bba5b074')
- @utils.services('network')
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
@@ -68,7 +69,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ae1c55a8-552b-44d4-bfb6-2a115a15d0ba')
- @utils.services('network')
def test_delete_nonexistent_floating_ip(self):
# Negative test:Deletion of a nonexistent floating IP
# from project should fail
@@ -79,7 +79,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('595fa616-1a71-4670-9614-46564ac49a4c')
- @utils.services('network')
def test_associate_nonexistent_floating_ip(self):
# Negative test:Association of a non existent floating IP
# to specific server should fail
@@ -90,7 +89,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0a081a66-e568-4e6b-aa62-9587a876dca8')
- @utils.services('network')
def test_dissociate_nonexistent_floating_ip(self):
# Negative test:Dissociation of a non existent floating IP should fail
# Dissociating non existent floating IP
@@ -100,7 +98,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('804b4fcb-bbf5-412f-925d-896672b61eb3')
- @utils.services('network')
def test_associate_ip_to_server_without_passing_floating_ip(self):
# Negative test:Association of empty floating IP to specific server
# should raise NotFound or BadRequest(In case of Nova V2.1) exception.
@@ -110,7 +107,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('58a80596-ffb2-11e6-9393-fa163e4fa634')
- @utils.services('network')
@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):
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 04d18c0..2314433 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -26,6 +26,8 @@
@classmethod
def skip_checks(cls):
super(FloatingIPDetailsTestJSON, cls).skip_checks()
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@@ -53,7 +55,6 @@
super(FloatingIPDetailsTestJSON, cls).resource_cleanup()
@decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
- @utils.services('network')
def test_list_floating_ips(self):
# Positive test:Should return the list of floating IPs
body = self.client.list_floating_ips()['floating_ips']
@@ -64,7 +65,6 @@
self.assertIn(self.floating_ip[i], floating_ips)
@decorators.idempotent_id('eef497e0-8ff7-43c8-85ef-558440574f84')
- @utils.services('network')
def test_get_floating_ip_details(self):
# Positive test:Should be able to GET the details of floatingIP
# Creating a floating IP for which details are to be checked
@@ -86,7 +86,6 @@
self.assertEqual(floating_ip_id, body['id'])
@decorators.idempotent_id('df389fc8-56f5-43cc-b290-20eda39854d3')
- @utils.services('network')
def test_list_floating_ip_pools(self):
# Positive test:Should return the list of floating IP Pools
floating_ip_pools = self.pools_client.list_floating_ip_pools()
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 f0b9fa5..0ade872 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
@@ -28,6 +28,8 @@
@classmethod
def skip_checks(cls):
super(FloatingIPDetailsNegativeTestJSON, cls).skip_checks()
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@@ -38,7 +40,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7ab18834-4a4b-4f28-a2c5-440579866695')
- @utils.services('network')
def test_get_nonexistent_floating_ip_details(self):
# Negative test:Should not be able to GET the details
# of non-existent floating IP
diff --git a/tempest/api/compute/security_groups/base.py b/tempest/api/compute/security_groups/base.py
index 5260a99..54a6da8 100644
--- a/tempest/api/compute/security_groups/base.py
+++ b/tempest/api/compute/security_groups/base.py
@@ -24,6 +24,12 @@
class BaseSecurityGroupsTest(base.BaseV2ComputeTest):
@classmethod
+ def skip_checks(cls):
+ super(BaseSecurityGroupsTest, cls).skip_checks()
+ if not utils.get_service_list()['network']:
+ raise cls.skipException("network service not enabled.")
+
+ @classmethod
def setup_credentials(cls):
# A network and a subnet will be created for these tests
cls.set_network_resources(network=True, subnet=True)
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 06edc1c..4c99ea6 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute.security_groups import base
-from tempest.common import utils
from tempest.lib import decorators
@@ -55,7 +54,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('850795d7-d4d3-4e55-b527-a774c0123d3a')
- @utils.services('network')
def test_security_group_rules_create(self):
# Positive test: Creation of Security Group rule
# should be successful
@@ -73,7 +71,6 @@
self._check_expected_response(rule)
@decorators.idempotent_id('7a01873e-3c38-4f30-80be-31a043cfe2fd')
- @utils.services('network')
def test_security_group_rules_create_with_optional_cidr(self):
# Positive test: Creation of Security Group rule
# with optional argument cidr
@@ -96,7 +93,6 @@
self._check_expected_response(rule)
@decorators.idempotent_id('7f5d2899-7705-4d4b-8458-4505188ffab6')
- @utils.services('network')
def test_security_group_rules_create_with_optional_group_id(self):
# Positive test: Creation of Security Group rule
# with optional argument group_id
@@ -125,7 +121,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('a6154130-5a55-4850-8be4-5e9e796dbf17')
- @utils.services('network')
def test_security_group_rules_list(self):
# Positive test: Created Security Group rules should be
# in the list of all rules
@@ -163,7 +158,6 @@
self.assertNotEmpty([i for i in rules if i['id'] == rule2_id])
@decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf')
- @utils.services('network')
def test_security_group_rules_delete_when_peer_group_deleted(self):
# Positive test:rule will delete when peer group deleting
# Creating a Security Group to add rules to it
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 f344d84..8283aae 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
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute.security_groups import base
-from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -29,7 +28,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1d507e98-7951-469b-82c3-23f1e6b8c254')
- @utils.services('network')
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
@@ -46,7 +44,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2244d7e4-adb7-4ecb-9930-2d77e123ce4f')
- @utils.services('network')
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
@@ -63,7 +60,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8bd56d02-3ffa-4d67-9933-b6b9a01d6089')
- @utils.services('network')
def test_create_security_group_rule_duplicate(self):
# Negative test: Create Security Group rule duplicate should fail
# Creating a Security Group to add rule to it
@@ -88,7 +84,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('84c81249-9f6e-439c-9bbf-cbb0d2cddbdf')
- @utils.services('network')
def test_create_security_group_rule_with_invalid_ip_protocol(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid ip_protocol
@@ -108,7 +103,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('12bbc875-1045-4f7a-be46-751277baedb9')
- @utils.services('network')
def test_create_security_group_rule_with_invalid_from_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid from_port
@@ -127,7 +121,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ff88804d-144f-45d1-bf59-dd155838a43a')
- @utils.services('network')
def test_create_security_group_rule_with_invalid_to_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid to_port
@@ -146,7 +139,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('00296fa9-0576-496a-ae15-fbab843189e0')
- @utils.services('network')
def test_create_security_group_rule_with_invalid_port_range(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid port range.
@@ -165,7 +157,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('56fddcca-dbb8-4494-a0db-96e9f869527c')
- @utils.services('network')
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
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 01b19ca..eeb423e 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute.security_groups import base
-from tempest.common import utils
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -30,7 +29,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('eb2b087d-633d-4d0d-a7bd-9e6ba35b32de')
- @utils.services('network')
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
# Create 3 Security Groups
@@ -62,7 +60,6 @@
for m_group in deleted_sgs))
@decorators.idempotent_id('ecc0da4a-2117-48af-91af-993cca39a615')
- @utils.services('network')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
# with char space between name along with
@@ -83,7 +80,6 @@
self.client.wait_for_resource_deletion(securitygroup['id'])
@decorators.idempotent_id('fe4abc0d-83f5-4c50-ad11-57a1127297a2')
- @utils.services('network')
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.
@@ -125,7 +121,6 @@
self.client.delete_security_group(sg2['id'])
@decorators.idempotent_id('7d4e1d3c-3209-4d6d-b020-986304ebad1f')
- @utils.services('network')
def test_update_security_groups(self):
# Update security group name and description
# Create a security group
@@ -144,7 +139,6 @@
self.assertEqual(s_new_des, fetched_group['description'])
@decorators.idempotent_id('79517d60-535a-438f-af3d-e6feab1cbea7')
- @utils.services('network')
def test_list_security_groups_by_server(self):
# Create a couple security groups that we will use
# for the server resource this test creates
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 e727dbb..9c44bb2 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -16,7 +16,6 @@
import testtools
from tempest.api.compute.security_groups import base
-from tempest.common import utils
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -34,7 +33,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('673eaec1-9b3e-48ed-bdf1-2786c1b9661c')
- @utils.services('network')
def test_security_group_get_nonexistent_group(self):
# Negative test:Should not be able to GET the details
# of non-existent Security Group
@@ -46,7 +44,6 @@
condition=CONF.service_available.neutron)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1759c3cb-b0fc-44b7-86ce-c99236be911d')
- @utils.services('network')
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
@@ -69,7 +66,6 @@
condition=CONF.service_available.neutron)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('777b6f14-aca9-4758-9e84-38783cfa58bc')
- @utils.services('network')
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
@@ -85,7 +81,6 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron allows duplicate names for security groups")
@decorators.attr(type=['negative'])
- @utils.services('network')
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
# be created
@@ -99,7 +94,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('36a1629f-c6da-4a26-b8b8-55e7e5d5cd58')
- @utils.services('network')
def test_delete_the_default_security_group(self):
# Negative test:Deletion of the "default" Security Group should Fail
default_security_group_id = None
@@ -115,7 +109,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6727c00b-214c-4f9e-9a52-017ac3e98411')
- @utils.services('network')
def test_delete_nonexistent_security_group(self):
# Negative test:Deletion of a non-existent Security Group should fail
non_exist_id = self.generate_random_security_group_id()
@@ -124,7 +117,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1438f330-8fa4-4aeb-8a94-37c250106d7f')
- @utils.services('network')
def test_delete_security_group_without_passing_id(self):
# Negative test:Deletion of a Security Group with out passing ID
# should Fail
@@ -135,7 +127,6 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron does not check the security group ID")
@decorators.attr(type=['negative'])
- @utils.services('network')
def test_update_security_group_with_invalid_sg_id(self):
# Update security_group with invalid sg_id should fail
s_name = data_utils.rand_name('sg')
@@ -150,7 +141,6 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron does not check the security group name")
@decorators.attr(type=['negative'])
- @utils.services('network')
def test_update_security_group_with_invalid_sg_name(self):
# Update security_group with invalid sg_name should fail
securitygroup = self.create_security_group()
@@ -165,7 +155,6 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron does not check the security group description")
@decorators.attr(type=['negative'])
- @utils.services('network')
def test_update_security_group_with_invalid_sg_des(self):
# Update security_group with invalid sg_des should fail
securitygroup = self.create_security_group()
@@ -178,7 +167,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('27edee9c-873d-4da6-a68a-3c256efebe8f')
- @utils.services('network')
def test_update_non_existent_security_group(self):
# Update a non-existent Security Group should Fail
non_exist_id = self.generate_random_security_group_id()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 4870a3d..b5fc39c 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -23,6 +23,7 @@
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common import api_version_utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -369,7 +370,11 @@
"been successful as it should have been "
"deleted during rotation.", oldest_backup)
- image1_id = data_utils.parse_image_id(resp['location'])
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+ image1_id = resp['image_id']
+ else:
+ image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
waiters.wait_for_image_status(glance_client,
image1_id, 'active')
@@ -380,7 +385,11 @@
backup_type='daily',
rotation=2,
name=backup2).response
- image2_id = data_utils.parse_image_id(resp['location'])
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+ image2_id = resp['image_id']
+ else:
+ image2_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(glance_client.delete_image, image2_id)
waiters.wait_for_image_status(glance_client,
image2_id, 'active')
@@ -419,7 +428,11 @@
backup_type='daily',
rotation=2,
name=backup3).response
- image3_id = data_utils.parse_image_id(resp['location'])
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+ image3_id = resp['image_id']
+ else:
+ image3_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(glance_client.delete_image, image3_id)
# the first back up should be deleted
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index cd5418f..34faf5f 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -26,7 +26,7 @@
LOG = logging.getLogger(__name__)
-class ExtensionsTestJSON(base.BaseV2ComputeTest):
+class ExtensionsTest(base.BaseV2ComputeTest):
@decorators.idempotent_id('3bb27738-b759-4e0d-a5fa-37d7a6df07d1')
def test_list_extensions(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 502bc1b..e0fed58 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.compute import base
from tempest.common import compute
from tempest.common.utils.linux import remote_client
@@ -143,6 +141,10 @@
self.assertEqual(server['id'], body['serverId'])
self.assertEqual(attachment['volumeId'], body['volumeId'])
self.assertEqual(attachment['id'], body['id'])
+ self.servers_client.detach_volume(server['id'],
+ attachment['volumeId'])
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, attachment['volumeId'], 'available')
class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
@@ -155,6 +157,12 @@
min_microversion = '2.20'
max_microversion = 'latest'
+ @classmethod
+ def skip_checks(cls):
+ super(AttachVolumeShelveTestJSON, cls).skip_checks()
+ if not CONF.compute_feature_enabled.shelve:
+ raise cls.skipException('Shelve is not available.')
+
def _count_volumes(self, server):
# Count number of volumes on an instance
volumes = 0
@@ -202,8 +210,6 @@
self.assertEqual(number_of_volumes, counted_volumes)
@decorators.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
def test_attach_volume_shelved_or_offload_server(self):
# Create server, count number of volumes on it, shelve
# server and attach pre-created volume to shelved server
@@ -229,8 +235,6 @@
self.assertIsNotNone(volume_attachment['device'])
@decorators.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
def test_detach_volume_shelved_or_offload_server(self):
# Count number of volumes on instance, shelve
# server and attach pre-created volume to shelved server
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
index 0f436eb..b8ca81d 100644
--- a/tempest/api/compute/volumes/test_volume_snapshots.py
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.compute import base
from tempest.common import waiters
from tempest import config
@@ -38,6 +36,9 @@
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ if not CONF.volume_feature_enabled.snapshot:
+ skip_msg = ("Cinder volume snapshots are disabled")
+ raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
@@ -46,8 +47,6 @@
cls.snapshots_client = cls.snapshots_extensions_client
@decorators.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
- @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
- 'Cinder volume snapshots are disabled')
def test_volume_snapshot_create_get_list_delete(self):
volume = self.create_volume()
self.addCleanup(self.delete_volume, volume['id'])
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 4bc987f..17db3ea 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -14,9 +14,12 @@
# under the License.
from tempest.api.identity import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+CONF = config.CONF
+
class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
@@ -130,7 +133,14 @@
self.addCleanup(self.groups_client.delete_group, group['id'])
group_ids.append(group['id'])
# List and Verify Groups
- body = self.groups_client.list_groups()['groups']
+ # When domain specific drivers are enabled the operations
+ # of listing all users and listing all groups are not supported,
+ # they need a domain filter to be specified
+ if CONF.identity_feature_enabled.domain_specific_drivers:
+ body = self.groups_client.list_groups(
+ domain_id=self.domain['id'])['groups']
+ else:
+ body = self.groups_client.list_groups()['groups']
for g in body:
fetched_ids.append(g['id'])
missing_groups = [g for g in group_ids if g not in fetched_ids]
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 47a3580..506c729 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -14,9 +14,12 @@
# under the License.
from tempest.api.identity import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+CONF = config.CONF
+
class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
@@ -82,6 +85,11 @@
def test_list_users_with_name(self):
# List users with name
params = {'name': self.domain_enabled_user['name']}
+ # When domain specific drivers are enabled the operations
+ # of listing all users and listing all groups are not supported,
+ # they need a domain filter to be specified
+ if CONF.identity_feature_enabled.domain_specific_drivers:
+ params['domain_id'] = self.domain_enabled_user['domain_id']
self._list_users_with_params(params, 'name',
self.domain_enabled_user,
self.non_domain_enabled_user)
@@ -89,7 +97,18 @@
@decorators.idempotent_id('b30d4651-a2ea-4666-8551-0c0e49692635')
def test_list_users(self):
# List users
- body = self.users_client.list_users()['users']
+ # When domain specific drivers are enabled the operations
+ # of listing all users and listing all groups are not supported,
+ # they need a domain filter to be specified
+ if CONF.identity_feature_enabled.domain_specific_drivers:
+ body_enabled_user = self.users_client.list_users(
+ domain_id=self.domain_enabled_user['domain_id'])['users']
+ body_non_enabled_user = self.users_client.list_users(
+ domain_id=self.non_domain_enabled_user['domain_id'])['users']
+ body = (body_enabled_user + body_non_enabled_user)
+ else:
+ body = self.users_client.list_users()['users']
+
fetched_ids = [u['id'] for u in body]
missing_users = [u['id'] for u in self.users
if u['id'] not in fetched_ids]
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 37f5ec2..a471bd6 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -41,6 +41,8 @@
api_extensions
"""
+ _project_network_cidr = CONF.network.project_network_cidr
+
@classmethod
def skip_checks(cls):
super(AllowedAddressPairTestJSON, cls).skip_checks()
@@ -103,7 +105,7 @@
@decorators.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4')
def test_update_port_with_cidr_address_pair(self):
# Update allowed address pair with cidr
- cidr = str(netaddr.IPNetwork(CONF.network.project_network_cidr))
+ cidr = str(netaddr.IPNetwork(self._project_network_cidr))
self._update_port_with_address(cidr)
@decorators.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933')
@@ -133,3 +135,4 @@
class AllowedAddressPairIpV6TestJSON(AllowedAddressPairTestJSON):
_ip_version = 6
+ _project_network_cidr = CONF.network.project_network_v6_cidr
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 206d1a8..88340c1 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -374,22 +374,36 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_external_network_visibility(self):
- """Verifies user can see external networks but not subnets."""
+ public_network_id = CONF.network.public_network_id
+
+ # find external network matching public_network_id
body = self.networks_client.list_networks(**{'router:external': True})
- networks = [network['id'] for network in body['networks']]
- self.assertNotEmpty(networks, "No external networks found")
+ external_network = next((network for network in body['networks']
+ if network['id'] == public_network_id), None)
+ self.assertIsNotNone(external_network, "Public network %s not found "
+ "in external network list"
+ % public_network_id)
nonexternal = [net for net in body['networks'] if
not net['router:external']]
self.assertEmpty(nonexternal, "Found non-external networks"
" in filtered list (%s)." % nonexternal)
- self.assertIn(CONF.network.public_network_id, networks)
+
# only check the public network ID because the other networks may
# belong to other tests and their state may have changed during this
# test
- body = self.subnets_client.list_subnets(
- network_id=CONF.network.public_network_id)
- self.assertEmpty(body['subnets'], "Public subnets visible")
+ body = self.subnets_client.list_subnets(network_id=public_network_id)
+
+ # check subnet visibility of external_network
+ if external_network['shared']:
+ self.assertNotEmpty(body['subnets'], "Subnets should be visible "
+ "for shared public network %s"
+ % public_network_id)
+ else:
+ self.assertEmpty(body['subnets'], "Subnets should not be visible "
+ "for non-shared public "
+ "network %s"
+ % public_network_id)
@decorators.idempotent_id('c72c1c0c-2193-4aca-ccc4-b1442640bbbb')
@utils.requires_ext(extension="standard-attr-description",
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 471f39a..41849bc 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -43,6 +44,8 @@
snapshot_id = self.snapshot['id']
self.admin_snapshots_client.reset_snapshot_status(snapshot_id,
status)
+ waiters.wait_for_volume_resource_status(self.snapshots_client,
+ snapshot_id, status)
super(SnapshotsActionsTest, self).tearDown()
def _create_reset_and_force_delete_temp_snapshot(self, status=None):
@@ -50,10 +53,11 @@
# and force delete temp snapshot
temp_snapshot = self.create_snapshot(volume_id=self.volume['id'])
if status:
- self.admin_snapshots_client.\
- reset_snapshot_status(temp_snapshot['id'], status)
- self.admin_snapshots_client.\
- force_delete_snapshot(temp_snapshot['id'])
+ self.admin_snapshots_client.reset_snapshot_status(
+ temp_snapshot['id'], status)
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, temp_snapshot['id'], status)
+ self.admin_snapshots_client.force_delete_snapshot(temp_snapshot['id'])
self.snapshots_client.wait_for_resource_deletion(temp_snapshot['id'])
def _get_progress_alias(self):
@@ -63,18 +67,19 @@
def test_reset_snapshot_status(self):
# Reset snapshot status to creating
status = 'creating'
- self.admin_snapshots_client.\
- reset_snapshot_status(self.snapshot['id'], status)
- snapshot_get = self.admin_snapshots_client.show_snapshot(
- self.snapshot['id'])['snapshot']
- self.assertEqual(status, snapshot_get['status'])
+ self.admin_snapshots_client.reset_snapshot_status(
+ self.snapshot['id'], status)
+ waiters.wait_for_volume_resource_status(self.snapshots_client,
+ self.snapshot['id'], status)
@decorators.idempotent_id('41288afd-d463-485e-8f6e-4eea159413eb')
def test_update_snapshot_status(self):
# Reset snapshot status to creating
status = 'creating'
- self.admin_snapshots_client.\
- reset_snapshot_status(self.snapshot['id'], status)
+ self.admin_snapshots_client.reset_snapshot_status(
+ self.snapshot['id'], status)
+ waiters.wait_for_volume_resource_status(self.snapshots_client,
+ self.snapshot['id'], status)
# Update snapshot status to error
progress = '80%'
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index e4ec442..ce0cbd2 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import random
-
from tempest.api.volume import base
from tempest.lib import decorators
@@ -42,20 +40,25 @@
"The count of volume hosts is < 2, "
"response of list hosts is: %s" % hosts)
- # Note(jeremyZ): Host in volume is always presented in two formats:
- # <host-name> or <host-name>@<driver-name>. Since Mitaka is EOL,
- # both formats can be chosen for test.
- host_names = [host['host_name'] for host in hosts]
- self.assertNotEmpty(host_names, "No available volume host is found, "
- "all hosts that found are: %s" % hosts)
+ # Note(jeremyZ): The show host API is to show volume usage info on the
+ # specified cinder-volume host. If the host does not run cinder-volume
+ # service, or the cinder-volume service is disabled on the host, the
+ # show host API should fail (return code: 404). The cinder-volume host
+ # is presented in format: <host-name>@driver-name.
+ c_vol_hosts = [host['host_name'] for host in hosts
+ if (host['service'] == 'cinder-volume'
+ and host['service-state'] == 'enabled')]
+ self.assertNotEmpty(c_vol_hosts,
+ "No available cinder-volume host is found, "
+ "all hosts that found are: %s" % hosts)
- # Choose a random host to get and check its elements
- host_details = self.admin_hosts_client.show_host(
- random.choice(host_names))['host']
- self.assertNotEmpty(host_details)
+ # Check each cinder-volume host.
host_detail_keys = ['project', 'volume_count', 'snapshot_count',
'host', 'total_volume_gb', 'total_snapshot_gb']
- for detail in host_details:
- self.assertIn('resource', detail)
- for key in host_detail_keys:
- self.assertIn(key, detail['resource'])
+ for host in c_vol_hosts:
+ host_details = self.admin_hosts_client.show_host(host)['host']
+ self.assertNotEmpty(host_details)
+ for detail in host_details:
+ self.assertIn('resource', detail)
+ for key in host_detail_keys:
+ self.assertIn(key, detail['resource'])
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index f99e03d..8d09217 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -30,6 +30,8 @@
if status:
self.admin_volume_client.reset_volume_status(
temp_volume['id'], status=status)
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, temp_volume['id'], status)
self.admin_volume_client.force_delete_volume(temp_volume['id'])
self.volumes_client.wait_for_resource_deletion(temp_volume['id'])
@@ -37,14 +39,15 @@
def test_volume_reset_status(self):
# test volume reset status : available->error->available
volume = self.create_volume()
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.volumes_client, volume['id'], 'available')
self.addCleanup(self.admin_volume_client.reset_volume_status,
volume['id'], status='available')
for status in ['error', 'available', 'maintenance']:
self.admin_volume_client.reset_volume_status(
volume['id'], status=status)
- volume_get = self.admin_volume_client.show_volume(
- volume['id'])['volume']
- self.assertEqual(status, volume_get['status'])
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, volume['id'], status)
@decorators.idempotent_id('21737d5a-92f2-46d7-b009-a0cc0ee7a570')
def test_volume_force_delete_when_volume_is_creating(self):
@@ -88,6 +91,8 @@
# Reset volume's status to error
self.admin_volume_client.reset_volume_status(volume_id, status='error')
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_id, 'error')
# Force detach volume
self.admin_volume_client.force_detach_volume(
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index afc3281..375aacb 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -99,8 +99,7 @@
'available')
# Verify Import Backup
- backups = self.admin_backups_client.list_backups(
- detail=True)['backups']
+ backups = self.admin_backups_client.list_backups()['backups']
self.assertIn(new_id, [b['id'] for b in backups])
# Restore backup
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 9142dc3..63ef85b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -72,6 +72,11 @@
if cls._api_version == 3:
cls.backups_client = cls.os_primary.backups_v3_client
cls.volumes_client = cls.os_primary.volumes_v3_client
+ cls.messages_client = cls.os_primary.volume_v3_messages_client
+ cls.versions_client = cls.os_primary.volume_v3_versions_client
+ cls.groups_client = cls.os_primary.groups_v3_client
+ cls.group_snapshots_client = (
+ cls.os_primary.group_snapshots_v3_client)
else:
cls.backups_client = cls.os_primary.backups_v2_client
cls.volumes_client = cls.os_primary.volumes_v2_client
@@ -82,10 +87,6 @@
cls.availability_zone_client = (
cls.os_primary.volume_v2_availability_zone_client)
cls.volume_limits_client = cls.os_primary.volume_v2_limits_client
- cls.messages_client = cls.os_primary.volume_v3_messages_client
- cls.versions_client = cls.os_primary.volume_v3_versions_client
- cls.groups_client = cls.os_primary.groups_v3_client
- cls.group_snapshots_client = cls.os_primary.group_snapshots_v3_client
def setUp(self):
super(BaseVolumeTest, self).setUp()
@@ -259,6 +260,11 @@
cls.admin_volume_client = cls.os_admin.volumes_v2_client
if cls._api_version == 3:
cls.admin_volume_client = cls.os_admin.volumes_v3_client
+ cls.admin_groups_client = cls.os_admin.groups_v3_client
+ cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
+ cls.admin_group_snapshots_client = \
+ cls.os_admin.group_snapshots_v3_client
+ cls.admin_group_types_client = cls.os_admin.group_types_v3_client
cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client
cls.admin_snapshot_manage_client = \
cls.os_admin.snapshot_manage_v2_client
@@ -274,11 +280,6 @@
cls.os_admin.volume_capabilities_v2_client
cls.admin_scheduler_stats_client = \
cls.os_admin.volume_scheduler_stats_v2_client
- cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
- cls.admin_groups_client = cls.os_admin.groups_v3_client
- cls.admin_group_snapshots_client = \
- cls.os_admin.group_snapshots_v3_client
- cls.admin_group_types_client = cls.os_admin.group_types_v3_client
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index eea65f1..be5638e 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -112,6 +112,10 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
self.volume['id'], 'available')
+ image_info = self.images_client.show_image(image_id)
+ self.assertEqual(image_name, image_info['name'])
+ self.assertEqual(CONF.volume.disk_format, image_info['disk_format'])
+
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index f2d8801..1e240b8 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -83,6 +83,9 @@
# 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)
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 8593d3a..b5f98ea 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -50,7 +50,7 @@
return
def str_vol(vol):
- return "%s:%s" % (vol['id'], vol[self.name])
+ return "%s:%s" % (vol['id'], vol['name'])
raw_msg = "Could not find volumes %s in expected list %s; fetched %s"
self.fail(raw_msg % ([str_vol(v) for v in missing_vols],
@@ -60,7 +60,6 @@
@classmethod
def resource_setup(cls):
super(VolumesListTestJSON, cls).resource_setup()
- cls.name = cls.VOLUME_FIELDS[1]
existing_volumes = cls.volumes_client.list_volumes()['volumes']
cls.volume_id_list = [vol['id'] for vol in existing_volumes]
@@ -117,22 +116,20 @@
@decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
def test_volume_list_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {self.name: volume[self.name]}
+ params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
params=params)['volumes']
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0][self.name],
- volume[self.name])
+ self.assertEqual(fetched_vol[0]['name'], volume['name'])
@decorators.idempotent_id('2de3a6d4-12aa-403b-a8f2-fdeb42a89623')
def test_volume_list_details_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {self.name: volume[self.name]}
+ params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0][self.name],
- volume[self.name])
+ self.assertEqual(fetched_vol[0]['name'], volume['name'])
@decorators.idempotent_id('39654e13-734c-4dab-95ce-7613bf8407ce')
def test_volumes_list_by_status(self):
@@ -213,7 +210,7 @@
def test_volume_list_param_display_name_and_status(self):
# Test to list volume when display name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {self.name: volume[self.name],
+ params = {'name': volume['name'],
'status': 'available'}
self._list_by_param_value_and_assert(params)
@@ -221,7 +218,7 @@
def test_volume_list_with_detail_param_display_name_and_status(self):
# Test to list volume when name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {self.name: volume[self.name],
+ params = {'name': volume['name'],
'status': 'available'}
self._list_by_param_value_and_assert(params, with_detail=True)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 47196ec..df0f5a5 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -198,9 +198,27 @@
body = rest_client.ResponseBody(body.response, body['server'])
servers = [body]
- # The name of the method to associate a floating IP to as server is too
- # long for PEP8 compliance so:
- assoc = clients.compute_floating_ips_client.associate_floating_ip_to_server
+ def _setup_validation_fip():
+ if CONF.service_available.neutron:
+ ifaces = clients.interfaces_client.list_interfaces(server['id'])
+ validation_port = None
+ for iface in ifaces['interfaceAttachments']:
+ if iface['net_id'] == tenant_network['id']:
+ validation_port = iface['port_id']
+ break
+ if not validation_port:
+ # NOTE(artom) This will get caught by the catch-all clause in
+ # the wait_until loop below
+ raise ValueError('Unable to setup floating IP for validation: '
+ 'port not found on tenant network')
+ clients.floating_ips_client.update_floatingip(
+ validation_resources['floating_ip']['id'],
+ port_id=validation_port)
+ else:
+ fip_client = clients.compute_floating_ips_client
+ fip_client.associate_floating_ip_to_server(
+ floating_ip=validation_resources['floating_ip']['ip'],
+ server_id=servers[0]['id'])
if wait_until:
for server in servers:
@@ -212,9 +230,7 @@
# creation will fail with the condition above (l.58).
if CONF.validation.run_validation and validatable:
if CONF.validation.connect_method == 'floating':
- assoc(floating_ip=validation_resources[
- 'floating_ip']['ip'],
- server_id=servers[0]['id'])
+ _setup_validation_fip()
except Exception:
with excutils.save_and_reraise_exception():
diff --git a/tempest/config.py b/tempest/config.py
index af9eefc..e78a07f 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -234,6 +234,12 @@
deprecated_reason="This feature flag was introduced to "
"support testing of old OpenStack versions, "
"which are not supported anymore"),
+ cfg.BoolOpt('domain_specific_drivers',
+ default=False,
+ help='Are domain specific drivers enabled? '
+ 'This configuration value should be same as '
+ '[identity]->domain_specific_drivers_enabled '
+ 'in keystone.conf.'),
cfg.BoolOpt('security_compliance',
default=False,
help='Does the environment have the security compliance '
diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
index 1371b3c..98f174d 100644
--- a/tempest/lib/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
import testtools
from tempest.lib.common import api_version_request
@@ -19,6 +20,7 @@
LATEST_MICROVERSION = 'latest'
+LOG = logging.getLogger(__name__)
class BaseMicroversionTest(object):
@@ -120,3 +122,60 @@
api_microversion,
response_header))
raise exceptions.InvalidHTTPResponseHeader(msg)
+
+
+def compare_version_header_to_response(api_microversion_header_name,
+ api_microversion,
+ response_header,
+ operation='eq'):
+ """Compares API microversion in response header to ``api_microversion``.
+
+ Compare the ``api_microversion`` value in response header if microversion
+ header is present in response, otherwise return false.
+
+ To make this function work for APIs which do not return microversion
+ header in response (example compute v2.0), this function does *not* raise
+ InvalidHTTPResponseHeader.
+
+ :param api_microversion_header_name: Microversion header name. Example:
+ 'Openstack-Api-Version'.
+ :param api_microversion: Microversion number. Example:
+
+ * '2.10' for the old-style header name, 'X-OpenStack-Nova-API-Version'
+ * 'Compute 2.10' for the new-style header name, 'Openstack-Api-Version'
+
+ :param response_header: Response header where microversion is
+ expected to be present.
+ :param operation: The boolean operation to use to compare the
+ ``api_microversion`` to the microversion in ``response_header``.
+ Can be 'lt', 'eq', 'gt', 'le', 'ne', 'ge'. Default is 'eq'. The
+ operation type should be based on the order of the arguments:
+ ``api_microversion`` <operation> ``response_header`` microversion.
+ :returns: True if the comparison is logically true, else False if the
+ comparison is logically false or if ``api_microversion_header_name`` is
+ missing in the ``response_header``.
+ :raises InvalidParam: If the operation is not lt, eq, gt, le, ne or ge.
+ """
+ api_microversion_header_name = api_microversion_header_name.lower()
+ if api_microversion_header_name not in response_header:
+ return False
+
+ op = getattr(api_version_request.APIVersionRequest,
+ '__%s__' % operation, None)
+
+ if op is None:
+ msg = ("Operation %s is invalid. Valid options include: lt, eq, gt, "
+ "le, ne, ge." % operation)
+ LOG.debug(msg)
+ raise exceptions.InvalidParam(invalid_param=msg)
+
+ # Remove "volume" from "volume <microversion>", for example, so that the
+ # microversion can be converted to `APIVersionRequest`.
+ api_version = api_microversion.split(' ')[-1]
+ resp_version = response_header[api_microversion_header_name].split(' ')[-1]
+ if not op(
+ api_version_request.APIVersionRequest(api_version),
+ api_version_request.APIVersionRequest(resp_version)):
+ return False
+
+ return True
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index c538c72..9b2e87e 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -276,3 +276,7 @@
class InvalidTestResource(TempestException):
message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
+
+
+class InvalidParam(TempestException):
+ message = ("Invalid Parameter passed: %(invalid_param)s")
diff --git a/tempest/test.py b/tempest/test.py
index f390ae4..a4cc2cc 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -220,10 +220,13 @@
resource_setup or at test level.
"""
identity_version = cls.get_identity_version()
- if 'admin' in cls.credentials and not credentials.is_admin_available(
- identity_version=identity_version):
- msg = "Missing Identity Admin API credentials in configuration."
- raise cls.skipException(msg)
+ # setting force_tenant_isolation to True also needs admin credentials.
+ if ('admin' in cls.credentials or
+ getattr(cls, 'force_tenant_isolation', False)):
+ if not credentials.is_admin_available(
+ identity_version=identity_version):
+ raise cls.skipException(
+ "Missing Identity Admin API credentials in configuration.")
if 'alt' in cls.credentials and not credentials.is_alt_available(
identity_version=identity_version):
msg = "Missing a 2nd set of API credentials in configuration."
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 7ac347d..6e1250f 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -13,6 +13,7 @@
# under the License.
import argparse
+import atexit
import os
import shutil
import subprocess
@@ -25,6 +26,7 @@
from tempest.tests import base
DEVNULL = open(os.devnull, 'wb')
+atexit.register(DEVNULL.close)
class TestTempestRun(base.TestCase):
@@ -68,6 +70,34 @@
self.assertEqual('i_am_a_fun_little_regex',
self.run_cmd._build_regex(args))
+ def test__build_whitelist_file(self):
+ args = mock.Mock(spec=argparse.Namespace)
+ setattr(args, 'smoke', False)
+ setattr(args, 'regex', None)
+ self.tests = tempfile.NamedTemporaryFile(
+ prefix='whitelist', delete=False)
+ self.tests.write(b"volume \n compute")
+ self.tests.close()
+ setattr(args, 'whitelist_file', self.tests.name)
+ setattr(args, 'blacklist_file', None)
+ self.assertEqual("volume|compute",
+ self.run_cmd._build_regex(args))
+ os.unlink(self.tests.name)
+
+ def test__build_blacklist_file(self):
+ args = mock.Mock(spec=argparse.Namespace)
+ setattr(args, 'smoke', False)
+ setattr(args, 'regex', None)
+ self.tests = tempfile.NamedTemporaryFile(
+ prefix='blacklist', delete=False)
+ self.tests.write(b"volume \n compute")
+ self.tests.close()
+ setattr(args, 'whitelist_file', None)
+ setattr(args, 'blacklist_file', self.tests.name)
+ self.assertEqual("^((?!compute|volume).)*$",
+ self.run_cmd._build_regex(args))
+ os.unlink(self.tests.name)
+
class TestRunReturnCode(base.TestCase):
def setUp(self):
diff --git a/tempest/tests/lib/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py
index c063556..b99e8d4 100644
--- a/tempest/tests/lib/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -92,24 +92,106 @@
def test_header_matches(self):
microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1'
- test_respose = {microversion_header_name: request_microversion}
+ test_response = {microversion_header_name: request_microversion}
api_version_utils.assert_version_header_matches_request(
- microversion_header_name, request_microversion, test_respose)
+ microversion_header_name, request_microversion, test_response)
def test_header_does_not_match(self):
microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1'
- test_respose = {microversion_header_name: '2.2'}
+ test_response = {microversion_header_name: '2.2'}
self.assertRaises(
exceptions.InvalidHTTPResponseHeader,
api_version_utils.assert_version_header_matches_request,
- microversion_header_name, request_microversion, test_respose)
+ microversion_header_name, request_microversion, test_response)
def test_header_not_present(self):
microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1'
- test_respose = {}
+ test_response = {}
self.assertRaises(
exceptions.InvalidHTTPResponseHeader,
api_version_utils.assert_version_header_matches_request,
- microversion_header_name, request_microversion, test_respose)
+ microversion_header_name, request_microversion, test_response)
+
+ def test_compare_versions_less_than(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.2'
+ test_response = {microversion_header_name: '2.1'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "lt"))
+
+ def test_compare_versions_less_than_equal(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.2'
+ test_response = {microversion_header_name: '2.1'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "le"))
+
+ def test_compare_versions_greater_than_equal(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.1'
+ test_response = {microversion_header_name: '2.2'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "ge"))
+
+ def test_compare_versions_greater_than(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.1'
+ test_response = {microversion_header_name: '2.2'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "gt"))
+
+ def test_compare_versions_equal(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.11'
+ test_response = {microversion_header_name: '2.1'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "eq"))
+
+ def test_compare_versions_not_equal(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.1'
+ test_response = {microversion_header_name: '2.1'}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "ne"))
+
+ def test_compare_versions_with_name_in_microversion(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = 'volume 3.1'
+ test_response = {microversion_header_name: 'volume 3.1'}
+ self.assertTrue(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "eq"))
+
+ def test_compare_versions_invalid_operation(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.1'
+ test_response = {microversion_header_name: '2.1'}
+ self.assertRaises(
+ exceptions.InvalidParam,
+ api_version_utils.compare_version_header_to_response,
+ microversion_header_name, request_microversion, test_response,
+ "foo")
+
+ def test_compare_versions_header_not_present(self):
+ microversion_header_name = 'x-openstack-xyz-api-version'
+ request_microversion = '2.1'
+ test_response = {}
+ self.assertFalse(
+ api_version_utils.compare_version_header_to_response(
+ microversion_header_name, request_microversion, test_response,
+ "eq"))