Merge "Fix deletion of network quotas"
diff --git a/.zuul.yaml b/.zuul.yaml
index 70f582e..80d49d8 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -339,6 +339,13 @@
nodeset: openstack-two-node-bionic
# This job runs on Bionic from stable/stein on.
branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
+ vars:
+ devstack_localrc:
+ USE_PYTHON3: False
+ group-vars:
+ subnode:
+ devstack_localrc:
+ USE_PYTHON3: False
- job:
name: tempest-multinode-full
diff --git a/HACKING.rst b/HACKING.rst
index 204b3c7..f8be0ad 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -12,7 +12,6 @@
tempest/scenario tests
- [T104] Scenario tests require a services decorator
- [T105] Tests cannot use setUpClass/tearDownClass
-- [T106] vim configuration should not be kept in source files.
- [T107] Check that a service tag isn't in the module path
- [T108] Check no hyphen at the end of rand_name() argument
- [T109] Cannot use testtools.skip decorator; instead use
diff --git a/releasenotes/notes/add-subnet-id-config-option-fac3d6f12abfc171.yaml b/releasenotes/notes/add-subnet-id-config-option-fac3d6f12abfc171.yaml
new file mode 100644
index 0000000..a1bd4c5
--- /dev/null
+++ b/releasenotes/notes/add-subnet-id-config-option-fac3d6f12abfc171.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - A new config option 'subnet_id' is added to section
+ 'network' to specify subnet which should be used for
+ allocation of IPs for VMs created during testing.
+ It should be used when the tested network contains more
+ than one subnet otherwise test of external connectivity
+ will fail. (Fixes bug #1856671)
diff --git a/releasenotes/notes/introduce-attachments-client-add-show-attachment-api-c3111f7e560a87b3.yaml b/releasenotes/notes/introduce-attachments-client-add-show-attachment-api-c3111f7e560a87b3.yaml
new file mode 100644
index 0000000..a058137
--- /dev/null
+++ b/releasenotes/notes/introduce-attachments-client-add-show-attachment-api-c3111f7e560a87b3.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ A new attachments client library has been introduced for the volume
+ service.
+
+ Initially only the show_attachment API is provided. This API requires a
+ minimum volume API microversion of ``3.27``.
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 1f7fb70..91b0b5f 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -20,14 +20,12 @@
It works only when used with some specific tox environments
('all', 'all-plugin'.)
- Multi-line and commented regexs can be achieved by doing this:
+ In the following example only api scenario and third party tests
+ will be executed.
::
vars:
- tempest_test_regex: |
- (?x) # Ignore comments and whitespaces
- # Line with only a comment.
- (tempest\.(api|scenario|thirdparty)).*$ # Run only api scenario and third party
+ tempest_test_regex: (tempest\.(api|scenario|thirdparty)).*$
.. zuul:rolevar:: tempest_test_blacklist
@@ -48,14 +46,9 @@
It works only when used with some specific tox environments
('all', 'all-plugin'.)
- Multi-line and commented regexs can be achieved by doing this:
-
::
vars:
- tempest_black_regex: |
- (?x) # Ignore comments and whitespaces
- # Line with only a comment.
- (tempest.api.identity).*$
+ tempest_black_regex: (tempest.api.identity).*$
.. zuul:rolevar:: tox_extra_args
:default: ''
diff --git a/setup.cfg b/setup.cfg
index d246c68..04511e1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,7 +6,7 @@
author = OpenStack
author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/tempest/latest/
-requires-python = >=3.6
+python-requires = >=3.6
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
@@ -49,5 +49,3 @@
oslo.config.opts =
tempest.config = tempest.config:list_opts
-[wheel]
-universal = 1
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 4d27a22..789965e 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -64,7 +64,7 @@
# Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
# spec as a user with admin privileges.
# Assigning extra specs values that are to be set
- specs = {"key1": "value1", "key2": "value2"}
+ specs = {'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'}
# SET extra specs to the flavor created in setUp
set_body = self.admin_flavors_client.set_flavor_extra_spec(
self.flavor['id'], **specs)['extra_specs']
@@ -74,30 +74,33 @@
self.flavor['id'])['extra_specs'])
self.assertEqual(get_body, specs)
- # UPDATE the value of the extra specs key1
- update_body = \
- self.admin_flavors_client.update_flavor_extra_spec(
- self.flavor['id'], "key1", key1="value")
- self.assertEqual({"key1": "value"}, update_body)
+ # UPDATE the value of the extra specs 'hw:numa_nodes'
+ update_body = self.admin_flavors_client.update_flavor_extra_spec(
+ self.flavor['id'], "hw:numa_nodes", **{'hw:numa_nodes': '2'})
+ self.assertEqual({'hw:numa_nodes': '2'}, update_body)
- # GET extra specs and verify the value of the key2
+ # GET extra specs and verify the value of the 'hw:cpu_policy'
# is the same as before
get_body = self.admin_flavors_client.list_flavor_extra_specs(
self.flavor['id'])['extra_specs']
- self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
+ self.assertEqual(
+ get_body, {'hw:numa_nodes': '2', 'hw:cpu_policy': 'shared'}
+ )
# UNSET extra specs that were set in this test
- self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'],
- "key1")
- self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'],
- "key2")
+ self.admin_flavors_client.unset_flavor_extra_spec(
+ self.flavor['id'], 'hw:numa_nodes'
+ )
+ self.admin_flavors_client.unset_flavor_extra_spec(
+ self.flavor['id'], 'hw:cpu_policy'
+ )
get_body = self.admin_flavors_client.list_flavor_extra_specs(
self.flavor['id'])['extra_specs']
self.assertEmpty(get_body)
@decorators.idempotent_id('a99dad88-ae1c-4fba-aeb4-32f898218bd0')
def test_flavor_non_admin_get_all_keys(self):
- specs = {"key1": "value1", "key2": "value2"}
+ specs = {'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'}
self.admin_flavors_client.set_flavor_extra_spec(self.flavor['id'],
**specs)
body = (self.flavors_client.list_flavor_extra_specs(
@@ -108,11 +111,14 @@
@decorators.idempotent_id('12805a7f-39a3-4042-b989-701d5cad9c90')
def test_flavor_non_admin_get_specific_key(self):
+ specs = {'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'}
body = self.admin_flavors_client.set_flavor_extra_spec(
- self.flavor['id'], key1="value1", key2="value2")['extra_specs']
- self.assertEqual(body['key1'], 'value1')
- self.assertIn('key2', body)
+ self.flavor['id'], **specs
+ )['extra_specs']
+ self.assertEqual(body['hw:numa_nodes'], '1')
+ self.assertIn('hw:cpu_policy', body)
+
body = self.flavors_client.show_flavor_extra_spec(
- self.flavor['id'], 'key1')
- self.assertEqual(body['key1'], 'value1')
- self.assertNotIn('key2', body)
+ self.flavor['id'], 'hw:numa_nodes')
+ self.assertEqual(body['hw:numa_nodes'], '1')
+ self.assertNotIn('hw:cpu_policy', body)
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index 5cde39e..9f89293 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -68,32 +68,36 @@
self.assertRaises(lib_exc.Forbidden,
self.flavors_client.set_flavor_extra_spec,
self.flavor['id'],
- key1="value1", key2="value2")
+ **{'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'})
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1ebf4ef8-759e-48fe-a801-d451d80476fb')
def test_flavor_non_admin_update_specific_key(self):
# non admin user is not allowed to update flavor extra spec
body = self.admin_flavors_client.set_flavor_extra_spec(
- self.flavor['id'], key1="value1", key2="value2")['extra_specs']
- self.assertEqual(body['key1'], 'value1')
+ self.flavor['id'],
+ **{'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'}
+ )['extra_specs']
+ self.assertEqual(body['hw:numa_nodes'], '1')
self.assertRaises(lib_exc.Forbidden,
self.flavors_client.
update_flavor_extra_spec,
self.flavor['id'],
- 'key1',
- key1='value1_new')
+ 'hw:numa_nodes',
+ **{'hw:numa_nodes': '1'})
@decorators.attr(type=['negative'])
@decorators.idempotent_id('28f12249-27c7-44c1-8810-1f382f316b11')
def test_flavor_non_admin_unset_keys(self):
self.admin_flavors_client.set_flavor_extra_spec(
- self.flavor['id'], key1="value1", key2="value2")
+ self.flavor['id'],
+ **{'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'}
+ )
self.assertRaises(lib_exc.Forbidden,
self.flavors_client.unset_flavor_extra_spec,
self.flavor['id'],
- 'key1')
+ 'hw:numa_nodes')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('440b9f3f-3c7f-4293-a106-0ceda350f8de')
@@ -101,7 +105,7 @@
self.assertRaises(lib_exc.NotFound,
self.admin_flavors_client.unset_flavor_extra_spec,
self.flavor['id'],
- 'nonexistent_key')
+ 'hw:cpu_thread_policy')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('329a7be3-54b2-48be-8052-bf2ce4afd898')
@@ -109,7 +113,7 @@
self.assertRaises(lib_exc.NotFound,
self.flavors_client.show_flavor_extra_spec,
self.flavor['id'],
- "nonexistent_key")
+ 'hw:cpu_thread_policy')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('25b822b8-9f49-44f6-80de-d99f0482e5cb')
@@ -118,8 +122,8 @@
self.assertRaises(lib_exc.BadRequest,
self.admin_flavors_client.update_flavor_extra_spec,
self.flavor['id'],
- "key2",
- key1="value")
+ 'hw:numa_nodes',
+ **{'hw:cpu_policy': 'shared'})
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f5889590-bf66-41cc-b4b1-6e6370cfd93f')
@@ -128,6 +132,5 @@
self.assertRaises(lib_exc.BadRequest,
self.admin_flavors_client.update_flavor_extra_spec,
self.flavor['id'],
- "key1",
- key1="value",
- key2="value")
+ 'hw:numa_nodes',
+ **{'hw:numa_nodes': '1', 'hw:cpu_policy': 'shared'})
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index 371b506..edcb1a7 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -23,6 +23,7 @@
class TestVolumeSwapBase(base.BaseV2ComputeAdminTest):
+ create_default_network = True
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/admin/test_volumes_negative.py b/tempest/api/compute/admin/test_volumes_negative.py
index 4a7f36f..133f4bc 100644
--- a/tempest/api/compute/admin/test_volumes_negative.py
+++ b/tempest/api/compute/admin/test_volumes_negative.py
@@ -13,6 +13,7 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import utils
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -22,6 +23,7 @@
class VolumesAdminNegativeTest(base.BaseV2ComputeAdminTest):
+ create_default_network = True
@classmethod
def skip_checks(cls):
@@ -57,3 +59,66 @@
self.admin_servers_client.update_attached_volume,
self.server['id'], volume['id'],
volumeId=nonexistent_volume)
+
+
+class UpdateMultiattachVolumeNegativeTest(base.BaseV2ComputeAdminTest):
+
+ min_microversion = '2.60'
+ volume_min_microversion = '3.27'
+
+ @classmethod
+ def skip_checks(self):
+ super(UpdateMultiattachVolumeNegativeTest, self).skip_checks()
+ if not CONF.compute_feature_enabled.volume_multiattach:
+ raise self.skipException('Volume multi-attach is not available.')
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('7576d497-b7c6-44bd-9cc5-c5b4e50fec71')
+ @utils.services('volume')
+ def test_multiattach_rw_volume_update_failure(self):
+
+ # Create two multiattach capable volumes.
+ vol1 = self.create_volume(multiattach=True)
+ vol2 = self.create_volume(multiattach=True)
+
+ # Create two instances.
+ server1 = self.create_test_server(wait_until='ACTIVE')
+ server2 = self.create_test_server(wait_until='ACTIVE')
+
+ # Attach vol1 to both of these instances.
+ vol1_attachment1 = self.attach_volume(server1, vol1)
+ vol1_attachment2 = self.attach_volume(server2, vol1)
+
+ # Assert that we now have two attachments.
+ vol1 = self.volumes_client.show_volume(vol1['id'])['volume']
+ self.assertEqual(2, len(vol1['attachments']))
+
+ # By default both of these attachments should have an attach_mode of
+ # read-write, assert that here to ensure the following calls to update
+ # the volume will be rejected.
+ for volume_attachment in vol1['attachments']:
+ attachment_id = volume_attachment['attachment_id']
+ attachment = self.attachments_client.show_attachment(
+ attachment_id)['attachment']
+ self.assertEqual('rw', attachment['attach_mode'])
+
+ # Assert that a BadRequest is raised when we attempt to update volume1
+ # to volume2 on server1 or server2.
+ self.assertRaises(lib_exc.BadRequest,
+ self.admin_servers_client.update_attached_volume,
+ server1['id'], vol1['id'], volumeId=vol2['id'])
+ self.assertRaises(lib_exc.BadRequest,
+ self.admin_servers_client.update_attached_volume,
+ server2['id'], vol1['id'], volumeId=vol2['id'])
+
+ # Fetch the volume 1 to check the current attachments.
+ vol1 = self.volumes_client.show_volume(vol1['id'])['volume']
+ vol1_attachment_ids = [a['id'] for a in vol1['attachments']]
+
+ # Assert that volume 1 is still attached to both server 1 and 2.
+ self.assertIn(vol1_attachment1['id'], vol1_attachment_ids)
+ self.assertIn(vol1_attachment2['id'], vol1_attachment_ids)
+
+ # Assert that volume 2 has no attachments.
+ vol2 = self.volumes_client.show_volume(vol2['id'])['volume']
+ self.assertEqual([], vol2['attachments'])
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 8d0962d..eab2a8d 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -108,6 +108,7 @@
cls.versions_client = cls.os_primary.compute_versions_client
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
+ cls.attachments_client = cls.os_primary.attachments_client_latest
if CONF.service_available.glance:
if CONF.image_feature_enabled.api_v1:
cls.images_client = cls.os_primary.image_client
@@ -226,7 +227,7 @@
@classmethod
def create_test_server(cls, validatable=False, volume_backed=False,
- validation_resources=None, **kwargs):
+ validation_resources=None, clients=None, **kwargs):
"""Wrapper utility that returns a test server.
This wrapper utility calls the common create test server and
@@ -238,6 +239,7 @@
:param volume_backed: Whether the instance is volume backed or not.
:param validation_resources: Dictionary of validation resources as
returned by `get_class_validation_resources`.
+ :param clients: Client manager, defaults to os_primary.
:param kwargs: Extra arguments are passed down to the
`compute.create_test_server` call.
"""
@@ -254,8 +256,11 @@
not tenant_network):
kwargs['networks'] = 'none'
+ if clients is None:
+ clients = cls.os_primary
+
body, servers = compute.create_test_server(
- cls.os_primary,
+ clients,
validatable,
validation_resources=validation_resources,
tenant_network=tenant_network,
@@ -266,11 +271,11 @@
# and then wait for all
for server in servers:
cls.addClassResourceCleanup(waiters.wait_for_server_termination,
- cls.servers_client, server['id'])
+ clients.servers_client, server['id'])
for server in servers:
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
- cls.servers_client.delete_server, server['id'])
+ clients.servers_client.delete_server, server['id'])
return body
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index eef2781..ef33685 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -25,6 +25,7 @@
class ImagesTestJSON(base.BaseV2ComputeTest):
+ create_default_network = True
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 512c9d2..37f9be3 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -30,6 +30,7 @@
class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest):
+ create_default_network = True
def tearDown(self):
"""Terminate test instances created after a test is executed."""
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index df8da07..c1af6c7 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -86,12 +86,16 @@
# apparently not enough? Add cleanup here.
self.addCleanup(self.delete_server, server['id'])
self._wait_for_validation(server, validation_resources)
+ try:
+ fip = set([validation_resources['floating_ip']['ip']])
+ except KeyError:
+ fip = ()
ifs = (self.interfaces_client.list_interfaces(server['id'])
['interfaceAttachments'])
body = waiters.wait_for_interface_status(
self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE')
ifs[0]['port_state'] = body['port_state']
- return server, ifs
+ return server, ifs, fip
class AttachInterfacesTestJSON(AttachInterfacesTestBase):
@@ -226,7 +230,7 @@
@decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051')
@utils.services('network')
def test_create_list_show_delete_interfaces_by_network_port(self):
- server, ifs = self._create_server_get_interfaces()
+ server, ifs, _ = self._create_server_get_interfaces()
interface_count = len(ifs)
self.assertGreater(interface_count, 0)
@@ -268,7 +272,7 @@
raise self.skipException("Only owner network supports "
"creating interface by fixed ip.")
- server, ifs = self._create_server_get_interfaces()
+ server, ifs, _ = self._create_server_get_interfaces()
interface_count = len(ifs)
self.assertGreater(interface_count, 0)
@@ -354,9 +358,8 @@
not CONF.network.shared_physical_network):
raise self.skipException("Only owner network supports "
"creating interface by fixed ip.")
-
# Add and Remove the fixed IP to server.
- server, ifs = self._create_server_get_interfaces()
+ server, ifs, fip = self._create_server_get_interfaces()
original_interface_count = len(ifs) # This is the number of ports.
self.assertGreater(original_interface_count, 0)
# Get the starting list of IPs on the server.
@@ -369,6 +372,9 @@
self.assertEqual(1, len(addresses), addresses) # number of networks
# Keep track of the original addresses so we can know which IP is new.
original_ips = [addr['addr'] for addr in list(addresses.values())[0]]
+ # Make sure the floating IP possibly assigned during
+ # server creation is always present in the set of original ips.
+ original_ips = set(original_ips).union(fip)
original_ip_count = len(original_ips)
self.assertGreater(original_ip_count, 0, addresses) # at least 1
network_id = ifs[0]['net_id']
@@ -376,40 +382,22 @@
# fixed IP on the same network (and same port since we only have one
# port).
self.servers_client.add_fixed_ip(server['id'], networkId=network_id)
- # Wait for the ips count to increase by one.
- def _get_server_floating_ips():
- _floating_ips = []
- _server = self.os_primary.servers_client.show_server(
- server['id'])['server']
- for _ip_set in _server['addresses']:
- for _ip in _server['addresses'][_ip_set]:
- if _ip['OS-EXT-IPS:type'] == 'floating':
- _floating_ips.append(_ip['addr'])
- return _floating_ips
-
- def _wait_for_ip_increase():
+ def _wait_for_ip_change(expected_count):
_addresses = self.os_primary.servers_client.list_addresses(
server['id'])['addresses']
- _ips = [addr['addr'] for addr in list(_addresses.values())[0]]
- LOG.debug("Wait for IP increase. All IPs still associated to "
+ _ips = set([addr['addr'] for addr in list(_addresses.values())[0]])
+ # Make sure possible floating ip is always present in the set.
+ _ips = _ips.union(fip)
+ LOG.debug("Wait for change of IPs. All IPs still associated to "
"the server %(id)s: %(ips)s",
{'id': server['id'], 'ips': _ips})
- if len(_ips) == original_ip_count + 1:
- return True
- elif len(_ips) == original_ip_count:
- return False
- # If not, lets remove any floating IP from the list and check again
- _fips = _get_server_floating_ips()
- _ips = [_ip for _ip in _ips if _ip not in _fips]
- LOG.debug("Wait for IP increase. Fixed IPs still associated to "
- "the server %(id)s: %(ips)s",
- {'id': server['id'], 'ips': _ips})
- return len(_ips) == original_ip_count + 1
+ return len(_ips) == expected_count
+ # Wait for the ips count to increase by one.
if not test_utils.call_until_true(
- _wait_for_ip_increase, CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ _wait_for_ip_change, CONF.compute.build_timeout,
+ CONF.compute.build_interval, original_ip_count + 1):
raise lib_exc.TimeoutException(
'Timed out while waiting for IP count to increase.')
@@ -428,26 +416,8 @@
break
self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
# Wait for the interface count to decrease by one.
-
- def _wait_for_ip_decrease():
- _addresses = self.os_primary.servers_client.list_addresses(
- server['id'])['addresses']
- _ips = [addr['addr'] for addr in list(_addresses.values())[0]]
- LOG.debug("Wait for IP decrease. All IPs still associated to "
- "the server %(id)s: %(ips)s",
- {'id': server['id'], 'ips': _ips})
- if len(_ips) == original_ip_count:
- return True
- # If not, lets remove any floating IP from the list and check again
- _fips = _get_server_floating_ips()
- _ips = [_ip for _ip in _ips if _ip not in _fips]
- LOG.debug("Wait for IP decrease. Fixed IPs still associated to "
- "the server %(id)s: %(ips)s",
- {'id': server['id'], 'ips': _ips})
- return len(_ips) == original_ip_count
-
if not test_utils.call_until_true(
- _wait_for_ip_decrease, CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ _wait_for_ip_change, CONF.compute.build_timeout,
+ CONF.compute.build_interval, original_ip_count):
raise lib_exc.TimeoutException(
'Timed out while waiting for IP count to decrease.')
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index 482ba09..5688af1 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -20,6 +20,7 @@
class ServerMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+ create_default_network = True
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index df0d79d..2dd1fe2 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -114,6 +114,13 @@
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
user_groups = self.users_client.list_user_groups(user['id'])['groups']
+ # The `membership_expires_at` attribute is present when listing user
+ # group memberships, and is not an attribute of the groups themselves.
+ # Therefore we remove it from the comparison.
+ for g in user_groups:
+ if 'membership_expires_at' in g:
+ self.assertIsNone(g['membership_expires_at'])
+ del(g['membership_expires_at'])
self.assertEqual(sorted(groups, key=lambda k: k['name']),
sorted(user_groups, key=lambda k: k['name']))
self.assertEqual(2, len(user_groups))
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
index 85f6896..2b9719a 100644
--- a/tempest/api/network/test_tags.py
+++ b/tempest/api/network/test_tags.py
@@ -103,9 +103,10 @@
List tags.
Remove a tag.
- v2.0 of the Neutron API is assumed. The tag-ext extension allows users to
- set tags on the following resources: subnets, ports, routers and
- subnetpools.
+ v2.0 of the Neutron API is assumed. The tag-ext or standard-attr-tag
+ extension allows users to set tags on the following resources: subnets,
+ ports, routers and subnetpools.
+ from stein release the tag-ext has been renamed to standard-attr-tag
"""
# NOTE(felipemonteiro): The supported resource names are plural. Use
@@ -115,8 +116,12 @@
@classmethod
def skip_checks(cls):
super(TagsExtTest, cls).skip_checks()
- if not utils.is_extension_enabled('tag-ext', 'network'):
- msg = "tag-ext extension not enabled."
+ # Added condition to support backward compatiblity since
+ # tag-ext has been renamed to standard-attr-tag
+ if not (utils.is_extension_enabled('tag-ext', 'network') or
+ utils.is_extension_enabled('standard-attr-tag', 'network')):
+ msg = ("neither tag-ext nor standard-attr-tag extensions "
+ "are enabled.")
raise cls.skipException(msg)
@classmethod
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index f695f51..c57766e 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -113,7 +113,8 @@
self._delete_group_snapshot(group_snapshot)
group_snapshots = self.group_snapshots_client.list_group_snapshots()[
'group_snapshots']
- self.assertEmpty(group_snapshots)
+ self.assertNotIn((group_snapshot['name'], group_snapshot['id']),
+ [(m['name'], m['id']) for m in group_snapshots])
@decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
def test_create_group_from_group_snapshot(self):
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 3e0deef..5bac3d8 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -23,6 +23,7 @@
class VolumesActionsTest(base.BaseVolumeAdminTest):
+ create_default_network = True
def _create_reset_and_force_delete_temp_volume(self, status=None):
# Create volume, reset volume status, and force delete temp volume
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 1bfd075..bcbcf43 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -30,6 +30,9 @@
tempest.test.BaseTestCase):
"""Base test case class for all Cinder API tests."""
+ # Set this to True in subclasses to create a default network. See
+ # https://bugs.launchpad.net/tempest/+bug/1844568
+ create_default_network = False
_api_version = 2
# if api_v2 is not enabled while api_v3 is enabled, the volume v2 classes
# should be transferred to volume v3 classes.
@@ -63,7 +66,9 @@
@classmethod
def setup_credentials(cls):
- cls.set_network_resources()
+ cls.set_network_resources(
+ network=cls.create_default_network,
+ subnet=cls.create_default_network)
super(BaseVolumeTest, cls).setup_credentials()
@classmethod
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index 00a3375..4d64a95 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -23,7 +23,7 @@
# NOTE(zhufl): This inherits from BaseVolumeAdminTest because
# it requires force_tenant_isolation=True, which need admin
# credentials to create non-admin users for the tests.
-class AbsoluteLimitsTests(base.BaseVolumeAdminTest): # noqa
+class AbsoluteLimitsTests(base.BaseVolumeAdminTest): # noqa: T115
# avoid existing volumes of pre-defined tenant
force_tenant_isolation = True
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index be5638e..9edffc6 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -25,6 +25,7 @@
class VolumesActionsTest(base.BaseVolumeTest):
+ create_default_network = True
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index c3f44e2..2267045 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -60,6 +60,7 @@
class VolumesExtendAttachedTest(base.BaseVolumeTest):
"""Tests extending the size of an attached volume."""
+ create_default_network = True
# We need admin credentials for getting instance action event details. By
# default a non-admin can list and show instance actions if they own the
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 72e7290..bf221e8 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -25,6 +25,7 @@
class VolumesSnapshotTestJSON(base.BaseVolumeTest):
+ create_default_network = True
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index 8a416ea..f4f039c 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -109,7 +109,7 @@
snap_list = self.snapshots_client.list_snapshots(
sort_key=sort_key, sort_dir=sort_dir)['snapshots']
self.assertNotEmpty(snap_list)
- if sort_key is 'display_name':
+ if sort_key == 'display_name':
sort_key = 'name'
# Note: On Cinder API, 'display_name' works as a sort key
# on a request, a volume name appears as 'name' on the response.
diff --git a/tempest/clients.py b/tempest/clients.py
index 6aed92e..1db93a0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -263,6 +263,8 @@
self.volume_v3.MessagesClient())
self.volume_versions_client_latest = (
self.volume_v3.VersionsClient())
+ self.attachments_client_latest = (
+ self.volume_v3.AttachmentsClient())
# TODO(gmann): Below alias for service clients have been
# deprecated and will be removed in future. Start using the alias
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index acd4124..469b214 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -13,6 +13,7 @@
# under the License.
from oslo_log import log as logging
+from six.moves.urllib import parse as urllib
from tempest import clients
from tempest.common import credentials_factory as credentials
@@ -833,7 +834,15 @@
def list(self):
client = self.client
- images = client.list_images(params={"all_tenants": True})['images']
+ response = client.list_images()
+ images = []
+ images.extend(response['images'])
+ while 'next' in response:
+ parsed = urllib.urlparse(response['next'])
+ marker = urllib.parse_qs(parsed.query)['marker'][0]
+ response = client.list_images(params={"marker": marker})
+ images.extend(response['images'])
+
if not self.is_save_state:
images = [image for image in images if image['id']
not in self.saved_state_json['images'].keys()]
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index b547cc6..e242301 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -124,6 +124,12 @@
raise lib_exc.DeleteErrorException(
"Server %s failed to delete and is in ERROR status" %
server_id)
+ if server_status == 'SOFT_DELETED':
+ # Soft-deleted instances need to be forcibly deleted to
+ # prevent some test cases from failing.
+ LOG.debug("Automatically force-deleting soft-deleted server %s",
+ server_id)
+ client.force_delete_server(server_id)
if int(time.time()) - start_time >= client.build_timeout:
raise lib_exc.TimeoutException
diff --git a/tempest/config.py b/tempest/config.py
index 5a2d722..9685745 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -699,6 +699,11 @@
cfg.StrOpt('floating_network_name',
help="Default floating network name. Used to allocate floating "
"IPs when neutron is enabled."),
+ cfg.StrOpt('subnet_id',
+ default="",
+ help="Subnet id of subnet which is used for allocation of "
+ "floating IPs. Specify when two or more subnets are "
+ "present in network."),
cfg.StrOpt('public_router_id',
default="",
help="Id of the public router that provides external "
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 2c40cb1..6a97a00 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -15,6 +15,7 @@
import os
import re
+from hacking import core
import pycodestyle
@@ -25,7 +26,6 @@
TEST_DEFINITION = re.compile(r'^\s*def test.*')
SETUP_TEARDOWN_CLASS_DEFINITION = re.compile(r'^\s+def (setUp|tearDown)Class')
SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)')
-VI_HEADER_RE = re.compile(r"^#\s+vim?:.+")
RAND_NAME_HYPHEN_RE = re.compile(r".*rand_name\(.+[\-\_][\"\']\)")
mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
TESTTOOLS_SKIP_DECORATOR = re.compile(r'\s*@testtools\.skip\((.*)\)')
@@ -39,6 +39,7 @@
_HAVE_NEGATIVE_DECORATOR = False
+@core.flake8ext
def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
"""Check for client imports from tempest/api & tempest/scenario tests
@@ -53,6 +54,7 @@
" in tempest/api/* or tempest/scenario/* tests"))
+@core.flake8ext
def scenario_tests_need_service_tags(physical_line, filename,
previous_logical):
"""Check that scenario tests have service tags
@@ -67,6 +69,7 @@
"T104: Scenario tests require a service decorator")
+@core.flake8ext
def no_setup_teardown_class_for_tests(physical_line, filename):
if pycodestyle.noqa(physical_line):
@@ -80,20 +83,7 @@
"T105: (setUp|tearDown)Class can not be used in tests")
-def no_vi_headers(physical_line, line_number, lines):
- """Check for vi editor configuration in source files.
-
- By default vi modelines can only appear in the first or
- last 5 lines of a source file.
-
- T106
- """
- # NOTE(gilliard): line_number is 1-indexed
- if line_number <= 5 or line_number > len(lines) - 5:
- if VI_HEADER_RE.match(physical_line):
- return 0, "T106: Don't put vi configuration in source files"
-
-
+@core.flake8ext
def service_tags_not_in_module_path(physical_line, filename):
"""Check that a service tag isn't in the module path
@@ -117,6 +107,7 @@
"T107: service tag should not be in path")
+@core.flake8ext
def no_hyphen_at_end_of_rand_name(logical_line, filename):
"""Check no hyphen at the end of rand_name() argument
@@ -127,6 +118,7 @@
return 0, msg
+@core.flake8ext
def no_mutable_default_args(logical_line):
"""Check that mutable object isn't used as default argument
@@ -137,6 +129,7 @@
yield (0, msg)
+@core.flake8ext
def no_testtools_skip_decorator(logical_line):
"""Check that methods do not have the testtools.skip decorator
@@ -170,7 +163,8 @@
return True
-def get_resources_on_service_clients(logical_line, physical_line, filename,
+@core.flake8ext
+def get_resources_on_service_clients(physical_line, logical_line, filename,
line_number, lines):
"""Check that service client names of GET should be consistent
@@ -197,7 +191,8 @@
yield (0, msg)
-def delete_resources_on_service_clients(logical_line, physical_line, filename,
+@core.flake8ext
+def delete_resources_on_service_clients(physical_line, logical_line, filename,
line_number, lines):
"""Check that service client names of DELETE should be consistent
@@ -223,6 +218,7 @@
yield (0, msg)
+@core.flake8ext
def dont_import_local_tempest_into_lib(logical_line, filename):
"""Check that tempest.lib should not import local tempest code
@@ -244,6 +240,7 @@
yield (0, msg)
+@core.flake8ext
def use_rand_uuid_instead_of_uuid4(logical_line, filename):
"""Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
@@ -260,6 +257,7 @@
yield (0, msg)
+@core.flake8ext
def dont_use_config_in_tempest_lib(logical_line, filename):
"""Check that tempest.lib doesn't use tempest config
@@ -277,7 +275,8 @@
yield(0, msg)
-def dont_put_admin_tests_on_nonadmin_path(logical_line, physical_line,
+@core.flake8ext
+def dont_put_admin_tests_on_nonadmin_path(logical_line,
filename):
"""Check admin tests should exist under admin path
@@ -287,9 +286,6 @@
if 'tempest/api/' not in filename:
return
- if pycodestyle.noqa(physical_line):
- return
-
if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
return
@@ -298,6 +294,7 @@
yield(0, msg)
+@core.flake8ext
def unsupported_exception_attribute_PY3(logical_line):
"""Check Unsupported 'message' exception attribute in PY3
@@ -309,6 +306,7 @@
yield(0, msg)
+@core.flake8ext
def negative_test_attribute_always_applied_to_negative_tests(physical_line,
filename):
"""Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
@@ -330,22 +328,3 @@
" to all negative API tests"
)
_HAVE_NEGATIVE_DECORATOR = False
-
-
-def factory(register):
- register(import_no_clients_in_api_and_scenario_tests)
- register(scenario_tests_need_service_tags)
- register(no_setup_teardown_class_for_tests)
- register(no_vi_headers)
- register(service_tags_not_in_module_path)
- register(no_hyphen_at_end_of_rand_name)
- register(no_mutable_default_args)
- register(no_testtools_skip_decorator)
- register(get_resources_on_service_clients)
- register(delete_resources_on_service_clients)
- register(dont_import_local_tempest_into_lib)
- register(dont_use_config_in_tempest_lib)
- register(use_rand_uuid_instead_of_uuid4)
- register(dont_put_admin_tests_on_nonadmin_path)
- register(unsupported_exception_attribute_PY3)
- register(negative_test_attribute_always_applied_to_negative_tests)
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 8e6d3d5..3fee489 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -684,7 +684,7 @@
def __str__(self):
"""Represent only attributes included in self.ATTRIBUTES"""
- attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ attrs = [attr for attr in self.ATTRIBUTES if attr != 'password']
_repr = dict((k, getattr(self, k)) for k in attrs)
return str(_repr)
@@ -741,7 +741,7 @@
def __str__(self):
"""Represent only attributes included in self.ATTRIBUTES"""
- attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ attrs = [attr for attr in self.ATTRIBUTES if attr != 'password']
_repr = dict((k, getattr(self, k)) for k in attrs)
return str(_repr)
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index a1b7de3..e2fa836 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -11,6 +11,7 @@
# 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.services.volume.v3.attachments_client import AttachmentsClient
from tempest.lib.services.volume.v3.availability_zone_client \
import AvailabilityZoneClient
from tempest.lib.services.volume.v3.backups_client import BackupsClient
@@ -43,12 +44,11 @@
from tempest.lib.services.volume.v3.volume_manage_client import \
VolumeManageClient
from tempest.lib.services.volume.v3.volumes_client import VolumesClient
-
-__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'BaseClient',
- 'CapabilitiesClient', 'EncryptionTypesClient', 'ExtensionsClient',
- 'GroupSnapshotsClient', 'GroupTypesClient', 'GroupsClient',
- 'HostsClient', 'LimitsClient', 'MessagesClient', 'QosSpecsClient',
- 'QuotaClassesClient', 'QuotasClient', 'SchedulerStatsClient',
- 'ServicesClient', 'SnapshotManageClient', 'SnapshotsClient',
- 'TransfersClient', 'TypesClient', 'VersionsClient',
- 'VolumeManageClient', 'VolumesClient']
+__all__ = ['AttachmentsClient', 'AvailabilityZoneClient', 'BackupsClient',
+ 'BaseClient', 'CapabilitiesClient', 'EncryptionTypesClient',
+ 'ExtensionsClient', 'GroupSnapshotsClient', 'GroupTypesClient',
+ 'GroupsClient', 'HostsClient', 'LimitsClient', 'MessagesClient',
+ 'QosSpecsClient', 'QuotaClassesClient', 'QuotasClient',
+ 'SchedulerStatsClient', 'ServicesClient', 'SnapshotManageClient',
+ 'SnapshotsClient', 'TransfersClient', 'TypesClient',
+ 'VersionsClient', 'VolumeManageClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/attachments_client.py b/tempest/lib/services/volume/v3/attachments_client.py
new file mode 100644
index 0000000..5e448f7
--- /dev/null
+++ b/tempest/lib/services/volume/v3/attachments_client.py
@@ -0,0 +1,28 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume import base_client
+
+
+class AttachmentsClient(base_client.BaseClient):
+ """Client class to send CRUD attachment V3 API requests"""
+
+ def show_attachment(self, attachment_id):
+ """Show volume attachment."""
+ url = "attachments/%s" % (attachment_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index cb7acbf..99dd653 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -1008,13 +1008,18 @@
port_id, ip4 = self._get_server_port_id_and_ip4(thing)
else:
ip4 = None
- result = client.create_floatingip(
- floating_network_id=external_network_id,
- port_id=port_id,
- tenant_id=thing['tenant_id'],
- fixed_ip_address=ip4
- )
+
+ kwargs = {
+ 'floating_network_id': external_network_id,
+ 'port_id': port_id,
+ 'tenant_id': thing['tenant_id'],
+ 'fixed_ip_address': ip4,
+ }
+ if CONF.network.subnet_id:
+ kwargs['subnet_id'] = CONF.network.subnet_id
+ result = client.create_floatingip(**kwargs)
floating_ip = result['floatingip']
+
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
client.delete_floatingip,
floating_ip['id'])
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index f46c7e8..d8584ec 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -346,10 +346,19 @@
network_id=CONF.network.public_network_id)['subnets']
if s['ip_version'] == 4
]
- self.assertEqual(1, len(v4_subnets),
- "Found %d IPv4 subnets" % len(v4_subnets))
- external_ips = [v4_subnets[0]['gateway_ip']]
+ if len(v4_subnets) > 1:
+ self.assertTrue(
+ CONF.network.subnet_id,
+ "Found %d subnets. Specify subnet using configuration "
+ "option [network].subnet_id."
+ % len(v4_subnets))
+ subnet = self.os_admin.subnets_client.show_subnet(
+ CONF.network.subnet_id)['subnet']
+ external_ips = [subnet['gateway_ip']]
+ else:
+ external_ips = [v4_subnets[0]['gateway_ip']]
+
self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
external_ips)
diff --git a/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py b/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py
index 2774c44..8aed7d7 100644
--- a/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py
@@ -20,78 +20,116 @@
class TestApplicationCredentialsClient(base.BaseServiceTest):
FAKE_CREATE_APP_CRED = {
"application_credential": {
- "description": "fake application credential",
+ "name": "monitoring",
+ "secret": "rEaqvJka48mpv",
+ "description": "Application credential for monitoring.",
+ "expires_at": "2018-02-27T18:30:59Z",
"roles": [
+ {"name": "Reader"}
+ ],
+ "access_rules": [
{
- "id": "c60fdd45",
- "domain_id": None,
- "name": "Member"
+ "path": "/v2.0/metrics",
+ "method": "GET",
+ "service": "monitoring"
}
],
- "expires_at": "2019-02-27T18:30:59.999999Z",
- "secret": "_BVq0xU5L",
- "unrestricted": None,
- "project_id": "ddef321",
- "id": "5499a186",
- "name": "one"
+ "unrestricted": False
}
}
FAKE_LIST_APP_CREDS = {
+ "links": {
+ "self": "http://example.com/identity/v3/users/" +
+ "fd786d56402c4d1691372e7dee0d00b5/application_credentials",
+ "previous": None,
+ "next": None
+ },
"application_credentials": [
{
- "description": "fake application credential",
+ "description": "Application credential for backups.",
"roles": [
{
"domain_id": None,
- "name": "Member",
- "id": "c60fdd45",
+ "name": "Writer",
+ "id": "6aff702516544aeca22817fd3bc39683"
}
],
- "expires_at": "2018-02-27T18:30:59.999999Z",
- "unrestricted": None,
- "project_id": "ddef321",
- "id": "5499a186",
- "name": "one"
+ "access_rules": [
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/users/" +
+ "fd786d56402c4d1691372e7dee0d00b5/" +
+ "application_credentials/" +
+ "308a7e905eee4071aac5971744c061f6"
+ },
+ "expires_at": "2018-02-27T18:30:59.000000",
+ "unrestricted": False,
+ "project_id": "231c62fb0fbd485b995e8b060c3f0d98",
+ "id": "308a7e905eee4071aac5971744c061f6",
+ "name": "backups"
},
{
- "description": None,
+ "description": "Application credential for monitoring.",
"roles": [
{
- "id": "0f1837c8",
+ "id": "6aff702516544aeca22817fd3bc39683",
"domain_id": None,
- "name": "anotherrole"
- },
- {
- "id": "c60fdd45",
- "domain_id": None,
- "name": "Member"
+ "name": "Reader"
}
],
- "expires_at": None,
- "unrestricted": None,
- "project_id": "c5403d938",
- "id": "d441c904f",
- "name": "two"
+ "access_rules": [
+ {
+ "path": "/v2.0/metrics",
+ "id": "07d719df00f349ef8de77d542edf010c",
+ "service": "monitoring",
+ "method": "GET"
+ }
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/users/" +
+ "fd786d56402c4d1691372e7dee0d00b5/" +
+ "application_credentials/" +
+ "58d61ff8e6e34accb35874016d1dba8b"
+ },
+ "expires_at": "2018-02-27T18:30:59.000000",
+ "unrestricted": False,
+ "project_id": "231c62fb0fbd485b995e8b060c3f0d98",
+ "id": "58d61ff8e6e34accb35874016d1dba8b",
+ "name": "monitoring"
}
]
}
FAKE_APP_CRED_INFO = {
"application_credential": {
- "description": None,
+ "description": "Application credential for monitoring.",
"roles": [
{
+ "id": "6aff702516544aeca22817fd3bc39683",
"domain_id": None,
- "name": "Member",
- "id": "c60fdd45",
+ "name": "Reader"
}
],
- "expires_at": None,
- "unrestricted": None,
- "project_id": "ddef321",
- "id": "5499a186",
- "name": "one"
+ "access_rules": [
+ {
+ "path": "/v2.0/metrics",
+ "id": "07d719df00f349ef8de77d542edf010c",
+ "service": "monitoring",
+ "method": "GET"
+ }
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/users/" +
+ "fd786d56402c4d1691372e7dee0d00b5/" +
+ "application_credentials/" +
+ "58d61ff8e6e34accb35874016d1dba8b"
+ },
+ "expires_at": "2018-02-27T18:30:59.000000",
+ "unrestricted": False,
+ "project_id": "231c62fb0fbd485b995e8b060c3f0d98",
+ "id": "58d61ff8e6e34accb35874016d1dba8b",
+ "name": "monitoring"
}
}
@@ -118,7 +156,7 @@
self.FAKE_APP_CRED_INFO,
bytes_body,
user_id="123456",
- application_credential_id="5499a186")
+ application_credential_id="58d61ff8e6e34accb35874016d1dba8b")
def _test_list_app_creds(self, bytes_body=False):
self.check_service_client_function(
@@ -152,5 +190,5 @@
'tempest.lib.common.rest_client.RestClient.delete',
{},
user_id="123456",
- application_credential_id="5499a186",
+ application_credential_id="58d61ff8e6e34accb35874016d1dba8b",
status=204)
diff --git a/tempest/tests/lib/services/image/v2/test_namespaces_client.py b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
index 3b057ad..db1ffae 100644
--- a/tempest/tests/lib/services/image/v2/test_namespaces_client.py
+++ b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
@@ -18,16 +18,64 @@
class TestNamespacesClient(base.BaseServiceTest):
- FAKE_CREATE_SHOW_NAMESPACE = {
- "namespace": "OS::Compute::Hypervisor",
- "visibility": "public",
- "description": "Tempest",
- "display_name": u"\u2740(*\xb4\u25e1`*)\u2740",
- "protected": True
+ FAKE_CREATE_NAMESPACE = {
+ "created_at": "2016-05-19T16:05:48Z",
+ "description": "A metadata definitions namespace.",
+ "display_name": "An Example Namespace",
+ "namespace": "FredCo::SomeCategory::Example",
+ "owner": "c60b1d57c5034e0d86902aedf8c49be0",
+ "protected": True,
+ "schema": "/v2/schemas/metadefs/namespace",
+ "self": "/v2/metadefs/namespaces/"
+ "FredCo::SomeCategory::Example",
+ "updated_at": "2016-05-19T16:05:48Z",
+ "visibility": "public"
+ }
+
+ FAKE_SHOW_NAMESPACE = {
+ "created_at": "2016-06-28T14:57:10Z",
+ "description": "The libvirt compute driver options.",
+ "display_name": "libvirt Driver Options",
+ "namespace": "OS::Compute::Libvirt",
+ "owner": "admin",
+ "properties": {
+ "boot_menu": {
+ "description": "If true, enables the BIOS bootmenu.",
+ "enum": [
+ "true",
+ "false"
+ ],
+ "title": "Boot Menu",
+ "type": "string"
+ },
+ "serial_port_count": {
+ "description": "Specifies the count of serial ports.",
+ "minimum": 0,
+ "title": "Serial Port Count",
+ "type": "integer"
+ }
+ },
+ "protected": True,
+ "resource_type_associations": [
+ {
+ "created_at": "2016-06-28T14:57:10Z",
+ "name": "OS::Glance::Image",
+ "prefix": "hw_"
+ },
+ {
+ "created_at": "2016-06-28T14:57:10Z",
+ "name": "OS::Nova::Flavor",
+ "prefix": "hw:"
+ }
+ ],
+ "schema": "/v2/schemas/metadefs/namespace",
+ "self": "/v2/metadefs/namespaces/OS::Compute::Libvirt",
+ "visibility": "public"
}
FAKE_LIST_NAMESPACES = {
- "first": "/v2/metadefs/namespaces?sort_key=created_at&sort_dir=asc",
+ "first": "/v2/metadefs/namespaces?sort_key=created_at&"
+ "sort_dir=asc",
"namespaces": [
{
"created_at": "2014-08-28T17:13:06Z",
@@ -89,7 +137,7 @@
self.check_service_client_function(
self.client.show_namespace,
'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_CREATE_SHOW_NAMESPACE,
+ self.FAKE_SHOW_NAMESPACE,
bytes_body,
namespace="OS::Compute::Hypervisor")
@@ -104,7 +152,7 @@
self.check_service_client_function(
self.client.create_namespace,
'tempest.lib.common.rest_client.RestClient.post',
- self.FAKE_CREATE_SHOW_NAMESPACE,
+ self.FAKE_CREATE_NAMESPACE,
bytes_body,
namespace="OS::Compute::Hypervisor",
visibility="public", description="Tempest",
diff --git a/tempest/tests/lib/services/image/v2/test_resource_types_client.py b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
index 741b4eb..089e62e 100644
--- a/tempest/tests/lib/services/image/v2/test_resource_types_client.py
+++ b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
@@ -48,6 +48,28 @@
]
}
+ FAKE_CREATE_RESOURCE_TYPE_ASSOCIATION = {
+ "created_at": "2020-03-07T18:20:44Z",
+ "name": "OS::Glance::Image",
+ "prefix": "hw:",
+ "updated_at": "2020-03-07T18:20:44Z"
+ }
+
+ FAKE_LIST_RESOURCE_TYPE_ASSOCIATION = {
+ "resource_type_associations": [
+ {
+ "created_at": "2020-03-07T18:20:44Z",
+ "name": "OS::Nova::Flavor",
+ "prefix": "hw:"
+ },
+ {
+ "created_at": "2020-03-07T18:20:44Z",
+ "name": "OS::Glance::Image",
+ "prefix": "hw_"
+ }
+ ]
+ }
+
def setUp(self):
super(TestResourceTypesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -62,8 +84,48 @@
self.FAKE_LIST_RESOURCETYPES,
bytes_body)
+ def _test_create_resource_type_association(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_resource_type_association,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_RESOURCE_TYPE_ASSOCIATION,
+ bytes_body, status=201,
+ namespace_id="OS::Compute::Hypervisor",
+ name="OS::Glance::Image", prefix="hw_",
+ )
+
+ def _test_list_resource_type_association(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_resource_type_association,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_RESOURCE_TYPE_ASSOCIATION,
+ bytes_body,
+ namespace_id="OS::Compute::Hypervisor",
+ )
+
def test_list_resource_types_with_str_body(self):
self._test_list_resource_types()
def test_list_resource_types_with_bytes_body(self):
self._test_list_resource_types(bytes_body=True)
+
+ def test_delete_resource_type_association(self):
+ self.check_service_client_function(
+ self.client.delete_resource_type_association,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=204,
+ namespace_id="OS::Compute::Hypervisor",
+ resource_name="OS::Glance::Image",
+ )
+
+ def test_create_resource_type_association_with_str_body(self):
+ self._test_create_resource_type_association()
+
+ def test_create_resource_type_association_with_bytes_body(self):
+ self._test_create_resource_type_association(bytes_body=True)
+
+ def test_list_resource_type_association_with_str_body(self):
+ self._test_list_resource_type_association()
+
+ def test_list_resource_type_association_with_bytes_body(self):
+ self._test_list_resource_type_association(bytes_body=True)
diff --git a/tempest/tests/lib/services/object_storage/test_object_client.py b/tempest/tests/lib/services/object_storage/test_object_client.py
index a16d1d7..1749b03 100644
--- a/tempest/tests/lib/services/object_storage/test_object_client.py
+++ b/tempest/tests/lib/services/object_storage/test_object_client.py
@@ -69,7 +69,7 @@
# If the expected initial status is not 100, then an exception
# should be thrown and the connection closed
- if initial_status is 100:
+ if initial_status == 100:
status, reason = \
self.object_client.create_object_continue(cnt, obj, req_data)
else:
@@ -91,7 +91,7 @@
mock_poc.return_value.endheaders.assert_called_once_with()
# The following steps are only taken if the initial status is 100
- if initial_status is 100:
+ if initial_status == 100:
# Verify that the method returned what it was supposed to
self.assertEqual(status, 201)
diff --git a/tempest/tests/lib/services/volume/v3/test_attachments_client.py b/tempest/tests/lib/services/volume/v3/test_attachments_client.py
new file mode 100644
index 0000000..52c94e5
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_attachments_client.py
@@ -0,0 +1,46 @@
+# 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.services.volume.v3 import attachments_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+from oslo_utils.fixture import uuidsentinel as uuids
+
+
+class TestAttachmentsClient(base.BaseServiceTest):
+
+ FAKE_ATTACHMENT_INFO = {
+ "attachment": {
+ "status": "attaching",
+ "detached_at": "2015-09-16T09:28:52.000000",
+ "connection_info": {},
+ "attached_at": "2015-09-16T09:28:52.000000",
+ "attach_mode": "ro",
+ "instance": uuids.instance_id,
+ "volume_id": uuids.volume_id,
+ "id": uuids.id,
+ }
+ }
+
+ def setUp(self):
+ super(TestAttachmentsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = attachments_client.AttachmentsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def test_show_attachment(self):
+ self.check_service_client_function(
+ self.client.show_attachment,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ATTACHMENT_INFO, attachment_id=uuids.id)
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index 83c1abb..7c31185 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -101,17 +101,6 @@
'def test_fake:', './tempest/scenario/orchestration/test_fake.py',
"\n"))
- def test_no_vi_headers(self):
- # NOTE(mtreinish) The lines parameter is used only for finding the
- # line location in the file. So these tests just pass a list of an
- # arbitrary length to use for verifying the check function.
- self.assertTrue(checks.no_vi_headers(
- '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 1, range(250)))
- self.assertTrue(checks.no_vi_headers(
- '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 249, range(250)))
- self.assertFalse(checks.no_vi_headers(
- '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 149, range(250)))
-
def test_service_tags_not_in_module_path(self):
self.assertTrue(checks.service_tags_not_in_module_path(
"@utils.services('compute')",
diff --git a/test-requirements.txt b/test-requirements.txt
index 196387c..a50905f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-hacking>=1.1.0,<1.2.0 # Apache-2.0
+hacking>=3.0,<3.1.0;python_version>='3.5' # Apache-2.0
mock>=2.0.0 # BSD
coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 64adcbe..5ffef3e 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -36,6 +36,7 @@
# when the patches are merged.
BLACKLIST = [
'x/gce-api', # It looks gce-api doesn't support python3 yet.
+ 'x/glare', # To avoid sanity-job failure
'x/group-based-policy', # It looks this doesn't support python3 yet.
'x/intel-nfv-ci-tests', # https://review.opendev.org/#/c/634640/
'openstack/networking-generic-switch',
@@ -48,7 +49,9 @@
'openstack/neutron-dynamic-routing',
# https://review.opendev.org/#/c/637718/
'openstack/neutron-vpnaas', # https://review.opendev.org/#/c/637719/
+ 'x/tap-as-a-service', # To avoid sanity-job failure
'x/valet', # https://review.opendev.org/#/c/638339/
+ 'x/kingbird', # https://bugs.launchpad.net/kingbird/+bug/1869722
]
url = 'https://review.opendev.org/projects/'
diff --git a/tox.ini b/tox.ini
index 64921ef..e861c84 100644
--- a/tox.ini
+++ b/tox.ini
@@ -313,7 +313,6 @@
check-uuid --fix
[hacking]
-local-check-factory = tempest.hacking.checks.factory
import_exceptions = tempest.services
[flake8]
@@ -327,6 +326,26 @@
enable-extensions = H106,H203,H904
import-order-style = pep8
+[flake8:local-plugins]
+extension =
+ T102 = checks:import_no_clients_in_api_and_scenario_tests
+ T104 = checks:scenario_tests_need_service_tags
+ T105 = checks:no_setup_teardown_class_for_tests
+ T107 = checks:service_tags_not_in_module_path
+ T108 = checks:no_hyphen_at_end_of_rand_name
+ N322 = checks:no_mutable_default_args
+ T109 = checks:no_testtools_skip_decorator
+ T110 = checks:get_resources_on_service_clients
+ T111 = checks:delete_resources_on_service_clients
+ T112 = checks:dont_import_local_tempest_into_lib
+ T113 = checks:use_rand_uuid_instead_of_uuid4
+ T114 = checks:dont_use_config_in_tempest_lib
+ T115 = checks:dont_put_admin_tests_on_nonadmin_path
+ T116 = checks:unsupported_exception_attribute_PY3
+ T117 = checks:negative_test_attribute_always_applied_to_negative_tests
+paths =
+ ./tempest/hacking
+
[testenv:releasenotes]
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}