Merge "Add addOnException of unshelve_server when using sharing server"
diff --git a/.zuul.yaml b/.zuul.yaml
index ec6c59a..0588bc9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -14,6 +14,19 @@
post-run: playbooks/post-tempest.yaml
- job:
+ name: tempest-full
+ parent: devstack-tempest
+ description: |
+ Base integration test with Neutron networking and py27.
+ Former names for this job where:
+ * legacy-tempest-dsvm-neutron-full
+ * gate-tempest-dsvm-neutron-full-ubuntu-xenial
+ vars:
+ tox_venvlist: full
+ devstack_localrc:
+ ENABLE_FILE_INJECTION: True
+
+- job:
name: tempest-tox-plugin-sanity-check
parent: tox
description: |
@@ -69,7 +82,7 @@
- openstack/neutron-vpnaas
- openstack/nova-lxd
- openstack/novajoin-tempest-plugin
- - openstack/octavia
+ - openstack/octavia-tempest-plugin
- openstack/oswin-tempest-plugin
- openstack/panko
- openstack/patrole
@@ -98,4 +111,15 @@
- ^playbooks/
- ^roles/
- ^.zuul.yaml$
+ - tempest-full:
+ voting: false
+ irrelevant-files:
+ - ^(test-|)requirements.txt$
+ - ^.*\.rst$
+ - ^doc/.*$
+ - ^etc/.*$
+ - ^releasenotes/.*$
+ - ^setup.cfg$
+ - ^tempest/hacking/.*$
+ - ^tempest/tests/.*$
- tempest-tox-plugin-sanity-check
diff --git a/HACKING.rst b/HACKING.rst
index 57f0409..a3e9c26 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -121,38 +121,38 @@
Test fixtures and resources
---------------------------
Test level resources should be cleaned-up after the test execution. Clean-up
-is best scheduled using `addCleanup` which ensures that the resource cleanup
+is best scheduled using ``addCleanup`` which ensures that the resource cleanup
code is always invoked, and in reverse order with respect to the creation
order.
-Test class level resources should be defined in the `resource_setup` method of
-the test class, except for any credential obtained from the credentials
-provider, which should be set-up in the `setup_credentials` method.
-Cleanup is best scheduled using `addClassResourceCleanup` which ensures that
+Test class level resources should be defined in the ``resource_setup`` method
+of the test class, except for any credential obtained from the credentials
+provider, which should be set-up in the ``setup_credentials`` method.
+Cleanup is best scheduled using ``addClassResourceCleanup`` which ensures that
the cleanup code is always invoked, and in reverse order with respect to the
creation order.
In both cases - test level and class level cleanups - a wait loop should be
scheduled before the actual delete of resources with an asynchronous delete.
-The test base class `BaseTestCase` defines Tempest framework for class level
-fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be
+The test base class ``BaseTestCase`` defines Tempest framework for class level
+fixtures. ``setUpClass`` and ``tearDownClass`` are defined here and cannot be
overwritten by subclasses (enforced via hacking rule T105).
Set-up is split in a series of steps (setup stages), which can be overwritten
by test classes. Set-up stages are:
-- `skip_checks`
-- `setup_credentials`
-- `setup_clients`
-- `resource_setup`
+- ``skip_checks``
+- ``setup_credentials``
+- ``setup_clients``
+- ``resource_setup``
Tear-down is also split in a series of steps (teardown stages), which are
stacked for execution only if the corresponding setup stage had been
reached during the setup phase. Tear-down stages are:
-- `clear_credentials` (defined in the base test class)
-- `resource_cleanup`
+- ``clear_credentials`` (defined in the base test class)
+- ``resource_cleanup``
Skipping Tests
--------------
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 7189312..aca1845 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -352,6 +352,10 @@
.. _3.12: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12
+ * `3.13`_
+
+ .. _3.13: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id13
+
* `3.14`_
.. _3.14: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 2afb1e5..6f6621d 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -132,7 +132,7 @@
Plugin Structure
================
-While there are no hard and fast rules for the structure a plugin, there are
+While there are no hard and fast rules for the structure of a plugin, there are
basically no constraints on what the plugin looks like as long as the 2 steps
above are done. However, there are some recommended patterns to follow to make
it easy for people to contribute and work with your plugin. For example, if you
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index b57e98f..ddae6e2 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -62,9 +62,9 @@
The Old Way using subunit2sql directly
""""""""""""""""""""""""""""""""""""""
-SELECT * from tests where test_id like "%test_id%";
-(where $test_id is the full test_id, but truncated to the class because of
-setUpClass or tearDownClass failures)
+``SELECT * from tests where test_id like "%test_id%";``
+(where ``$test_id`` is the full test_id, but truncated to the class because of
+``setUpClass`` or ``tearDownClass`` failures)
You can access the infra mysql subunit2sql db w/ read-only permissions with:
@@ -74,15 +74,20 @@
* db_name: subunit2sql
For example if you were trying to remove the test with the id:
-tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor
+``tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor``
you would run the following:
-#. run: "mysql -u query -p -h logstash.openstack.org subunit2sql" to connect
- to the subunit2sql db
-#. run the query: MySQL [subunit2sql]> select * from tests where test_id like
- "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
+#. run the command: ``mysql -u query -p -h logstash.openstack.org subunit2sql``
+ to connect to the subunit2sql db
+#. run the query:
+
+ .. code-block:: console
+
+ MySQL [subunit2sql]> select * from tests where test_id like \
+ "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
+
which will return a table of all the tests in the class (but it will also
- catch failures in setUpClass and tearDownClass)
+ catch failures in ``setUpClass`` and ``tearDownClass``)
#. paste the output table with numbers and the mysql command you ran to
generate it into the etherpad.
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 49af95a..fff2405 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -61,7 +61,7 @@
which is executed in that order. Cleanup of resources provisioned during
the resource_setup must be scheduled right after provisioning using
-the addClassResourceCleanp helper. The resource cleanups stacked this way
+the addClassResourceCleanup helper. The resource cleanups stacked this way
are executed in reverse order during tearDownClass, before the cleanup of
test credentials takes place. An example of a TestCase which defines all
of these would be::
diff --git a/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml b/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml
new file mode 100644
index 0000000..14458d6
--- /dev/null
+++ b/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add update group types API to v3 ``group_types_client`` library;
+ min_microversion of this API is 3.11.
diff --git a/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
new file mode 100644
index 0000000..dbb6c46
--- /dev/null
+++ b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+ - |
+ Below config options or feature flags were deprecated for removal.
+ It's time to remove them as all supported stable branches are
+ good to handle them.
+
+ * ``[identity-feature-enabled].forbid_global_implied_dsr``
diff --git a/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
new file mode 100644
index 0000000..621731d
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - |
+ Remove the deprecated decorator ``skip_unless_attr`` in lib/decorators.py.
diff --git a/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
new file mode 100644
index 0000000..17866e5
--- /dev/null
+++ b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+ - |
+ The tox ostestr job (normally invoked with ``tox -eostestr``) has been
+ removed. This was lightly used, and in the near future ostestr will be
+ removed from the tempest requirements file. If you were relying on this
+ functionality you can replicate it by using the venv-tempest tox job. For
+ example, simply running ``tox -evenv-tempest -- ostestr`` will do the same
+ thing the old ostestr job did.
diff --git a/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
new file mode 100644
index 0000000..ddd1704
--- /dev/null
+++ b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new boolean configuration option
+ ``[compute-feature-enabled]/volume_backed_live_migration`` has been added.
+ If enabled, tests which validate the behavior of Nova's *volume-backed live
+ migration* feature will be executed. The option defaults to ``False``.
diff --git a/requirements.txt b/requirements.txt
index 023148b..cd74449 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,10 +9,10 @@
netaddr>=0.7.18 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
oslo.concurrency>=3.20.0 # Apache-2.0
-oslo.config>=4.6.0 # Apache-2.0
+oslo.config>=5.1.0 # Apache-2.0
oslo.log>=3.30.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=3.31.0 # Apache-2.0
+oslo.utils>=3.33.0 # Apache-2.0
six>=1.10.0 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
PyYAML>=3.10 # MIT
diff --git a/tempest/README.rst b/tempest/README.rst
index 663653e..62821de 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -12,10 +12,12 @@
and guidelines. Below is the overview of the Tempest respository structure
to make this clear.
-| tempest/
-| api/ - API tests
-| scenario/ - complex scenario tests
-| tests/ - unit tests for Tempest internals
+ .. code-block:: console
+
+ tempest/
+ api/ - API tests
+ scenario/ - complex scenario tests
+ tests/ - unit tests for Tempest internals
Each of these directories contains different types of tests. What
belongs in each directory, the rules and examples for good tests, are
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 411159b..dcd7b9b 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -132,7 +132,9 @@
def test_live_block_migration_paused(self):
self._test_live_migration(state='PAUSED')
- @decorators.skip_because(bug="1524898")
+ @testtools.skipUnless(CONF.compute_feature_enabled.
+ volume_backed_live_migration,
+ 'Volume-backed live migration not available')
@decorators.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
@utils.services('volume')
def test_volume_backed_live_migration(self):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 9ee8858..ac03cdc 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -439,7 +439,7 @@
# is already detached.
pass
- def attach_volume(self, server, volume, device=None, check_reserved=False):
+ def attach_volume(self, server, volume, device=None):
"""Attaches volume to server and waits for 'in-use' volume status.
The volume will be detached when the test tears down.
@@ -448,15 +448,10 @@
:param volume: The volume to attach.
:param device: Optional mountpoint for the attached volume. Note that
this is not guaranteed for all hypervisors and is not recommended.
- :param check_reserved: Consider a status of reserved as valid for
- completion. This is to handle new Cinder attach where we more
- accurately use 'reserved' for things like attaching to a shelved
- server.
"""
attach_kwargs = dict(volumeId=volume['id'])
if device:
attach_kwargs['device'] = device
-
attachment = self.servers_client.attach_volume(
server['id'], **attach_kwargs)['volumeAttachment']
# On teardown detach the volume and wait for it to be available. This
@@ -467,11 +462,8 @@
# Ignore 404s on detach in case the server is deleted or the volume
# is already detached.
self.addCleanup(self._detach_volume, server, volume)
- statuses = ['in-use']
- if check_reserved:
- statuses.append('reserved')
waiters.wait_for_volume_resource_status(self.volumes_client,
- volume['id'], statuses)
+ volume['id'], 'in-use')
return attachment
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 0248c65..0e8f681 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -78,8 +78,11 @@
return port
- def _check_interface(self, iface, port_id=None, network_id=None,
- fixed_ip=None, mac_addr=None):
+ def _check_interface(self, iface, server_id=None, port_id=None,
+ network_id=None, fixed_ip=None, mac_addr=None):
+ if server_id:
+ iface = waiters.wait_for_interface_status(
+ self.interfaces_client, server_id, iface['port_id'], 'ACTIVE')
if port_id:
self.assertEqual(iface['port_id'], port_id)
if network_id:
@@ -109,9 +112,8 @@
network_id = ifs[0]['net_id']
iface = self.interfaces_client.create_interface(
server['id'], net_id=network_id)['interfaceAttachment']
- iface = waiters.wait_for_interface_status(
- self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
- self._check_interface(iface, network_id=network_id)
+ self._check_interface(iface, server_id=server['id'],
+ network_id=network_id)
return iface
def _test_create_interface_by_port_id(self, server, ifs):
@@ -121,9 +123,8 @@
self.addCleanup(self.ports_client.delete_port, port_id)
iface = self.interfaces_client.create_interface(
server['id'], port_id=port_id)['interfaceAttachment']
- iface = waiters.wait_for_interface_status(
- self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
- self._check_interface(iface, port_id=port_id)
+ self._check_interface(iface, server_id=server['id'], port_id=port_id,
+ network_id=network_id)
return iface
def _test_create_interface_by_fixed_ips(self, server, ifs):
@@ -140,9 +141,8 @@
server['id'], net_id=network_id,
fixed_ips=fixed_ips)['interfaceAttachment']
self.addCleanup(self.ports_client.delete_port, iface['port_id'])
- iface = waiters.wait_for_interface_status(
- self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
- self._check_interface(iface, fixed_ip=ip_list[0])
+ self._check_interface(iface, server_id=server['id'],
+ fixed_ip=ip_list[0])
return iface
def _test_show_interface(self, server, ifs):
@@ -271,7 +271,8 @@
# attach the port to the server
iface = self.interfaces_client.create_interface(
server['id'], port_id=port_id)['interfaceAttachment']
- self._check_interface(iface, port_id=port_id)
+ self._check_interface(iface, server_id=server['id'],
+ port_id=port_id)
# detach the port from the server; this is a cast in the compute
# API so we have to poll the port until the device_id is unset.
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 9bef80f..297e8a8 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -223,8 +223,7 @@
num_vol = self._count_volumes(server, validation_resources)
self._shelve_server(server, validation_resources)
attachment = self.attach_volume(server, volume,
- device=('/dev/%s' % self.device),
- check_reserved=True)
+ device=('/dev/%s' % self.device))
# Unshelve the instance and check that attached volume exists
self._unshelve_server_and_check_volumes(
@@ -250,8 +249,7 @@
self._shelve_server(server, validation_resources)
# Attach and then detach the volume
- self.attach_volume(server, volume, device=('/dev/%s' % self.device),
- check_reserved=True)
+ self.attach_volume(server, volume, device=('/dev/%s' % self.device))
self.servers_client.detach_volume(server['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index eabb907..7a74869 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -41,3 +41,18 @@
self.assertRaises(lib_exc.BadRequest,
self.delete_volume, volume['id'])
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c')
+ def test_attach_attached_volume_to_same_server(self):
+ # Test attaching the same volume to the same instance once
+ # it's already attached. The nova/cinder validation for this differs
+ # depending on whether or not cinder v3.27 is being used to attach
+ # the volume to the instance.
+ server = self.create_test_server(wait_until='ACTIVE')
+ volume = self.create_volume()
+
+ self.attach_volume(server, volume)
+
+ self.assertRaises(lib_exc.BadRequest,
+ self.attach_volume, server, volume)
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 15b2008..ba19ff7 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -32,21 +32,18 @@
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_password()
for _ in range(2):
- cls.project = cls.projects_client.create_project(
+ project = cls.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'))['project']
- cls.projects.append(cls.project['id'])
+ cls.addClassResourceCleanup(
+ cls.projects_client.delete_project, project['id'])
+ cls.projects.append(project['id'])
cls.user_body = cls.users_client.create_user(
name=u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.projects[0])['user']
-
- @classmethod
- def resource_cleanup(cls):
- cls.users_client.delete_user(cls.user_body['id'])
- for p in cls.projects:
- cls.projects_client.delete_project(p)
- super(CredentialsTestJSON, cls).resource_cleanup()
+ cls.addClassResourceCleanup(
+ cls.users_client.delete_user, cls.user_body['id'])
def _delete_credential(self, cred_id):
self.creds_client.delete_credential(cred_id)
diff --git a/tempest/api/identity/admin/v3/test_domain_configuration.py b/tempest/api/identity/admin/v3/test_domain_configuration.py
index f731697..c4e0622 100644
--- a/tempest/api/identity/admin/v3/test_domain_configuration.py
+++ b/tempest/api/identity/admin/v3/test_domain_configuration.py
@@ -37,18 +37,6 @@
super(DomainConfigurationTestJSON, cls).setup_clients()
cls.client = cls.domain_config_client
- @classmethod
- def resource_setup(cls):
- super(DomainConfigurationTestJSON, cls).resource_setup()
- cls.group = cls.groups_client.create_group(
- name=data_utils.rand_name('group'),
- description=data_utils.rand_name('group-desc'))['group']
-
- @classmethod
- def resource_cleanup(cls):
- cls.groups_client.delete_group(cls.group['id'])
- super(DomainConfigurationTestJSON, cls).resource_cleanup()
-
def _create_domain_and_config(self, config):
domain = self.setup_test_domain()
config = self.client.create_domain_config(domain['id'], **config)[
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
index 49dbba1..eef93c2 100644
--- a/tempest/api/identity/admin/v3/test_endpoint_groups.py
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -15,6 +15,7 @@
from tempest.api.identity import base
from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
@@ -28,11 +29,12 @@
@classmethod
def resource_setup(cls):
super(EndPointGroupsTest, cls).resource_setup()
- cls.service_ids = list()
cls.endpoint_groups = list()
# Create endpoint group so as to use it for LIST test
service_id = cls._create_service()
+ cls.addClassResourceCleanup(
+ cls.services_client.delete_service, service_id)
name = data_utils.rand_name('service_group')
description = data_utils.rand_name('description')
@@ -42,18 +44,12 @@
name=name,
description=description,
filters=filters)['endpoint_group']
+ cls.addClassResourceCleanup(
+ cls.client.delete_endpoint_group, endpoint_group['id'])
cls.endpoint_groups.append(endpoint_group)
@classmethod
- def resource_cleanup(cls):
- for e in cls.endpoint_groups:
- cls.client.delete_endpoint_group(e['id'])
- for s in cls.service_ids:
- cls.services_client.delete_service(s)
- super(EndPointGroupsTest, cls).resource_cleanup()
-
- @classmethod
def _create_service(cls):
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
@@ -64,7 +60,6 @@
description=s_description))
service_id = service_data['service']['id']
- cls.service_ids.append(service_id)
return service_id
@decorators.idempotent_id('7c69e7a1-f865-402d-a2ea-44493017315a')
@@ -78,6 +73,9 @@
name=name,
description=description,
filters=filters)['endpoint_group']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_endpoint_group, endpoint_group['id'])
self.endpoint_groups.append(endpoint_group)
@@ -115,7 +113,6 @@
# Deleting the endpoint group created in this method
self.client.delete_endpoint_group(endpoint_group['id'])
- self.endpoint_groups.remove(endpoint_group)
# Checking whether endpoint group is deleted successfully
fetched_endpoints = \
@@ -136,10 +133,12 @@
name=name,
description=description,
filters=filters)['endpoint_group']
- self.endpoint_groups.append(endpoint_group)
+ self.addCleanup(self.client.delete_endpoint_group,
+ endpoint_group['id'])
# Creating new attr values to update endpoint group
service2_id = self._create_service()
+ self.addCleanup(self.services_client.delete_service, service2_id)
name2 = data_utils.rand_name('service_group2')
description2 = data_utils.rand_name('description2')
filters = {'service_id': service2_id}
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 5d48f68..874aaa4 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -15,6 +15,7 @@
from tempest.api.identity import base
from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
@@ -34,12 +35,18 @@
interfaces = ['public', 'internal']
cls.setup_endpoint_ids = list()
for i in range(2):
- cls._create_service()
+ service = cls._create_service()
+ cls.service_ids.append(service['id'])
+ cls.addClassResourceCleanup(
+ cls.services_client.delete_service, service['id'])
+
region = data_utils.rand_name('region')
url = data_utils.rand_url()
endpoint = cls.client.create_endpoint(
service_id=cls.service_ids[i], interface=interfaces[i],
url=url, region=region, enabled=True)['endpoint']
+ cls.addClassResourceCleanup(
+ cls.client.delete_endpoint, endpoint['id'])
cls.setup_endpoint_ids.append(endpoint['id'])
@classmethod
@@ -53,17 +60,7 @@
service_data = (
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description))
- service = service_data['service']
- cls.service_ids.append(service['id'])
- return service
-
- @classmethod
- def resource_cleanup(cls):
- for e in cls.setup_endpoint_ids:
- cls.client.delete_endpoint(e)
- for s in cls.service_ids:
- cls.services_client.delete_service(s)
- super(EndPointsTestJSON, cls).resource_cleanup()
+ return service_data['service']
@decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618')
def test_list_endpoints(self):
@@ -114,8 +111,8 @@
interface=interface,
url=url, region=region,
enabled=True)['endpoint']
-
- self.setup_endpoint_ids.append(endpoint['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_endpoint, endpoint['id'])
# Asserting Create Endpoint response body
self.assertEqual(region, endpoint['region'])
self.assertEqual(url, endpoint['url'])
@@ -137,7 +134,6 @@
# Deleting the endpoint created in this method
self.client.delete_endpoint(endpoint['id'])
- self.setup_endpoint_ids.remove(endpoint['id'])
# Checking whether endpoint is deleted successfully
fetched_endpoints = self.client.list_endpoints()['endpoints']
@@ -147,8 +143,20 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('37e8f15e-ee7c-4657-a1e7-f6b61e375eff')
def test_update_endpoint(self):
- # Creating an endpoint so as to check update endpoint
- # with new values
+ # NOTE(zhufl) Service2 should be created before endpoint_for_update
+ # is created, because Service2 must be deleted after
+ # endpoint_for_update is deleted, otherwise we will get a 404 error
+ # when deleting endpoint_for_update if endpoint's service is deleted.
+
+ # Creating service for updating endpoint with new service ID
+ s_name = data_utils.rand_name('service')
+ s_type = data_utils.rand_name('type')
+ s_description = data_utils.rand_name('description')
+ service2 = self._create_service(s_name=s_name, s_type=s_type,
+ s_description=s_description)
+ self.addCleanup(self.services_client.delete_service, service2['id'])
+
+ # Creating an endpoint so as to check update endpoint with new values
region1 = data_utils.rand_name('region')
url1 = data_utils.rand_url()
interface1 = 'public'
@@ -158,12 +166,7 @@
url=url1, region=region1,
enabled=True)['endpoint'])
self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
- # Creating service so as update endpoint with new service ID
- s_name = data_utils.rand_name('service')
- s_type = data_utils.rand_name('type')
- s_description = data_utils.rand_name('description')
- service2 = self._create_service(s_name=s_name, s_type=s_type,
- s_description=s_description)
+
# Updating endpoint with new values
region2 = data_utils.rand_name('region')
url2 = data_utils.rand_url()
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 70dd7b5..d54e222 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -30,7 +30,6 @@
@classmethod
def resource_setup(cls):
super(EndpointsNegativeTestJSON, cls).resource_setup()
- cls.service_ids = list()
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
@@ -38,14 +37,10 @@
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description)
['service'])
- cls.service_id = service_data['id']
- cls.service_ids.append(cls.service_id)
+ cls.addClassResourceCleanup(cls.services_client.delete_service,
+ service_data['id'])
- @classmethod
- def resource_cleanup(cls):
- for s in cls.service_ids:
- cls.services_client.delete_service(s)
- super(EndpointsNegativeTestJSON, cls).resource_cleanup()
+ cls.service_id = service_data['id']
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ac6c137e-4d3d-448f-8c83-4f13d0942651')
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index c0c79b9..68c0225 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -36,20 +36,19 @@
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'),
domain_id=cls.domain['id'])['project']
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.project['id'])
cls.group = cls.groups_client.create_group(
name=data_utils.rand_name('group-'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
+ cls.addClassResourceCleanup(cls.groups_client.delete_group,
+ cls.group['id'])
cls.user = cls.users_client.create_user(
name=u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
-
- @classmethod
- def resource_cleanup(cls):
- cls.groups_client.delete_group(cls.group['id'])
- cls.users_client.delete_user(cls.user['id'])
- cls.projects_client.delete_project(cls.project['id'])
- super(InheritsV3TestJSON, cls).resource_cleanup()
+ cls.addClassResourceCleanup(cls.users_client.delete_user,
+ cls.user['id'])
def _list_assertions(self, body, fetched_role_ids, role_id):
self.assertEqual(len(body), 1)
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 25dd52b..82664e8 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -27,32 +27,27 @@
# Create a domain
cls.domain = cls.create_domain()
# Create project with domain
- cls.projects = list()
cls.p1_name = data_utils.rand_name('project')
cls.p1 = cls.projects_client.create_project(
cls.p1_name, enabled=False,
domain_id=cls.domain['id'])['project']
- cls.projects.append(cls.p1)
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.p1['id'])
cls.project_ids.append(cls.p1['id'])
# Create default project
p2_name = data_utils.rand_name('project')
cls.p2 = cls.projects_client.create_project(p2_name)['project']
- cls.projects.append(cls.p2)
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.p2['id'])
cls.project_ids.append(cls.p2['id'])
# Create a new project (p3) using p2 as parent project
p3_name = data_utils.rand_name('project')
cls.p3 = cls.projects_client.create_project(
p3_name, parent_id=cls.p2['id'])['project']
- cls.projects.append(cls.p3)
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.p3['id'])
cls.project_ids.append(cls.p3['id'])
- @classmethod
- def resource_cleanup(cls):
- # Cleanup the projects created during setup in inverse order
- for project in reversed(cls.projects):
- cls.projects_client.delete_project(project['id'])
- super(ListProjectsTestJSON, cls).resource_cleanup()
-
@decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
def test_list_projects(self):
# List projects
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 88cd8be..c69e4c8 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -47,21 +47,18 @@
cls.domain_enabled_user = cls.users_client.create_user(
name=u1_name, password=alt_password,
email=cls.alt_email, domain_id=cls.domain['id'])['user']
+ cls.addClassResourceCleanup(cls.users_client.delete_user,
+ cls.domain_enabled_user['id'])
cls.users.append(cls.domain_enabled_user)
# Create default not enabled user
u2_name = data_utils.rand_name('test_user')
cls.non_domain_enabled_user = cls.users_client.create_user(
name=u2_name, password=alt_password,
email=cls.alt_email, enabled=False)['user']
+ cls.addClassResourceCleanup(cls.users_client.delete_user,
+ cls.non_domain_enabled_user['id'])
cls.users.append(cls.non_domain_enabled_user)
- @classmethod
- def resource_cleanup(cls):
- # Cleanup the users created during setup
- for user in cls.users:
- cls.users_client.delete_user(user['id'])
- super(UsersV3TestJSON, cls).resource_cleanup()
-
@decorators.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d')
def test_list_user_domains(self):
# List users with domain
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index d00e408..f22a528 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -34,14 +34,10 @@
r_description = data_utils.rand_name('description')
region = cls.client.create_region(
description=r_description)['region']
+ cls.addClassResourceCleanup(
+ cls.client.delete_region, region['id'])
cls.setup_regions.append(region)
- @classmethod
- def resource_cleanup(cls):
- for r in cls.setup_regions:
- cls.client.delete_region(r['id'])
- super(RegionsTestJSON, cls).resource_cleanup()
-
@decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
def test_create_update_get_delete_region(self):
# Create region
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index e7b005c..69cac33 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -32,6 +32,8 @@
for _ in range(3):
role_name = data_utils.rand_name(name='role')
role = cls.roles_client.create_role(name=role_name)['role']
+ cls.addClassResourceCleanup(cls.roles_client.delete_role,
+ role['id'])
cls.roles.append(role)
u_name = data_utils.rand_name('user')
u_desc = '%s description' % u_name
@@ -42,25 +44,23 @@
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'),
domain_id=cls.domain['id'])['project']
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.project['id'])
cls.group_body = cls.groups_client.create_group(
name=data_utils.rand_name('Group'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
+ cls.addClassResourceCleanup(cls.groups_client.delete_group,
+ cls.group_body['id'])
cls.user_body = cls.users_client.create_user(
name=u_name, description=u_desc, password=cls.u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
+ cls.addClassResourceCleanup(cls.users_client.delete_user,
+ cls.user_body['id'])
cls.role = cls.roles_client.create_role(
name=data_utils.rand_name('Role'))['role']
-
- @classmethod
- def resource_cleanup(cls):
- cls.roles_client.delete_role(cls.role['id'])
- cls.groups_client.delete_group(cls.group_body['id'])
- cls.users_client.delete_user(cls.user_body['id'])
- cls.projects_client.delete_project(cls.project['id'])
- for role in cls.roles:
- cls.roles_client.delete_role(role['id'])
- super(RolesV3TestJSON, cls).resource_cleanup()
+ cls.addClassResourceCleanup(cls.roles_client.delete_role,
+ cls.role['id'])
@decorators.attr(type='smoke')
@decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
@@ -338,14 +338,13 @@
# domain role to a global one
self._create_implied_role(domain_role1['id'], self.role['id'])
- if CONF.identity_feature_enabled.forbid_global_implied_dsr:
- # The contrary is not true: we can't create an inference rule
- # from a global role to a domain role
- self.assertRaises(
- lib_exc.Forbidden,
- self.roles_client.create_role_inference_rule,
- self.role['id'],
- domain_role1['id'])
+ # The contrary is not true: we can't create an inference rule
+ # from a global role to a domain role
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.roles_client.create_role_inference_rule,
+ self.role['id'],
+ domain_role1['id'])
@decorators.idempotent_id('3859df7e-5b78-4e4d-b10e-214c8953842a')
def test_assignments_for_domain_roles(self):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index c2a67e3..bdfda0a 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -92,7 +92,6 @@
cls.subnets = []
cls.ports = []
cls.routers = []
- cls.floating_ips = []
cls.ethertype = "IPv" + str(cls._ip_version)
if cls._ip_version == 4:
cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
@@ -104,11 +103,6 @@
@classmethod
def resource_cleanup(cls):
if CONF.service_available.neutron:
- # Clean up floating IPs
- for floating_ip in cls.floating_ips:
- test_utils.call_and_ignore_notfound_exc(
- cls.floating_ips_client.delete_floatingip,
- floating_ip['id'])
# Clean up ports
for port in cls.ports:
test_utils.call_and_ignore_notfound_exc(
@@ -222,7 +216,9 @@
body = cls.floating_ips_client.create_floatingip(
floating_network_id=external_network_id)
fip = body['floatingip']
- cls.floating_ips.append(fip)
+ cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+ cls.floating_ips_client.delete_floatingip,
+ fip['id'])
return fip
@classmethod
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index d7c85a2..3bbab11 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -44,14 +44,13 @@
for i in range(ord('a'), ord('f') + 1):
name = data_utils.rand_name(name='%s-' % six.int2byte(i))
cls.container_client.update_container(name)
+ cls.addClassResourceCleanup(base.delete_containers,
+ [name],
+ cls.container_client,
+ cls.object_client)
cls.containers.append(name)
cls.containers_count = len(cls.containers)
- @classmethod
- def resource_cleanup(cls):
- cls.delete_containers()
- super(AccountTest, cls).resource_cleanup()
-
@decorators.attr(type='smoke')
@decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
def test_list_containers(self):
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 51b0a1d..75111b6 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,16 +24,6 @@
class ContainerTest(base.BaseObjectTest):
- @classmethod
- def resource_setup(cls):
- super(ContainerTest, cls).resource_setup()
- cls.containers = []
-
- @classmethod
- def resource_cleanup(cls):
- cls.delete_containers()
- super(ContainerTest, cls).resource_cleanup()
-
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
self.assertHeaders(resp, 'Container', 'HEAD')
@@ -52,7 +42,10 @@
# create container
vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, _ = self.container_client.update_container(vers_container_name)
- self.containers.append(vers_container_name)
+ self.addCleanup(base.delete_containers,
+ [vers_container_name],
+ self.container_client,
+ self.object_client)
self.assertHeaders(resp, 'Container', 'PUT')
self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
@@ -61,7 +54,10 @@
resp, _ = self.container_client.update_container(
base_container_name,
**headers)
- self.containers.append(base_container_name)
+ self.addCleanup(base.delete_containers,
+ [base_container_name],
+ self.container_client,
+ self.object_client)
self.assertHeaders(resp, 'Container', 'PUT')
self.assertContainer(base_container_name, '0', '0',
vers_container_name)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
new file mode 100644
index 0000000..45f4caa
--- /dev/null
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -0,0 +1,202 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class BaseGroupSnapshotsTest(base.BaseVolumeAdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseGroupSnapshotsTest, cls).skip_checks()
+ if not CONF.volume_feature_enabled.snapshot:
+ raise cls.skipException("Cinder volume snapshots are disabled")
+
+ def _create_group_snapshot(self, **kwargs):
+ if 'name' not in kwargs:
+ kwargs['name'] = data_utils.rand_name(
+ self.__class__.__name__ + '-Group_Snapshot')
+
+ group_snapshot = self.group_snapshots_client.create_group_snapshot(
+ **kwargs)['group_snapshot']
+ group_snapshot['group_id'] = kwargs['group_id']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self._delete_group_snapshot, group_snapshot)
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client, group_snapshot['id'], 'available')
+ return group_snapshot
+
+ def _delete_group_snapshot(self, group_snapshot):
+ self.group_snapshots_client.delete_group_snapshot(group_snapshot['id'])
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for vol in vols:
+ for snap in snapshots:
+ if (vol['group_id'] == group_snapshot['group_id'] and
+ vol['id'] == snap['volume_id']):
+ self.snapshots_client.wait_for_resource_deletion(
+ snap['id'])
+ self.group_snapshots_client.wait_for_resource_deletion(
+ group_snapshot['id'])
+
+
+class GroupSnapshotsTest(BaseGroupSnapshotsTest):
+ _api_version = 3
+ min_microversion = '3.14'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
+ def test_group_snapshot_create_show_list_delete(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ grp = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
+
+ # Create volume
+ vol = self.create_volume(volume_type=volume_type['id'],
+ group_id=grp['id'])
+
+ # Create group snapshot
+ group_snapshot_name = data_utils.rand_name('group_snapshot')
+ group_snapshot = self._create_group_snapshot(
+ group_id=grp['id'], name=group_snapshot_name)
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if vol['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+ # Get a given group snapshot
+ group_snapshot = self.group_snapshots_client.show_group_snapshot(
+ group_snapshot['id'])['group_snapshot']
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+ # Get all group snapshots with details, check some detail-specific
+ # elements, and look for the created group snapshot
+ group_snapshots = self.group_snapshots_client.list_group_snapshots(
+ detail=True)['group_snapshots']
+ for grp_snapshot in group_snapshots:
+ self.assertIn('created_at', grp_snapshot)
+ self.assertIn('group_id', grp_snapshot)
+ self.assertIn((group_snapshot['name'], group_snapshot['id']),
+ [(m['name'], m['id']) for m in group_snapshots])
+
+ # Delete group snapshot
+ self._delete_group_snapshot(group_snapshot)
+ group_snapshots = self.group_snapshots_client.list_group_snapshots()[
+ 'group_snapshots']
+ self.assertEmpty(group_snapshots)
+
+ @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
+ def test_create_group_from_group_snapshot(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create Group
+ grp = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
+
+ # Create volume
+ vol = self.create_volume(volume_type=volume_type['id'],
+ group_id=grp['id'])
+
+ # Create group_snapshot
+ group_snapshot_name = data_utils.rand_name('group_snapshot')
+ group_snapshot = self._create_group_snapshot(
+ group_id=grp['id'], name=group_snapshot_name)
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if vol['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+
+ # Create Group from Group snapshot
+ grp_name2 = data_utils.rand_name('Group_from_snap')
+ grp2 = self.groups_client.create_group_from_source(
+ group_snapshot_id=group_snapshot['id'], name=grp_name2)['group']
+ self.addCleanup(self.delete_group, grp2['id'])
+ self.assertEqual(grp_name2, grp2['name'])
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == grp2['id']:
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, vol['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp2['id'], 'available')
+
+
+class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
+ _api_version = 3
+ min_microversion = '3.19'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
+ def test_reset_group_snapshot_status(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ group = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
+
+ # Create volume
+ volume = self.create_volume(volume_type=volume_type['id'],
+ group_id=group['id'])
+
+ # Create group snapshot
+ group_snapshot = self._create_group_snapshot(group_id=group['id'])
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if volume['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+
+ # Reset group snapshot status
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.group_snapshots_client,
+ group_snapshot['id'], 'available')
+ self.addCleanup(
+ self.admin_group_snapshots_client.reset_group_snapshot_status,
+ group_snapshot['id'], 'available')
+ for status in ['creating', 'available', 'error']:
+ self.admin_group_snapshots_client.reset_group_snapshot_status(
+ group_snapshot['id'], status)
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client, group_snapshot['id'], status)
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 0df5fbd..6723207 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -24,7 +24,7 @@
max_microversion = 'latest'
@decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
- def test_group_type_create_list_show(self):
+ def test_group_type_create_list_update_show(self):
# Create/list/show group type.
name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
description = data_utils.rand_name("group-type-description")
@@ -46,8 +46,19 @@
self.assertIsInstance(group_list, list)
self.assertNotEmpty(group_list)
+ update_params = {
+ 'name': data_utils.rand_name(
+ self.__class__.__name__ + '-updated-group-type'),
+ 'description': 'updated-group-type-desc'
+ }
+ updated_group_type = self.admin_group_types_client.update_group_type(
+ body['id'], **update_params)['group_type']
+ for key, expected_val in update_params.items():
+ self.assertEqual(expected_val, updated_group_type[key])
+
fetched_group_type = self.admin_group_types_client.show_group_type(
body['id'])['group_type']
+ params.update(update_params) # Add updated params to original params.
for key in params.keys():
self.assertEqual(params[key], fetched_group_type[key],
'%s of the fetched group_type is different '
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index 6b53d85..2f6eb6b 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -17,54 +17,14 @@
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
CONF = config.CONF
-class BaseGroupsTest(base.BaseVolumeAdminTest):
-
- def _delete_group(self, grp_id, delete_volumes=True):
- self.groups_client.delete_group(grp_id, delete_volumes)
- vols = self.volumes_client.list_volumes(detail=True)['volumes']
- for vol in vols:
- if vol['group_id'] == grp_id:
- self.volumes_client.wait_for_resource_deletion(vol['id'])
- self.groups_client.wait_for_resource_deletion(grp_id)
-
- def _delete_group_snapshot(self, group_snapshot_id, grp_id):
- self.group_snapshots_client.delete_group_snapshot(group_snapshot_id)
- vols = self.volumes_client.list_volumes(detail=True)['volumes']
- snapshots = self.snapshots_client.list_snapshots(
- detail=True)['snapshots']
- for vol in vols:
- for snap in snapshots:
- if (vol['group_id'] == grp_id and
- vol['id'] == snap['volume_id']):
- self.snapshots_client.wait_for_resource_deletion(
- snap['id'])
- self.group_snapshots_client.wait_for_resource_deletion(
- group_snapshot_id)
-
- def _create_group(self, group_type, volume_type, grp_name=None):
- if not grp_name:
- grp_name = data_utils.rand_name('Group')
- grp = self.groups_client.create_group(
- group_type=group_type['id'],
- volume_types=[volume_type['id']],
- name=grp_name)['group']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self._delete_group, grp['id'])
- waiters.wait_for_volume_resource_status(
- self.groups_client, grp['id'], 'available')
- self.assertEqual(grp_name, grp['name'])
- return grp
-
-
-class GroupsTest(BaseGroupsTest):
+class GroupsTest(base.BaseVolumeAdminTest):
_api_version = 3
- min_microversion = '3.14'
+ min_microversion = '3.13'
max_microversion = 'latest'
@decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
@@ -77,13 +37,15 @@
# Create group
grp1_name = data_utils.rand_name('Group1')
- grp1 = self._create_group(group_type, volume_type,
- grp_name=grp1_name)
+ grp1 = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']],
+ name=grp1_name)
grp1_id = grp1['id']
grp2_name = data_utils.rand_name('Group2')
- grp2 = self._create_group(group_type, volume_type,
- grp_name=grp2_name)
+ grp2 = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']],
+ name=grp2_name)
grp2_id = grp2['id']
# Create volume
@@ -125,143 +87,12 @@
# Delete group
# grp1 has a volume so delete_volumes flag is set to True by default
- self._delete_group(grp1_id)
+ self.delete_group(grp1_id)
# grp2 is empty so delete_volumes flag can be set to False
- self._delete_group(grp2_id, delete_volumes=False)
+ self.delete_group(grp2_id, delete_volumes=False)
grps = self.groups_client.list_groups(detail=True)['groups']
self.assertEmpty(grps)
- @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
- def test_group_snapshot_create_show_list_delete(self):
- # Create volume type
- volume_type = self.create_volume_type()
-
- # Create group type
- group_type = self.create_group_type()
-
- # Create group
- grp = self._create_group(group_type, volume_type)
-
- # Create volume
- vol = self.create_volume(volume_type=volume_type['id'],
- group_id=grp['id'])
-
- # Create group snapshot
- group_snapshot_name = data_utils.rand_name('group_snapshot')
- group_snapshot = (
- self.group_snapshots_client.create_group_snapshot(
- group_id=grp['id'],
- name=group_snapshot_name)['group_snapshot'])
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self._delete_group_snapshot,
- group_snapshot['id'], grp['id'])
- snapshots = self.snapshots_client.list_snapshots(
- detail=True)['snapshots']
- for snap in snapshots:
- if vol['id'] == snap['volume_id']:
- waiters.wait_for_volume_resource_status(
- self.snapshots_client, snap['id'], 'available')
- waiters.wait_for_volume_resource_status(
- self.group_snapshots_client,
- group_snapshot['id'], 'available')
- self.assertEqual(group_snapshot_name, group_snapshot['name'])
-
- # Get a given group snapshot
- group_snapshot = self.group_snapshots_client.show_group_snapshot(
- group_snapshot['id'])['group_snapshot']
- self.assertEqual(group_snapshot_name, group_snapshot['name'])
-
- # Get all group snapshots with details, check some detail-specific
- # elements, and look for the created group snapshot
- group_snapshots = (self.group_snapshots_client.list_group_snapshots(
- detail=True)['group_snapshots'])
- for grp_snapshot in group_snapshots:
- self.assertIn('created_at', grp_snapshot)
- self.assertIn('group_id', grp_snapshot)
- self.assertIn((group_snapshot['name'], group_snapshot['id']),
- [(m['name'], m['id']) for m in group_snapshots])
-
- # Delete group snapshot
- self._delete_group_snapshot(group_snapshot['id'], grp['id'])
- group_snapshots = (self.group_snapshots_client.list_group_snapshots()
- ['group_snapshots'])
- self.assertEmpty(group_snapshots)
-
- @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
- def test_create_group_from_group_snapshot(self):
- # Create volume type
- volume_type = self.create_volume_type()
-
- # Create group type
- group_type = self.create_group_type()
-
- # Create Group
- grp = self._create_group(group_type, volume_type)
-
- # Create volume
- vol = self.create_volume(volume_type=volume_type['id'],
- group_id=grp['id'])
-
- # Create group_snapshot
- group_snapshot_name = data_utils.rand_name('group_snapshot')
- group_snapshot = (
- self.group_snapshots_client.create_group_snapshot(
- group_id=grp['id'],
- name=group_snapshot_name)['group_snapshot'])
- self.addCleanup(self._delete_group_snapshot,
- group_snapshot['id'], grp['id'])
- self.assertEqual(group_snapshot_name, group_snapshot['name'])
- snapshots = self.snapshots_client.list_snapshots(
- detail=True)['snapshots']
- for snap in snapshots:
- if vol['id'] == snap['volume_id']:
- waiters.wait_for_volume_resource_status(
- self.snapshots_client, snap['id'], 'available')
- waiters.wait_for_volume_resource_status(
- self.group_snapshots_client, group_snapshot['id'], 'available')
-
- # Create Group from Group snapshot
- grp_name2 = data_utils.rand_name('Group_from_snap')
- grp2 = self.groups_client.create_group_from_source(
- group_snapshot_id=group_snapshot['id'], name=grp_name2)['group']
- self.addCleanup(self._delete_group, grp2['id'])
- self.assertEqual(grp_name2, grp2['name'])
- vols = self.volumes_client.list_volumes(detail=True)['volumes']
- for vol in vols:
- if vol['group_id'] == grp2['id']:
- waiters.wait_for_volume_resource_status(
- self.volumes_client, vol['id'], 'available')
- waiters.wait_for_volume_resource_status(
- self.groups_client, grp2['id'], 'available')
-
- @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
- def test_create_group_from_group(self):
- # Create volume type
- volume_type = self.create_volume_type()
-
- # Create group type
- group_type = self.create_group_type()
-
- # Create Group
- grp = self._create_group(group_type, volume_type)
-
- # Create volume
- self.create_volume(volume_type=volume_type['id'], group_id=grp['id'])
-
- # Create Group from Group
- grp_name2 = data_utils.rand_name('Group_from_grp')
- grp2 = self.groups_client.create_group_from_source(
- source_group_id=grp['id'], name=grp_name2)['group']
- self.addCleanup(self._delete_group, grp2['id'])
- self.assertEqual(grp_name2, grp2['name'])
- vols = self.volumes_client.list_volumes(detail=True)['volumes']
- for vol in vols:
- if vol['group_id'] == grp2['id']:
- waiters.wait_for_volume_resource_status(
- self.volumes_client, vol['id'], 'available')
- waiters.wait_for_volume_resource_status(
- self.groups_client, grp2['id'], 'available')
-
@decorators.idempotent_id('4a8a6fd2-8b3b-4641-8f54-6a6f99320006')
def test_group_update(self):
# Create volume type
@@ -271,7 +102,8 @@
group_type = self.create_group_type()
# Create Group
- grp = self._create_group(group_type, volume_type)
+ grp = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
# Create volumes
grp_vols = []
@@ -317,56 +149,42 @@
self.assertEqual(2, len(grp_vols))
-class GroupsV319Test(BaseGroupsTest):
+class GroupsV314Test(base.BaseVolumeAdminTest):
_api_version = 3
- min_microversion = '3.19'
+ min_microversion = '3.14'
max_microversion = 'latest'
- @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
- def test_reset_group_snapshot_status(self):
+ @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
+ def test_create_group_from_group(self):
# Create volume type
volume_type = self.create_volume_type()
# Create group type
group_type = self.create_group_type()
- # Create group
- group = self._create_group(group_type, volume_type)
+ # Create Group
+ grp = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
# Create volume
- volume = self.create_volume(volume_type=volume_type['id'],
- group_id=group['id'])
+ self.create_volume(volume_type=volume_type['id'], group_id=grp['id'])
- # Create group snapshot
- group_snapshot_name = data_utils.rand_name('group_snapshot')
- group_snapshot = (self.group_snapshots_client.create_group_snapshot(
- group_id=group['id'], name=group_snapshot_name)['group_snapshot'])
- self.addCleanup(self._delete_group_snapshot,
- group_snapshot['id'], group['id'])
- snapshots = self.snapshots_client.list_snapshots(
- detail=True)['snapshots']
- for snap in snapshots:
- if volume['id'] == snap['volume_id']:
+ # Create Group from Group
+ grp_name2 = data_utils.rand_name('Group_from_grp')
+ grp2 = self.groups_client.create_group_from_source(
+ source_group_id=grp['id'], name=grp_name2)['group']
+ self.addCleanup(self.delete_group, grp2['id'])
+ self.assertEqual(grp_name2, grp2['name'])
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == grp2['id']:
waiters.wait_for_volume_resource_status(
- self.snapshots_client, snap['id'], 'available')
+ self.volumes_client, vol['id'], 'available')
waiters.wait_for_volume_resource_status(
- self.group_snapshots_client, group_snapshot['id'], 'available')
-
- # Reset group snapshot status
- self.addCleanup(waiters.wait_for_volume_resource_status,
- self.group_snapshots_client,
- group_snapshot['id'], 'available')
- self.addCleanup(
- self.admin_group_snapshots_client.reset_group_snapshot_status,
- group_snapshot['id'], 'available')
- for status in ['creating', 'available', 'error']:
- self.admin_group_snapshots_client.reset_group_snapshot_status(
- group_snapshot['id'], status)
- waiters.wait_for_volume_resource_status(
- self.group_snapshots_client, group_snapshot['id'], status)
+ self.groups_client, grp2['id'], 'available')
-class GroupsV320Test(BaseGroupsTest):
+class GroupsV320Test(base.BaseVolumeAdminTest):
_api_version = 3
min_microversion = '3.20'
max_microversion = 'latest'
@@ -380,7 +198,8 @@
group_type = self.create_group_type()
# Create group
- group = self._create_group(group_type, volume_type)
+ group = self.create_group(group_type=group_type['id'],
+ volume_types=[volume_type['id']])
# Reset group status
self.addCleanup(waiters.wait_for_volume_resource_status,
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 9ff7160..3b21b28 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -35,6 +35,9 @@
def skip_checks(cls):
super(SnapshotManageAdminTest, cls).skip_checks()
+ if not CONF.volume_feature_enabled.snapshot:
+ raise cls.skipException("Cinder volume snapshots are disabled")
+
if not CONF.volume_feature_enabled.manage_snapshot:
raise cls.skipException("Manage snapshot tests are disabled")
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 63ef85b..ea3bb5a 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -242,6 +242,27 @@
self.servers_client.delete_server, body['id'])
return body
+ def create_group(self, **kwargs):
+ if 'name' not in kwargs:
+ kwargs['name'] = data_utils.rand_name(
+ self.__class__.__name__ + '-Group')
+
+ group = self.groups_client.create_group(**kwargs)['group']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.delete_group, group['id'])
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, group['id'], 'available')
+ return group
+
+ def delete_group(self, group_id, delete_volumes=True):
+ self.groups_client.delete_group(group_id, delete_volumes)
+ if delete_volumes:
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == group_id:
+ self.volumes_client.wait_for_resource_deletion(vol['id'])
+ self.groups_client.wait_for_resource_deletion(group_id)
+
class BaseVolumeAdminTest(BaseVolumeTest):
"""Base test case class for all Volume Admin API tests."""
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index de28a30..b73bdf2 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -18,6 +18,7 @@
import testtools
from tempest.api.volume import base
+from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -104,6 +105,7 @@
@decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
@testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
"Attached volume extend is disabled.")
+ @utils.services('compute')
def test_extend_attached_volume(self):
"""This is a happy path test which does the following:
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index c2f8627..92eae02 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -15,18 +15,18 @@
# under the License.
"""
-Utility for creating **accounts.yaml** file for concurrent test runs.
+Utility for creating ``accounts.yaml`` file for concurrent test runs.
Creates one primary user, one alt user, one swift admin, one stack owner
and one admin (optionally) for each concurrent thread. The utility creates
-user for each tenant. The **accounts.yaml** file will be valid and contain
+user for each tenant. The ``accounts.yaml`` file will be valid and contain
credentials for created users, so each user will be in separate tenant and
have the username, tenant_name, password and roles.
-**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``.
+**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``
Positional Arguments
--------------------
-**accounts_file.yaml** (Required) Provide an output accounts yaml file. Utility
+``accounts_file.yaml`` (Required) Provide an output accounts yaml file. Utility
creates a .yaml file in the directory where the command is ran. The appropriate
name for the file is *accounts.yaml* and it should be placed in *tempest/etc*
directory.
@@ -40,62 +40,62 @@
You're probably familiar with these, but just to remind:
-======== ======================== ====================
-Param CLI Environment Variable
-======== ======================== ====================
-Username --os-username OS_USERNAME
-Password --os-password OS_PASSWORD
-Project --os-project-name OS_PROJECT_NAME
-Tenant --os-tenant-name (depr.) OS_TENANT_NAME
-Domain --os-domain-name OS_DOMAIN_NAME
-======== ======================== ====================
+======== ============================ ====================
+Param CLI Environment Variable
+======== ============================ ====================
+Username ``--os-username`` OS_USERNAME
+Password ``--os-password`` OS_PASSWORD
+Project ``--os-project-name`` OS_PROJECT_NAME
+Tenant ``--os-tenant-name`` (depr.) OS_TENANT_NAME
+Domain ``--os-domain-name`` OS_DOMAIN_NAME
+======== ============================ ====================
Optional Arguments
------------------
-**-h**, **--help** (Optional) Shows help message with the description of
-utility and its arguments, and exits.
+* ``-h, --help`` (Optional) Shows help message with the description of
+ utility and its arguments, and exits.
-**-c /etc/tempest.conf**, **--config-file /etc/tempest.conf** (Optional) Path
-to tempest config file. If not specified, it searches for tempest.conf in these
-locations:
+* ``-c, --config-file /etc/tempest.conf`` (Optional) Path
+ to tempest config file. If not specified, it searches for tempest.conf in
+ these locations:
-- ./etc/
-- /etc/tempest
-- ~/.tempest/
-- ~/
-- /etc/
+ - ./etc/
+ - /etc/tempest
+ - ~/.tempest/
+ - ~/
+ - /etc/
-**--os-username <auth-user-name>** (Optional) Name used for authentication with
-the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User should
-have permissions to create new user accounts and tenants.
+* ``--os-username <auth-user-name>`` (Optional) Name used for authentication
+ with the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User
+ should have permissions to create new user accounts and tenants.
-**--os-password <auth-password>** (Optional) Password used for authentication
-with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
+* ``--os-password <auth-password>`` (Optional) Password used for authentication
+ with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
-**--os-project-name <auth-project-name>** (Optional) Project to request
-authorization on. Defaults to env[OS_PROJECT_NAME].
+* ``--os-project-name <auth-project-name>`` (Optional) Project to request
+ authorization on. Defaults to env[OS_PROJECT_NAME].
-**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
-request authorization on. Defaults to env[OS_TENANT_NAME].
+* ``--os-tenant-name <auth-tenant-name>`` (Optional, deprecated) Tenant to
+ request authorization on. Defaults to env[OS_TENANT_NAME].
-**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
-belong to. Defaults to env[OS_DOMAIN_NAME].
+* ``--os-domain-name <auth-domain-name>`` (Optional) Domain the user and
+ project belong to. Defaults to env[OS_DOMAIN_NAME].
-**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
-will have the prefix with the given TAG in its name. Using tag is recommended
-for the further using, cleaning resources.
+* ``--tag TAG`` (Optional) Resources tag. Each created resource (user, project)
+ will have the prefix with the given TAG in its name. Using tag is recommended
+ for the further using, cleaning resources.
-**-r CONCURRENCY**, **--concurrency CONCURRENCY** (Optional) Concurrency count
-(default: 1). The number of accounts required can be estimated as
-CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
-a different tenant. This is required to provide isolation between test for
-running in parallel.
+* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count
+ (default: 1). The number of accounts required can be estimated as
+ CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
+ a different tenant. This is required to provide isolation between test for
+ running in parallel.
-**--with-admin** (Optional) Creates admin for each concurrent group
-(default: False).
+* ``--with-admin`` (Optional) Creates admin for each concurrent group
+ (default: False).
-**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
-using the specified version of the identity API. (default: '3').
+* ``-i, --identity-version VERSION`` (Optional) Provisions accounts
+ using the specified version of the identity API. (default: '3').
To see help on specific argument, please do: ``tempest account-generator
[OPTIONS] <accounts_file.yaml> -h``.
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index d0aa7dc..29abd49 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -28,45 +28,48 @@
Example Run
-----------
-**WARNING: If step 1 is skipped in the example below, the cleanup procedure
-may delete resources that existed in the cloud before the test run. This
-may cause an unwanted destruction of cloud resources, so use caution with
-this command.**
+.. warning::
-``$ tempest cleanup --init-saved-state``
+ If step 1 is skipped in the example below, the cleanup procedure
+ may delete resources that existed in the cloud before the test run. This
+ may cause an unwanted destruction of cloud resources, so use caution with
+ this command.
-``$ # Actual running of Tempest tests``
+ Examples::
-``$ tempest cleanup``
+ $ tempest cleanup --init-saved-state
+ $ # Actual running of Tempest tests
+ $ tempest cleanup
Runtime Arguments
-----------------
-**--init-saved-state**: Initializes the saved state of the OpenStack deployment
-and will output a ``saved_state.json`` file containing resources from your
-deployment that will be preserved from the cleanup command. This should be
-done prior to running Tempest tests.
+* ``--init-saved-state``: Initializes the saved state of the OpenStack
+ deployment and will output a ``saved_state.json`` file containing resources
+ from your deployment that will be preserved from the cleanup command. This
+ should be done prior to running Tempest tests.
-**--delete-tempest-conf-objects**: If option is present, then the command will
-delete the admin project in addition to the resources associated with them on
-clean up. If option is not present, the command will delete the resources
-associated with the Tempest and alternate Tempest users and projects but will
-not delete the projects themselves.
+* ``--delete-tempest-conf-objects``: If option is present, then the command
+ will delete the admin project in addition to the resources associated with
+ them on clean up. If option is not present, the command will delete the
+ resources associated with the Tempest and alternate Tempest users and
+ projects but will not delete the projects themselves.
-**--dry-run**: Creates a report (``./dry_run.json``) of the projects that will
-be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the global
-objects that will be removed (domains, flavors, images, roles, projects,
-and users). Once the cleanup command is executed (e.g. run without
-parameters), running it again with **--dry-run** should yield an empty report.
+* ``--dry-run``: Creates a report (``./dry_run.json``) of the projects that
+ will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the
+ global objects that will be removed (domains, flavors, images, roles,
+ projects, and users). Once the cleanup command is executed (e.g. run without
+ parameters), running it again with ``--dry-run`` should yield an empty
+ report.
-**--help**: Print the help text for the command and parameters.
+* ``--help``: Print the help text for the command and parameters.
.. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the
projects that ``tempest cleanup`` will loop through to delete child
objects, but the command will, by default, not delete the projects
themselves. This may differ from the ``projects`` list as you can clean
the Tempest and alternate Tempest users and projects but they will not be
- deleted unless the **--delete-tempest-conf-objects** flag is used to
+ deleted unless the ``--delete-tempest-conf-objects`` flag is used to
force their deletion.
"""
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index f07f197..0d847bd 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -19,11 +19,11 @@
==============
Tempest run has several options:
- * **--regex/-r**: This is a selection regex like what testr uses. It will run
- any tests that match on re.match() with the regex
- * **--smoke/-s**: Run all the tests tagged as smoke
+* ``--regex, -r``: This is a selection regex like what testr uses. It will run
+ any tests that match on re.match() with the regex
+* ``--smoke, -s``: Run all the tests tagged as smoke
-There are also the **--blacklist-file** and **--whitelist-file** options that
+There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
let you pass a filepath to tempest run with the file format being a line
separated regex, with '#' used to signify the start of a comment on a line.
For example::
@@ -44,21 +44,21 @@
When combined with a whitelist file all the regexes from the file and the CLI
regexes will be ORed.
-You can also use the **--list-tests** option in conjunction with selection
+You can also use the ``--list-tests`` option in conjunction with selection
arguments to list which tests will be run.
-You can also use the **--load-list** option that lets you pass a filepath to
+You can also use the ``--load-list`` option that lets you pass a filepath to
tempest run with the file format being in a non-regex format, similar to the
-tests generated by the **--list-tests** option. You can specify target tests
+tests generated by the ``--list-tests`` option. You can specify target tests
by removing unnecessary tests from a list file which is generated from
-**--list-tests** option.
+``--list-tests`` option.
Test Execution
==============
There are several options to control how the tests are executed. By default
tempest will run in parallel with a worker for each CPU present on the machine.
-If you want to adjust the number of workers use the **--concurrency** option
-and if you want to run tests serially use **--serial/-t**
+If you want to adjust the number of workers use the ``--concurrency`` option
+and if you want to run tests serially use ``--serial/-t``
Running with Workspaces
-----------------------
@@ -82,7 +82,7 @@
===========
By default tempest run's output to STDOUT will be generated using the
subunit-trace output filter. But, if you would prefer a subunit v2 stream be
-output to STDOUT use the **--subunit** flag
+output to STDOUT use the ``--subunit`` flag
Combining Runs
==============
@@ -90,7 +90,7 @@
There are certain situations in which you want to split a single run of tempest
across 2 executions of tempest run. (for example to run part of the tests
serially and others in parallel) To accomplish this but still treat the results
-as a single run you can leverage the **--combine** option which will append
+as a single run you can leverage the ``--combine`` option which will append
the current run's results with the previous runs.
"""
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index f9ebe20..f0ade7e 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -21,17 +21,14 @@
Runtime Arguments
-----------------
-**--subunit, -s**: (Optional) The path to the subunit file being parsed,
-defaults to stdin
-
-**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
-stored in
-
-**--output-file, -o**: (Optional) The path where the JSON output will be
-written to. This contains more information than is present in stdout.
-
-**--ports, -p**: (Optional) The path to a JSON file describing the ports being
-used by different services
+* ``--subunit, -s``: (Optional) The path to the subunit file being parsed,
+ defaults to stdin
+* ``--non-subunit-name, -n``: (Optional) The file_name that the logs are being
+ stored in
+* ``--output-file, -o``: (Optional) The path where the JSON output will be
+ written to. This contains more information than is present in stdout.
+* ``--ports, -p``: (Optional) The path to a JSON file describing the ports
+ being used by different services
Usage
-----
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 8166b4f..929a584 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -26,28 +26,28 @@
register
--------
-Registers a new tempest workspace via a given --name and --path
+Registers a new tempest workspace via a given ``--name`` and ``--path``
rename
------
-Renames a tempest workspace from --old-name to --new-name
+Renames a tempest workspace from ``--old-name`` to ``--new-name``
move
----
-Changes the path of a given tempest workspace --name to --path
+Changes the path of a given tempest workspace ``--name`` to ``--path``
remove
------
-Deletes the entry for a given tempest workspace --name
+Deletes the entry for a given tempest workspace ``--name``
---rmdir Deletes the given tempest workspace directory
+``--rmdir`` Deletes the given tempest workspace directory
General Options
===============
- **--workspace_path**: Allows the user to specify a different location for the
- workspace.yaml file containing the workspace definitions
- instead of ~/.tempest/workspace.yaml
+* ``--workspace_path``: Allows the user to specify a different location for the
+ workspace.yaml file containing the workspace definitions instead of
+ ``~/.tempest/workspace.yaml``
"""
import os
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 86fe3f5..638ad9b 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -229,7 +229,7 @@
clients.servers_client, server['id'], wait_until)
# Multiple validatable servers are not supported for now. Their
- # creation will fail with the condition above (l.58).
+ # creation will fail with the condition above.
if CONF.validation.run_validation and validatable:
if CONF.validation.connect_method == 'floating':
_setup_validation_fip()
@@ -289,13 +289,21 @@
def create_websocket(url):
url = urlparse.urlparse(url)
- if url.scheme == 'https':
- client_socket = ssl.wrap_socket(socket.socket(socket.AF_INET,
- socket.SOCK_STREAM))
+ for res in socket.getaddrinfo(url.hostname, url.port,
+ socket.AF_UNSPEC, socket.SOCK_STREAM):
+ af, socktype, proto, _, sa = res
+ client_socket = socket.socket(af, socktype, proto)
+ if url.scheme == 'https':
+ client_socket = ssl.wrap_socket(client_socket)
+ client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ client_socket.connect(sa)
+ except socket.error:
+ client_socket.close()
+ continue
+ break
else:
- client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- client_socket.connect((url.hostname, url.port))
+ raise socket.error('WebSocket creation failed')
# Turn the Socket into a WebSocket to do the communication
return _WebSocket(client_socket, url)
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 5a86caa..aa81864 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -78,7 +78,7 @@
decorators.attr(type=list(args))(f)
@functools.wraps(f)
- def wrapper(self, *func_args, **func_kwargs):
+ def wrapper(*func_args, **func_kwargs):
service_list = get_service_list()
for service in args:
@@ -86,7 +86,7 @@
msg = 'Skipped because the %s service is not available' % (
service)
raise testtools.TestCase.skipException(msg)
- return f(self, *func_args, **func_kwargs)
+ return f(*func_args, **func_kwargs)
return wrapper
return decorator
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 10afee0..08e2a12 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -179,15 +179,13 @@
raise lib_exc.TimeoutException(message)
-def wait_for_volume_resource_status(client, resource_id, statuses):
- """Waits for a volume resource to reach any of the specified statuses.
+def wait_for_volume_resource_status(client, resource_id, status):
+ """Waits for a volume resource to reach a given status.
This function is a common function for volume, snapshot and backup
resources. The function extracts the name of the desired resource from
the client class name of the resource.
"""
- if not isinstance(statuses, list):
- statuses = [statuses]
resource_name = re.findall(
r'(volume|group-snapshot|snapshot|backup|group)',
client.resource_type)[-1].replace('-', '_')
@@ -195,11 +193,11 @@
resource_status = show_resource(resource_id)[resource_name]['status']
start = int(time.time())
- while resource_status not in statuses:
+ while resource_status != status:
time.sleep(client.build_interval)
resource_status = show_resource(resource_id)[
'{}'.format(resource_name)]['status']
- if resource_status == 'error' and resource_status not in statuses:
+ if resource_status == 'error' and resource_status != status:
raise exceptions.VolumeResourceBuildErrorException(
resource_name=resource_name, resource_id=resource_id)
if resource_name == 'volume' and resource_status == 'error_restoring':
@@ -208,11 +206,11 @@
if int(time.time()) - start >= client.build_timeout:
message = ('%s %s failed to reach %s status (current %s) '
'within the required time (%s s).' %
- (resource_name, resource_id, statuses, resource_status,
+ (resource_name, resource_id, status, resource_status,
client.build_timeout))
raise lib_exc.TimeoutException(message)
LOG.info('%s %s reached %s after waiting for %f seconds',
- resource_name, resource_id, statuses, time.time() - start)
+ resource_name, resource_id, status, time.time() - start)
def wait_for_volume_retype(client, volume_id, new_volume_type):
diff --git a/tempest/config.py b/tempest/config.py
index b14d4fd..fc95df8 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -229,17 +229,6 @@
"Empty list indicates all extensions are disabled. "
"To get the list of extensions run: "
"'openstack extension list --identity'"),
- # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed
- # in Newton and Ocata. This option can be removed after Mitaka is end of
- # life.
- cfg.BoolOpt('forbid_global_implied_dsr',
- default=False,
- help='Does the environment forbid global roles implying '
- 'domain specific ones?',
- deprecated_for_removal=True,
- 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? '
@@ -312,9 +301,9 @@
default=0,
help='Time in seconds before a shelved instance is eligible '
'for removing from a host. -1 never offload, 0 offload '
- 'when shelved. This time should be the same as the time '
- 'of nova.conf, and some tests will run for as long as the '
- 'time.'),
+ 'when shelved. This configuration value should be same as '
+ '[nova.DEFAULT]->shelved_offload_time in nova.conf, and '
+ 'some tests will run for as long as the time.'),
cfg.IntOpt('min_compute_nodes',
default=1,
help=('The minimum number of compute nodes expected. This will '
@@ -484,6 +473,10 @@
default=False,
help='Does the test environment support in-place swapping of '
'volumes attached to a server instance?'),
+ cfg.BoolOpt('volume_backed_live_migration',
+ default=False,
+ help='Does the test environment support volume-backed live '
+ 'migration?'),
]
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index cd4092b..1676a28 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -31,7 +31,7 @@
except Exception as e:
caller = test_utils.find_test_caller() or "not found"
if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
- message = ('Initializing SSH connection to %(ip)s failed. '
+ message = ('Executing command on %(ip)s failed. '
'Error: %(error)s' % {'ip': self.ip_address,
'error': e})
message = '(%s) %s' % (caller, message)
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index ef1003b..e99dd24 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -15,7 +15,6 @@
import functools
import uuid
-import debtcollector.removals
from oslo_log import log as logging
import six
import testtools
@@ -56,9 +55,9 @@
"""
def decorator(f):
@functools.wraps(f)
- def wrapper(self, *func_args, **func_kwargs):
+ def wrapper(*func_args, **func_kwargs):
try:
- return f(self, *func_args, **func_kwargs)
+ return f(*func_args, **func_kwargs)
except Exception as exc:
exc_status_code = getattr(exc, 'status_code', None)
if status_code is None or status_code == exc_status_code:
@@ -87,25 +86,6 @@
return decorator
-@debtcollector.removals.remove(removal_version='Queen')
-class skip_unless_attr(object):
- """Decorator to skip tests if a specified attr does not exists or False"""
- def __init__(self, attr, msg=None):
- self.attr = attr
- self.message = msg or ("Test case attribute %s not found "
- "or False") % attr
-
- def __call__(self, func):
- @functools.wraps(func)
- def _skipper(*args, **kw):
- """Wrapped skipper function."""
- testobj = args[0]
- if not getattr(testobj, self.attr, False):
- raise testtools.TestCase.skipException(self.message)
- func(*args, **kw)
- return _skipper
-
-
def attr(**kwargs):
"""A decorator which applies the testtools attr decorator
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 9b2e87e..13af890 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -96,7 +96,7 @@
class Conflict(ClientRestClientException):
status_code = 409
- message = "An object with that identifier already exists"
+ message = "Conflict with state of target resource"
class Gone(ClientRestClientException):
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
index 36cf8e3..9542e8f 100644
--- a/tempest/lib/services/network/metering_label_rules_client.py
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -16,6 +16,12 @@
class MeteringLabelRulesClient(base.BaseNetworkClient):
def create_metering_label_rule(self, **kwargs):
+ """Create metering label rule.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#create-metering-label-rule
+ """
uri = '/metering/metering-label-rules'
post_data = {'metering_label_rule': kwargs}
return self.create_resource(uri, post_data)
@@ -29,5 +35,11 @@
return self.delete_resource(uri)
def list_metering_label_rules(self, **filters):
+ """List metering label rules.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#list-metering-label-rules
+ """
uri = '/metering/metering-label-rules'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index 752b253..fdd3d6b 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -18,6 +18,12 @@
class QuotasClient(base.BaseNetworkClient):
def update_quotas(self, tenant_id, **kwargs):
+ """Update quota for a project.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#update-quota-for-a-project
+ """
put_body = {'quota': kwargs}
uri = '/quotas/%s' % tenant_id
return self.update_resource(uri, put_body)
diff --git a/tempest/lib/services/network/service_providers_client.py b/tempest/lib/services/network/service_providers_client.py
index 0ee9bc3..01313a0 100644
--- a/tempest/lib/services/network/service_providers_client.py
+++ b/tempest/lib/services/network/service_providers_client.py
@@ -16,6 +16,11 @@
class ServiceProvidersClient(base.BaseNetworkClient):
def list_service_providers(self, **filters):
- """Lists service providers."""
+ """Lists service providers.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#list-service-providers
+ """
uri = '/service-providers'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index 67f01a6..6b097c1 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -34,7 +34,7 @@
Account Metadata can be created, updated or deleted based on
metadata header or value. For detailed info, please refer to the
official API reference:
- http://developer.openstack.org/api-ref/object-storage/?expanded=create-update-or-delete-account-metadata-detail
+ https://developer.openstack.org/api-ref/object-store/#create-update-or-delete-account-metadata
"""
headers = {}
if create_update_metadata:
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 2da8e24..430e0d4 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -97,7 +97,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/object-storage/?expanded=show-container-details-and-list-objects-detail
+ https://developer.openstack.org/api-ref/object-store/#show-container-details-and-list-objects
"""
url = str(container_name)
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 97bac48..6181472 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -75,3 +75,16 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def update_group_type(self, group_type_id, **kwargs):
+ """Updates a group type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#update-group-type
+ """
+ post_body = json.dumps({'group_type': kwargs})
+ resp, body = self.put('group_types/%s' % group_type_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index ad300c2..efcd139 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -15,6 +15,7 @@
Any scenario test should have a real-life use case. An example would be:
- "As operator I want to start with a blank environment":
+
1. upload a glance image
2. deploy a vm from it
3. ssh to the guest
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 64ea8f6..beb039c 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -208,7 +208,19 @@
# boot instance from EBS image
instance = self.create_server(image_id=image['id'])
- # just ensure that instance booted
+
+ # Verify the server was created from the image
+ created_volume = instance['os-extended-volumes:volumes_attached']
+ self.assertNotEmpty(created_volume, "No volume attachment found.")
+ created_volume_info = self.volumes_client.show_volume(
+ created_volume[0]['id'])['volume']
+ self.assertEqual(instance['id'],
+ created_volume_info['attachments'][0]['server_id'])
+ self.assertEqual(created_volume[0]['id'],
+ created_volume_info['attachments'][0]['volume_id'])
+ self.assertEqual(
+ volume_origin['volume_image_metadata']['image_id'],
+ created_volume_info['volume_image_metadata']['image_id'])
# delete instance
self._delete_server(instance)
diff --git a/tempest/tests/lib/services/network/test_networks_client.py b/tempest/tests/lib/services/network/test_networks_client.py
new file mode 100644
index 0000000..078f4b0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_networks_client.py
@@ -0,0 +1,242 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNetworksClient(base.BaseServiceTest):
+
+ FAKE_NETWORKS = {
+ "networks": [
+ {
+ "admin_state_up": True,
+ "availability_zone_hints": [],
+ "availability_zones": [
+ "nova"
+ ],
+ "created_at": "2016-03-08T20:19:41",
+ "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "mtu": 0,
+ "name": "net1",
+ "port_security_enabled": True,
+ "project_id": "4fd44f30292945e481c7b8a0c8908869",
+ "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+ "router:external": False,
+ "shared": False,
+ "status": "ACTIVE",
+ "subnets": [
+ "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+ ],
+ "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+ "updated_at": "2016-03-08T20:19:41",
+ "vlan_transparent": True,
+ "description": ""
+ },
+ {
+ "admin_state_up": True,
+ "availability_zone_hints": [],
+ "availability_zones": [
+ "nova"
+ ],
+ "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+ "mtu": 0,
+ "name": "net2",
+ "port_security_enabled": True,
+ "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819",
+ "router:external": False,
+ "shared": False,
+ "status": "ACTIVE",
+ "subnets": [
+ "08eae331-0402-425a-923c-34f7cfe39c1b"
+ ],
+ "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "updated_at": "2016-03-08T20:19:41",
+ "vlan_transparent": False,
+ "description": ""
+ }
+ ]
+ }
+
+ FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+
+ FAKE_NETWORK1 = {
+ "name": "net1",
+ "admin_state_up": True,
+ "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+ }
+
+ FAKE_NETWORK2 = {
+ "name": "net2",
+ "admin_state_up": True,
+ "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819"
+ }
+
+ FAKE_NETWORKS_REQ = {
+ "networks": [
+ FAKE_NETWORK1,
+ FAKE_NETWORK2
+ ]
+ }
+
+ FAKE_DHCP_AGENT_NETWORK_ID = "80515c45-651f-4f9a-b82b-2ca8a7301a8d"
+
+ FAKE_DHCP_AGENTS = {
+ "agents": [
+ {
+ "binary": "neutron-dhcp-agent",
+ "description": None,
+ "admin_state_up": True,
+ "heartbeat_timestamp": "2017-06-22 18:29:50",
+ "availability_zone": "nova",
+ "alive": True,
+ "topic": "dhcp_agent",
+ "host": "osboxes",
+ "agent_type": "DHCP agent",
+ "resource_versions": {},
+ "created_at": "2017-06-19 21:39:51",
+ "started_at": "2017-06-19 21:39:51",
+ "id": "b6cfb7a1-6ac4-4980-993c-9d295d37062e",
+ "configurations": {
+ "subnets": 2,
+ "dhcp_lease_duration": 86400,
+ "dhcp_driver": "neutron.agent.linux.dhcp.Dnsmasq",
+ "networks": 1,
+ "log_agent_heartbeats": False,
+ "ports": 3
+ }
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestNetworksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.networks_client = networks_client.NetworksClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_networks(self, bytes_body=False):
+ self.check_service_client_function(
+ self.networks_client.list_networks,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_NETWORKS,
+ bytes_body,
+ 200)
+
+ def _test_create_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.networks_client.create_network,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"network": self.FAKE_NETWORKS["networks"][0]},
+ bytes_body,
+ 201,
+ **self.FAKE_NETWORK1)
+
+ def _test_create_bulk_networks(self, bytes_body=False):
+ self.check_service_client_function(
+ self.networks_client.create_bulk_networks,
+ "tempest.lib.common.rest_client.RestClient.post",
+ self.FAKE_NETWORKS,
+ bytes_body,
+ 201,
+ networks=self.FAKE_NETWORKS_REQ)
+
+ def _test_show_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.networks_client.show_network,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"network": self.FAKE_NETWORKS["networks"][0]},
+ bytes_body,
+ 200,
+ network_id=self.FAKE_NETWORK_ID)
+
+ def _test_update_network(self, bytes_body=False):
+ update_kwargs = {
+ "name": "sample_network_5_updated",
+ "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+ }
+
+ resp_body = {
+ "network": copy.deepcopy(
+ self.FAKE_NETWORKS["networks"][0]
+ )
+ }
+ resp_body["network"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.networks_client.update_network,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ network_id=self.FAKE_NETWORK_ID,
+ **update_kwargs)
+
+ def _test_list_dhcp_agents_on_hosting_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.networks_client.list_dhcp_agents_on_hosting_network,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_DHCP_AGENTS,
+ bytes_body,
+ 200,
+ network_id=self.FAKE_DHCP_AGENT_NETWORK_ID)
+
+ def test_delete_network(self):
+ self.check_service_client_function(
+ self.networks_client.delete_network,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ network_id=self.FAKE_NETWORK_ID)
+
+ def test_list_networks_with_str_body(self):
+ self._test_list_networks()
+
+ def test_list_networks_with_bytes_body(self):
+ self._test_list_networks(bytes_body=True)
+
+ def test_create_network_with_str_body(self):
+ self._test_create_network()
+
+ def test_create_network_with_bytes_body(self):
+ self._test_create_network(bytes_body=True)
+
+ def test_create_bulk_network_with_str_body(self):
+ self._test_create_bulk_networks()
+
+ def test_create_bulk_network_with_bytes_body(self):
+ self._test_create_bulk_networks(bytes_body=True)
+
+ def test_show_network_with_str_body(self):
+ self._test_show_network()
+
+ def test_show_network_with_bytes_body(self):
+ self._test_show_network(bytes_body=True)
+
+ def test_update_network_with_str_body(self):
+ self._test_update_network()
+
+ def test_update_network_with_bytes_body(self):
+ self._test_update_network(bytes_body=True)
+
+ def test_list_dhcp_agents_on_hosting_network_with_str_body(self):
+ self._test_list_dhcp_agents_on_hosting_network()
+
+ def test_list_dhcp_agents_on_hosting_network_with_bytes_body(self):
+ self._test_list_dhcp_agents_on_hosting_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
index 0f456a2..e86594e 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
from tempest.lib.services.volume.v3 import group_types_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -97,6 +99,18 @@
self.FAKE_LIST_GROUP_TYPES,
bytes_body)
+ def _test_update_group_types(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_INFO_GROUP_TYPE)
+ resp_body['group_type'].pop('created_at')
+
+ self.check_service_client_function(
+ self.client.update_group_type,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ resp_body,
+ bytes_body,
+ group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+ name='updated-group-type-name')
+
def test_create_group_type_with_str_body(self):
self._test_create_group_type()
@@ -122,3 +136,9 @@
def test_list_group_types_with_bytes_body(self):
self._test_list_group_types(bytes_body=True)
+
+ def test_update_group_types_with_str_body(self):
+ self._test_update_group_types()
+
+ def test_update_group_types_with_bytes_body(self):
+ self._test_update_group_types(bytes_body=True)
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index bbebcd3..ed0eea3 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -125,35 +125,6 @@
self.assertRaises(ValueError, self._test_helper, _id)
-class TestSkipUnlessAttrDecorator(base.TestCase):
- def _test_skip_unless_attr(self, attr, expected_to_skip=True):
- class TestFoo(test.BaseTestCase):
- expected_attr = not expected_to_skip
-
- @decorators.skip_unless_attr(attr)
- def test_foo(self):
- pass
-
- t = TestFoo('test_foo')
- if expected_to_skip:
- self.assertRaises(testtools.TestCase.skipException,
- t.test_foo)
- else:
- try:
- t.test_foo()
- except Exception:
- raise testtools.TestCase.failureException()
-
- def test_skip_attr_does_not_exist(self):
- self._test_skip_unless_attr('unexpected_attr')
-
- def test_skip_attr_false(self):
- self._test_skip_unless_attr('expected_attr')
-
- def test_no_skip_for_attr_exist_and_true(self):
- self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
-
-
class TestRelatedBugDecorator(base.TestCase):
def test_relatedbug_when_no_exception(self):
f = mock.Mock()
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
deleted file mode 100755
index 1f2b88b..0000000
--- a/tools/find_stack_traces.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2013 IBM Corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import gzip
-import pprint
-import re
-import sys
-
-import six
-import six.moves.urllib.request as urlreq
-
-
-pp = pprint.PrettyPrinter()
-
-NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d"
-
-NOVA_REGEX = r"(?P<timestamp>%s) (?P<pid>\d+ )?(?P<level>(ERROR|TRACE)) " \
- "(?P<module>[\w\.]+) (?P<msg>.*)" % (NOVA_TIMESTAMP)
-
-
-class StackTrace(object):
- timestamp = None
- pid = None
- level = ""
- module = ""
- msg = ""
-
- def __init__(self, timestamp=None, pid=None, level="", module="",
- msg=""):
- self.timestamp = timestamp
- self.pid = pid
- self.level = level
- self.module = module
- self.msg = msg
-
- def append(self, msg):
- self.msg = self.msg + msg
-
- def is_same(self, data):
- return (data['timestamp'] == self.timestamp and
- data['level'] == self.level)
-
- def not_none(self):
- return self.timestamp is not None
-
- def __str__(self):
- buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module)
- for line in self.msg.splitlines():
- buff = buff + line + "\n"
- return buff
-
-
-def hunt_for_stacktrace(url):
- """Return TRACE or ERROR lines out of logs."""
- req = urlreq.Request(url)
- req.add_header('Accept-Encoding', 'gzip')
- page = urlreq.urlopen(req)
- buf = six.StringIO(page.read())
- f = gzip.GzipFile(fileobj=buf)
- content = f.read()
-
- traces = []
- trace = StackTrace()
- for line in content.splitlines():
- m = re.match(NOVA_REGEX, line)
- if m:
- data = m.groupdict()
- if trace.not_none() and trace.is_same(data):
- trace.append(data['msg'] + "\n")
- else:
- trace = StackTrace(
- timestamp=data.get('timestamp'),
- pid=data.get('pid'),
- level=data.get('level'),
- module=data.get('module'),
- msg=data.get('msg'))
-
- else:
- if trace.not_none():
- traces.append(trace)
- trace = StackTrace()
-
- # once more at the end to pick up any stragglers
- if trace.not_none():
- traces.append(trace)
-
- return traces
-
-
-def log_url(url, log):
- return "%s/%s" % (url, log)
-
-
-def collect_logs(url):
- page = urlreq.urlopen(url)
- content = page.read()
- logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
- return logs
-
-
-def usage():
- print("""
-Usage: find_stack_traces.py <logurl>
-
-Hunts for stack traces in a devstack run. Must provide it a base log url
-from a tempest devstack run. Should start with http and end with /logs/.
-
-Returns a report listing stack traces out of the various files where
-they are found.
-""")
- sys.exit(0)
-
-
-def print_stats(items, fname, verbose=False):
- errors = len([x for x in items if x.level == "ERROR"])
- traces = len([x for x in items if x.level == "TRACE"])
- print("%d ERRORS found in %s" % (errors, fname))
- print("%d TRACES found in %s" % (traces, fname))
-
- if verbose:
- for item in items:
- print(item)
- print("\n\n")
-
-
-def main():
- if len(sys.argv) == 2:
- url = sys.argv[1]
- loglist = collect_logs(url)
-
- # probably wrong base url
- if not loglist:
- usage()
-
- for log in loglist:
- logurl = log_url(url, log)
- traces = hunt_for_stacktrace(logurl)
-
- if traces:
- print_stats(traces, log, verbose=True)
-
- else:
- usage()
-
-if __name__ == '__main__':
- main()
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 99df0d1..dd05438 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -28,12 +28,12 @@
try:
# For Python 3.0 and later
- from urllib.error import HTTPError as HTTPError
+ from urllib.error import HTTPError
import urllib.request as urllib
except ImportError:
# Fall back to Python 2's urllib2
import urllib2 as urllib
- from urllib2 import HTTPError as HTTPError
+ from urllib2 import HTTPError
url = 'https://review.openstack.org/projects/'
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
deleted file mode 100755
index 43468e4..0000000
--- a/tools/tox_install.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-# Client constraint file contains this client version pin that is in conflict
-# with installing the client from source. We should remove the version pin in
-# the constraints file before applying it for from-source installation.
-
-CONSTRAINTS_FILE=$1
-shift 1
-
-set -e
-
-# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
-# published to logs.openstack.org for easy debugging.
-localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
-
-if [[ $CONSTRAINTS_FILE != http* ]]; then
- CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
-fi
-# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
-curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile
-
-pip install -c$localfile openstack-requirements
-
-# This is the main purpose of the script: Allow local installation of
-# the current repo. It is listed in constraints file and thus any
-# install will be constrained and we need to unconstrain it.
-edit-constraints $localfile -- $CLIENT_NAME
-
-pip install -c$localfile -U $*
-exit $?
diff --git a/tox.ini b/tox.ini
index 21696eb..e7ea1e2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,9 +8,8 @@
setenv =
VIRTUAL_ENV={envdir}
OS_TEST_PATH=./tempest/test_discover
- BRANCH_NAME=master
- CLIENT_NAME=tempest
deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-r{toxinidir}/requirements.txt
[testenv]
@@ -18,14 +17,12 @@
VIRTUAL_ENV={envdir}
OS_LOG_CAPTURE=1
PYTHONWARNINGS=default::DeprecationWarning
- BRANCH_NAME=master
- CLIENT_NAME=tempest
passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
usedevelop = True
-install_command =
- {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
+install_command = pip install {opts} {packages}
whitelist_externals = *
deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
@@ -36,7 +33,17 @@
commands = oslo-config-generator --config-file tempest/cmd/config-generator.tempest.conf
[testenv:cover]
-commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
+setenv =
+ {[testenv]setenv}
+ PYTHON=coverage run --source tempest --parallel-mode
+commands =
+ coverage erase
+ find . -type f -name "*.pyc" -delete
+ stestr --test-path ./tempest/tests run {posargs}
+ coverage combine
+ coverage html -d cover
+ coverage xml -o cover/coverage.xml
+ coverage report
[testenv:all]
envdir = .tox/tempest
@@ -50,17 +57,6 @@
find . -type f -name "*.pyc" -delete
tempest run --regex {posargs}
-[testenv:ostestr]
-sitepackages = {[tempestenv]sitepackages}
-# 'all' includes slow tests
-setenv =
- {[tempestenv]setenv}
- OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
-deps = {[tempestenv]deps}
-commands =
- find . -type f -name "*.pyc" -delete
- ostestr {posargs}
-
[testenv:all-plugin]
sitepackages = True
# 'all' includes slow tests