Merge "Fix test_list_flavors for compute microversion 2.55"
diff --git a/.zuul.yaml b/.zuul.yaml
index 87e277c..9c53ba9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -180,6 +180,7 @@
USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true
ENABLE_VOLUME_MULTIATTACH: true
+ GLANCE_USE_IMPORT_WORKFLOW: True
devstack_services:
s-account: false
s-container: false
@@ -270,6 +271,7 @@
USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true
ENABLE_VOLUME_MULTIATTACH: true
+ GLANCE_USE_IMPORT_WORKFLOW: True
- job:
name: tempest-integrated-object-storage
@@ -676,7 +678,6 @@
voting: false
irrelevant-files: *tempest-irrelevant-files
- devstack-plugin-ceph-tempest-py3:
- voting: false
irrelevant-files: *tempest-irrelevant-files
- neutron-grenade-multinode:
irrelevant-files: *tempest-irrelevant-files
@@ -718,19 +719,19 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-ipv6-only:
irrelevant-files: *tempest-irrelevant-files-2
+ - devstack-plugin-ceph-tempest-py3:
+ irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- tempest-cinder-v2-api:
irrelevant-files: *tempest-irrelevant-files
- tempest-all:
irrelevant-files: *tempest-irrelevant-files
- - legacy-tempest-dsvm-neutron-dvr-multinode-full:
- irrelevant-files: *tempest-irrelevant-files
- neutron-tempest-dvr-ha-multinode-full:
irrelevant-files: *tempest-irrelevant-files
- nova-tempest-v2-api:
irrelevant-files: *tempest-irrelevant-files
- - legacy-tempest-dsvm-lvm-multibackend:
+ - cinder-tempest-lvm-multibackend:
irrelevant-files: *tempest-irrelevant-files
- tempest-pg-full:
irrelevant-files: *tempest-irrelevant-files
diff --git a/doc/source/conf.py b/doc/source/conf.py
index dd7c544..59a2f64 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -24,6 +24,7 @@
import os
import subprocess
+import sys
# Build the plugin registry
def build_plugin_registry(app):
@@ -31,16 +32,20 @@
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir)
+def autodoc_skip_member_handler(app, what, name, obj, skip, options):
+ return skip or (what == "class" and not name.startswith("test"))
+
def setup(app):
+ app.connect('autodoc-skip-member', autodoc_skip_member_handler)
if os.getenv('GENERATE_TEMPEST_PLUGIN_LIST', 'true').lower() == 'true':
app.connect('builder-inited', build_plugin_registry)
-
-
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('../../tempest'))
+sys.path.insert(0, os.path.abspath('../../tempest/api'))
# -- General configuration -----------------------------------------------------
@@ -112,7 +117,7 @@
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['tempest.']
@@ -205,5 +210,9 @@
u'OpenStack Foundation', 'manual'),
]
-# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False
+
+latex_elements = {
+ 'maxlistdepth': 20,
+ 'printindex': '\\footnotesize\\raggedright\\printindex'
+}
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 36828e0..c43e420 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -193,10 +193,6 @@
There are also options in the ``scenario`` section for images:
#. ``img_file``
-#. ``img_dir``
-#. ``aki_img_file``
-#. ``ari_img_file``
-#. ``ami_img_file``
#. ``img_container_format``
#. ``img_disk_format``
@@ -205,13 +201,9 @@
Tempest where an image file is located and describe its metadata for when it is
uploaded.
-The behavior of these options is a bit convoluted (which will likely be fixed in
-future versions). You first need to specify ``img_dir``, which is the directory
-in which Tempest will look for the image files. First, it will check if the
-filename set for ``img_file`` could be found in ``img_dir``. If it is found then
-the ``img_container_format`` and ``img_disk_format`` options are used to upload
-that image to glance. However, if it is not found, Tempest will look for the
-three uec image file name options as a fallback. If neither is found, the tests
+You first need to specify full path of the image using ``img_file`` option.
+If it is found then the ``img_container_format`` and ``img_disk_format``
+options are used to upload that image to glance. If it's not found, the tests
requiring an image to upload will fail.
It is worth pointing out that using `cirros`_ is a very good choice for running
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f878888..66e68ea 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -56,6 +56,13 @@
supported_version
+Description of Tests
+--------------------
+.. toctree::
+ :maxdepth: 2
+
+ tests/modules
+
For Contributors
================
@@ -80,6 +87,7 @@
microversion_testing
test_removal
write_tests
+ requirement_upper_constraint_for_tempest
Plugins
-------
diff --git a/doc/source/plugins/plugin.rst b/doc/source/plugins/plugin.rst
index a9e2059..ab1b0b1 100644
--- a/doc/source/plugins/plugin.rst
+++ b/doc/source/plugins/plugin.rst
@@ -43,7 +43,7 @@
In order to create the basic structure with base classes and test directories
you can use the tempest-plugin-cookiecutter project::
- > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter
+ > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter.git
Cloning into 'tempest-plugin-cookiecutter'...
remote: Counting objects: 17, done.
diff --git a/doc/source/requirement_upper_constraint_for_tempest.rst b/doc/source/requirement_upper_constraint_for_tempest.rst
new file mode 100644
index 0000000..2eebdda
--- /dev/null
+++ b/doc/source/requirement_upper_constraint_for_tempest.rst
@@ -0,0 +1,56 @@
+Requirements Upper Constraint for Tempest
+=========================================
+
+Tempest is branchless and supported stable branches use Tempest
+master and all EM stable branches use old compatible Tempest version
+for their testing. This means the OpenStack installed upper-constraints
+might not be compatible with Tempest used for stable branch testing.
+For example, if Tempest master is used for testing the stable/stein
+then stable/stein constraint might not be compatible with Tempest master so
+we need to use master upper-constraints there. That is why we use virtual
+env for Tempest installation and running tests so that we can control Tempest
+required constraint from system wide installed constraints.
+
+Devstack takes care of using the master upper-constraints when Tempest master
+is used. But when old Tempest is used then devstack alone cannot handle the
+compatible constraints because Tempest in-tree tox.ini also set the
+upper-constraints which are master constraints so if devstack set the different
+constraints than what we have in tox.ini we end up re-creation of venv which
+flush all previously installed tempest plugins in that venv. More details are
+on `this ML thread <http://lists.openstack.org/pipermail/openstack-discuss/2020-April/014388.html>`_
+
+To solve that problem we have two ways:
+
+#. Set UPPER_CONSTRAINTS_FILE to compatible constraint path
+ This option is not easy as it requires to set this env var everywhere
+ Tempest tox env is used like in devstack, grenade, projects side, zuulv3 roles etc.
+
+#. Pin upper-constraints in tox.ini
+ If we can pin the upper-constraints in tox.ini on every release with the branch
+ constraint at the time of release then we can solve it in an easy way because tox
+ can use the compatible constraint at the time of venv creation itself. But this can
+ again mismatch with the devstack set constraint so we need to follow the below process
+ to make it work.
+
+How to pin upper-constraints in tox.ini
+---------------------------------------
+
+This has to be done exactly before we cut the Tempest new major version bump
+release for the cycle.
+
+Step1: Add the pin constraint proposal in `QA office hour <https://wiki.openstack.org/wiki/Meetings/QATeamMeeting#Agenda_for_next_Office_hours>`_.
+ Pin constraint proposal includes:
+
+ - pin constraint patch. `Example patch 720578 <https://review.opendev.org/#/c/720578/>`_
+ - revert of pin constraint patch. `Example patch 721724 <https://review.opendev.org/#/c/721724/>`_
+
+Step2: Approve pin constraint and its revert patch together.
+ During office hour we need to check that there are no open patches for
+ Tempest release and accordingly we fast approve the 'pin constraint' and its
+ revert patch during office hour itself. Remember 'pin constraint patch' has to be
+ the last commit to include in Tempest release.
+
+Step3: Use 'pin constraint patch' hash for the Tempest new release.
+ By using the 'pin constraint patch' hash we make sure tox.ini in Tempest
+ released tag has the compatible stable constraint not the master one.
+ For Example `Tempest 24.0.0 <https://opendev.org/openstack/tempest/src/tag/24.0.0/tox.ini#L14>`_
diff --git a/doc/source/sampleconf.rst b/doc/source/sampleconf.rst
index c290140..45164a3 100644
--- a/doc/source/sampleconf.rst
+++ b/doc/source/sampleconf.rst
@@ -10,4 +10,6 @@
The sample configuration can also be viewed in `file form <_static/tempest.conf.sample>`_.
-.. literalinclude:: _static/tempest.conf.sample
+.. only:: html
+
+ .. literalinclude:: _static/tempest.conf.sample
diff --git a/doc/source/tests/modules.rst b/doc/source/tests/modules.rst
new file mode 100644
index 0000000..026a7a5
--- /dev/null
+++ b/doc/source/tests/modules.rst
@@ -0,0 +1,21 @@
+Description of Tests
+====================
+
+OpenStack Services Integration Tests
+------------------------------------
+.. toctree::
+ :maxdepth: 2
+
+ scenario/modules
+
+OpenStack Services API Tests
+----------------------------
+.. toctree::
+ :maxdepth: 2
+
+ compute/modules
+ identity/modules
+ image/modules
+ network/modules
+ object_storage/modules
+ volume/modules
diff --git a/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml
new file mode 100644
index 0000000..d2a644e
--- /dev/null
+++ b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml
@@ -0,0 +1,10 @@
+---
+fixes:
+ - |
+ Fixed bug #1890060. tempest subunit_describe_calls --verbose not working with Cliff CLI.
+ The subunit_describe_calls --verbose argument was a boolean and worked in the non Cliff CLI
+ which is now deprecated, but does not work with cliff since --verbase is a standard cliff
+ argument which is an int. Since the tool is in lib directory we cannot change the interface,
+ so we add a new argument -a --all-stdout that will allow cliff CLI to support the
+ feature in subunnit_describe_calls to print request and response headers and bodies
+ to stdout.
\ No newline at end of file
diff --git a/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml b/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml
new file mode 100644
index 0000000..bbb1901
--- /dev/null
+++ b/releasenotes/notes/Fix-KeyError-bug-in-v3-volumes_client-ff5d9b894f2257c8.yaml
@@ -0,0 +1,10 @@
+---
+fixes:
+ - |
+ is_resource_deleted method of v3 volumes_client might have returned
+ a KeyError exception due to an incorrect accessing of a volume id
+ in the case the volume was in error_deleting state.
+ incorrect code - volume['id']
+ correct code - volume['volume']['id']
+ More details about the issue can be found at
+ https://bugs.launchpad.net/tempest/+bug/1887980
diff --git a/releasenotes/notes/Remove-deprecated-image-scenario-options-b573c60e873ab451.yaml b/releasenotes/notes/Remove-deprecated-image-scenario-options-b573c60e873ab451.yaml
new file mode 100644
index 0000000..018d01d
--- /dev/null
+++ b/releasenotes/notes/Remove-deprecated-image-scenario-options-b573c60e873ab451.yaml
@@ -0,0 +1,15 @@
+---
+upgrade:
+ - |
+ The following deprecated image scenario options are removed after a ~4
+ year deprecation period.
+
+ * ``ami_img_file``
+ * ``ari_img_file``
+ * ``aki_img_file``
+
+ Starting Tempest 25.0.0 release, CONF.scenario.img_file need a full path
+ for the image. CONF.scenario.img_dir was deprecated and will be removed
+ in the next release. Till Tempest 25.0.0, old behavior is maintained and
+ keep working but starting Tempest 26.0.0, you need to specify the full path
+ in CONF.scenario.img_file config option.
diff --git a/releasenotes/notes/Set-default-of-operator_role-to-member-f9c3abd2ebde23b7.yaml b/releasenotes/notes/Set-default-of-operator_role-to-member-f9c3abd2ebde23b7.yaml
new file mode 100644
index 0000000..980f4ca
--- /dev/null
+++ b/releasenotes/notes/Set-default-of-operator_role-to-member-f9c3abd2ebde23b7.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ ``Member`` role has been deprecated and replaced by ``member``. Therefore
+ the default value of config option ``[object-storage].operator_role`` is
+ changed to ``member``. (Fixes bug #1330132)
diff --git a/releasenotes/notes/account-generator-accepts-positive-numbers-only-33a366a297494ef7.yaml b/releasenotes/notes/account-generator-accepts-positive-numbers-only-33a366a297494ef7.yaml
new file mode 100644
index 0000000..dfee1db
--- /dev/null
+++ b/releasenotes/notes/account-generator-accepts-positive-numbers-only-33a366a297494ef7.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Concurrency parameter for account-generator command was accepting
+ negative values and zero. The concurrency parameter now accepts only
+ positive numbers. When a negative value or zero is passed to the
+ program then the program ends and help is displayed.
diff --git a/releasenotes/notes/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml b/releasenotes/notes/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml
new file mode 100644
index 0000000..26fe01a
--- /dev/null
+++ b/releasenotes/notes/add-can-migrate-between-any-hosts-config-option-x8ah4f9737a28e9b.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - Add a new config option can_migrate_between_any_hosts in the
+ compute-feature-enabled section, which can be set to False for environment
+ with non homogeneous compute nodes, so that it can select a destination
+ host for migrating automatically, otherwise the testcase may fail
+ unexpectedly. e.g., if source host is with CPU "E5-2699 v4" and the
+ selected target host is with CPU "E5-2670 v3", the live-migration will
+ fail because of the downgrade issue.
diff --git a/releasenotes/notes/image_import_testing_support-22ba4bcb9f2fb848.yaml b/releasenotes/notes/image_import_testing_support-22ba4bcb9f2fb848.yaml
new file mode 100644
index 0000000..b0180cc
--- /dev/null
+++ b/releasenotes/notes/image_import_testing_support-22ba4bcb9f2fb848.yaml
@@ -0,0 +1,17 @@
+---
+features:
+ - |
+ Add glance image import APIs function to v2
+ images_client library.
+
+ * stage_image_file
+ * info_import
+ * info_stores
+ * image_import
+other:
+ - |
+ New configuration options
+ ``CONF.glance.image_feature_enabled.image_import`` has been introduced
+ to enable the image import tests. If your glance deployement support
+ image import functionality then you can enable the image import tests
+ via this flag. Default value of this new config option is false.
diff --git a/releasenotes/notes/network-swap-to-project_id-a1d7fdf6c5e1cf44.yaml b/releasenotes/notes/network-swap-to-project_id-a1d7fdf6c5e1cf44.yaml
new file mode 100644
index 0000000..4cf0c76
--- /dev/null
+++ b/releasenotes/notes/network-swap-to-project_id-a1d7fdf6c5e1cf44.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ [`blueprint blueprint adopt-oslo-versioned-objects-for-db <https://blueprints.launchpad.net/neutron/+spec/adopt-oslo-versioned-objects-for-db>`_]
+ Any reference to "tenant_id" in Network objects is replaced with
+ "project_id".
diff --git a/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml b/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml
new file mode 100644
index 0000000..4d26210
--- /dev/null
+++ b/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - |
+ A number of Compute APIs that only worked with the XenAPI virt driver have
+ been removed in the Compute service. As a result, their corresponding tests
+ are now disabled by default. They can be re-enabled using the new
+ ``[compute_feature_enabled] xenapi_apis`` config option.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 71df749..9a9de43 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -98,7 +98,7 @@
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
diff --git a/requirements.txt b/requirements.txt
index bf38fae..d8738f0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
cliff!=2.9.0,>=2.8.0 # Apache-2.0
-jsonschema>=2.6.0 # MIT
+jsonschema>=3.2.0 # MIT
testtools>=2.2.0 # MIT
paramiko>=2.0.0 # LGPLv2.1+
netaddr>=0.7.18 # BSD
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 0901374..4cc5fdd 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -13,12 +13,22 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+CONF = config.CONF
+
+# TODO(stephenfin): Remove these tests once the nova Ussuri branch goes EOL
class AgentsAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Agents API"""
+ """Tests Compute Agents API"""
+
+ @classmethod
+ def skip_checks(cls):
+ super(AgentsAdminTestJSON, cls).skip_checks()
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise cls.skipException('The os-agents API is not supported.')
@classmethod
def setup_clients(cls):
@@ -46,7 +56,7 @@
@decorators.idempotent_id('1fc6bdc8-0b6d-4cc7-9f30-9b04fabe5b90')
def test_create_agent(self):
- # Create an agent.
+ """Test creating a compute agent"""
params = self._param_helper(
hypervisor='kvm', os='win', architecture='x86',
version='7.0', url='xxx://xxxx/xxx/xxx',
@@ -58,6 +68,7 @@
@decorators.idempotent_id('dc9ffd51-1c50-4f0e-a820-ae6d2a568a9e')
def test_update_agent(self):
+ """Test updating a compute agent"""
# Create and update an agent.
body = self.client.create_agent(**self.params_agent)['agent']
self.addCleanup(self.client.delete_agent, body['agent_id'])
@@ -71,7 +82,7 @@
@decorators.idempotent_id('470e0b89-386f-407b-91fd-819737d0b335')
def test_delete_agent(self):
- # Create an agent and delete it.
+ """Test deleting a compute agent"""
body = self.client.create_agent(**self.params_agent)['agent']
self.client.delete_agent(body['agent_id'])
@@ -82,7 +93,7 @@
@decorators.idempotent_id('6a326c69-654b-438a-80a3-34bcc454e138')
def test_list_agents(self):
- # Create an agent and list all agents.
+ """Test listing compute agents"""
body = self.client.create_agent(**self.params_agent)['agent']
self.addCleanup(self.client.delete_agent, body['agent_id'])
agents = self.client.list_agents()['agents']
@@ -91,7 +102,7 @@
@decorators.idempotent_id('eabadde4-3cd7-4ec4-a4b5-5a936d2d4408')
def test_list_agents_with_filter(self):
- # Create agents and list the agent builds by the filter.
+ """Test listing compute agents by the filter"""
body = self.client.create_agent(**self.params_agent)['agent']
self.addCleanup(self.client.delete_agent, body['agent_id'])
params = self._param_helper(
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 7a3bfdf..2716259 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -71,10 +71,11 @@
class AggregatesAdminTestJSON(AggregatesAdminTestBase):
+ """Tests Aggregates API that require admin privileges"""
@decorators.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
def test_aggregate_create_delete(self):
- # Create and delete an aggregate.
+ """Test create/delete aggregate"""
aggregate = self._create_test_aggregate()
self.assertIsNone(aggregate['availability_zone'])
@@ -83,7 +84,7 @@
@decorators.idempotent_id('5873a6f8-671a-43ff-8838-7ce430bb6d0b')
def test_aggregate_create_delete_with_az(self):
- # Create and delete an aggregate.
+ """Test create/delete aggregate with availability_zone"""
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self._create_test_aggregate(availability_zone=az_name)
self.assertEqual(az_name, aggregate['availability_zone'])
@@ -93,7 +94,7 @@
@decorators.idempotent_id('68089c38-04b1-4758-bdf0-cf0daec4defd')
def test_aggregate_create_verify_entry_in_list(self):
- # Create an aggregate and ensure it is listed.
+ """Test listing aggregate should contain the created aggregate"""
aggregate = self._create_test_aggregate()
aggregates = self.client.list_aggregates()['aggregates']
self.assertIn((aggregate['id'], aggregate['availability_zone']),
@@ -102,7 +103,7 @@
@decorators.idempotent_id('36ec92ca-7a73-43bc-b920-7531809e8540')
def test_aggregate_create_update_metadata_get_details(self):
- # Create an aggregate and ensure its details are returned.
+ """Test set/get aggregate metadata"""
aggregate = self._create_test_aggregate()
body = self.client.show_aggregate(aggregate['id'])['aggregate']
self.assertEqual(aggregate['name'], body['name'])
@@ -121,7 +122,7 @@
@decorators.idempotent_id('4d2b2004-40fa-40a1-aab2-66f4dab81beb')
def test_aggregate_create_update_with_az(self):
- # Update an aggregate and ensure properties are updated correctly
+ """Test create/update aggregate with availability_zone"""
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self._create_test_aggregate(
@@ -148,7 +149,7 @@
@decorators.idempotent_id('c8e85064-e79b-4906-9931-c11c24294d02')
def test_aggregate_add_remove_host(self):
- # Add a host to the given aggregate and remove.
+ """Test adding host to and removing host from aggregate"""
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
aggregate = self._create_test_aggregate(name=aggregate_name)
@@ -169,7 +170,10 @@
@decorators.idempotent_id('7f6a1cc5-2446-4cdb-9baa-b6ae0a919b72')
def test_aggregate_add_host_list(self):
- # Add a host to the given aggregate and list.
+ """Test listing aggregate contains the host added to the aggregate
+
+ Add a host to the given aggregate and list.
+ """
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
aggregate = self._create_test_aggregate(name=aggregate_name)
@@ -188,7 +192,10 @@
@decorators.idempotent_id('eeef473c-7c52-494d-9f09-2ed7fc8fc036')
def test_aggregate_add_host_get_details(self):
- # Add a host to the given aggregate and get details.
+ """Test showing aggregate contains the host added to the aggregate
+
+ Add a host to the given aggregate and get details.
+ """
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
aggregate = self._create_test_aggregate(name=aggregate_name)
@@ -204,7 +211,7 @@
@decorators.idempotent_id('96be03c7-570d-409c-90f8-e4db3c646996')
def test_aggregate_add_host_create_server_with_az(self):
- # Add a host to the given aggregate and create a server.
+ """Test adding a host to the given aggregate and creating a server"""
self.useFixture(fixtures.LockFixture('availability_zone'))
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self._create_test_aggregate(availability_zone=az_name)
@@ -233,6 +240,11 @@
class AggregatesAdminTestV241(AggregatesAdminTestBase):
+ """Tests Aggregates API that require admin privileges
+
+ Tests Aggregates API that require admin privileges with compute
+ microversion greater than 2.40.
+ """
min_microversion = '2.41'
# NOTE(gmann): This test tests the Aggregate APIs response schema
@@ -241,6 +253,11 @@
@decorators.idempotent_id('fdf24d9e-8afa-4700-b6aa-9c498351504f')
def test_create_update_show_aggregate_add_remove_host(self):
+ """Test response schema of aggregates API
+
+ Test response schema of aggregates API(create/update/show/add host/
+ remove host) with compute microversion greater than 2.40.
+ """
# Update and add a host to the given aggregate and get details.
self.useFixture(fixtures.LockFixture('availability_zone'))
# Checking create aggregate API response schema
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index d5adfed..7b115ce 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -49,7 +49,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('86a1cb14-da37-4a70-b056-903fd56dfe29')
def test_aggregate_create_as_user(self):
- # Regular user is not allowed to create an aggregate.
+ """Regular user is not allowed to create an aggregate"""
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
self.assertRaises(lib_exc.Forbidden,
self.aggregates_client.create_aggregate,
@@ -58,7 +58,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3b8a1929-3793-4e92-bcb4-dfa572ee6c1d')
def test_aggregate_create_aggregate_name_length_less_than_1(self):
- # the length of aggregate name should >= 1 and <=255
+ """The length of aggregate name should >=1"""
self.assertRaises(lib_exc.BadRequest,
self.client.create_aggregate,
name='')
@@ -66,7 +66,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4c194563-543b-4e70-a719-557bbe947fac')
def test_aggregate_create_aggregate_name_length_exceeds_255(self):
- # the length of aggregate name should >= 1 and <=255
+ """The length of aggregate name should <=255"""
aggregate_name = 'a' * 256
self.assertRaises(lib_exc.BadRequest,
self.client.create_aggregate,
@@ -75,7 +75,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c23a291-b0b1-487b-b464-132e061151b3')
def test_aggregate_create_with_existent_aggregate_name(self):
- # creating an aggregate with existent aggregate name is forbidden
+ """Creating an aggregate with existent aggregate name is forbidden"""
aggregate = self._create_test_aggregate()
self.assertRaises(lib_exc.Conflict,
self.client.create_aggregate,
@@ -84,7 +84,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('cd6de795-c15d-45f1-8d9e-813c6bb72a3d')
def test_aggregate_delete_as_user(self):
- # Regular user is not allowed to delete an aggregate.
+ """Regular user is not allowed to delete an aggregate"""
aggregate = self._create_test_aggregate()
self.assertRaises(lib_exc.Forbidden,
self.aggregates_client.delete_aggregate,
@@ -93,14 +93,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b7d475a6-5dcd-4ff4-b70a-cd9de66a6672')
def test_aggregate_list_as_user(self):
- # Regular user is not allowed to list aggregates.
+ """Regular user is not allowed to list aggregates"""
self.assertRaises(lib_exc.Forbidden,
self.aggregates_client.list_aggregates)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('557cad12-34c9-4ff4-95f0-22f0dfbaf7dc')
def test_aggregate_get_details_as_user(self):
- # Regular user is not allowed to get aggregate details.
+ """Regular user is not allowed to get aggregate details"""
aggregate = self._create_test_aggregate()
self.assertRaises(lib_exc.Forbidden,
self.aggregates_client.show_aggregate,
@@ -109,21 +109,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c74f4bf1-4708-4ff2-95a0-f49eaca951bd')
def test_aggregate_delete_with_invalid_id(self):
- # Delete an aggregate with invalid id should raise exceptions.
+ """Delete an aggregate with invalid id should raise exceptions"""
self.assertRaises(lib_exc.NotFound,
self.client.delete_aggregate, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3c916244-2c46-49a4-9b55-b20bb0ae512c')
def test_aggregate_get_details_with_invalid_id(self):
- # Get aggregate details with invalid id should raise exceptions.
+ """Get aggregate details with invalid id should raise exceptions"""
self.assertRaises(lib_exc.NotFound,
self.client.show_aggregate, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0ef07828-12b4-45ba-87cc-41425faf5711')
def test_aggregate_add_non_exist_host(self):
- # Adding a non-exist host to an aggregate should raise exceptions.
+ """Adding a non-exist host to an aggregate should fail"""
while True:
non_exist_host = data_utils.rand_name('nonexist_host')
if non_exist_host not in self.hosts:
@@ -135,7 +135,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7324c334-bd13-4c93-8521-5877322c3d51')
def test_aggregate_add_host_as_user(self):
- # Regular user is not allowed to add a host to an aggregate.
+ """Regular user is not allowed to add a host to an aggregate"""
aggregate = self._create_test_aggregate()
self.assertRaises(lib_exc.Forbidden,
self.aggregates_client.add_host,
@@ -144,7 +144,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('19dd44e1-c435-4ee1-a402-88c4f90b5950')
def test_aggregate_add_existent_host(self):
- # Adding already existing host to aggregate should fail.
+ """Adding already existing host to aggregate should fail"""
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate = self._create_test_aggregate()
@@ -158,7 +158,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7a53af20-137a-4e44-a4ae-e19260e626d9')
def test_aggregate_remove_host_as_user(self):
- # Regular user is not allowed to remove a host from an aggregate.
+ """Regular user is not allowed to remove a host from an aggregate"""
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate = self._create_test_aggregate()
@@ -173,7 +173,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('95d6a6fa-8da9-4426-84d0-eec0329f2e4d')
def test_aggregate_remove_nonexistent_host(self):
- # Removing not existing host from aggregate should fail.
+ """Removing not existing host from aggregate should fail"""
aggregate = self._create_test_aggregate()
self.assertRaises(lib_exc.NotFound, self.client.remove_host,
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index bbd39b6..3eb0d9a 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -27,12 +27,12 @@
@decorators.idempotent_id('d3431479-8a09-4f76-aa2d-26dc580cb27c')
def test_get_availability_zone_list(self):
- # List of availability zone
+ """Test listing availability zones"""
availability_zone = self.client.list_availability_zones()
self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
@decorators.idempotent_id('ef726c58-530f-44c2-968c-c7bed22d5b8c')
def test_get_availability_zone_list_detail(self):
- # List of availability zones and available services
+ """Test listing availability zones with detail"""
availability_zone = self.client.list_availability_zones(detail=True)
self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
diff --git a/tempest/api/compute/admin/test_availability_zone_negative.py b/tempest/api/compute/admin/test_availability_zone_negative.py
index a58c22c..6e576e8 100644
--- a/tempest/api/compute/admin/test_availability_zone_negative.py
+++ b/tempest/api/compute/admin/test_availability_zone_negative.py
@@ -18,7 +18,7 @@
class AZAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Availability Zone API List"""
+ """Negative Tests of Availability Zone API List"""
@classmethod
def setup_clients(cls):
@@ -28,8 +28,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bf34dca2-fdc3-4073-9c02-7648d9eae0d7')
def test_get_availability_zone_list_detail_with_non_admin_user(self):
- # List of availability zones and available services with
- # non-administrator user
+ """Test listing availability zone with detail by non-admin user
+
+ List of availability zones and available services with
+ non-administrator user is not allowed.
+ """
+
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.list_availability_zones, detail=True)
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index 711b441..c4a8bf5 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -27,6 +27,8 @@
class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
+ """Test creating servers with specific flavor"""
+
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
@@ -41,7 +43,7 @@
@testtools.skipUnless(CONF.validation.run_validation,
'Instance validation tests are disabled.')
def test_verify_created_server_ephemeral_disk(self):
- # Verify that the ephemeral disk is created when creating server
+ """Verify that the ephemeral disk is created when creating server"""
flavor_base = self.flavors_client.show_flavor(
self.flavor_ref)['flavor']
diff --git a/tempest/api/compute/admin/test_delete_server.py b/tempest/api/compute/admin/test_delete_server.py
index 58cac57..c625939 100644
--- a/tempest/api/compute/admin/test_delete_server.py
+++ b/tempest/api/compute/admin/test_delete_server.py
@@ -19,6 +19,8 @@
class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest):
+ """Test deletion of servers"""
+
# NOTE: Server creations of each test class should be under 10
# for preventing "Quota exceeded for instances".
@@ -30,7 +32,7 @@
@decorators.idempotent_id('99774678-e072-49d1-9d2a-49a59bc56063')
def test_delete_server_while_in_error_state(self):
- # Delete a server while it's VM state is error
+ """Delete a server while it's VM state is error"""
server = self.create_test_server(wait_until='ACTIVE')
self.admin_client.reset_state(server['id'], state='error')
# Verify server's state
@@ -43,7 +45,7 @@
@decorators.idempotent_id('73177903-6737-4f27-a60c-379e8ae8cf48')
def test_admin_delete_servers_of_others(self):
- # Administrator can delete servers of others
+ """Administrator can delete servers of others"""
server = self.create_test_server(wait_until='ACTIVE')
self.admin_client.delete_server(server['id'])
waiters.wait_for_server_termination(self.servers_client, server['id'])
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 66c2c2d..9de3da9 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -22,6 +22,7 @@
class FixedIPsTestJson(base.BaseV2ComputeAdminTest):
+ """Test fixed ips API"""
@classmethod
def skip_checks(cls):
@@ -56,13 +57,16 @@
@decorators.idempotent_id('16b7d848-2f7c-4709-85a3-2dfb4576cc52')
def test_list_fixed_ip_details(self):
+ """Test getting fixed ip details"""
fixed_ip = self.client.show_fixed_ip(self.ip)
self.assertEqual(fixed_ip['fixed_ip']['address'], self.ip)
@decorators.idempotent_id('5485077b-7e46-4cec-b402-91dc3173433b')
def test_set_reserve(self):
+ """Test reserving fixed ip"""
self.client.reserve_fixed_ip(self.ip, reserve="None")
@decorators.idempotent_id('7476e322-b9ff-4710-bf82-49d51bac6e2e')
def test_set_unreserve(self):
+ """Test unreserving fixed ip"""
self.client.reserve_fixed_ip(self.ip, unreserve="None")
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index 7d41f46..1629faa 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -22,6 +22,7 @@
class FixedIPsNegativeTestJson(base.BaseV2ComputeAdminTest):
+ """Negative tests of fixed ips API"""
@classmethod
def skip_checks(cls):
@@ -58,12 +59,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f17f47d-daad-4adc-986e-12370c93e407')
def test_list_fixed_ip_details_with_non_admin_user(self):
+ """Test listing fixed ip with detail by non-admin user is forbidden"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.show_fixed_ip, self.ip)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ce60042c-fa60-4836-8d43-1c8e3359dc47')
def test_set_reserve_with_non_admin_user(self):
+ """Test reserving fixed ip by non-admin user is forbidden"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reserve_fixed_ip,
self.ip, reserve="None")
@@ -71,6 +74,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f1f7a35b-0390-48c5-9803-5f27461439db')
def test_set_unreserve_with_non_admin_user(self):
+ """Test unreserving fixed ip by non-admin user is forbidden"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reserve_fixed_ip,
self.ip, unreserve="None")
@@ -78,6 +82,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f51cf464-7fc5-4352-bc3e-e75cfa2cb717')
def test_set_reserve_with_invalid_ip(self):
+ """Test reserving invalid fixed ip should fail"""
# NOTE(maurosr): since this exercises the same code snippet, we do it
# only for reserve action
# NOTE(eliqiao): in Juno, the exception is NotFound, but in master, we
@@ -90,6 +95,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fd26ef50-f135-4232-9d32-281aab3f9176')
def test_fixed_ip_with_invalid_action(self):
+ """Test operating fixed ip with invalid action should fail"""
self.assertRaises(lib_exc.BadRequest,
self.client.reserve_fixed_ip,
self.ip, invalid_action="None")
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index f42f53a..d6b6b7e 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -107,7 +107,10 @@
"""
def verify_flavor_response_extension(flavor):
# check some extensions for the flavor create/show/detail response
- self.assertEqual(flavor['swap'], '')
+ if self.is_requested_microversion_compatible('2.74'):
+ self.assertEqual(flavor['swap'], '')
+ else:
+ self.assertEqual(flavor['swap'], 0)
self.assertEqual(int(flavor['rxtx_factor']), 1)
self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'], 0)
self.assertEqual(flavor['os-flavor-access:is_public'], True)
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index b8e2b42..87ab7c7 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -43,8 +43,12 @@
@decorators.idempotent_id('ea2c2211-29fa-4db9-97c3-906d36fad3e0')
def test_flavor_access_list_with_private_flavor(self):
- # Test to make sure that list flavor access on a newly created
- # private flavor will return an empty access list
+ """Test listing flavor access for a private flavor
+
+ Listing flavor access on a newly created private flavor will return
+ an empty access list.
+ """
+ # Test to make sure that
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
@@ -54,7 +58,7 @@
@decorators.idempotent_id('59e622f6-bdf6-45e3-8ba8-fedad905a6b4')
def test_flavor_access_add_remove(self):
- # Test to add and remove flavor access to a given tenant.
+ """Test add/remove flavor access to a given project"""
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 45ca10a..ac09cb0 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -46,7 +46,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0621c53e-d45d-40e7-951d-43e5e257b272')
def test_flavor_access_list_with_public_flavor(self):
- # Test to list flavor access with exceptions by querying public flavor
+ """Test listing flavor access of a public flavor should fail"""
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='True')
self.assertRaises(lib_exc.NotFound,
@@ -56,7 +56,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41eaaade-6d37-4f28-9c74-f21b46ca67bd')
def test_flavor_non_admin_add(self):
- # Test to add flavor access as a user without admin privileges.
+ """Test adding flavor access by a non-admin user is forbidden"""
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
self.assertRaises(lib_exc.Forbidden,
@@ -67,7 +67,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('073e79a6-c311-4525-82dc-6083d919cb3a')
def test_flavor_non_admin_remove(self):
- # Test to remove flavor access as a user without admin privileges.
+ """Test removing flavor access by a non-admin user should fail"""
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
@@ -84,6 +84,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f3592cc0-0306-483c-b210-9a7b5346eddc')
def test_add_flavor_access_duplicate(self):
+ """Test adding duplicate flavor access to same flavor should fail"""
# Create a new flavor.
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
@@ -104,6 +105,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1f710927-3bc7-4381-9f82-0ca6e42644b7')
def test_remove_flavor_access_not_found(self):
+ """Test removing non existent flavor access should fail"""
# Create a new flavor.
flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
disk=self.disk, is_public='False')
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
index 31b9217..d904cbd 100644
--- a/tempest/api/compute/admin/test_flavors_microversions.py
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -18,6 +18,8 @@
class FlavorsV255TestJSON(base.BaseV2ComputeAdminTest):
+ """Test flavors API with compute microversion greater than 2.54"""
+
min_microversion = '2.55'
max_microversion = 'latest'
@@ -26,6 +28,11 @@
@decorators.idempotent_id('61976b25-488d-41dc-9dcb-cb9693a7b075')
def test_crud_flavor(self):
+ """Test create/show/update/list flavor
+
+ Check the response schema of flavors API with microversion greater
+ than 2.54.
+ """
flavor_id = data_utils.rand_int_id(start=1000)
# Checking create API response schema
new_flavor_id = self.create_flavor(ram=512,
@@ -44,6 +51,7 @@
class FlavorsV261TestJSON(FlavorsV255TestJSON):
+ """Test flavors API with compute microversion greater than 2.60"""
min_microversion = '2.61'
max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index 2d7e1a7..786c7f0 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -63,7 +63,7 @@
@decorators.idempotent_id('2c8f145f-8012-4cb8-ac7e-95a587f0e4ab')
@utils.services('network')
def test_create_list_delete_floating_ips_bulk(self):
- # Create, List and delete the Floating IPs Bulk
+ """Creating, listing and deleting the Floating IPs Bulk"""
pool = 'test_pool'
# NOTE(GMann): Reserving the IP range but those are not attached
# anywhere. Using the below mentioned interface which is not ever
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 31fe2b5..30f3388 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -18,7 +18,7 @@
class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests hosts API using admin privileges."""
+ """Tests nova hosts API using admin privileges."""
max_microversion = '2.42'
@@ -29,13 +29,13 @@
@decorators.idempotent_id('9bfaf98d-e2cb-44b0-a07e-2558b2821e4f')
def test_list_hosts(self):
- # Listing hosts.
+ """Listing nova hosts"""
hosts = self.client.list_hosts()['hosts']
self.assertGreaterEqual(len(hosts), 2, str(hosts))
@decorators.idempotent_id('5dc06f5b-d887-47a2-bb2a-67762ef3c6de')
def test_list_hosts_with_zone(self):
- # Listing hosts with specified availability zone
+ """Listing nova hosts with specified availability zone"""
self.useFixture(fixtures.LockFixture('availability_zone'))
hosts = self.client.list_hosts()['hosts']
host = hosts[0]
@@ -45,23 +45,27 @@
@decorators.idempotent_id('9af3c171-fbf4-4150-a624-22109733c2a6')
def test_list_hosts_with_a_blank_zone(self):
- # Listing hosts with blank availability zone.
- # If send the request with a blank zone, the request will be successful
- # and it will return all the hosts list
+ """Listing nova hosts with blank availability zone
+
+ If send the request with a blank zone, the request will be successful
+ and it will return all the hosts list
+ """
hosts = self.client.list_hosts(zone='')['hosts']
self.assertNotEmpty(hosts)
@decorators.idempotent_id('c6ddbadb-c94e-4500-b12f-8ffc43843ff8')
def test_list_hosts_with_nonexistent_zone(self):
- # Listing hosts with not existing availability zone.
- # If send the request with a nonexistent zone, the request will be
- # successful and no hosts will be returned
+ """Listing nova hosts with not existing availability zone.
+
+ If send the request with a nonexistent zone, the request will be
+ successful and no hosts will be returned
+ """
hosts = self.client.list_hosts(zone='xxx')['hosts']
self.assertEmpty(hosts)
@decorators.idempotent_id('38adbb12-aee2-4498-8aec-329c72423aa4')
def test_show_host_detail(self):
- # Showing host details.
+ """Showing nova host details"""
hosts = self.client.list_hosts()['hosts']
hosts = [host for host in hosts if host['service'] == 'compute']
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index e8733c8..e9436bc 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -39,21 +39,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dd032027-0210-4d9c-860e-69b1b8deed5f')
def test_list_hosts_with_non_admin_user(self):
- # Non admin user is not allowed to list hosts.
+ """Non admin user is not allowed to list hosts"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.list_hosts)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e75b0a1a-041f-47a1-8b4a-b72a6ff36d3f')
def test_show_host_detail_with_nonexistent_hostname(self):
- # Showing host detail with not existing hostname should fail.
+ """Showing host detail with not existing hostname should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.show_host, 'nonexistent_hostname')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('19ebe09c-bfd4-4b7c-81a2-e2e0710f59cc')
def test_show_host_detail_with_non_admin_user(self):
- # Non admin user is not allowed to show host details.
+ """Non admin user is not allowed to show host details"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.show_host,
self.hostname)
@@ -61,7 +61,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e40c72b1-0239-4ed6-ba21-81a184df1f7c')
def test_update_host_with_non_admin_user(self):
- # Non admin user is not allowed to update host.
+ """Non admin user is not allowed to update host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.update_host,
self.hostname,
@@ -71,8 +71,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fbe2bf3e-3246-4a95-a59f-94e4e298ec77')
def test_update_host_with_invalid_status(self):
- # Updating host to invalid status should fail,
- # 'status' can only be 'enable' or 'disable'.
+ """Updating host to invalid status should fail
+
+ 'status' can only be 'enable' or 'disable'.
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname,
@@ -82,8 +84,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ab1e230e-5e22-41a9-8699-82b9947915d4')
def test_update_host_with_invalid_maintenance_mode(self):
- # Updating host to invalid maintenance mode should fail,
- # 'maintenance_mode' can only be 'enable' or 'disable'.
+ """Updating host to invalid maintenance mode should fail
+
+ 'maintenance_mode' can only be 'enable' or 'disable'.
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname,
@@ -93,8 +97,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0cd85f75-6992-4a4a-b1bd-d11e37fd0eee')
def test_update_host_without_param(self):
- # Updating host without param should fail,
- # 'status' or 'maintenance_mode' is needed for host update.
+ """Updating host without param should fail
+
+ 'status' or 'maintenance_mode' is needed for host update
+ """
self.assertRaises(lib_exc.BadRequest,
self.client.update_host,
self.hostname)
@@ -102,7 +108,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('23c92146-2100-4d68-b2d6-c7ade970c9c1')
def test_update_nonexistent_host(self):
- # Updating not existing host should fail.
+ """Updating not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.update_host,
'nonexistent_hostname',
@@ -112,7 +118,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0d981ac3-4320-4898-b674-82b61fbb60e4')
def test_startup_nonexistent_host(self):
- # Starting up not existing host should fail.
+ """Starting up not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.startup_host,
'nonexistent_hostname')
@@ -120,7 +126,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f4ebb7e-b2ae-4e5b-a38f-0fd1bb0ddfca')
def test_startup_host_with_non_admin_user(self):
- # Non admin user is not allowed to startup host.
+ """Non admin user is not allowed to startup host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.startup_host,
self.hostname)
@@ -128,7 +134,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9e637444-29cf-4244-88c8-831ae82c31b6')
def test_shutdown_nonexistent_host(self):
- # Shutting down not existing host should fail.
+ """Shutting down not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.shutdown_host,
'nonexistent_hostname')
@@ -136,7 +142,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a803529c-7e3f-4d3c-a7d6-8e1c203d27f6')
def test_shutdown_host_with_non_admin_user(self):
- # Non admin user is not allowed to shutdown host.
+ """Non admin user is not allowed to shutdown host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.shutdown_host,
self.hostname)
@@ -144,7 +150,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f86bfd7b-0b13-4849-ae29-0322e83ee58b')
def test_reboot_nonexistent_host(self):
- # Rebooting not existing host should fail.
+ """Rebooting not existing host should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.reboot_host,
'nonexistent_hostname')
@@ -152,7 +158,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('02d79bb9-eb57-4612-abf6-2cb38897d2f8')
def test_reboot_host_with_non_admin_user(self):
- # Non admin user is not allowed to reboot host.
+ """Non admin user is not allowed to reboot host"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_client.reboot_host,
self.hostname)
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index e45aac5..347193d 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -36,19 +36,19 @@
@decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
def test_get_hypervisor_list(self):
- # List of hypervisor and available hypervisors hostname
+ """List of hypervisor and available hypervisors hostname"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('1e7fdac2-b672-4ad1-97a4-bad0e3030118')
def test_get_hypervisor_list_details(self):
- # Display the details of the all hypervisor
+ """Display the details of the all hypervisor"""
hypers = self.client.list_hypervisors(detail=True)['hypervisors']
self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('94ff9eae-a183-428e-9cdb-79fde71211cc')
def test_get_hypervisor_show_details(self):
- # Display the details of the specified hypervisor
+ """Display the details of the specified hypervisor"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@@ -59,14 +59,14 @@
@decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
def test_get_hypervisor_stats(self):
- # Verify the stats of the all hypervisor
+ """Verify the stats of the all hypervisor"""
stats = (self.client.show_hypervisor_statistics()
['hypervisor_statistics'])
self.assertNotEmpty(stats)
@decorators.idempotent_id('91a50d7d-1c2b-4f24-b55a-a1fe20efca70')
def test_get_hypervisor_uptime(self):
- # Verify that GET shows the specified hypervisor uptime
+ """Verify that GET shows the specified hypervisor uptime"""
hypers = self._list_hypervisors()
# Ironic will register each baremetal node as a 'hypervisor',
@@ -106,10 +106,13 @@
class HypervisorAdminV228Test(HypervisorAdminTestBase):
+ """Tests Hypervisors API higher than 2.27 that require admin privileges"""
+
min_microversion = '2.28'
@decorators.idempotent_id('d46bab64-0fbe-4eb8-9133-e6ee56188cc5')
def test_get_list_hypervisor_details(self):
+ """Test listing and showing hypervisor details"""
# NOTE(zhufl): This test tests the hypervisor APIs response schema
# for 2.28 microversion. No specific assert or behaviour verification
# is needed.
@@ -119,11 +122,13 @@
class HypervisorAdminUnderV252Test(HypervisorAdminTestBase):
+ """Tests Hypervisors API below 2.53 that require admin privileges"""
+
max_microversion = '2.52'
@decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
def test_get_hypervisor_show_servers(self):
- # Show instances about the specific hypervisors
+ """Test showing instances about the specific hypervisors"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
@@ -134,7 +139,7 @@
@decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
def test_search_hypervisor(self):
- # Searching for hypervisors by its name.
+ """Test searching for hypervisors by its name"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers, "No hypervisors found.")
hypers = self.client.search_hypervisor(
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 723b93c..9aaffd9 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -40,8 +40,9 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
def test_show_nonexistent_hypervisor(self):
- # Showing not existing hypervisor should fail.
+ """Test showing non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
+
self.assertRaises(
lib_exc.NotFound,
self.client.show_hypervisor,
@@ -50,7 +51,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('51e663d0-6b89-4817-a465-20aca0667d03')
def test_show_hypervisor_with_non_admin_user(self):
- # Non admin user is not allowed to show hypervisor.
+ """Test showing hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -62,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849')
def test_get_hypervisor_stats_with_non_admin_user(self):
- # Non admin user is not allowed to get hypervisor stats.
+ """Test getting hypervisor stats by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.show_hypervisor_statistics)
@@ -70,7 +71,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f60aa680-9a3a-4c7d-90e1-fae3a4891303')
def test_get_nonexistent_hypervisor_uptime(self):
- # Getting uptime of not existing hypervisor should fail.
+ """Test showing uptime of non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
@@ -81,7 +82,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6c3461f9-c04c-4e2a-bebb-71dc9cb47df2')
def test_get_hypervisor_uptime_with_non_admin_user(self):
- # Non admin user is not allowed to get hypervisor uptime.
+ """Test showing uptime of hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -93,7 +94,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('51b3d536-9b14-409c-9bce-c6f7c794994e')
def test_get_hypervisor_list_with_non_admin_user(self):
- # List of hypervisor and available services with non admin user
+ """Test listing hypervisors by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.list_hypervisors)
@@ -101,19 +102,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dc02db05-e801-4c5f-bc8e-d915290ab345')
def test_get_hypervisor_list_details_with_non_admin_user(self):
- # Non admin user is not allowed to list hypervisor details.
+ """Test listing hypervisor details by non admin user should fail"""
self.assertRaises(
lib_exc.Forbidden,
self.non_adm_client.list_hypervisors, detail=True)
class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase):
+ """Tests Hypervisors API below ver 2.53 that require admin privileges"""
+
max_microversion = '2.52'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
def test_show_servers_with_non_admin_user(self):
- # Non admin user is not allowed to show servers on hypervisor.
+ """Test showing hypervisor servers by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -125,7 +128,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
def test_show_servers_with_nonexistent_hypervisor(self):
- # Showing servers on not existing hypervisor should fail.
+ """Test showing servers on non existent hypervisor should fail"""
nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
@@ -136,7 +139,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
def test_search_hypervisor_with_non_admin_user(self):
- # Non admin user is not allowed to search hypervisor.
+ """Test searching hypervisor by non admin user should fail"""
hypers = self._list_hypervisors()
self.assertNotEmpty(hypers)
@@ -148,7 +151,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
def test_search_nonexistent_hypervisor(self):
- # Searching not existing hypervisor should fail.
+ """Test searching non existent hypervisor should fail"""
self.assertRaises(
lib_exc.NotFound,
self.client.search_hypervisor,
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
index 40ed532..3068127 100644
--- a/tempest/api/compute/admin/test_keypairs_v210.py
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -19,6 +19,8 @@
class KeyPairsV210TestJSON(base.BaseKeypairTest):
+ """Tests KeyPairs API with microversion higher than 2.9"""
+
credentials = ['primary', 'admin']
min_microversion = '2.10'
@@ -48,6 +50,13 @@
@decorators.idempotent_id('3c8484af-cfb3-48f6-b8ba-d5d58bbf3eac')
def test_admin_manage_keypairs_for_other_users(self):
+ """Test admin managing keypairs for other users
+
+ First admin creates a keypair for an other user, then admin lists
+ keypairs filtered by that user, and keypairs created for that user
+ should appear in the result and keypairs not created for that user
+ should not appear in the result.
+ """
user_id = self.non_admin_client.user_id
key_list = self._create_and_check_keypairs(user_id)
first_keyname = key_list[0]['name']
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index a845c72..941315e 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -71,6 +71,10 @@
def _live_migrate(self, server_id, target_host, state,
volume_backed=False):
+ # If target_host is None, check whether source host is different with
+ # the new host after migration.
+ if target_host is None:
+ source_host = self.get_host_for_server(server_id)
self._migrate_server_to(server_id, target_host, volume_backed)
waiters.wait_for_server_status(self.servers_client, server_id, state)
migration_list = (self.admin_migration_client.list_migrations()
@@ -82,8 +86,12 @@
if (live_migration['instance_uuid'] == server_id):
msg += "\n%s" % live_migration
msg += "]"
- self.assertEqual(target_host, self.get_host_for_server(server_id),
- msg)
+ if target_host is None:
+ self.assertNotEqual(source_host,
+ self.get_host_for_server(server_id), msg)
+ else:
+ self.assertEqual(target_host, self.get_host_for_server(server_id),
+ msg)
class LiveMigrationTest(LiveMigrationTestBase):
@@ -105,7 +113,11 @@
server_id = self.create_test_server(wait_until="ACTIVE",
volume_backed=volume_backed)['id']
source_host = self.get_host_for_server(server_id)
- destination_host = self.get_host_other_than(server_id)
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ # not to specify a host so that the scheduler will pick one
+ destination_host = None
+ else:
+ destination_host = self.get_host_other_than(server_id)
if state == 'PAUSED':
self.admin_servers_client.pause_server(server_id)
@@ -123,11 +135,17 @@
self._live_migrate(server_id, source_host, state, volume_backed)
@decorators.idempotent_id('1dce86b8-eb04-4c03-a9d8-9c1dc3ee0c7b')
+ @testtools.skipUnless(CONF.compute_feature_enabled.
+ block_migration_for_live_migration,
+ 'Block Live migration not available')
def test_live_block_migration(self):
"""Test live migrating an active server"""
self._test_live_migration()
@decorators.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
+ @testtools.skipUnless(CONF.compute_feature_enabled.
+ block_migration_for_live_migration,
+ 'Block Live migration not available')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_live_block_migration_paused(self):
@@ -155,7 +173,11 @@
"""Test live migrating a server with volume attached"""
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
- target_host = self.get_host_other_than(server_id)
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ # not to specify a host so that the scheduler will pick one
+ target_host = None
+ else:
+ target_host = self.get_host_other_than(server_id)
volume = self.create_volume()
diff --git a/tempest/api/compute/admin/test_live_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
index 8327a3b..80c0525 100644
--- a/tempest/api/compute/admin/test_live_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -24,6 +24,8 @@
class LiveMigrationNegativeTest(base.BaseV2ComputeAdminTest):
+ """Negative tests of live migration"""
+
@classmethod
def skip_checks(cls):
super(LiveMigrationNegativeTest, cls).skip_checks()
@@ -40,7 +42,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fb7856e-ae92-44c9-861a-af62d7830bcb')
def test_invalid_host_for_migration(self):
- # Migrating to an invalid host should not change the status
+ """Test migrating to an invalid host should not change the status"""
target_host = data_utils.rand_name('host')
server = self.create_test_server(wait_until="ACTIVE")
@@ -52,6 +54,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6e2f94f5-2ee8-4830-bef5-5bc95bb0795b')
def test_live_block_migration_suspended(self):
+ """Test migrating a suspended server should not change the status"""
server = self.create_test_server(wait_until="ACTIVE")
self.admin_servers_client.suspend_server(server['id'])
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 0060ffe..9d5e0c9 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -97,9 +97,11 @@
class QuotasAdminTestJSON(QuotasAdminTestBase):
+ """Test compute quotas by admin user"""
+
@decorators.idempotent_id('3b0a7c8f-cf58-46b8-a60c-715a32a8ba7d')
def test_get_default_quotas(self):
- # Admin can get the default resource quota set for a tenant
+ """Test admin can get the default compute quota set for a project"""
expected_quota_set = self.default_quota_set | set(['id'])
quota_set = self.adm_client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
@@ -109,7 +111,7 @@
@decorators.idempotent_id('55fbe2bf-21a9-435b-bbd2-4162b0ed799a')
def test_update_all_quota_resources_for_tenant(self):
- # Admin can update all the resource quota limits for a tenant
+ """Test admin can update all the compute quota limits for a project"""
default_quota_set = self.adm_client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
new_quota_set = {'metadata_items': 256, 'ram': 10240,
@@ -140,11 +142,12 @@
# TODO(afazekas): merge these test cases
@decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
def test_get_updated_quotas(self):
+ """Test that GET shows the updated quota set of project"""
self._get_updated_quotas()
@decorators.idempotent_id('389d04f0-3a41-405f-9317-e5f86e3c44f0')
def test_delete_quota(self):
- # Admin can delete the resource quota set for a project
+ """Test admin can delete the compute quota set for a project"""
project_name = data_utils.rand_name('ram_quota_project')
project_desc = project_name + '-desc'
project = identity.identity_utils(self.os_admin).create_project(
@@ -165,26 +168,40 @@
class QuotasAdminTestV236(QuotasAdminTestBase):
- min_microversion = '2.36'
+ """Test compute quotas with microversion greater than 2.35
+
# NOTE(gmann): This test tests the Quota APIs response schema
# for 2.36 microversion. No specific assert or behaviour verification
# is needed.
+ """
+
+ min_microversion = '2.36'
@decorators.idempotent_id('4268b5c9-92e5-4adc-acf1-3a2798f3d803')
def test_get_updated_quotas(self):
- # Checking Quota update, get, get details APIs response schema
+ """Test compute quotas API with microversion greater than 2.35
+
+ Checking compute quota update, get, get details APIs response schema.
+ """
self._get_updated_quotas()
class QuotasAdminTestV257(QuotasAdminTestBase):
- min_microversion = '2.57'
+ """Test compute quotas with microversion greater than 2.56
+
# NOTE(gmann): This test tests the Quota APIs response schema
# for 2.57 microversion. No specific assert or behaviour verification
# is needed.
+ """
+
+ min_microversion = '2.57'
@decorators.idempotent_id('e641e6c6-e86c-41a4-9e5c-9493c0ae47ad')
def test_get_updated_quotas(self):
- # Checking Quota update, get, get details APIs response schema
+ """Test compute quotas API with microversion greater than 2.56
+
+ Checking compute quota update, get, get details APIs response schema.
+ """
self._get_updated_quotas()
@@ -212,6 +229,7 @@
# 'danger' flag.
@decorators.idempotent_id('7932ab0f-5136-4075-b201-c0e2338df51a')
def test_update_default_quotas(self):
+ """Test updating default compute quota class set"""
# get the current 'default' quota class values
body = (self.adm_client.show_quota_class_set('default')
['quota_class_set'])
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index f90ff92..04dbc2d 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -53,10 +53,12 @@
class QuotasAdminNegativeTest(QuotasAdminNegativeTestBase):
+ """Negative tests of nova quotas"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('733abfe8-166e-47bb-8363-23dbd7ff3476')
def test_update_quota_normal_user(self):
+ """Test updating nova quota by normal user should fail"""
self.assertRaises(lib_exc.Forbidden,
self.client.update_quota_set,
self.demo_tenant_id,
@@ -67,7 +69,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('91058876-9947-4807-9f22-f6eb17140d9b')
def test_create_server_when_cpu_quota_is_full(self):
- # Disallow server creation when tenant's vcpu quota is full
+ """Disallow server creation when tenant's vcpu quota is full"""
self._update_quota('cores', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
@@ -75,7 +77,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6fdd7012-584d-4327-a61c-49122e0d5864')
def test_create_server_when_memory_quota_is_full(self):
- # Disallow server creation when tenant's memory quota is full
+ """Disallow server creation when tenant's memory quota is full"""
self._update_quota('ram', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
@@ -83,13 +85,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7c6be468-0274-449a-81c3-ac1c32ee0161')
def test_create_server_when_instances_quota_is_full(self):
- # Once instances quota limit is reached, disallow server creation
+ """Once instances quota limit is reached, disallow server creation"""
self._update_quota('instances', 0)
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
class QuotasSecurityGroupAdminNegativeTest(QuotasAdminNegativeTestBase):
+ """Negative tests of nova security group quota"""
+
max_microversion = '2.35'
@decorators.skip_because(bug="1186354",
@@ -98,7 +102,7 @@
@decorators.idempotent_id('7c6c8f3b-2bf6-4918-b240-57b136a66aa0')
@utils.services('network')
def test_security_groups_exceed_limit(self):
- # Negative test: Creation Security Groups over limit should FAIL
+ """Negative test: Creation Security Groups over limit should FAIL"""
# Set the quota to number of used security groups
sg_quota = self.limits_client.show_limits()['limits']['absolute'][
'totalSecurityGroupsUsed']
@@ -117,7 +121,7 @@
@decorators.idempotent_id('6e9f436d-f1ed-4f8e-a493-7275dfaa4b4d')
@utils.services('network')
def test_security_groups_rules_exceed_limit(self):
- # Negative test: Creation of Security Group Rules should FAIL
+ """Negative test: Creation of Security Group Rules should FAIL"""
# when we reach limit maxSecurityGroupRules
self._update_quota('security_group_rules', 0)
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
index 6215c37..8f14cbc 100644
--- a/tempest/api/compute/admin/test_server_diagnostics_negative.py
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -18,6 +18,7 @@
class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
+ """Negative tests of server diagnostics"""
@classmethod
def setup_clients(cls):
@@ -27,7 +28,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
def test_get_server_diagnostics_by_non_admin(self):
- # Non-admin user cannot view server diagnostics according to policy
+ """Test getting server diagnostics by non-admin user is forbidden
+
+ Non-admin user cannot view server diagnostics according to policy.
+ """
server_id = self.create_test_server(wait_until='ACTIVE')['id']
self.assertRaises(lib_exc.Forbidden,
self.client.show_server_diagnostics, server_id)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 170b2cc..ab1b49a 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -14,10 +14,13 @@
from tempest.api.compute import base
from tempest.common import waiters
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
+CONF = config.CONF
+
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Servers API using admin privileges"""
@@ -45,7 +48,7 @@
@decorators.idempotent_id('06f960bb-15bb-48dc-873d-f96e89be7870')
def test_list_servers_filter_by_error_status(self):
- # Filter the list of servers by server error status
+ """Test filtering the list of servers by server error status"""
params = {'status': 'error'}
self.client.reset_state(self.s1_id, state='error')
body = self.non_admin_client.list_servers(**params)
@@ -61,6 +64,7 @@
@decorators.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb')
def test_list_servers_detailed_filter_by_invalid_status(self):
+ """Test filtering the list of servers by invalid server status"""
params = {'status': 'invalid_status'}
if self.is_requested_microversion_compatible('2.37'):
body = self.client.list_servers(detail=True, **params)
@@ -72,8 +76,11 @@
@decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
def test_list_servers_by_admin(self):
- # Listing servers by admin user returns a list which doesn't
- # contain the other tenants' server by default
+ """Test listing servers by admin without other projects
+
+ Listing servers by admin user returns a list which doesn't
+ contain the other projects' server by default.
+ """
body = self.client.list_servers(detail=True)
servers = body['servers']
@@ -85,8 +92,11 @@
@decorators.idempotent_id('9f5579ae-19b4-4985-a091-2a5d56106580')
def test_list_servers_by_admin_with_all_tenants(self):
- # Listing servers by admin user with all tenants parameter
- # Here should be listed all servers
+ """Test listing servers by admin with all tenants
+
+ Listing servers by admin user with all tenants parameter,
+ all servers should be listed.
+ """
params = {'all_tenants': ''}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
@@ -98,8 +108,10 @@
@decorators.related_bug('1659811')
@decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b')
def test_list_servers_by_admin_with_specified_tenant(self):
- # In nova v2, tenant_id is ignored unless all_tenants is specified
+ """Test listing servers by admin with specified project
+ In nova v2, tenant_id is ignored unless all_tenants is specified.
+ """
# List the primary tenant but get nothing due to odd specified behavior
tenant_id = self.non_admin_client.tenant_id
params = {'tenant_id': tenant_id}
@@ -128,7 +140,7 @@
@decorators.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f')
def test_list_servers_filter_by_exist_host(self):
- # Filter the list of servers by existent host
+ """Test filtering the list of servers by existent host"""
server = self.client.show_server(self.s1_id)['server']
hostname = server['OS-EXT-SRV-ATTR:host']
params = {'host': hostname, 'all_tenants': '1'}
@@ -144,6 +156,7 @@
@decorators.idempotent_id('ee8ae470-db70-474d-b752-690b7892cab1')
def test_reset_state_server(self):
+ """Test resetting server state to error/active"""
# Reset server's state to 'error'
self.client.reset_state(self.s1_id, state='error')
@@ -160,9 +173,11 @@
@decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442')
def test_rebuild_server_in_error_state(self):
- # The server in error state should be rebuilt using the provided
- # image and changed to ACTIVE state
+ """Test rebuilding server in error state
+ The server in error state should be rebuilt using the provided
+ image and changed to ACTIVE state.
+ """
# resetting vm state require admin privilege
self.client.reset_state(self.s1_id, state='error')
rebuilt_server = self.non_admin_client.rebuild_server(
@@ -188,6 +203,11 @@
@decorators.idempotent_id('7a1323b4-a6a2-497a-96cb-76c07b945c71')
def test_reset_network_inject_network_info(self):
+ """Test resetting and injecting network info of a server"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'The resetNetwork server action is not supported.')
+
# Reset Network of a Server
server = self.create_test_server(wait_until='ACTIVE')
self.client.reset_network(server['id'])
@@ -196,6 +216,7 @@
@decorators.idempotent_id('fdcd9b33-0903-4e00-a1f7-b5f6543068d6')
def test_create_server_with_scheduling_hint(self):
+ """Test creating server with scheduling hint"""
# Create a server with scheduler hints.
hints = {
'same_host': self.s1_id
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index f720b84..f52d4c0 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -26,7 +26,7 @@
class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Servers API using admin privileges"""
+ """Negative Tests of Servers API using admin privileges"""
@classmethod
def setup_clients(cls):
@@ -47,6 +47,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_using_overlimit_ram(self):
+ """Test resizing server using over limit ram should fail"""
# NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
self.useFixture(fixtures.LockFixture('compute_quotas'))
quota_set = self.quotas_client.show_quota_set(
@@ -69,6 +70,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_using_overlimit_vcpus(self):
+ """Test resizing server using over limit vcpus should fail"""
# NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
self.useFixture(fixtures.LockFixture('compute_quotas'))
quota_set = self.quotas_client.show_quota_set(
@@ -89,6 +91,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b0b4d8af-1256-41ef-9ee7-25f1c19dde80')
def test_reset_state_server_invalid_state(self):
+ """Test resetting server state to invalid state value should fail"""
self.assertRaises(lib_exc.BadRequest,
self.client.reset_state, self.s1_id,
state='invalid')
@@ -96,6 +99,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4cdcc984-fab0-4577-9a9d-6d558527ee9d')
def test_reset_state_server_invalid_type(self):
+ """Test resetting server state to invalid state type should fail"""
self.assertRaises(lib_exc.BadRequest,
self.client.reset_state, self.s1_id,
state=1)
@@ -103,13 +107,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e741298b-8df2-46f0-81cb-8f814ff2504c')
def test_reset_state_server_nonexistent_server(self):
+ """Test resetting a non existent server's state should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.reset_state, '999', state='error')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221')
def test_migrate_non_existent_server(self):
- # migrate a non existent server
+ """Test migrating a non existent server should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.migrate_server,
data_utils.rand_uuid())
@@ -121,6 +126,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_migrate_server_invalid_state(self):
+ """Test migrating a server with invalid state should fail"""
# create server.
server = self.create_test_server(wait_until='ACTIVE')
server_id = server['id']
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index bf846e5..24518a8 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -19,7 +19,10 @@
class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Services API. List and Enable/Disable require admin privileges."""
+ """Tests Nova Services API.
+
+ List and Enable/Disable require admin privileges.
+ """
@classmethod
def setup_clients(cls):
@@ -28,13 +31,13 @@
@decorators.idempotent_id('5be41ef4-53d1-41cc-8839-5c2a48a1b283')
def test_list_services(self):
- # Listing nova services
+ """Listing nova services"""
services = self.client.list_services()['services']
self.assertNotEmpty(services)
@decorators.idempotent_id('f345b1ec-bc6e-4c38-a527-3ca2bc00bef5')
def test_get_service_by_service_binary_name(self):
- # Listing nova services by binary name.
+ """Listing nova services by binary name"""
binary_name = 'nova-compute'
services = self.client.list_services(binary=binary_name)['services']
self.assertNotEmpty(services)
@@ -43,7 +46,7 @@
@decorators.idempotent_id('affb42d5-5b4b-43c8-8b0b-6dca054abcca')
def test_get_service_by_host_name(self):
- # Listing nova services by host name.
+ """Listing nova services by host name"""
services = self.client.list_services()['services']
host_name = services[0]['host']
services_on_host = [service for service in services if
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 033caa8..a4d7d3f 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -43,6 +43,9 @@
Expect all services to be returned when the request contains invalid
parameters.
"""
+ if not self.is_requested_microversion_compatible('2.74'):
+ raise self.skipException(
+ "From microversion 2.75 invalid parameters are not allowed.")
services = self.client.list_services()['services']
services_xxx = (self.client.list_services(xxx='nova-compute')
['services'])
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index d4c60b3..c24f420 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -26,6 +26,7 @@
class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
+ """Test tenant usages"""
@classmethod
def setup_clients(cls):
@@ -67,7 +68,7 @@
@decorators.idempotent_id('062c8ae9-9912-4249-8b51-e38d664e926e')
def test_list_usage_all_tenants(self):
- # Get usage for all tenants
+ """Test getting usage for all tenants"""
tenant_usage = self.call_until_valid(
self.adm_client.list_tenant_usages, VALID_WAIT,
start=self.start, end=self.end, detailed="1")['tenant_usages'][0]
@@ -75,7 +76,7 @@
@decorators.idempotent_id('94135049-a4c5-4934-ad39-08fa7da4f22e')
def test_get_usage_tenant(self):
- # Get usage for a specific tenant
+ """Test getting usage for a specific tenant"""
tenant_usage = self.call_until_valid(
self.adm_client.show_tenant_usage, VALID_WAIT,
self.tenant_id, start=self.start, end=self.end)['tenant_usage']
@@ -84,7 +85,7 @@
@decorators.idempotent_id('9d00a412-b40e-4fd9-8eba-97b496316116')
def test_get_usage_tenant_with_non_admin_user(self):
- # Get usage for a specific tenant with non admin user
+ """Test getting usage for a specific tenant with non admin user"""
tenant_usage = self.call_until_valid(
self.client.show_tenant_usage, VALID_WAIT,
self.tenant_id, start=self.start, end=self.end)['tenant_usage']
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
index cb60b8d..4b5a5d5 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
@@ -21,6 +21,7 @@
class TenantUsagesNegativeTestJSON(base.BaseV2ComputeAdminTest):
+ """Negative tests of compute tenant usages API"""
@classmethod
def setup_clients(cls):
@@ -43,7 +44,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8b21e135-d94b-4991-b6e9-87059609c8ed')
def test_get_usage_tenant_with_empty_tenant_id(self):
- # Get usage for a specific tenant empty
+ """Test getting tenant usage with empty tenant id should fail"""
params = {'start': self.start,
'end': self.end}
self.assertRaises(lib_exc.NotFound,
@@ -53,7 +54,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4079dd2a-9e8d-479f-869d-6fa985ce45b6')
def test_get_usage_tenant_with_invalid_date(self):
- # Get usage for tenant with invalid date
+ """Test getting tenant usage with invalid time range should fail"""
params = {'start': self.end,
'end': self.start}
self.assertRaises(lib_exc.BadRequest,
@@ -63,7 +64,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bbe6fe2c-15d8-404c-a0a2-44fad0ad5cc7')
def test_list_usage_all_tenants_with_non_admin_user(self):
- # Get usage for all tenants with non admin user
+ """Test listing usage of all tenants by non-admin user is forbidden"""
params = {'start': self.start,
'end': self.end,
'detailed': "1"}
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
index 0e6c016..5917931 100644
--- a/tempest/api/compute/certificates/test_certificates.py
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -21,6 +21,7 @@
class CertificatesV2TestJSON(base.BaseV2ComputeTest):
+ """Test Certificates API"""
@classmethod
def skip_checks(cls):
@@ -30,10 +31,10 @@
@decorators.idempotent_id('c070a441-b08e-447e-a733-905909535b1b')
def test_create_root_certificate(self):
- # create certificates
+ """Test creating root certificate"""
self.certificates_client.create_certificate()
@decorators.idempotent_id('3ac273d0-92d2-4632-bdfc-afbc21d4606c')
def test_get_root_certificate(self):
- # get the root certificate
+ """Test getting root certificate details"""
self.certificates_client.show_certificate('root')
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index e5047fe..9ab75c5 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -18,11 +18,12 @@
class FlavorsV2TestJSON(base.BaseV2ComputeTest):
+ """Tests Flavors"""
@decorators.attr(type='smoke')
@decorators.idempotent_id('e36c0eaa-dff5-4082-ad1f-3f9a80aa3f59')
def test_list_flavors(self):
- # List of all flavors should contain the expected flavor
+ """List of all flavors should contain the expected flavor"""
flavors = self.flavors_client.list_flavors()['flavors']
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'],
@@ -34,7 +35,7 @@
@decorators.idempotent_id('6e85fde4-b3cd-4137-ab72-ed5f418e8c24')
def test_list_flavors_with_detail(self):
- # Detailed list of all flavors should contain the expected flavor
+ """Detailed list of all flavors should contain the expected flavor"""
flavors = self.flavors_client.list_flavors(detail=True)['flavors']
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
self.assertIn(flavor, flavors)
@@ -42,20 +43,20 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('1f12046b-753d-40d2-abb6-d8eb8b30cb2f')
def test_get_flavor(self):
- # The expected flavor details should be returned
+ """The expected flavor details should be returned"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
self.assertEqual(self.flavor_ref, flavor['id'])
@decorators.idempotent_id('8d7691b3-6ed4-411a-abc9-2839a765adab')
def test_list_flavors_limit_results(self):
- # Only the expected number of flavors should be returned
+ """Only the expected number of flavors should be returned"""
params = {'limit': 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
self.assertEqual(1, len(flavors))
@decorators.idempotent_id('b26f6327-2886-467a-82be-cef7a27709cb')
def test_list_flavors_detailed_limit_results(self):
- # Only the expected number of flavors (detailed) should be returned
+ """Only the expected number of flavors(detailed) should be returned"""
params = {'limit': 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
@@ -63,7 +64,7 @@
@decorators.idempotent_id('e800f879-9828-4bd0-8eae-4f17189951fb')
def test_list_flavors_using_marker(self):
- # The list of flavors should start from the provided marker
+ """The list of flavors should start from the provided marker"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -74,7 +75,7 @@
@decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107')
def test_list_flavors_detailed_using_marker(self):
- # The list of flavors should start from the provided marker
+ """The list of flavors should start from the provided marker"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -86,7 +87,7 @@
@decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79')
def test_list_flavors_detailed_filter_by_min_disk(self):
- # The detailed list of flavors should be filtered by disk space
+ """The detailed list of flavors should be filtered by disk space"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -97,7 +98,7 @@
@decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292')
def test_list_flavors_detailed_filter_by_min_ram(self):
- # The detailed list of flavors should be filtered by RAM
+ """The detailed list of flavors should be filtered by RAM"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -108,7 +109,7 @@
@decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4')
def test_list_flavors_filter_by_min_disk(self):
- # The list of flavors should be filtered by disk space
+ """The list of flavors should be filtered by disk space"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
@@ -118,7 +119,7 @@
@decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa')
def test_list_flavors_filter_by_min_ram(self):
- # The list of flavors should be filtered by RAM
+ """The list of flavors should be filtered by RAM"""
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
flavor_id = flavor['id']
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 2adc482..6097bbc 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -25,13 +25,13 @@
class FloatingIPsTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips API with compute microversion less than 2.36"""
max_microversion = '2.35'
@decorators.idempotent_id('f7bfb946-297e-41b8-9e8c-aba8e9bb5194')
def test_allocate_floating_ip(self):
- # Positive test:Allocation of a new floating IP to a project
- # should be successful
+ """Test allocating a floating ip to a project"""
body = self.client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
floating_ip_id_allocated = body['id']
@@ -45,8 +45,7 @@
@decorators.idempotent_id('de45e989-b5ca-4a9b-916b-04a52e7bbb8b')
def test_delete_floating_ip(self):
- # Positive test:Deletion of valid floating IP from project
- # should be successful
+ """Test deleting a valid floating ip from project"""
# Creating the floating IP that is to be deleted in this method
floating_ip_body = self.client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
@@ -59,6 +58,7 @@
class FloatingIPsAssociationTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips association with microversion less than 2.44"""
max_microversion = '2.43'
@@ -80,9 +80,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_disassociate_floating_ip(self):
- # Positive test:Associate and disassociate the provided floating IP
- # to a specific server should be successful
-
+ """Test associate/disassociate floating ip to a server"""
# Association of floating IP to fixed IP address
self.client.associate_floating_ip_to_server(
self.floating_ip,
@@ -102,6 +100,12 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_already_associated_floating_ip(self):
+ """Test associating an already associated floating ip
+
+ First associate a floating ip to server1, then associate the floating
+ ip to server2, the floating ip will be associated to server2 and no
+ longer associated to server1.
+ """
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
# Create server so as to use for Multiple association
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 9257458..e99e218 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -25,6 +25,7 @@
class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips API with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -46,8 +47,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6e0f059b-e4dd-48fb-8207-06e3bba5b074')
def test_allocate_floating_ip_from_nonexistent_pool(self):
- # Negative test:Allocation of a new floating IP from a nonexistent_pool
- # to a project should fail
+ """Test allocating floating ip from non existent pool should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.create_floating_ip,
pool="non_exist_pool")
@@ -55,15 +55,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ae1c55a8-552b-44d4-bfb6-2a115a15d0ba')
def test_delete_nonexistent_floating_ip(self):
- # Negative test:Deletion of a nonexistent floating IP
- # from project should fail
-
+ """Test deleting non existent floating ip should fail"""
# Deleting the non existent floating IP
self.assertRaises(lib_exc.NotFound, self.client.delete_floating_ip,
self.non_exist_id)
class FloatingIPsAssociationNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ips API with compute microversion less than 2.44"""
max_microversion = '2.43'
@@ -76,8 +75,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('595fa616-1a71-4670-9614-46564ac49a4c')
def test_associate_nonexistent_floating_ip(self):
- # Negative test:Association of a non existent floating IP
- # to specific server should fail
+ """Test associating non existent floating ip to server should fail"""
# Associating non existent floating IP
self.assertRaises(lib_exc.NotFound,
self.client.associate_floating_ip_to_server,
@@ -86,7 +84,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0a081a66-e568-4e6b-aa62-9587a876dca8')
def test_dissociate_nonexistent_floating_ip(self):
- # Negative test:Dissociation of a non existent floating IP should fail
+ """Test dissociating non existent floating ip should fail"""
# Dissociating non existent floating IP
self.assertRaises(lib_exc.NotFound,
self.client.disassociate_floating_ip_from_server,
@@ -95,7 +93,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('804b4fcb-bbf5-412f-925d-896672b61eb3')
def test_associate_ip_to_server_without_passing_floating_ip(self):
- # Negative test:Association of empty floating IP to specific server
+ """Test associating empty floating ip to server should fail"""
# should raise NotFound or BadRequest(In case of Nova V2.1) exception.
self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest),
self.client.associate_floating_ip_to_server,
@@ -106,10 +104,13 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_associate_ip_to_server_with_floating_ip(self):
- # The VM have one port
- # Associate floating IP A to the VM
- # Associate floating IP B which is from same pool with floating IP A
- # to the VM, should raise BadRequest exception
+ """Test associating floating ip to server already with floating ip
+
+ 1. The VM have one port
+ 2. Associate floating IP A to the VM
+ 3. Associate floating IP B which is from same pool with floating IP A
+ to the VM, should raise BadRequest exception
+ """
body = self.client.create_floating_ip(
pool=CONF.network.public_network_id)['floating_ip']
self.addCleanup(self.client.delete_floating_ip, body['id'])
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 944f798..6bfee95 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -21,6 +21,7 @@
class FloatingIPDetailsTestJSON(base.BaseFloatingIPsTest):
+ """Test floating ip details with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -37,7 +38,7 @@
@decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
def test_list_floating_ips(self):
- # Positive test:Should return the list of floating IPs
+ """Test listing floating ips"""
body = self.client.list_floating_ips()['floating_ips']
floating_ips = body
self.assertNotEmpty(floating_ips,
@@ -47,7 +48,7 @@
@decorators.idempotent_id('eef497e0-8ff7-43c8-85ef-558440574f84')
def test_get_floating_ip_details(self):
- # Positive test:Should be able to GET the details of floatingIP
+ """Test getting floating ip details"""
# Creating a floating IP for which details are to be checked
body = self.client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
@@ -68,7 +69,7 @@
@decorators.idempotent_id('df389fc8-56f5-43cc-b290-20eda39854d3')
def test_list_floating_ip_pools(self):
- # Positive test:Should return the list of floating IP Pools
+ """Test listing floating ip pools"""
floating_ip_pools = self.pools_client.list_floating_ip_pools()
self.assertNotEmpty(floating_ip_pools['floating_ip_pools'],
"Expected floating IP Pools. Got zero.")
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index d69248c..aa0320d 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -23,14 +23,18 @@
class FloatingIPDetailsNegativeTestJSON(base.BaseFloatingIPsTest):
+ """Negative tests of floating ip detail
+
+ Negative tests of floating ip detail with compute microversion less
+ than 2.36.
+ """
max_microversion = '2.35'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7ab18834-4a4b-4f28-a2c5-440579866695')
def test_get_nonexistent_floating_ip_details(self):
- # Negative test:Should not be able to GET the details
- # of non-existent floating IP
+ """Test getting non existent floating ip should fail"""
# Creating a non-existent floatingIP id
if CONF.service_available.neutron:
non_exist_id = data_utils.rand_uuid()
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 1f3af5f..561265f 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -28,6 +28,8 @@
class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+ """Test image metadata with compute microversion less than 2.39"""
+
max_microversion = '2.38'
@classmethod
@@ -89,7 +91,10 @@
@decorators.idempotent_id('37ec6edd-cf30-4c53-bd45-ae74db6b0531')
def test_list_image_metadata(self):
- # All metadata key/value pairs for an image should be returned
+ """Test listing image metadata
+
+ All metadata key/value pairs for an image should be returned.
+ """
resp_metadata = self.client.list_image_metadata(self.image_id)
expected = {'metadata': {
'os_version': 'value1', 'os_distro': 'value2'}}
@@ -97,7 +102,10 @@
@decorators.idempotent_id('ece7befc-d3ce-42a4-b4be-c3067a418c29')
def test_set_image_metadata(self):
- # The metadata for the image should match the new values
+ """Test setting image metadata
+
+ The metadata for the image should match the new values.
+ """
req_metadata = {'os_version': 'value2', 'architecture': 'value3'}
self.client.set_image_metadata(self.image_id,
req_metadata)
@@ -108,7 +116,10 @@
@decorators.idempotent_id('7b491c11-a9d5-40fe-a696-7f7e03d3fea2')
def test_update_image_metadata(self):
- # The metadata for the image should match the updated values
+ """Test updating image medata
+
+ The metadata for the image should match the updated values.
+ """
req_metadata = {'os_version': 'alt1', 'architecture': 'value3'}
self.client.update_image_metadata(self.image_id,
req_metadata)
@@ -122,15 +133,21 @@
@decorators.idempotent_id('4f5db52f-6685-4c75-b848-f4bb363f9aa6')
def test_get_image_metadata_item(self):
- # The value for a specific metadata key should be returned
+ """Test getting image metadata item
+
+ The value for a specific metadata key should be returned.
+ """
meta = self.client.show_image_metadata_item(self.image_id,
'os_distro')['meta']
self.assertEqual('value2', meta['os_distro'])
@decorators.idempotent_id('f2de776a-4778-4d90-a5da-aae63aee64ae')
def test_set_image_metadata_item(self):
- # The value provided for the given meta item should be set for
- # the image
+ """Test setting image metadata item
+
+ The value provided for the given meta item should be set for
+ the image.
+ """
meta = {'os_version': 'alt'}
self.client.set_image_metadata_item(self.image_id,
'os_version', meta)
@@ -140,7 +157,10 @@
@decorators.idempotent_id('a013796c-ba37-4bb5-8602-d944511def14')
def test_delete_image_metadata_item(self):
- # The metadata value/key pair should be deleted from the image
+ """Test deleting image metadata item
+
+ The metadata value/key pair should be deleted from the image.
+ """
self.client.delete_image_metadata_item(self.image_id,
'os_version')
resp_metadata = self.client.list_image_metadata(self.image_id)
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 407fb08..b9806c7 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -20,6 +20,11 @@
class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of image metadata
+
+ Negative tests of image metadata with compute microversion less than 2.39.
+ """
+
max_microversion = '2.38'
@classmethod
@@ -30,15 +35,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('94069db2-792f-4fa8-8bd3-2271a6e0c095')
def test_list_nonexistent_image_metadata(self):
- # Negative test: List on nonexistent image
- # metadata should not happen
+ """Test listing metadata of a non existence image should fail"""
self.assertRaises(lib_exc.NotFound, self.client.list_image_metadata,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a403ef9e-9f95-427c-b70a-3ce3388796f1')
def test_update_nonexistent_image_metadata(self):
- # Negative test:An update should not happen for a non-existent image
+ """Test updating metadata of a non existence image should fail"""
meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
self.assertRaises(lib_exc.NotFound,
self.client.update_image_metadata,
@@ -47,7 +51,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41ae052c-6ee6-405c-985e-5712393a620d')
def test_get_nonexistent_image_metadata_item(self):
- # Negative test: Get on non-existent image should not happen
+ """Test getting metadata of a non existence image should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.show_image_metadata_item,
data_utils.rand_uuid(), 'os_version')
@@ -55,7 +59,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dc64f2ce-77e8-45b0-88c8-e15041d08eaf')
def test_set_nonexistent_image_metadata(self):
- # Negative test: Metadata should not be set to a non-existent image
+ """Test setting metadata of a non existence image should fail"""
meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
self.assertRaises(lib_exc.NotFound, self.client.set_image_metadata,
data_utils.rand_uuid(), meta)
@@ -63,8 +67,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2154fd03-ab54-457c-8874-e6e3eb56e9cf')
def test_set_nonexistent_image_metadata_item(self):
- # Negative test: Metadata item should not be set to a
- # nonexistent image
+ """Test setting metadata item of a non existence image should fail"""
meta = {'os_distro': 'alt'}
self.assertRaises(lib_exc.NotFound,
self.client.set_image_metadata_item,
@@ -74,8 +77,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('848e157f-6bcf-4b2e-a5dd-5124025a8518')
def test_delete_nonexistent_image_metadata_item(self):
- # Negative test: Shouldn't be able to delete metadata
- # item from non-existent image
+ """Test deleting metadata item of a non existence image should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.delete_image_metadata_item,
data_utils.rand_uuid(), 'os_distro')
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index ef33685..91ce1f9 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -25,6 +25,8 @@
class ImagesTestJSON(base.BaseV2ComputeTest):
+ """Test server images"""
+
create_default_network = True
@classmethod
@@ -48,6 +50,7 @@
@decorators.idempotent_id('aa06b52b-2db5-4807-b218-9441f75d74e3')
def test_delete_saving_image(self):
+ """Test deleting server image while it is in 'SAVING' state"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
# wait for server active to avoid conflict when deleting server
@@ -74,6 +77,7 @@
@decorators.idempotent_id('aaacd1d0-55a2-4ce8-818a-b5439df8adc9')
def test_create_image_from_stopped_server(self):
+ """Test creating server image from stopped server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.stop_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
@@ -91,6 +95,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_create_image_from_paused_server(self):
+ """Test creating server image from paused server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.pause_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
@@ -109,6 +114,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
'Suspend is not available.')
def test_create_image_from_suspended_server(self):
+ """Test creating server image from suspended server"""
server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.suspend_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 2400348..5ff2a6a 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -42,11 +42,12 @@
class ImagesNegativeTestJSON(ImagesNegativeTestBase):
+ """Negative tests of server image"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6cd5a89d-5b47-46a7-93bc-3916f0d84973')
def test_create_image_from_deleted_server(self):
- # An image should not be created if the server instance is removed
+ """Check server image should not be created if the server is removed"""
server = self.create_test_server(wait_until='ACTIVE')
# Delete server before trying to create image
@@ -61,7 +62,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('82c5b0c4-9dbd-463c-872b-20c4755aae7f')
def test_create_image_from_invalid_server(self):
- # An image should not be created with invalid server id
+ """Check server image should not be created with invalid server id"""
# Create a new image with invalid server id
meta = {'image_type': 'test'}
self.assertRaises(lib_exc.NotFound, self.create_image_from_server,
@@ -70,7 +71,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ec176029-73dc-4037-8d72-2e4ff60cf538')
def test_create_image_specify_uuid_35_characters_or_less(self):
- # Return an error if Image ID passed is 35 characters or less
+ """Check server image should not be created for invalid server id
+
+ Return an error if server id passed is 35 characters or less
+ """
snapshot_name = data_utils.rand_name('test-snap')
test_uuid = ('a' * 35)
self.assertRaises(lib_exc.NotFound, self.client.create_image,
@@ -79,7 +83,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('36741560-510e-4cc2-8641-55fe4dfb2437')
def test_create_image_specify_uuid_37_characters_or_more(self):
- # Return an error if Image ID passed is 37 characters or more
+ """Check server image should not be created for invalid server id
+
+ Return an error if sever id passed is 37 characters or more
+ """
snapshot_name = data_utils.rand_name('test-snap')
test_uuid = ('a' * 37)
self.assertRaises(lib_exc.NotFound, self.client.create_image,
@@ -87,20 +94,23 @@
class ImagesDeleteNegativeTestJSON(ImagesNegativeTestBase):
+ """Negative tests of server image
+
+ Negative tests of server image with compute microversion less than 2.36.
+ """
max_microversion = '2.35'
@decorators.attr(type=['negative'])
@decorators.idempotent_id('381acb65-785a-4942-94ce-d8f8c84f1f0f')
def test_delete_image_with_invalid_image_id(self):
- # An image should not be deleted with invalid image id
+ """Check an image should not be deleted with invalid image id"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('137aef61-39f7-44a1-8ddf-0adf82511701')
def test_delete_non_existent_image(self):
- # Return an error while trying to delete a non-existent image
-
+ """Check trying to delete a non-existent image should fail"""
non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
non_existent_image_id)
@@ -108,13 +118,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e6e41425-af5c-4fe6-a4b5-7b7b963ffda5')
def test_delete_image_blank_id(self):
- # Return an error while trying to delete an image with blank Id
+ """Check trying to delete an image with blank id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('924540c3-f1f1-444c-8f58-718958b6724e')
def test_delete_image_non_hex_string_id(self):
- # Return an error while trying to delete an image with non hex id
+ """Check trying to delete an image with non hex id should fail"""
invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
invalid_image_id)
@@ -122,13 +132,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('68e2c175-bd26-4407-ac0f-4ea9ce2139ea')
def test_delete_image_negative_image_id(self):
- # Return an error while trying to delete an image with negative id
+ """Check trying to delete an image with negative id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b340030d-82cd-4066-a314-c72fb7c59277')
def test_delete_image_with_id_over_character_limit(self):
- # Return an error while trying to delete image with id over limit
+ """Check trying to delete image with id over limit should fail"""
invalid_image_id = data_utils.rand_uuid() + "1"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
invalid_image_id)
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index cbb65bb..4dc23a7 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -21,6 +21,8 @@
class ListImagesTestJSON(base.BaseV2ComputeTest):
+ """Test listing server images with compute microversion less than 2.36"""
+
max_microversion = '2.35'
@classmethod
@@ -37,20 +39,26 @@
@decorators.idempotent_id('490d0898-e12a-463f-aef0-c50156b9f789')
def test_get_image(self):
- # Returns the correct details for a single image
+ """Test getting the correct details for a single server image"""
image = self.client.show_image(self.image_ref)['image']
self.assertEqual(self.image_ref, image['id'])
@decorators.idempotent_id('fd51b7f4-d4a3-4331-9885-866658112a6f')
def test_list_images(self):
- # The list of all images should contain the image
+ """Test listing server images
+
+ The list of all images should contain the image
+ """
images = self.client.list_images()['images']
found = [i for i in images if i['id'] == self.image_ref]
self.assertNotEmpty(found)
@decorators.idempotent_id('9f94cb6b-7f10-48c5-b911-a0b84d7d4cd6')
def test_list_images_with_detail(self):
- # Detailed list of all images should contain the expected images
+ """Test listing server images with detail
+
+ Detailed list of all images should contain the expected images
+ """
images = self.client.list_images(detail=True)['images']
found = [i for i in images if i['id'] == self.image_ref]
self.assertNotEmpty(found)
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 66abb21..8df2e84 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -19,11 +19,16 @@
class KeyPairsV2TestJSON(base.BaseKeypairTest):
+ """Test keypairs API with compute microversion less than 2.2"""
+
max_microversion = '2.1'
@decorators.idempotent_id('1d1dbedb-d7a0-432a-9d09-83f543c3c19b')
def test_keypairs_create_list_delete(self):
- # Keypairs created should be available in the response list
+ """Test create/list/delete keypairs
+
+ Keypairs created should be available in the response list
+ """
# Create 3 keypairs
key_list = list()
for _ in range(3):
@@ -48,7 +53,7 @@
@decorators.idempotent_id('6c1d3123-4519-4742-9194-622cb1714b7d')
def test_keypair_create_delete(self):
- # Keypair should be created, verified and deleted
+ """Test create/delete keypair"""
k_name = data_utils.rand_name('keypair')
keypair = self.create_keypair(k_name)
key_name = keypair['name']
@@ -58,7 +63,7 @@
@decorators.idempotent_id('a4233d5d-52d8-47cc-9a25-e1864527e3df')
def test_get_keypair_detail(self):
- # Keypair should be created, Got details by name and deleted
+ """Test getting keypair detail by keypair name"""
k_name = data_utils.rand_name('keypair')
self.create_keypair(k_name)
keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
@@ -68,7 +73,7 @@
@decorators.idempotent_id('39c90c6a-304a-49dd-95ec-2366129def05')
def test_keypair_create_with_pub_key(self):
- # Keypair should be created with a given public key
+ """Test creating keypair with a given public key"""
k_name = data_utils.rand_name('keypair')
pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs"
"Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd"
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 81635ca..40bea3f 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -21,10 +21,12 @@
class KeyPairsNegativeTestJSON(base.BaseKeypairTest):
+ """Negative tests of keypairs API"""
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('29cca892-46ae-4d48-bc32-8fe7e731eb81')
def test_keypair_create_with_invalid_pub_key(self):
- # Keypair should not be created with a non RSA public key
+ """Test keypair should not be created with a non RSA public key"""
pub_key = "ssh-rsa JUNK nova@ubuntu"
self.assertRaises(lib_exc.BadRequest,
self.create_keypair, pub_key=pub_key)
@@ -32,7 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7cc32e47-4c42-489d-9623-c5e2cb5a2fa5')
def test_keypair_delete_nonexistent_key(self):
- # Non-existent key deletion should throw a proper error
+ """Test non-existent key deletion should throw a proper error"""
k_name = data_utils.rand_name("keypair-non-existent")
self.assertRaises(lib_exc.NotFound,
self.keypairs_client.delete_keypair,
@@ -41,7 +43,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dade320e-69ca-42a9-ba4a-345300f127e0')
def test_create_keypair_with_empty_public_key(self):
- # Keypair should not be created with an empty public key
+ """Test keypair should not be created with an empty public key"""
pub_key = ' '
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
pub_key=pub_key)
@@ -49,7 +51,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fc100c19-2926-4b9c-8fdc-d0589ee2f9ff')
def test_create_keypair_when_public_key_bits_exceeds_maximum(self):
- # Keypair should not be created when public key bits are too long
+ """Test keypair should not be created when public key are too long"""
pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
pub_key=pub_key)
@@ -57,7 +59,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0359a7f1-f002-4682-8073-0c91e4011b7c')
def test_create_keypair_with_duplicate_name(self):
- # Keypairs with duplicate names should not be created
+ """Test keypairs with duplicate names should not be created"""
k_name = data_utils.rand_name('keypair')
self.keypairs_client.create_keypair(name=k_name)
# Now try the same keyname to create another key
@@ -68,14 +70,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1398abe1-4a84-45fb-9294-89f514daff00')
def test_create_keypair_with_empty_name_string(self):
- # Keypairs with name being an empty string should not be created
+ """Test keypairs with empty name should not be created"""
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
'')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3faa916f-779f-4103-aca7-dc3538eee1b7')
def test_create_keypair_with_long_keynames(self):
- # Keypairs with name longer than 255 chars should not be created
+ """Test keypairs with name longer than 255 should not be created"""
k_name = 'keypair-'.ljust(260, '0')
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
@@ -83,7 +85,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
def test_create_keypair_invalid_name(self):
- # Keypairs with name being an invalid name should not be created
+ """Test keypairs with an invalid name should not be created"""
k_name = r'key_/.\@:'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
diff --git a/tempest/api/compute/keypairs/test_keypairs_v22.py b/tempest/api/compute/keypairs/test_keypairs_v22.py
index 1aff262..e229c37 100644
--- a/tempest/api/compute/keypairs/test_keypairs_v22.py
+++ b/tempest/api/compute/keypairs/test_keypairs_v22.py
@@ -18,6 +18,8 @@
class KeyPairsV22TestJSON(test_keypairs.KeyPairsV2TestJSON):
+ """Test keypairs API with compute microversion greater than 2.1"""
+
min_microversion = '2.2'
max_microversion = 'latest'
@@ -43,9 +45,11 @@
@decorators.idempotent_id('8726fa85-7f98-4b20-af9e-f710a4f3391c')
def test_keypairsv22_create_list_show(self):
+ """Test create/list/show keypair"""
self._test_keypairs_create_list_show()
@decorators.idempotent_id('89d59d43-f735-441a-abcf-0601727f47b6')
def test_keypairsv22_create_list_show_with_type(self):
+ """Test create/list/show keypair with keypair type"""
keypair_type = 'x509'
self._test_keypairs_create_list_show(keypair_type=keypair_type)
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 8c2202e..c729069 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -18,6 +18,11 @@
class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
+ """Test compute absolute limits
+
+ Test compute absolute limits with compute microversion less than 2.57
+ """
+
max_microversion = '2.56'
@classmethod
@@ -27,12 +32,17 @@
@decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b')
def test_absLimits_get(self):
+ """Test getting nova absolute limits"""
# To check if all limits are present in the response (will be checked
# by schema)
self.client.show_limits()
class AbsoluteLimitsV257TestJSON(base.BaseV2ComputeTest):
+ """Test compute absolute limits
+
+ Test compute absolute limits with compute microversion greater than 2.56
+ """
min_microversion = '2.57'
max_microversion = 'latest'
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index 500638a..de6a9b9 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -20,6 +20,7 @@
class AbsoluteLimitsNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of nova absolute limits"""
def setUp(self):
# NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
@@ -34,7 +35,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
def test_max_metadata_exceed_limit(self):
- # We should not create vm with metadata over maxServerMeta limit
+ """Test creating server with metadata over limit should fail
+
+ We should not create server with metadata over maxServerMeta limit
+ """
# Get max limit value
limits = self.client.show_limits()['limits']
max_meta = limits['absolute']['maxServerMeta']
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 4c99ea6..59848f6 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -18,6 +18,10 @@
class SecurityGroupRulesTestJSON(base.BaseSecurityGroupsTest):
+ """Test security group rules API
+
+ Test security group rules API with compute microversion less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -55,8 +59,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('850795d7-d4d3-4e55-b527-a774c0123d3a')
def test_security_group_rules_create(self):
- # Positive test: Creation of Security Group rule
- # should be successful
+ """Test creating security group rules"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
securitygroup_id = security_group['id']
@@ -72,10 +75,7 @@
@decorators.idempotent_id('7a01873e-3c38-4f30-80be-31a043cfe2fd')
def test_security_group_rules_create_with_optional_cidr(self):
- # Positive test: Creation of Security Group rule
- # with optional argument cidr
- # should be successful
-
+ """Test creating security group rules with optional field cidr"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
parent_group_id = security_group['id']
@@ -94,10 +94,7 @@
@decorators.idempotent_id('7f5d2899-7705-4d4b-8458-4505188ffab6')
def test_security_group_rules_create_with_optional_group_id(self):
- # Positive test: Creation of Security Group rule
- # with optional argument group_id
- # should be successful
-
+ """Test creating security group rules with optional field group id"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
parent_group_id = security_group['id']
@@ -122,8 +119,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('a6154130-5a55-4850-8be4-5e9e796dbf17')
def test_security_group_rules_list(self):
- # Positive test: Created Security Group rules should be
- # in the list of all rules
+ """Test listing security group rules"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
securitygroup_id = security_group['id']
@@ -159,7 +155,7 @@
@decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf')
def test_security_group_rules_delete_when_peer_group_deleted(self):
- # Positive test:rule will delete when peer group deleting
+ """Test security group rule gets deleted when peer group is deleted"""
# Creating a Security Group to add rules to it
security_group = self.create_security_group()
sg1_id = security_group['id']
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 8283aae..3d000ca 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -20,6 +20,11 @@
class SecurityGroupRulesNegativeTestJSON(base.BaseSecurityGroupsTest):
+ """Negative tests of security group rules API
+
+ Negative tests of security group rules API with compute microversion
+ less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -29,8 +34,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1d507e98-7951-469b-82c3-23f1e6b8c254')
def test_create_security_group_rule_with_non_existent_id(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with non existent Parent group id
+ """Test creating security group rule with non existent parent group
+
+ Negative test: Creation of security group rule should fail
+ with non existent parent group id.
+ """
# Adding rules to the non existent Security Group id
parent_group_id = self.generate_random_security_group_id()
ip_protocol = 'tcp'
@@ -45,8 +53,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2244d7e4-adb7-4ecb-9930-2d77e123ce4f')
def test_create_security_group_rule_with_invalid_id(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with Parent group id which is not integer
+ """Test creating security group rule with invalid parent group id
+
+ Negative test: Creation of security group rule should fail
+ with parent group id which is not integer.
+ """
# Adding rules to the non int Security Group id
parent_group_id = data_utils.rand_name('non_int_id')
ip_protocol = 'tcp'
@@ -61,7 +72,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8bd56d02-3ffa-4d67-9933-b6b9a01d6089')
def test_create_security_group_rule_duplicate(self):
- # Negative test: Create Security Group rule duplicate should fail
+ """Test creating duplicate security group rule should fail"""
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -85,8 +96,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('84c81249-9f6e-439c-9bbf-cbb0d2cddbdf')
def test_create_security_group_rule_with_invalid_ip_protocol(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid ip_protocol
+ """Test creating security group rule with invalid ip protocol
+
+ Negative test: Creation of security group rule should fail
+ with invalid ip_protocol.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -104,8 +118,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('12bbc875-1045-4f7a-be46-751277baedb9')
def test_create_security_group_rule_with_invalid_from_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid from_port
+ """Test creating security group rule with invalid from_port
+
+ Negative test: Creation of security group rule should fail
+ with invalid from_port.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -122,8 +139,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ff88804d-144f-45d1-bf59-dd155838a43a')
def test_create_security_group_rule_with_invalid_to_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid to_port
+ """Test creating security group rule with invalid to_port
+
+ Negative test: Creation of security group rule should fail
+ with invalid to_port.
+ """
# Creating a Security Group to add rule to it
sg = self.create_security_group()
# Adding rules to the created Security Group
@@ -140,8 +160,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('00296fa9-0576-496a-ae15-fbab843189e0')
def test_create_security_group_rule_with_invalid_port_range(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid port range.
+ """Test creating security group rule with invalid port range
+
+ Negative test: Creation of security group rule should fail
+ with invalid port range.
+ """
# Creating a Security Group to add rule to it.
sg = self.create_security_group()
# Adding a rule to the created Security Group
@@ -158,8 +181,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('56fddcca-dbb8-4494-a0db-96e9f869527c')
def test_delete_security_group_rule_with_non_existent_id(self):
- # Negative test: Deletion of Security Group rule should be FAIL
- # with non existent id
+ """Test deleting non existent security group rule should fail"""
non_existent_rule_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound,
self.rules_client.delete_security_group_rule,
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 62d5bea..671a779 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -21,6 +21,7 @@
class SecurityGroupsTestJSON(base.BaseSecurityGroupsTest):
+ """Test security groups API with compute microversion less than 2.36"""
@classmethod
def setup_clients(cls):
@@ -30,7 +31,10 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('eb2b087d-633d-4d0d-a7bd-9e6ba35b32de')
def test_security_groups_create_list_delete(self):
- # Positive test:Should return the list of Security Groups
+ """Test create/list/delete security groups
+
+ Positive test: Should return the list of security groups.
+ """
# Create 3 Security Groups
security_group_list = []
for _ in range(3):
@@ -60,9 +64,11 @@
@decorators.idempotent_id('ecc0da4a-2117-48af-91af-993cca39a615')
def test_security_group_create_get_delete(self):
- # Security Group should be created, fetched and deleted
- # with char space between name along with
- # leading and trailing spaces
+ """Test create/get/delete security group
+
+ Security group should be created, fetched and deleted
+ with char space between name along with leading and trailing spaces.
+ """
s_name = ' %s ' % data_utils.rand_name('securitygroup ')
securitygroup = self.create_security_group(name=s_name)
securitygroup_name = securitygroup['name']
@@ -80,8 +86,11 @@
@decorators.idempotent_id('fe4abc0d-83f5-4c50-ad11-57a1127297a2')
def test_server_security_groups(self):
- # Checks that security groups may be added and linked to a server
- # and not deleted if the server is active.
+ """Test adding security groups to a server
+
+ Checks that security groups may be added and linked to a server
+ and not deleted if the server is active.
+ """
# Create a couple security groups that we will use
# for the server resource this test creates
sg = self.create_security_group()
@@ -121,7 +130,7 @@
@decorators.idempotent_id('7d4e1d3c-3209-4d6d-b020-986304ebad1f')
def test_update_security_groups(self):
- # Update security group name and description
+ """Test updating security group name and description"""
# Create a security group
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
@@ -139,6 +148,11 @@
@decorators.idempotent_id('79517d60-535a-438f-af3d-e6feab1cbea7')
def test_list_security_groups_by_server(self):
+ """Test listing security groups by server
+
+ Create security groups and add them to a server, then list security
+ groups by server, the added security groups should be in the list.
+ """
# Create a couple security groups that we will use
# for the server resource this test creates
sg = self.create_security_group()
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 9c44bb2..4607112 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -25,6 +25,11 @@
class SecurityGroupsNegativeTestJSON(base.BaseSecurityGroupsTest):
+ """Negative tests of security groups API
+
+ Negative tests of security groups API with compute microversion
+ less than 2.36.
+ """
@classmethod
def setup_clients(cls):
@@ -34,8 +39,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('673eaec1-9b3e-48ed-bdf1-2786c1b9661c')
def test_security_group_get_nonexistent_group(self):
- # Negative test:Should not be able to GET the details
- # of non-existent Security Group
+ """Test getting non existent security group details should fail"""
non_exist_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound, self.client.show_security_group,
non_exist_id)
@@ -45,8 +49,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1759c3cb-b0fc-44b7-86ce-c99236be911d')
def test_security_group_create_with_invalid_group_name(self):
- # Negative test: Security Group should not be created with group name
- # as an empty string/with white spaces/chars more than 255
+ """Test creating security group with invalid group name should fail
+
+ Negative test: Security group should not be created with group name
+ as an empty string, or group name with white spaces, or group name
+ with chars more than 255.
+ """
s_description = data_utils.rand_name('description')
# Create Security Group with empty string as group name
self.assertRaises(lib_exc.BadRequest,
@@ -67,9 +75,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('777b6f14-aca9-4758-9e84-38783cfa58bc')
def test_security_group_create_with_invalid_group_description(self):
- # Negative test: Security Group should not be created with description
- # longer than 255 chars. Empty description is allowed by the API
- # reference, however.
+ """Test creating security group with invalid group description
+
+ Negative test: Security group should not be created with description
+ longer than 255 chars. Empty description is allowed by the API
+ reference, however.
+ """
s_name = data_utils.rand_name('securitygroup')
# Create Security Group with group description longer than 255 chars
s_description = 'description-'.ljust(260, '0')
@@ -82,8 +93,7 @@
"Neutron allows duplicate names for security groups")
@decorators.attr(type=['negative'])
def test_security_group_create_with_duplicate_name(self):
- # Negative test:Security Group with duplicate name should not
- # be created
+ """Test creating security group with duplicate name should fail"""
s_name = data_utils.rand_name('securitygroup')
s_description = data_utils.rand_name('description')
self.create_security_group(name=s_name, description=s_description)
@@ -95,7 +105,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('36a1629f-c6da-4a26-b8b8-55e7e5d5cd58')
def test_delete_the_default_security_group(self):
- # Negative test:Deletion of the "default" Security Group should Fail
+ """Test deleting "default" security group should fail"""
default_security_group_id = None
body = self.client.list_security_groups()['security_groups']
for i in range(len(body)):
@@ -110,7 +120,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6727c00b-214c-4f9e-9a52-017ac3e98411')
def test_delete_nonexistent_security_group(self):
- # Negative test:Deletion of a non-existent Security Group should fail
+ """Test deleting non existent security group should fail"""
non_exist_id = self.generate_random_security_group_id()
self.assertRaises(lib_exc.NotFound,
self.client.delete_security_group, non_exist_id)
@@ -118,8 +128,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1438f330-8fa4-4aeb-8a94-37c250106d7f')
def test_delete_security_group_without_passing_id(self):
- # Negative test:Deletion of a Security Group with out passing ID
- # should Fail
+ """Test deleting security group passing empty group id should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.delete_security_group, '')
@@ -128,7 +137,7 @@
"Neutron does not check the security group ID")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_id(self):
- # Update security_group with invalid sg_id should fail
+ """Test updating security group with invalid group id should fail"""
s_name = data_utils.rand_name('sg')
s_description = data_utils.rand_name('description')
# Create a non int sg_id
@@ -142,7 +151,7 @@
"Neutron does not check the security group name")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_name(self):
- # Update security_group with invalid sg_name should fail
+ """Test updating security group to invalid group name should fail"""
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
# Update Security Group with group name longer than 255 chars
@@ -156,7 +165,7 @@
"Neutron does not check the security group description")
@decorators.attr(type=['negative'])
def test_update_security_group_with_invalid_sg_des(self):
- # Update security_group with invalid sg_des should fail
+ """Test updating security group to invalid description should fail"""
securitygroup = self.create_security_group()
securitygroup_id = securitygroup['id']
# Update Security Group with group description longer than 255 chars
@@ -168,7 +177,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('27edee9c-873d-4da6-a68a-3c256efebe8f')
def test_update_non_existent_security_group(self):
- # Update a non-existent Security Group should Fail
+ """Test updating a non existent security group should fail"""
non_exist_id = self.generate_random_security_group_id()
s_name = data_utils.rand_name('sg')
s_description = data_utils.rand_name('description')
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index c1af6c7..0601bbe 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -99,6 +99,7 @@
class AttachInterfacesTestJSON(AttachInterfacesTestBase):
+ """Test attaching interfaces"""
def wait_for_port_detach(self, port_id):
"""Waits for the port's device_id to be unset.
@@ -230,6 +231,7 @@
@decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051')
@utils.services('network')
def test_create_list_show_delete_interfaces_by_network_port(self):
+ """Test create/list/show/delete interfaces by network port"""
server, ifs, _ = self._create_server_get_interfaces()
interface_count = len(ifs)
self.assertGreater(interface_count, 0)
@@ -262,6 +264,7 @@
@decorators.idempotent_id('d290c06c-f5b3-11e7-8ec8-002293781009')
@utils.services('network')
def test_create_list_show_delete_interfaces_by_fixed_ip(self):
+ """Test create/list/show/delete interfaces by fixed ip"""
# NOTE(zhufl) By default only project that is admin or network owner
# or project with role advsvc is authorised to create interfaces with
# fixed-ip, so if we don't create network for each project, do not
@@ -290,7 +293,7 @@
@decorators.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
def test_reassign_port_between_servers(self):
- """Tests the following:
+ """Tests reassigning port between servers
1. Create a port in Neutron.
2. Create two servers in Nova.
@@ -343,12 +346,15 @@
class AttachInterfacesUnderV243Test(AttachInterfacesTestBase):
+ """Test attaching interfaces with compute microversion less than 2.44"""
+
max_microversion = '2.43'
@decorators.attr(type='smoke')
@decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9')
@utils.services('network')
def test_add_remove_fixed_ip(self):
+ """Test adding and removing fixed ip from server"""
# NOTE(zhufl) By default only project that is admin or network owner
# or project with role advsvc is authorised to add interfaces with
# fixed-ip, so if we don't create network for each project, do not
diff --git a/tempest/api/compute/servers/test_availability_zone.py b/tempest/api/compute/servers/test_availability_zone.py
index 36828d6..d239149 100644
--- a/tempest/api/compute/servers/test_availability_zone.py
+++ b/tempest/api/compute/servers/test_availability_zone.py
@@ -27,6 +27,6 @@
@decorators.idempotent_id('a8333aa2-205c-449f-a828-d38c2489bf25')
def test_get_availability_zone_list_with_non_admin_user(self):
- # List of availability zone with non-administrator user
+ """List of availability zone with non-administrator user"""
availability_zone = self.client.list_availability_zones()
self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 4f0dbad..48f32a8 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -27,6 +27,11 @@
class ServersTestJSON(base.BaseV2ComputeTest):
+ """Test creating server and verifying the server attributes
+
+ This is to create server booted from image and with disk_config 'AUTO'
+ """
+
disk_config = 'AUTO'
volume_backed = False
@@ -62,13 +67,12 @@
disk_config=disk_config,
adminPass=cls.password,
volume_backed=cls.volume_backed)
- cls.server = (cls.client.show_server(server_initial['id'])
- ['server'])
+ cls.server = cls.client.show_server(server_initial['id'])['server']
@decorators.attr(type='smoke')
@decorators.idempotent_id('5de47127-9977-400a-936f-abcfbec1218f')
def test_verify_server_details(self):
- # Verify the specified server attributes are set correctly
+ """Verify the specified server attributes are set correctly"""
self.assertEqual(self.accessIPv4, self.server['accessIPv4'])
# NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4)
# Here we compare directly with the canonicalized format.
@@ -86,7 +90,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('9a438d88-10c6-4bcd-8b5b-5b6e25e1346f')
def test_list_servers(self):
- # The created server should be in the list of all servers
+ """The created server should be in the list of all servers"""
body = self.client.list_servers()
servers = body['servers']
found = [i for i in servers if i['id'] == self.server['id']]
@@ -94,7 +98,7 @@
@decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
def test_list_servers_with_detail(self):
- # The created server should be in the detailed list of all servers
+ """The created server should be in the detailed list of all servers"""
body = self.client.list_servers(detail=True)
servers = body['servers']
found = [i for i in servers if i['id'] == self.server['id']]
@@ -104,8 +108,11 @@
@testtools.skipUnless(CONF.validation.run_validation,
'Instance validation tests are disabled.')
def test_verify_created_server_vcpus(self):
- # Verify that the number of vcpus reported by the instance matches
- # the amount stated by the flavor
+ """The created server should have the same specification as the flavor
+
+ Verify that the number of vcpus reported by the instance matches
+ the amount stated by the flavor
+ """
flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
validation_resources = self.get_class_validation_resources(
self.os_primary)
@@ -123,7 +130,7 @@
@testtools.skipUnless(CONF.validation.run_validation,
'Instance validation tests are disabled.')
def test_host_name_is_same_as_server_name(self):
- # Verify the instance host name is the same as the server name
+ """Verify the instance host name is the same as the server name"""
validation_resources = self.get_class_validation_resources(
self.os_primary)
linux_client = remote_client.RemoteClient(
@@ -145,6 +152,10 @@
class ServersTestManualDisk(ServersTestJSON):
+ """Test creating server and verifying the server attributes
+
+ This is to create server booted from image and with disk_config 'MANUAL'
+ """
disk_config = 'MANUAL'
@classmethod
@@ -156,7 +167,11 @@
class ServersTestBootFromVolume(ServersTestJSON):
- """Run the `ServersTestJSON` tests with a volume backed VM"""
+ """Test creating server and verifying the server attributes
+
+ This is to create server booted from volume and with disk_config 'AUTO'
+ """
+ # Run the `ServersTestJSON` tests with a volume backed VM
volume_backed = True
@classmethod
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index a7db88a..ee25a22 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -26,6 +26,7 @@
class DeleteServersTestJSON(base.BaseV2ComputeTest):
+ """Test deleting servers in various states"""
create_default_network = True
# NOTE: Server creations of each test class should be under 10
@@ -38,21 +39,21 @@
@decorators.idempotent_id('9e6e0c87-3352-42f7-9faf-5d6210dbd159')
def test_delete_server_while_in_building_state(self):
- # Delete a server while it's VM state is Building
+ """Test deleting a server while it's VM state is Building"""
server = self.create_test_server(wait_until='BUILD')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
@decorators.idempotent_id('925fdfb4-5b13-47ea-ac8a-c36ae6fddb05')
def test_delete_active_server(self):
- # Delete a server while it's VM state is Active
+ """Test deleting a server while it's VM state is Active"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
@decorators.idempotent_id('546d368c-bb6c-4645-979a-83ed16f3a6be')
def test_delete_server_while_in_shutoff_state(self):
- # Delete a server while it's VM state is Shutoff
+ """Test deleting a server while it's VM state is Shutoff"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.stop_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF')
@@ -63,7 +64,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
def test_delete_server_while_in_pause_state(self):
- # Delete a server while it's VM state is Pause
+ """Test deleting a server while it's VM state is Pause"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.pause_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'PAUSED')
@@ -74,7 +75,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
'Suspend is not available.')
def test_delete_server_while_in_suspended_state(self):
- # Delete a server while it's VM state is Suspended
+ """Test deleting a server while it's VM state is Suspended"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.suspend_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SUSPENDED')
@@ -85,7 +86,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
def test_delete_server_while_in_shelved_state(self):
- # Delete a server while it's VM state is Shelved
+ """Test deleting a server while it's VM state is Shelved"""
server = self.create_test_server(wait_until='ACTIVE')
compute.shelve_server(self.client, server['id'])
@@ -96,7 +97,7 @@
@testtools.skipIf(not CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_delete_server_while_in_verify_resize_state(self):
- # Delete a server while it's VM state is VERIFY_RESIZE
+ """Test deleting a server while it's VM state is VERIFY_RESIZE"""
server = self.create_test_server(wait_until='ACTIVE')
self.client.resize_server(server['id'], self.flavor_ref_alt)
waiters.wait_for_server_status(self.client, server['id'],
@@ -107,7 +108,7 @@
@decorators.idempotent_id('d0f3f0d6-d9b6-4a32-8da4-23015dcab23c')
@utils.services('volume')
def test_delete_server_while_in_attached_volume(self):
- # Delete a server while a volume is attached to it
+ """Test deleting a server while a volume is attached to it"""
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 8879369..a7e2187 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -103,6 +103,7 @@
class TaggedBootDevicesTest(DeviceTaggingBase):
+ """Test tagged boot device with compute microversion equals 2.32"""
min_microversion = '2.32'
# NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
@@ -149,6 +150,16 @@
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@utils.services('network', 'volume', 'image')
def test_tagged_boot_devices(self):
+ """Test tagged boot devices
+
+ 1. Create volumes
+ 2. Create networks
+ 3. Create subnets
+ 4. Create ports
+ 5. Create server, specifying tags for items in networks and
+ block_device_mapping_v2,
+ 6. Verify tagged devices are in server via metadata service
+ """
# Create volumes
# The create_volume methods waits for the volumes to be available and
# the base class will clean them up on tearDown.
@@ -300,11 +311,14 @@
class TaggedBootDevicesTest_v242(TaggedBootDevicesTest):
+ """Test tagged boot devices with compute microversion greater than 2.41"""
+
min_microversion = '2.42'
max_microversion = 'latest'
class TaggedAttachmentsTest(DeviceTaggingBase):
+ """Test tagged attachments with compute microversion greater than 2.48"""
min_microversion = '2.49'
max_microversion = 'latest'
@@ -342,6 +356,16 @@
@decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c')
@utils.services('network', 'volume', 'image')
def test_tagged_attachment(self):
+ """Test tagged attachment
+
+ 1. Create network
+ 2. Create subnet
+ 3. Create volume
+ 4. Create server
+ 5. Attach tagged networks and volume
+ 6. Verify tagged devices are in server via metadata service
+ 7. Detach tagged networks and volume
+ """
# Create network
net = self.networks_client.create_network(
name=data_utils.rand_name(
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 5b8e7ab..e5e051a 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -24,6 +24,8 @@
class ServerDiskConfigTestJSON(base.BaseV2ComputeTest):
+ """Test disk config option of server"""
+
create_default_network = True
@classmethod
@@ -49,7 +51,7 @@
@decorators.idempotent_id('bef56b09-2e8c-4883-a370-4950812f430e')
def test_rebuild_server_with_manual_disk_config(self):
- # A server should be rebuilt using the manual disk config option
+ """A server should be rebuilt using the manual disk config option"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
self._update_server_with_disk_config(server['id'],
@@ -68,7 +70,7 @@
@decorators.idempotent_id('9c9fae77-4feb-402f-8450-bf1c8b609713')
def test_rebuild_server_with_auto_disk_config(self):
- # A server should be rebuilt using the auto disk config option
+ """A server should be rebuilt using the auto disk config option"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
self._update_server_with_disk_config(server['id'],
@@ -89,7 +91,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_resize_server_from_manual_to_auto(self):
- # A server should be resized from manual to auto disk config
+ """A server should be resized from manual to auto disk config"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
self._update_server_with_disk_config(server['id'],
@@ -105,7 +107,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_resize_server_from_auto_to_manual(self):
- # A server should be resized from auto to manual disk config
+ """A server should be resized from auto to manual disk config"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
self._update_server_with_disk_config(server['id'],
@@ -119,7 +121,7 @@
@decorators.idempotent_id('5ef18867-358d-4de9-b3c9-94d4ba35742f')
def test_update_server_from_auto_to_manual(self):
- # A server should be updated from auto to manual disk config
+ """A server should be updated from auto to manual disk config"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
self._update_server_with_disk_config(server['id'],
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 00837eb..5ab592a 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -19,6 +19,8 @@
class InstanceActionsTestJSON(base.BaseV2ComputeTest):
+ """Test instance actions API"""
+
create_default_network = True
@classmethod
@@ -34,7 +36,7 @@
@decorators.idempotent_id('77ca5cc5-9990-45e0-ab98-1de8fead201a')
def test_list_instance_actions(self):
- # List actions of the provided server
+ """Test listing actions of the provided server"""
self.client.reboot_server(self.server['id'], type='HARD')
waiters.wait_for_server_status(self.client,
self.server['id'], 'ACTIVE')
@@ -47,7 +49,7 @@
@decorators.idempotent_id('aacc71ca-1d70-4aa5-bbf6-0ff71470e43c')
def test_get_instance_action(self):
- # Get the action details of the provided server
+ """Test getting the action details of the provided server"""
body = self.client.show_instance_action(
self.server['id'], self.request_id)['instanceAction']
self.assertEqual(self.server['id'], body['instance_uuid'])
@@ -55,6 +57,8 @@
class InstanceActionsV221TestJSON(base.BaseV2ComputeTest):
+ """Test instance actions with compute microversion greater than 2.20"""
+
create_default_network = True
min_microversion = '2.21'
@@ -67,8 +71,11 @@
@decorators.idempotent_id('0a0f85d4-10fa-41f6-bf80-a54fb4aa2ae1')
def test_get_list_deleted_instance_actions(self):
+ """Test listing actions for deleted instance
- # List actions of the deleted server
+ Listing actions for deleted instance should succeed and the returned
+ actions should contain 'create' and 'delete'.
+ """
server = self.create_test_server(wait_until='ACTIVE')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
index 4b5a2c3..dd2bf06 100644
--- a/tempest/api/compute/servers/test_instance_actions_negative.py
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -20,6 +20,8 @@
class InstanceActionsNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of instance actions"""
+
create_default_network = True
@classmethod
@@ -35,7 +37,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('67e1fce6-7ec2-45c6-92d4-0a8f1a632910')
def test_list_instance_actions_non_existent_server(self):
- # List actions of the non-existent server id
+ """Test listing actions for non existent instance should fail"""
non_existent_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.list_instance_actions,
@@ -44,6 +46,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0269f40a-6f18-456c-b336-c03623c897f1')
def test_get_instance_action_invalid_request(self):
- # Get the action details of the provided server with invalid request
+ """Test getting instance action with invalid request_id should fail"""
self.assertRaises(lib_exc.NotFound, self.client.show_instance_action,
self.server['id'], '999')
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 3dffd01..990dd52 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -26,6 +26,7 @@
class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
+ """Test listing servers filtered by specified attribute"""
@classmethod
def setup_credentials(cls):
@@ -71,7 +72,7 @@
@testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt,
"Need distinct images to run this test")
def test_list_servers_filter_by_image(self):
- # Filter the list of servers by image
+ """Filter the list of servers by image"""
params = {'image': self.image_ref}
body = self.client.list_servers(**params)
servers = body['servers']
@@ -82,7 +83,7 @@
@decorators.idempotent_id('573637f5-7325-47bb-9144-3476d0416908')
def test_list_servers_filter_by_flavor(self):
- # Filter the list of servers by flavor
+ """Filter the list of servers by flavor"""
params = {'flavor': self.flavor_ref_alt}
body = self.client.list_servers(**params)
servers = body['servers']
@@ -93,7 +94,7 @@
@decorators.idempotent_id('9b067a7b-7fee-4f6a-b29c-be43fe18fc5a')
def test_list_servers_filter_by_server_name(self):
- # Filter the list of servers by server name
+ """Filter the list of servers by server name"""
params = {'name': self.s1_name}
body = self.client.list_servers(**params)
servers = body['servers']
@@ -104,7 +105,7 @@
@decorators.idempotent_id('ca78e20e-fddb-4ce6-b7f7-bcbf8605e66e')
def test_list_servers_filter_by_active_status(self):
- # Filter the list of servers by server active status
+ """Filter the list of servers by server active status"""
params = {'status': 'active'}
body = self.client.list_servers(**params)
servers = body['servers']
@@ -115,7 +116,7 @@
@decorators.idempotent_id('451dbbb2-f330-4a9f-b0e1-5f5d2cb0f34c')
def test_list_servers_filter_by_shutoff_status(self):
- # Filter the list of servers by server shutoff status
+ """Filter the list of servers by server shutoff status"""
params = {'status': 'shutoff'}
self.client.stop_server(self.s1['id'])
waiters.wait_for_server_status(self.client, self.s1['id'],
@@ -132,21 +133,30 @@
@decorators.idempotent_id('614cdfc1-d557-4bac-915b-3e67b48eee76')
def test_list_servers_filter_by_limit(self):
- # Verify only the expected number of servers are returned
+ """Filter the list of servers by limit 1
+
+ Verify only the expected number of servers are returned (one server)
+ """
params = {'limit': 1}
servers = self.client.list_servers(**params)
self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
@decorators.idempotent_id('b1495414-2d93-414c-8019-849afe8d319e')
def test_list_servers_filter_by_zero_limit(self):
- # Verify only the expected number of servers are returned
+ """Filter the list of servers by limit 0
+
+ Verify only the expected number of servers are returned (no server)
+ """
params = {'limit': 0}
servers = self.client.list_servers(**params)
self.assertEmpty(servers['servers'])
@decorators.idempotent_id('37791bbd-90c0-4de0-831e-5f38cba9c6b3')
def test_list_servers_filter_by_exceed_limit(self):
- # Verify only the expected number of servers are returned
+ """Filter the list of servers by exceeded limit
+
+ Verify only the expected number of servers are returned (all servers)
+ """
params = {'limit': 100000}
servers = self.client.list_servers(**params)
all_servers = self.client.list_servers()
@@ -157,7 +167,7 @@
@testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt,
"Need distinct images to run this test")
def test_list_servers_detailed_filter_by_image(self):
- # Filter the detailed list of servers by image
+ """"Filter the detailed list of servers by image"""
params = {'image': self.image_ref}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
@@ -168,7 +178,7 @@
@decorators.idempotent_id('80c574cc-0925-44ba-8602-299028357dd9')
def test_list_servers_detailed_filter_by_flavor(self):
- # Filter the detailed list of servers by flavor
+ """Filter the detailed list of servers by flavor"""
params = {'flavor': self.flavor_ref_alt}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
@@ -179,7 +189,7 @@
@decorators.idempotent_id('f9eb2b70-735f-416c-b260-9914ac6181e4')
def test_list_servers_detailed_filter_by_server_name(self):
- # Filter the detailed list of servers by server name
+ """Filter the detailed list of servers by server name"""
params = {'name': self.s1_name}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
@@ -190,7 +200,7 @@
@decorators.idempotent_id('de2612ab-b7dd-4044-b0b1-d2539601911f')
def test_list_servers_detailed_filter_by_server_status(self):
- # Filter the detailed list of servers by server status
+ """Filter the detailed list of servers by server status"""
params = {'status': 'active'}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
@@ -204,6 +214,7 @@
@decorators.idempotent_id('e9f624ee-92af-4562-8bec-437945a18dcb')
def test_list_servers_filtered_by_name_wildcard(self):
+ """Filter the list of servers by part of server name"""
# List all servers that contains '-instance' in name
params = {'name': '-instance'}
body = self.client.list_servers(**params)
@@ -226,6 +237,7 @@
@decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b')
def test_list_servers_filtered_by_name_regex(self):
+ """Filter the list of servers by server name regular expression"""
# list of regex that should match s1, s2 and s3
regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
for regex in regexes:
@@ -250,7 +262,7 @@
@decorators.idempotent_id('43a1242e-7b31-48d1-88f2-3f72aa9f2077')
def test_list_servers_filtered_by_ip(self):
- # Filter servers by ip
+ """Filter the list of servers by server ip address"""
# Here should be listed 1 server
if not self.fixed_network_name:
msg = 'fixed_network_name needs to be configured to run this test'
@@ -284,22 +296,32 @@
for ip in ip_list:
self.assertNotIn(ip_list[ip], map(lambda x: x['id'], servers))
- @decorators.skip_because(bug="1540645")
@decorators.idempotent_id('a905e287-c35e-42f2-b132-d02b09f3654a')
def test_list_servers_filtered_by_ip_regex(self):
- # Filter servers by regex ip
- # List all servers filtered by part of ip address.
- # Here should be listed all servers
+ """Filter the list of servers by part of server ip address"""
if not self.fixed_network_name:
msg = 'fixed_network_name needs to be configured to run this test'
raise self.skipException(msg)
- self.s1 = self.client.show_server(self.s1['id'])['server']
- addr_spec = self.s1['addresses'][self.fixed_network_name][0]
- ip = addr_spec['addr'][0:-3]
+ # query addresses of the 3 servers
+ addrs = []
+ for s in [self.s1, self.s2, self.s3]:
+ s_show = self.client.show_server(s['id'])['server']
+ addr_spec = s_show['addresses'][self.fixed_network_name][0]
+ addrs.append(addr_spec['addr'])
+ # find common part of the 3 ip addresses
+ prefix = ''
+ addrs_len = [len(a) for a in addrs]
+ addrs_len.sort()
+ # iterate over the smallest length of an ip
+ for i in range(addrs_len[0]):
+ if not addrs[0][i] == addrs[1][i] == addrs[2][i]:
+ break
+ prefix += addrs[0][i]
+
if addr_spec['version'] == 4:
- params = {'ip': ip}
+ params = {'ip': prefix}
else:
- params = {'ip6': ip}
+ params = {'ip6': prefix}
# capture all servers in case something goes wrong
all_servers = self.client.list_servers(detail=True)
body = self.client.list_servers(**params)
@@ -317,7 +339,10 @@
@decorators.idempotent_id('67aec2d0-35fe-4503-9f92-f13272b867ed')
def test_list_servers_detailed_limit_results(self):
- # Verify only the expected number of detailed results are returned
+ """Filter the detailed list of servers by limit 1
+
+ Verify only the expected number of servers are returned (one server)
+ """
params = {'limit': 1}
servers = self.client.list_servers(detail=True, **params)
self.assertEqual(1, len(servers['servers']))
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index b95db5c..3d55696 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -20,6 +20,8 @@
class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of listing servers"""
+
create_default_network = True
@classmethod
@@ -45,7 +47,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('24a26f1a-1ddc-4eea-b0d7-a90cc874ad8f')
def test_list_servers_with_a_deleted_server(self):
- # Verify deleted servers do not show by default in list servers
+ """Test that deleted servers do not show by default in list servers"""
# List servers and verify server not returned
body = self.client.list_servers()
servers = body['servers']
@@ -56,7 +58,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
def test_list_servers_by_non_existing_image(self):
- # Listing servers for a non existing image returns empty list
+ """Test listing servers for a non existing image returns empty list"""
body = self.client.list_servers(image='non_existing_image')
servers = body['servers']
self.assertEmpty(servers)
@@ -64,7 +66,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
def test_list_servers_by_non_existing_flavor(self):
- # Listing servers by non existing flavor returns empty list
+ """Test listing servers by non existing flavor returns empty list"""
body = self.client.list_servers(flavor='non_existing_flavor')
servers = body['servers']
self.assertEmpty(servers)
@@ -72,7 +74,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c')
def test_list_servers_by_non_existing_server_name(self):
- # Listing servers for a non existent server name returns empty list
+ """Test listing servers for a non existent server name
+
+ Listing servers for a non existent server name should return empty
+ list.
+ """
+
body = self.client.list_servers(name='non_existing_server_name')
servers = body['servers']
self.assertEmpty(servers)
@@ -80,9 +87,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
def test_list_servers_status_non_existing(self):
- # When invalid status is specified, up to microversion 2.37,
- # an empty list is returned, and starting from microversion 2.38,
- # a 400 error is returned in that case.
+ """Test listing servers with non existing status
+
+ When invalid status is specified, up to microversion 2.37,
+ an empty list is returned, and starting from microversion 2.38,
+ a 400 error is returned in that case.
+ """
+
if self.is_requested_microversion_compatible('2.37'):
body = self.client.list_servers(status='non_existing_status')
servers = body['servers']
@@ -94,6 +105,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
def test_list_servers_by_limits_greater_than_actual_count(self):
+ """Test listing servers by limit greater than actual count
+
+ Listing servers by limit greater than actual count should return
+ all servers.
+ """
+
# Gather the complete list of servers in the project for reference
full_list = self.client.list_servers()['servers']
# List servers by specifying a greater value for limit
@@ -104,21 +121,21 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6')
def test_list_servers_by_limits_pass_string(self):
- # Return an error if a string value is passed for limit
+ """Test listing servers by non-integer limit should fail"""
self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
limit='testing')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('62610dd9-4713-4ee0-8beb-fd2c1aa7f950')
def test_list_servers_by_limits_pass_negative_value(self):
- # Return an error if a negative value for limit is passed
+ """Test listing servers by negative limit should fail"""
self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
limit=-1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('87d12517-e20a-4c9c-97b6-dd1628d6d6c9')
def test_list_servers_by_changes_since_invalid_date(self):
- # Return an error when invalid date format is passed
+ """Test listing servers by invalid changes-since format should fail"""
params = {'changes-since': '2011/01/01'}
self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
**params)
@@ -126,7 +143,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f')
def test_list_servers_by_changes_since_future_date(self):
- # Return an empty list when a date in the future is passed.
+ """Test listing servers by a future changes-since date
+
+ Return an empty list when a date in the future is passed as
+ changes-since value.
+ """
+
# updated_at field may haven't been set at the point in the boot
# process where build_request still exists, so add
# {'status': 'ACTIVE'} along with changes-since as filter.
@@ -138,7 +160,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570')
def test_list_servers_detail_server_is_deleted(self):
- # Server details are not listed for a deleted server
+ """Test listing servers detail should not contain deleted server"""
body = self.client.list_servers(detail=True)
servers = body['servers']
actual = [srv for srv in servers
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index dcadace..10c76bb 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -19,11 +19,15 @@
class MultipleCreateTestJSON(base.BaseV2ComputeTest):
+ """Test creating multiple servers in one request"""
create_default_network = True
@decorators.idempotent_id('61e03386-89c3-449c-9bb1-a06f423fd9d1')
def test_multiple_create(self):
- # Creating server with min_count=2, 2 servers will be created.
+ """Test creating multiple servers in one request
+
+ Creating server with min_count=2, 2 servers will be created.
+ """
tenant_network = self.get_tenant_network()
body, servers = compute.create_test_server(
self.os_primary,
@@ -40,8 +44,12 @@
@decorators.idempotent_id('864777fb-2f1e-44e3-b5b9-3eb6fa84f2f7')
def test_multiple_create_with_reservation_return(self):
- # Creating multiple servers with return_reservation_id=True,
- # reservation_id will be returned.
+ """Test creating multiple servers with return_reservation_id=True
+
+ Creating multiple servers with return_reservation_id=True,
+ reservation_id will be returned.
+ """
+
body = self.create_test_server(wait_until='ACTIVE',
min_count=1,
max_count=2,
diff --git a/tempest/api/compute/servers/test_multiple_create_negative.py b/tempest/api/compute/servers/test_multiple_create_negative.py
index 6bdf83b..3a970dd 100644
--- a/tempest/api/compute/servers/test_multiple_create_negative.py
+++ b/tempest/api/compute/servers/test_multiple_create_negative.py
@@ -19,11 +19,12 @@
class MultipleCreateNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of creating multiple servers in one request"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('daf29d8d-e928-4a01-9a8c-b129603f3fc0')
def test_min_count_less_than_one(self):
- # Creating server with min_count=0 should fail.
+ """Test creating server with min_count=0 should fail"""
invalid_min_count = 0
self.assertRaises(lib_exc.BadRequest, self.create_test_server,
min_count=invalid_min_count)
@@ -31,7 +32,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('999aa722-d624-4423-b813-0d1ac9884d7a')
def test_min_count_non_integer(self):
- # Creating server with non-integer min_count should fail.
+ """Test creating server with non-integer min_count should fail"""
invalid_min_count = 2.5
self.assertRaises(lib_exc.BadRequest, self.create_test_server,
min_count=invalid_min_count)
@@ -39,7 +40,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a6f9c2ab-e060-4b82-b23c-4532cb9390ff')
def test_max_count_less_than_one(self):
- # Creating server with max_count < 1 shoudld fail.
+ """Test creating server with max_count < 1 shoudld fail"""
invalid_max_count = 0
self.assertRaises(lib_exc.BadRequest, self.create_test_server,
max_count=invalid_max_count)
@@ -47,7 +48,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c5698d1-d7af-4c80-b971-9d403135eea2')
def test_max_count_non_integer(self):
- # Creating server with non-integer max_count should fail.
+ """Test creating server with non-integer max_count should fail"""
invalid_max_count = 2.5
self.assertRaises(lib_exc.BadRequest, self.create_test_server,
max_count=invalid_max_count)
@@ -55,7 +56,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('476da616-f1ef-4271-a9b1-b9fc87727cdf')
def test_max_count_less_than_min_count(self):
- # Creating server with max_count less than min_count should fail.
+ """Test creating server with max_count < min_count should fail"""
min_count = 3
max_count = 2
self.assertRaises(lib_exc.BadRequest, self.create_test_server,
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 68e09e7..7931ca9 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -33,6 +33,8 @@
class NoVNCConsoleTestJSON(base.BaseV2ComputeTest):
+ """Test novnc console"""
+
create_default_network = True
@classmethod
@@ -181,6 +183,7 @@
@decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
def test_novnc(self):
+ """Test accessing novnc console of server"""
if self.use_get_remote_console:
body = self.client.get_remote_console(
self.server['id'], console_type='novnc',
@@ -200,6 +203,11 @@
@decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
def test_novnc_bad_token(self):
+ """Test accessing novnc console with bad token
+
+ Do the WebSockify HTTP Request to novnc proxy with a bad token,
+ the novnc proxy should reject the connection and closed it.
+ """
if self.use_get_remote_console:
body = self.client.get_remote_console(
self.server['id'], console_type='novnc',
diff --git a/tempest/api/compute/servers/test_server_addresses_negative.py b/tempest/api/compute/servers/test_server_addresses_negative.py
index f33c6d9..e7444d2 100644
--- a/tempest/api/compute/servers/test_server_addresses_negative.py
+++ b/tempest/api/compute/servers/test_server_addresses_negative.py
@@ -20,6 +20,7 @@
class ServerAddressesNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of listing server addresses"""
create_default_network = True
@classmethod
@@ -36,7 +37,7 @@
@decorators.idempotent_id('02c3f645-2d2e-4417-8525-68c0407d001b')
@utils.services('network')
def test_list_server_addresses_invalid_server_id(self):
- # List addresses request should fail if server id not in system
+ """List addresses request should fail if server id not in system"""
self.assertRaises(lib_exc.NotFound, self.client.list_addresses,
'999')
@@ -44,7 +45,7 @@
@decorators.idempotent_id('a2ab5144-78c0-4942-a0ed-cc8edccfd9ba')
@utils.services('network')
def test_list_server_addresses_by_network_neg(self):
- # List addresses by network should fail if network name not valid
+ """List addresses by network should fail if network name not valid"""
self.assertRaises(lib_exc.NotFound,
self.client.list_addresses_by_network,
self.server['id'], 'invalid')
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 4b5efaa..4c0d021 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -82,18 +82,18 @@
@decorators.idempotent_id('5dc57eda-35b7-4af7-9e5f-3c2be3d2d68b')
def test_create_delete_server_group_with_affinity_policy(self):
- # Create and Delete the server-group with affinity policy
+ """Test Create/Delete the server-group with affinity policy"""
self._create_delete_server_group(self.policy)
@decorators.idempotent_id('3645a102-372f-4140-afad-13698d850d23')
def test_create_delete_server_group_with_anti_affinity_policy(self):
- # Create and Delete the server-group with anti-affinity policy
+ """Test Create/Delete the server-group with anti-affinity policy"""
policy = ['anti-affinity']
self._create_delete_server_group(policy)
@decorators.idempotent_id('154dc5a4-a2fe-44b5-b99e-f15806a4a113')
def test_create_delete_multiple_server_groups_with_same_name_policy(self):
- # Create and Delete the server-groups with same name and same policy
+ """Test Create/Delete the server-groups with same name and policy"""
server_groups = []
server_group_name = data_utils.rand_name('server-group')
for _ in range(0, 2):
@@ -108,14 +108,14 @@
@decorators.idempotent_id('b3545034-dd78-48f0-bdc2-a4adfa6d0ead')
def test_show_server_group(self):
- # Get the server-group
+ """Test getting the server-group detail"""
body = self.client.show_server_group(
self.created_server_group['id'])['server_group']
self.assertEqual(self.created_server_group, body)
@decorators.idempotent_id('d4874179-27b4-4d7d-80e4-6c560cdfe321')
def test_list_server_groups(self):
- # List the server-group
+ """Test listing the server-groups"""
body = self.client.list_server_groups()['server_groups']
self.assertIn(self.created_server_group, body)
@@ -124,7 +124,7 @@
compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
'ServerGroupAffinityFilter is not available.')
def test_create_server_with_scheduler_hint_group(self):
- # Create a server with the scheduler hint "group".
+ """Test creating a server with the scheduler hint 'group'"""
hints = {'group': self.created_server_group['id']}
server = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 9d87e1c..9f93e76 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -14,13 +14,26 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest.lib import decorators
+CONF = config.CONF
+
+# TODO(stephenfin): Remove these tests once the nova Ussuri branch goes EOL
class ServerMetadataTestJSON(base.BaseV2ComputeTest):
+ """Test server metadata"""
+
create_default_network = True
@classmethod
+ def skip_checks(cls):
+ super(ServerMetadataTestJSON, cls).skip_checks()
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise cls.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
+ @classmethod
def setup_clients(cls):
super(ServerMetadataTestJSON, cls).setup_clients()
cls.client = cls.servers_client
@@ -37,7 +50,10 @@
@decorators.idempotent_id('479da087-92b3-4dcf-aeb3-fd293b2d14ce')
def test_list_server_metadata(self):
- # All metadata key/value pairs for a server should be returned
+ """Test listing server metadata
+
+ All metadata key/value pairs for a server should be returned.
+ """
resp_metadata = (self.client.list_server_metadata(self.server['id'])
['metadata'])
@@ -47,7 +63,10 @@
@decorators.idempotent_id('211021f6-21de-4657-a68f-908878cfe251')
def test_set_server_metadata(self):
- # The server's metadata should be replaced with the provided values
+ """Test setting server metadata
+
+ The server's metadata should be replaced with the provided values
+ """
# Create a new set of metadata for the server
req_metadata = {'meta2': 'data2', 'meta3': 'data3'}
self.client.set_server_metadata(self.server['id'], req_metadata)
@@ -60,8 +79,10 @@
@decorators.idempotent_id('344d981e-0c33-4997-8a5d-6c1d803e4134')
def test_update_server_metadata(self):
- # The server's metadata values should be updated to the
- # provided values
+ """Test updating server metadata
+
+ The server's metadata values should be updated to the provided values.
+ """
meta = {'key1': 'alt1', 'key3': 'value3'}
self.client.update_server_metadata(self.server['id'], meta)
@@ -73,8 +94,11 @@
@decorators.idempotent_id('0f58d402-e34a-481d-8af8-b392b17426d9')
def test_update_metadata_empty_body(self):
- # The original metadata should not be lost if empty metadata body is
- # passed
+ """Test updating server metadata to empty values
+
+ The original server metadata should not be lost if empty metadata
+ body is passed.
+ """
meta = {}
self.client.update_server_metadata(self.server['id'], meta)
resp_metadata = (self.client.list_server_metadata(self.server['id'])
@@ -84,15 +108,19 @@
@decorators.idempotent_id('3043c57d-7e0e-49a6-9a96-ad569c265e6a')
def test_get_server_metadata_item(self):
- # The value for a specific metadata key should be returned
+ """Test getting specific server metadata item"""
meta = self.client.show_server_metadata_item(self.server['id'],
'key2')['meta']
self.assertEqual('value2', meta['key2'])
@decorators.idempotent_id('58c02d4f-5c67-40be-8744-d3fa5982eb1c')
def test_set_server_metadata_item(self):
- # The item's value should be updated to the provided value
- # Update the metadata value
+ """Test updating specific server metadata item
+
+ The metadata item's value should be updated to the provided value.
+ """
+
+ # Update the metadata value.
meta = {'nova': 'alt'}
self.client.set_server_metadata_item(self.server['id'], 'nova', meta)
@@ -104,7 +132,10 @@
@decorators.idempotent_id('127642d6-4c7b-4486-b7cd-07265a378658')
def test_delete_server_metadata_item(self):
- # The metadata value/key pair should be deleted from the server
+ """Test deleting server metadata item
+
+ The metadata value/key pair should be deleted from the server.
+ """
self.client.delete_server_metadata_item(self.server['id'], 'key1')
# Verify the metadata item has been removed
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index 5688af1..a697b95 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -14,12 +14,17 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
+CONF = config.CONF
+
class ServerMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of server metadata"""
+
create_default_network = True
@classmethod
@@ -36,6 +41,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fe114a8f-3a57-4eff-9ee2-4e14628df049')
def test_server_create_metadata_key_too_long(self):
+ """Test creating server with too long metadata key should fail"""
# Attempt to start a server with a meta-data key that is > 255
# characters
@@ -52,7 +58,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('92431555-4d8b-467c-b95b-b17daa5e57ff')
def test_create_server_metadata_blank_key(self):
- # Blank key should trigger an error.
+ """Test creating server with blank metadata key should fail"""
meta = {'': 'data1'}
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -61,6 +67,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4d9cd7a3-2010-4b41-b8fe-3bbf0b169466')
def test_server_metadata_non_existent_server(self):
+ """Test getting metadata item for a non existent server should fail"""
# GET on a non-existent server should not succeed
non_existent_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
@@ -71,7 +78,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f408e78e-3066-4097-9299-3b0182da812e')
def test_list_server_metadata_non_existent_server(self):
- # List metadata on a non-existent server should not succeed
+ """Test listing metadata for a non existent server should fail"""
non_existent_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.list_server_metadata,
@@ -80,8 +87,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0025fbd6-a4ba-4cde-b8c2-96805dcfdabc')
def test_wrong_key_passed_in_body(self):
- # Raise BadRequest if key in uri does not match
- # the key passed in body.
+ """Test setting server metadata item with wrong key in body
+
+ Raise BadRequest if key in uri does not match the key passed in body.
+ """
meta = {'testkey': 'testvalue'}
self.assertRaises(lib_exc.BadRequest,
self.client.set_server_metadata_item,
@@ -90,7 +99,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0df38c2a-3d4e-4db5-98d8-d4d9fa843a12')
def test_set_metadata_non_existent_server(self):
- # Set metadata on a non-existent server should not succeed
+ """Test setting metadata for a non existent server should fail"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
non_existent_server_id = data_utils.rand_uuid()
meta = {'meta1': 'data1'}
self.assertRaises(lib_exc.NotFound,
@@ -101,7 +114,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('904b13dc-0ef2-4e4c-91cd-3b4a0f2f49d8')
def test_update_metadata_non_existent_server(self):
- # An update should not happen for a non-existent server
+ """Test updating metadata for a non existent server should fail"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
non_existent_server_id = data_utils.rand_uuid()
meta = {'key1': 'value1', 'key2': 'value2'}
self.assertRaises(lib_exc.NotFound,
@@ -112,7 +129,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a452f38c-05c2-4b47-bd44-a4f0bf5a5e48')
def test_update_metadata_with_blank_key(self):
- # Blank key should trigger an error
+ """Test updating server metadata to blank key should fail"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
meta = {'': 'data1'}
self.assertRaises(lib_exc.BadRequest,
self.client.update_server_metadata,
@@ -121,7 +142,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6bbd88e1-f8b3-424d-ba10-ae21c45ada8d')
def test_delete_metadata_non_existent_server(self):
- # Should not be able to delete metadata item from a non-existent server
+ """Test deleting metadata item from a non existent server
+
+ Should not be able to delete metadata item from a non-existent server.
+ """
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
non_existent_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.delete_server_metadata_item,
@@ -131,9 +159,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d8c0a210-a5c3-4664-be04-69d96746b547')
def test_metadata_items_limit(self):
- # A 403 Forbidden or 413 Overlimit (old behaviour) exception
- # will be raised while exceeding metadata items limit for
- # tenant.
+ """Test set/update server metadata over limit should fail
+
+ A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ will be raised while exceeding metadata items limit for project.
+ """
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
quota_set = self.quotas_client.show_quota_set(
self.tenant_id)['quota_set']
quota_metadata = quota_set['metadata_items']
@@ -157,8 +191,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('96100343-7fa9-40d8-80fa-d29ef588ce1c')
def test_set_server_metadata_blank_key(self):
- # Raise a bad request error for blank key.
- # set_server_metadata will replace all metadata with new value
+ """Test setting server metadata with blank key should fail"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
meta = {'': 'data1'}
self.assertRaises(lib_exc.BadRequest,
self.client.set_server_metadata,
@@ -167,8 +204,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('64a91aee-9723-4863-be44-4c9d9f1e7d0e')
def test_set_server_metadata_missing_metadata(self):
- # Raise a bad request error for a missing metadata field
- # set_server_metadata will replace all metadata with new value
+ """Test setting server metadata without metadata field should fail"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
meta = {'meta1': 'data1'}
self.assertRaises(lib_exc.BadRequest,
self.client.set_server_metadata,
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
index 7b31ede..f61d4fd 100644
--- a/tempest/api/compute/servers/test_server_password.py
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -19,6 +19,8 @@
class ServerPasswordTestJSON(base.BaseV2ComputeTest):
+ """Test server password"""
+
create_default_network = True
@classmethod
@@ -28,8 +30,10 @@
@decorators.idempotent_id('f83b582f-62a8-4f22-85b0-0dee50ff783a')
def test_get_server_password(self):
+ """Test getting password of a server"""
self.servers_client.show_password(self.server['id'])
@decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea')
def test_delete_server_password(self):
+ """Test deleting password from a server"""
self.servers_client.delete_password(self.server['id'])
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index 4f484e2..ba2adbb 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -28,6 +28,7 @@
class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
+ """Test servers with injected files"""
@classmethod
def setup_credentials(cls):
@@ -51,6 +52,7 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('3cfe87fd-115b-4a02-b942-7dc36a337fdf')
def test_create_server_with_personality(self):
+ """Test creating server with file injection"""
file_contents = 'This is a test file.'
file_path = '/test.txt'
personality = [{'path': file_path,
@@ -85,6 +87,7 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('128966d8-71fc-443c-8cab-08e24114ecc9')
def test_rebuild_server_with_personality(self):
+ """Test injecting file when rebuilding server"""
validation_resources = self.get_test_validation_resources(
self.os_primary)
server = self.create_test_server(
@@ -107,8 +110,11 @@
@decorators.idempotent_id('176cd8c9-b9e8-48ee-a480-180beab292bf')
def test_personality_files_exceed_limit(self):
- # Server creation should fail if greater than the maximum allowed
- # number of files are injected into the server.
+ """Test creating server with injected files over limitation
+
+ Server creation should fail if greater than the maximum allowed
+ number of files are injected into the server.
+ """
file_contents = 'This is a test file.'
personality = []
limits = self.limits_client.show_limits()['limits']
@@ -131,8 +137,11 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('52f12ee8-5180-40cc-b417-31572ea3d555')
def test_can_create_server_with_max_number_personality_files(self):
- # Server should be created successfully if maximum allowed number of
- # files is injected into the server during creation.
+ """Test creating server with maximum allowed number of injected files
+
+ Server should be created successfully if maximum allowed number of
+ files is injected into the server during creation.
+ """
file_contents = 'This is a test file.'
limits = self.limits_client.show_limits()['limits']
max_file_limit = limits['absolute']['maxPersonality']
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 1247494..5445113 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -53,9 +53,11 @@
class ServerRescueTestJSON(ServerRescueTestBase):
+ """Test server rescue"""
@decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
def test_rescue_unrescue_instance(self):
+ """Test rescue/unrescue server"""
password = data_utils.rand_password()
server = self.create_test_server(adminPass=password,
wait_until='ACTIVE')
@@ -68,6 +70,7 @@
class ServerRescueTestJSONUnderV235(ServerRescueTestBase):
+ """Test server rescue with compute microversion less than 2.36"""
max_microversion = '2.35'
@@ -81,7 +84,7 @@
@testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
"Floating ips are not available")
def test_rescued_vm_associate_dissociate_floating_ip(self):
- # Association of floating IP to a rescued vm
+ """Test associate/dissociate floating ip for rescued server"""
floating_ip_body = self.floating_ips_client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(self.floating_ips_client.delete_floating_ip,
@@ -96,6 +99,7 @@
@decorators.idempotent_id('affca41f-7195-492d-8065-e09eee245404')
def test_rescued_vm_add_remove_security_group(self):
+ """Test add/remove security group to for rescued server"""
# Add Security group
sg = self.create_security_group()
self.servers_client.add_security_group(self.rescued_server_id,
@@ -154,33 +158,43 @@
class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
+ """Test rescuing server specifying type of device for the rescue disk"""
@decorators.idempotent_id('947004c3-e8ef-47d9-9f00-97b74f9eaf96')
def test_stable_device_rescue_cdrom_ide(self):
+ """Test rescuing server with cdrom and ide as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='cdrom', hw_rescue_bus='ide')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('16865750-1417-4854-bcf7-496e6753c01e')
def test_stable_device_rescue_disk_virtio(self):
+ """Test rescuing server with disk and virtio as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('12340157-6306-4745-bdda-cfa019908b48')
def test_stable_device_rescue_disk_scsi(self):
+ """Test rescuing server with disk and scsi as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='scsi')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('647d04cf-ad35-4956-89ab-b05c5c16f30c')
def test_stable_device_rescue_disk_usb(self):
+ """Test rescuing server with disk and usb as the rescue disk"""
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='usb')
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('a3772b42-00bf-4310-a90b-1cc6fd3e7eab')
def test_stable_device_rescue_disk_virtio_with_volume_attached(self):
+ """Test rescuing server with volume attached
+
+ Attach a volume to the server and then rescue the server with disk
+ and virtio as the rescue disk.
+ """
server_id, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio')
server = self.servers_client.show_server(server_id)['server']
@@ -192,12 +206,22 @@
class ServerBootFromVolumeStableRescueTest(BaseServerStableDeviceRescueTest):
+ """Test rescuing server specifying type of device for the rescue disk
+
+ Test rescuing server specifying type of device for the rescue disk with
+ compute microversion greater than 2.86.
+ """
min_microversion = '2.87'
@decorators.attr(type='slow')
@decorators.idempotent_id('48f123cb-922a-4065-8db6-b9a9074a556b')
def test_stable_device_rescue_bfv_blank_volume(self):
+ """Test rescuing server with blank volume as block_device_mapping_v2
+
+ Create a server with block_device_mapping_v2 with blank volume,
+ then rescue the server with disk and virtio as the rescue disk.
+ """
block_device_mapping_v2 = [{
"boot_index": "0",
"source_type": "blank",
@@ -211,6 +235,11 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('e4636333-c928-40fc-98b7-70a23eef4224')
def test_stable_device_rescue_bfv_image_volume(self):
+ """Test rescuing server with blank volume as block_device_mapping_v2
+
+ Create a server with block_device_mapping_v2 with image volume,
+ then rescue the server with disk and virtio as the rescue disk.
+ """
block_device_mapping_v2 = [{
"boot_index": "0",
"source_type": "image",
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index caceb64..9bcf062 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -27,6 +27,7 @@
class ServerRescueNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of server rescue"""
@classmethod
def skip_checks(cls):
@@ -75,7 +76,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_rescue_paused_instance(self):
- # Rescue a paused server
+ """Test rescuing a paused server should fail"""
self.servers_client.pause_server(self.server_id)
self.addCleanup(self._unpause, self.server_id)
waiters.wait_for_server_status(self.servers_client,
@@ -87,13 +88,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('db22b618-f157-4566-a317-1b6d467a8094')
def test_rescued_vm_reboot(self):
+ """Test rebooing a rescued server should fail"""
self.assertRaises(lib_exc.Conflict, self.servers_client.reboot_server,
self.rescue_id, type='HARD')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6dfc0a55-3a77-4564-a144-1587b7971dde')
def test_rescue_non_existent_server(self):
- # Rescue a non-existing server
+ """Test rescuing a non-existing server should fail"""
non_existent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.servers_client.rescue_server,
@@ -102,6 +104,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('70cdb8a1-89f8-437d-9448-8844fd82bf46')
def test_rescued_vm_rebuild(self):
+ """Test rebuilding a rescued server should fail"""
self.assertRaises(lib_exc.Conflict,
self.servers_client.rebuild_server,
self.rescue_id,
@@ -111,6 +114,7 @@
@utils.services('volume')
@decorators.attr(type=['negative'])
def test_rescued_vm_attach_volume(self):
+ """Test attaching volume to a rescued server should fail"""
volume = self.create_volume()
# Rescue the server
@@ -130,6 +134,7 @@
@utils.services('volume')
@decorators.attr(type=['negative'])
def test_rescued_vm_detach_volume(self):
+ """Test detaching volume from a rescued server should fail"""
volume = self.create_volume()
# Attach the volume to the server
diff --git a/tempest/api/compute/servers/test_server_tags.py b/tempest/api/compute/servers/test_server_tags.py
index 3893b01..619f480 100644
--- a/tempest/api/compute/servers/test_server_tags.py
+++ b/tempest/api/compute/servers/test_server_tags.py
@@ -22,6 +22,7 @@
class ServerTagsTestJSON(base.BaseV2ComputeTest):
+ """Test server tags with compute microversion greater than 2.25"""
min_microversion = '2.26'
max_microversion = 'latest'
@@ -54,6 +55,7 @@
@decorators.idempotent_id('8d95abe2-c658-4c42-9a44-c0258500306b')
def test_create_delete_tag(self):
+ """Test creating and deleting server tag"""
# Check that no tags exist.
fetched_tags = self.client.list_tags(self.server['id'])['tags']
self.assertEmpty(fetched_tags)
@@ -73,6 +75,7 @@
@decorators.idempotent_id('a2c1af8c-127d-417d-974b-8115f7e3d831')
def test_update_all_tags(self):
+ """Test updating all server tags"""
# Add server tags to the server.
tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
self._update_server_tags(self.server['id'], tags)
@@ -89,6 +92,7 @@
@decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e')
def test_delete_all_tags(self):
+ """Test deleting all server tags"""
# Add server tags to the server.
assigned_tags = [data_utils.rand_name('tag'),
data_utils.rand_name('tag')]
@@ -101,6 +105,7 @@
@decorators.idempotent_id('81279a66-61c3-4759-b830-a2dbe64cbe08')
def test_check_tag_existence(self):
+ """Test checking server tag existence"""
# Add server tag to the server.
assigned_tag = data_utils.rand_name('tag')
self._update_server_tags(self.server['id'], assigned_tag)
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 3a4bd6d..cc013e3 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -25,6 +25,7 @@
class ServersTestJSON(base.BaseV2ComputeTest):
+ """Test servers API"""
create_default_network = True
@classmethod
@@ -37,8 +38,11 @@
enable_instance_password,
'Instance password not available.')
def test_create_server_with_admin_password(self):
- # If an admin password is provided on server creation, the server's
- # root password should be set to that password.
+ """Test creating server with admin password
+
+ If an admin password is provided on server creation, the server's
+ root password should be set to that password.
+ """
server = self.create_test_server(adminPass='testpassword')
self.addCleanup(self.delete_server, server['id'])
@@ -47,8 +51,7 @@
@decorators.idempotent_id('8fea6be7-065e-47cf-89b8-496e6f96c699')
def test_create_with_existing_server_name(self):
- # Creating a server with a name that already exists is allowed
-
+ """Test creating a server with already existing name is allowed"""
# TODO(sdague): clear out try, we do cleanup one layer up
server_name = data_utils.rand_name(
self.__class__.__name__ + '-server')
@@ -69,8 +72,7 @@
@decorators.idempotent_id('f9e15296-d7f9-4e62-b53f-a04e89160833')
def test_create_specify_keypair(self):
- # Specify a keypair while creating a server
-
+ """Test creating server with keypair"""
key_name = data_utils.rand_name('key')
self.keypairs_client.create_keypair(name=key_name)
self.addCleanup(self.keypairs_client.delete_keypair, key_name)
@@ -97,7 +99,7 @@
@decorators.idempotent_id('5e6ccff8-349d-4852-a8b3-055df7988dd2')
def test_update_server_name(self):
- # The server name should be changed to the provided value
+ """Test updating server name to the provided value"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.delete_server, server['id'])
# Update instance name with non-ASCII characters
@@ -115,7 +117,7 @@
@decorators.idempotent_id('89b90870-bc13-4b73-96af-f9d4f2b70077')
def test_update_access_server_address(self):
- # The server's access addresses should reflect the provided values
+ """Test updating server's access addresses to the provided value"""
server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.delete_server, server['id'])
@@ -132,7 +134,7 @@
@decorators.idempotent_id('38fb1d02-c3c5-41de-91d3-9bc2025a75eb')
def test_create_server_with_ipv6_addr_only(self):
- # Create a server without an IPv4 address(only IPv6 address).
+ """Test creating server with ipv6 address only(no ipv4 address)"""
server = self.create_test_server(accessIPv6='2001:2001::3',
wait_until='ACTIVE')
self.addCleanup(self.delete_server, server['id'])
@@ -142,17 +144,22 @@
@decorators.related_bug('1730756')
@decorators.idempotent_id('defbaca5-d611-49f5-ae21-56ee25d2db49')
def test_create_server_specify_multibyte_character_name(self):
- # prefix character is:
- # http://unicode.org/cldr/utility/character.jsp?a=20A1
+ """Test creating server with multi character name
- # We use a string with 3 byte utf-8 character due to nova
- # will return 400(Bad Request) if we attempt to send a name which has
- # 4 byte utf-8 character.
+ prefix character is:
+ http://unicode.org/cldr/utility/character.jsp?a=20A1
+
+ We use a string with 3 byte utf-8 character due to nova
+ will return 400(Bad Request) if we attempt to send a name which has
+ 4 byte utf-8 character.
+ """
utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
self.create_test_server(name=utf8_name, wait_until='ACTIVE')
class ServerShowV247Test(base.BaseV2ComputeTest):
+ """Test servers API with compute microversion greater than 2.46"""
+
min_microversion = '2.47'
max_microversion = 'latest'
@@ -164,12 +171,14 @@
@decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
def test_show_server(self):
+ """Test getting server detail"""
server = self.create_test_server()
# All fields will be checked by API schema
self.servers_client.show_server(server['id'])
@decorators.idempotent_id('8de397c2-57d0-4b90-aa30-e5d668f21a8b')
def test_update_rebuild_list_server(self):
+ """Test update/rebuild/list server"""
server = self.create_test_server()
# Checking update API response schema
self.servers_client.update_server(server['id'])
@@ -184,6 +193,8 @@
class ServerShowV263Test(base.BaseV2ComputeTest):
+ """Test servers API with compute microversion greater than 2.62"""
+
min_microversion = '2.63'
max_microversion = 'latest'
@@ -195,6 +206,7 @@
'required to test image certificate validation.')
@decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c')
def test_show_update_rebuild_list_server(self):
+ """Test show/update/rebuild/list server"""
trusted_certs = CONF.compute.certified_image_trusted_certs
server = self.create_test_server(
image_id=CONF.compute.certified_image_ref,
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 7fa30b0..4f85048 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -30,6 +30,8 @@
class ServersNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of servers"""
+
create_default_network = True
def setUp(self):
@@ -58,7 +60,8 @@
server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
- server = cls.create_test_server()
+ # Wait until the instance is active to avoid the delete racing
+ server = cls.create_test_server(wait_until='ACTIVE')
cls.client.delete_server(server['id'])
waiters.wait_for_server_termination(cls.client, server['id'])
cls.deleted_server_id = server['id']
@@ -66,8 +69,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dbbfd247-c40c-449e-8f6c-d2aa7c7da7cf')
def test_server_name_blank(self):
- # Create a server with name parameter empty
-
+ """Creating a server with name parameter empty should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
name='')
@@ -77,8 +79,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.personality,
'Nova personality feature disabled')
def test_personality_file_contents_not_encoded(self):
- # Use an unencoded file when creating a server with personality
-
+ """Using an unencoded injected file to create server should fail"""
file_contents = 'This is a test file.'
person = [{'path': '/etc/testfile.txt',
'contents': file_contents}]
@@ -90,8 +91,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fcba1052-0a50-4cf3-b1ac-fae241edf02f')
def test_create_with_invalid_image(self):
- # Create a server with an unknown image
-
+ """Creating a server with an unknown image should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
image_id=-1)
@@ -99,8 +99,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('18f5227f-d155-4429-807c-ccb103887537')
def test_create_with_invalid_flavor(self):
- # Create a server with an unknown flavor
-
+ """Creating a server with an unknown flavor should fail"""
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
flavor=-1,)
@@ -108,8 +107,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7f70a4d1-608f-4794-9e56-cb182765972c')
def test_invalid_access_ip_v4_address(self):
- # An access IPv4 address must match a valid address pattern
+ """Creating a server with invalid ipv4 ip address should fail
+ An access IPv4 address must match a valid address pattern
+ """
IPv4 = '1.1.1.1.1.1'
self.assertRaises(lib_exc.BadRequest,
self.create_test_server, accessIPv4=IPv4)
@@ -117,8 +118,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5226dd80-1e9c-4d8a-b5f9-b26ca4763fd0')
def test_invalid_ip_v6_address(self):
- # An access IPv6 address must match a valid address pattern
+ """Creating a server with invalid ipv6 ip address should fail
+ An access IPv6 address must match a valid address pattern
+ """
IPv6 = 'notvalid'
self.assertRaises(lib_exc.BadRequest,
@@ -129,7 +132,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_nonexistent_server(self):
- # Resize a non-existent server
+ """Resizing a non-existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.resize_server,
@@ -140,7 +143,7 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_with_non_existent_flavor(self):
- # Resize a server with non-existent flavor
+ """Resizing a server with non existent flavor should fail"""
nonexistent_flavor = data_utils.rand_uuid()
self.assertRaises(lib_exc.BadRequest, self.client.resize_server,
self.server_id, flavor_ref=nonexistent_flavor)
@@ -150,14 +153,14 @@
'Resize not available.')
@decorators.attr(type=['negative'])
def test_resize_server_with_null_flavor(self):
- # Resize a server with null flavor
+ """Resizing a server with null flavor should fail"""
self.assertRaises(lib_exc.BadRequest, self.client.resize_server,
self.server_id, flavor_ref="")
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d4c023a0-9c55-4747-9dd5-413b820143c7')
def test_reboot_non_existent_server(self):
- # Reboot a non existent server
+ """Rebooting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
nonexistent_server, type='SOFT')
@@ -167,7 +170,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_pause_paused_server(self):
- # Pause a paused server.
+ """Pausing a paused server should fail"""
self.client.pause_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED')
self.assertRaises(lib_exc.Conflict,
@@ -178,7 +181,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('98fa0458-1485-440f-873b-fe7f0d714930')
def test_rebuild_deleted_server(self):
- # Rebuild a deleted server
+ """Rebuilding a deleted server should fail"""
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
self.deleted_server_id, self.image_ref)
@@ -187,14 +190,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
def test_reboot_deleted_server(self):
- # Reboot a deleted server
+ """Rebooting a deleted server should fail"""
self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
self.deleted_server_id, type='SOFT')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d86141a7-906e-4731-b187-d64a2ea61422')
def test_rebuild_non_existent_server(self):
- # Rebuild a non existent server
+ """Rebuilding a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
@@ -204,6 +207,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fd57f159-68d6-4c2a-902b-03070828a87e')
def test_create_numeric_server_name(self):
+ """Creating a server with numeric server name should fail"""
server_name = 12345
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -212,8 +216,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c3e0fb12-07fc-4d76-a22e-37409887afe8')
def test_create_server_name_length_exceeds_256(self):
- # Create a server with name length exceeding 255 characters
+ """Creating a server with name length exceeding limit should fail
+ Create a server with name length exceeding 255 characters, an error is
+ returned.
+ """
server_name = 'a' * 256
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -224,6 +231,11 @@
@utils.services('volume')
@decorators.idempotent_id('12146ac1-d7df-4928-ad25-b1f99e5286cd')
def test_create_server_invalid_bdm_in_2nd_dict(self):
+ """Creating a server with invalid block_device_mapping_v2 should fail
+
+ Create a server with invalid block_device_mapping_v2, an error is
+ returned.
+ """
volume = self.create_volume()
bdm_1st = {"source_type": "image",
"delete_on_termination": True,
@@ -243,10 +255,9 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4e72dc2d-44c5-4336-9667-f7972e95c402')
def test_create_with_invalid_network_uuid(self):
+ """Creating a server with invalid network uuid should fail"""
# Pass invalid network uuid while creating a server
-
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
-
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
networks=networks)
@@ -254,8 +265,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7a2efc39-530c-47de-b875-2dd01c8d39bd')
def test_create_with_non_existent_keypair(self):
+ """Creating a server with non-existent keypair should fail"""
# Pass a non-existent keypair while creating a server
-
key_name = data_utils.rand_name('key')
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -264,8 +275,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fc74810-0bd2-4cd7-8244-4f33a9db865a')
def test_create_server_metadata_exceeds_length_limit(self):
+ """Creating a server with metadata longer than limit should fail """
# Pass really long metadata while creating a server
-
metadata = {'a': 'b' * 260}
self.assertRaises((lib_exc.BadRequest, lib_exc.OverLimit),
self.create_test_server,
@@ -274,8 +285,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aa8eed43-e2cb-4ebf-930b-da14f6a21d81')
def test_update_name_of_non_existent_server(self):
- # Update name of a non-existent server
-
+ """Updating name of a non-existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
new_name = data_utils.rand_name(
self.__class__.__name__ + '-server') + '_updated'
@@ -286,18 +296,19 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('38204696-17c6-44da-9590-40f87fb5a899')
def test_update_server_set_empty_name(self):
- # Update name of the server to an empty string
-
+ """Updating name of the server to an empty string should fail"""
new_name = ''
-
self.assertRaises(lib_exc.BadRequest, self.client.update_server,
self.server_id, name=new_name)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c8e244c-dada-4590-9944-749c455b431f')
def test_update_server_name_length_exceeds_256(self):
- # Update name of server exceed the name length limit
+ """Updating name of server exceeding the name length limit should fail
+ Update name of server exceeding the name length limit, an error is
+ returned.
+ """
new_name = 'a' * 256
self.assertRaises(lib_exc.BadRequest,
self.client.update_server,
@@ -307,8 +318,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1041b4e6-514b-4855-96a5-e974b60870a3')
def test_delete_non_existent_server(self):
- # Delete a non existent server
-
+ """Deleting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
nonexistent_server)
@@ -316,23 +326,24 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('75f79124-277c-45e6-a373-a1d6803f4cc4')
def test_delete_server_pass_negative_id(self):
- # Pass an invalid string parameter to delete server
-
+ """Passing an invalid string parameter to delete server should fail"""
self.assertRaises(lib_exc.NotFound, self.client.delete_server, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f4d7279b-5fd2-4bf2-9ba4-ae35df0d18c5')
def test_delete_server_pass_id_exceeding_length_limit(self):
- # Pass a server ID that exceeds length limit to delete server
+ """Deleting server with a server ID exceeding length limit should fail
+ Pass a server ID that exceeds length limit to delete server, an error
+ is returned.
+ """
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
sys.maxsize + 1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c5fa6041-80cd-483b-aa6d-4e45f19d093c')
def test_create_with_nonexistent_security_group(self):
- # Create a server with a nonexistent security group
-
+ """Creating a server with a nonexistent security group should fail"""
security_groups = [{'name': 'does_not_exist'}]
self.assertRaises(lib_exc.BadRequest,
self.create_test_server,
@@ -341,7 +352,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3436b02f-1b1e-4f03-881e-c6a602327439')
def test_get_non_existent_server(self):
- # Get a non existent server details
+ """Getting a non existent server details should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.show_server,
nonexistent_server)
@@ -349,7 +360,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a31460a9-49e1-42aa-82ee-06e0bb7c2d03')
def test_stop_non_existent_server(self):
- # Stop a non existent server
+ """Stopping a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.servers_client.stop_server,
nonexistent_server)
@@ -359,7 +370,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_pause_non_existent_server(self):
- # pause a non existent server
+ """Pausing a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.pause_server,
nonexistent_server)
@@ -369,7 +380,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_unpause_non_existent_server(self):
- # unpause a non existent server
+ """Unpausing a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.unpause_server,
nonexistent_server)
@@ -379,7 +390,7 @@
'Pause is not available.')
@decorators.attr(type=['negative'])
def test_unpause_server_invalid_state(self):
- # unpause an active server.
+ """Unpausing an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.unpause_server,
self.server_id)
@@ -389,7 +400,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_suspend_non_existent_server(self):
- # suspend a non existent server
+ """Suspending a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.suspend_server,
nonexistent_server)
@@ -399,7 +410,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_suspend_server_invalid_state(self):
- # suspend a suspended server.
+ """Suspending a suspended server should fail"""
self.client.suspend_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id,
'SUSPENDED')
@@ -413,7 +424,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_resume_non_existent_server(self):
- # resume a non existent server
+ """Resuming a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.resume_server,
nonexistent_server)
@@ -423,7 +434,7 @@
'Suspend is not available.')
@decorators.attr(type=['negative'])
def test_resume_server_invalid_state(self):
- # resume an active server.
+ """Resuming an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.resume_server,
self.server_id)
@@ -431,7 +442,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7dd919e7-413f-4198-bebb-35e2a01b13e9')
def test_get_console_output_of_non_existent_server(self):
- # get the console output for a non existent server
+ """Getting the console output for a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.get_console_output,
@@ -440,7 +451,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6f47992b-5144-4250-9f8b-f00aa33950f3')
def test_force_delete_nonexistent_server_id(self):
- # force-delete a non existent server
+ """Force-deleting a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.force_delete_server,
@@ -449,7 +460,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c6d38cc-fcfb-437a-85b9-7b788af8bf01')
def test_restore_nonexistent_server_id(self):
- # restore-delete a non existent server
+ """Restore-deleting a non existent server should fail
+
+ We can restore a soft deleted server, but can't restore a non
+ existent server.
+ """
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.restore_soft_deleted_server,
@@ -458,7 +473,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fcadfab-bd6a-4753-8db7-4a51e51aade9')
def test_restore_server_invalid_state(self):
- # we can only restore-delete a server in 'soft-delete' state
+ """Restore-deleting a server not in 'soft-delete' state should fail
+
+ We can restore a soft deleted server, but can't restore a server that
+ is not in 'soft-delete' state.
+ """
self.assertRaises(lib_exc.Conflict,
self.client.restore_soft_deleted_server,
self.server_id)
@@ -468,7 +487,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_shelve_non_existent_server(self):
- # shelve a non existent server
+ """Shelving a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.shelve_server,
nonexistent_server)
@@ -478,7 +497,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_shelve_shelved_server(self):
- # shelve a shelved server.
+ """Shelving a shelved server should fail"""
compute.shelve_server(self.client, self.server_id)
def _unshelve_server():
@@ -508,7 +527,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_unshelve_non_existent_server(self):
- # unshelve a non existent server
+ """Unshelving a non existent server should fail"""
nonexistent_server = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.unshelve_server,
nonexistent_server)
@@ -518,7 +537,7 @@
'Shelve is not available.')
@decorators.attr(type=['negative'])
def test_unshelve_server_invalid_state(self):
- # unshelve an active server.
+ """Unshelving an active server should fail"""
self.assertRaises(lib_exc.Conflict,
self.client.unshelve_server,
self.server_id)
@@ -527,7 +546,7 @@
@decorators.idempotent_id('74085be3-a370-4ca2-bc51-2d0e10e0f573')
@utils.services('volume', 'image')
def test_create_server_from_non_bootable_volume(self):
- # Create a volume
+ """Creating a server from a non bootable volume should fail"""
volume = self.create_volume()
# Update volume bootable status to false
@@ -555,6 +574,8 @@
class ServersNegativeTestMultiTenantJSON(base.BaseV2ComputeTest):
+ """Negative tests of servers for multiple projects"""
+
create_default_network = True
credentials = ['primary', 'alt']
@@ -581,8 +602,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('543d84c1-dd2e-4c6d-8cb2-b9da0efaa384')
def test_update_server_of_another_tenant(self):
- # Update name of a server that belongs to another tenant
+ """Updating server that belongs to another project should fail
+ Update name of a server that belongs to another project, an error is
+ returned.
+ """
new_name = self.server_id + '_new'
self.assertRaises(lib_exc.NotFound,
self.alt_client.update_server, self.server_id,
@@ -591,7 +615,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c75009d-3eea-423e-bea3-61b09fd25f9c')
def test_delete_a_server_of_another_tenant(self):
- # Delete a server that belongs to another tenant
+ """Deleting a server that belongs to another project should fail"""
self.assertRaises(lib_exc.NotFound,
self.alt_client.delete_server,
self.server_id)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index dfd6ca4..b2e02c5 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -28,6 +28,8 @@
# TODO(mriedem): Remove this test class once the nova queens branch goes into
# extended maintenance mode.
class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
+ """Test virtual interfaces API with compute microversion less than 2.44"""
+
max_microversion = '2.43'
depends_on_nova_network = True
@@ -47,9 +49,7 @@
@decorators.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016')
@utils.services('network')
def test_list_virtual_interfaces(self):
- # Positive test:Should be able to GET the virtual interfaces list
- # for a given server_id
-
+ """Test listing virtual interfaces of a server"""
if CONF.service_available.neutron:
with testtools.ExpectedException(exceptions.BadRequest):
self.client.list_virtual_interfaces(self.server['id'])
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index f6e8bc9..5667281 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -23,6 +23,12 @@
# TODO(mriedem): Remove this test class once the nova queens branch goes into
# extended maintenance mode.
class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest):
+ """Negative tests of virtual interfaces API
+
+ Negative tests of virtual interfaces API for compute microversion less
+ than 2.44.
+ """
+
max_microversion = '2.43'
depends_on_nova_network = True
@@ -37,8 +43,7 @@
@decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c')
@utils.services('network')
def test_list_virtual_interfaces_invalid_server_id(self):
- # Negative test: Should not be able to GET virtual interfaces
- # for an invalid server_id
+ """Test listing virtual interfaces of an invalid server should fail"""
invalid_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.servers_client.list_virtual_interfaces,
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 12e7fea..3318876 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -27,10 +27,11 @@
class ExtensionsTest(base.BaseV2ComputeTest):
+ """Tests Compute Extensions API"""
@decorators.idempotent_id('3bb27738-b759-4e0d-a5fa-37d7a6df07d1')
def test_list_extensions(self):
- # List of all extensions
+ """Test listing compute extensions"""
if not CONF.compute_feature_enabled.api_extensions:
raise self.skipException('There are not any extensions configured')
extensions = self.extensions_client.list_extensions()['extensions']
@@ -50,6 +51,6 @@
@decorators.idempotent_id('05762f39-bdfa-4cdb-9b46-b78f8e78e2fd')
@utils.requires_ext(extension='os-consoles', service='compute')
def test_get_extension(self):
- # get the specified extensions
+ """Test getting specified compute extension details"""
extension = self.extensions_client.show_extension('os-consoles')
self.assertEqual('os-consoles', extension['extension']['alias'])
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
index 76131e2..97c26e4 100644
--- a/tempest/api/compute/test_networks.py
+++ b/tempest/api/compute/test_networks.py
@@ -20,6 +20,7 @@
class ComputeNetworksTest(base.BaseV2ComputeTest):
+ """Test compute networks API with compute microversion less than 2.36"""
max_microversion = '2.35'
@classmethod
@@ -35,5 +36,6 @@
@decorators.idempotent_id('3fe07175-312e-49a5-a623-5f52eeada4c2')
def test_list_networks(self):
+ """Test listing networks using compute networks API"""
networks = self.client.list_networks()['networks']
self.assertNotEmpty(networks, "No networks found.")
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index a62492d..5fe0e3b 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -20,6 +20,7 @@
class QuotasTestJSON(base.BaseV2ComputeTest):
+ """Test compute quotas"""
@classmethod
def skip_checks(cls):
@@ -59,7 +60,7 @@
@decorators.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107')
def test_get_quotas(self):
- # User can get the quota set for it's tenant
+ """Test user can get the compute quota set for it's project"""
expected_quota_set = self.default_quota_set | set(['id'])
quota_set = self.client.show_quota_set(self.tenant_id)['quota_set']
self.assertEqual(quota_set['id'], self.tenant_id)
@@ -75,7 +76,7 @@
@decorators.idempotent_id('9bfecac7-b966-4f47-913f-1a9e2c12134a')
def test_get_default_quotas(self):
- # User can get the default quota set for it's tenant
+ """Test user can get the default compute quota set for it's project"""
expected_quota_set = self.default_quota_set | set(['id'])
quota_set = (self.client.show_default_quota_set(self.tenant_id)
['quota_set'])
@@ -85,7 +86,7 @@
@decorators.idempotent_id('cd65d997-f7e4-4966-a7e9-d5001b674fdc')
def test_compare_tenant_quotas_with_default_quotas(self):
- # Tenants are created with the default quota values
+ """Test tenants are created with the default compute quota values"""
default_quota_set = \
self.client.show_default_quota_set(self.tenant_id)['quota_set']
tenant_quota_set = (self.client.show_quota_set(self.tenant_id)
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index f4eada0..17f4b80 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -18,6 +18,8 @@
class ComputeTenantNetworksTest(base.BaseV2ComputeTest):
+ """Test compute tenant networks API with microversion less than 2.36"""
+
max_microversion = '2.35'
@classmethod
@@ -34,8 +36,11 @@
@decorators.idempotent_id('edfea98e-bbe3-4c7a-9739-87b986baff26')
@utils.services('network')
def test_list_show_tenant_networks(self):
- # Fetch all networks that are visible to the tenant: this may include
- # shared and external networks
+ """Test list/show tenant networks
+
+ Fetch all networks that are visible to the tenant: this may include
+ shared and external networks.
+ """
tenant_networks = [
n['id'] for n in self.client.list_tenant_networks()['networks']
]
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 97813a5..d85e4f7 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -59,13 +59,17 @@
class AttachVolumeTestJSON(BaseAttachVolumeTest):
+ """Test attaching volume to server"""
@decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
# This test is conditionally marked slow if SSH validation is enabled.
@decorators.attr(type='slow', condition=CONF.validation.run_validation)
def test_attach_detach_volume(self):
- # Stop and Start a server with an attached volume, ensuring that
- # the volume remains attached.
+ """Test attaching and detaching volume from server
+
+ Stop and Start a server with an attached volume, ensuring that
+ the volume remains attached.
+ """
server, validation_resources = self._create_server()
# NOTE(andreaf) Create one remote client used throughout the test.
@@ -125,6 +129,13 @@
@decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
def test_list_get_volume_attachments(self):
+ """Test listing and getting volume attachments
+
+ First we attach one volume to the server, check listing and getting
+ the volume attachment of the server. Then we attach another volume to
+ the server, check listing and getting the volume attachments of the
+ server. Finally we detach the volumes from the server one by one.
+ """
# List volume attachment of the server
server, validation_resources = self._create_server()
volume_1st = self.create_volume()
@@ -244,8 +255,12 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
def test_attach_volume_shelved_or_offload_server(self):
- # Create server, count number of volumes on it, shelve
- # server and attach pre-created volume to shelved server
+ """Test attaching volume to shelved server
+
+ Create server, count number of volumes on it, shelve
+ server and attach pre-created volume to shelved server, then
+ unshelve the server and check that attached volume exists.
+ """
server, validation_resources = self._create_server()
volume = self.create_volume()
num_vol = self._count_volumes(server, validation_resources)
@@ -271,8 +286,12 @@
@decorators.attr(type='slow')
@decorators.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
def test_detach_volume_shelved_or_offload_server(self):
- # Count number of volumes on instance, shelve
- # server and attach pre-created volume to shelved server
+ """Test detaching volume from shelved server
+
+ Count number of volumes on server, shelve server and attach
+ pre-created volume to shelved server, then detach the volume, unshelve
+ the instance and check that we have the expected number of volume(s).
+ """
server, validation_resources = self._create_server()
volume = self.create_volume()
num_vol = self._count_volumes(server, validation_resources)
@@ -291,6 +310,12 @@
class AttachVolumeMultiAttachTest(BaseAttachVolumeTest):
+ """Test attaching one volume to multiple servers
+
+ Test attaching one volume to multiple servers with compute
+ microversion greater than 2.59.
+ """
+
min_microversion = '2.60'
max_microversion = 'latest'
@@ -367,6 +392,12 @@
@decorators.idempotent_id('8d5853f7-56e7-4988-9b0c-48cea3c7049a')
def test_list_get_volume_attachments_multiattach(self):
+ """Test listing and getting multiattached volume attachments
+
+ Attach a single volume to two servers, list attachments from the
+ volume and make sure the server uuids are in the list, then detach
+ the volume from servers one by one.
+ """
# Attach a single volume to two servers.
servers, volume, attachments = self._create_and_multiattach()
@@ -448,7 +479,10 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_resize_server_with_multiattached_volume(self):
- # Attach a single volume to multiple servers, then resize the servers
+ """Test resizing servers with multiattached volume
+
+ Attach a single volume to multiple servers, then resize the servers
+ """
servers, volume, _ = self._create_and_multiattach()
for server in servers:
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 9a506af..516f599 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -21,6 +21,8 @@
class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
+ """Negative tests of volume attaching"""
+
create_default_network = True
@classmethod
@@ -34,6 +36,7 @@
@decorators.related_bug('1630783', status_code=500)
@decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
def test_delete_attached_volume(self):
+ """Test deleting attachemd volume should fail"""
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
self.attach_volume(server, volume)
@@ -44,10 +47,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c')
def test_attach_attached_volume_to_same_server(self):
- # Test attaching the same volume to the same instance once
- # it's already attached. The nova/cinder validation for this differs
- # depending on whether or not cinder v3.27 is being used to attach
- # the volume to the instance.
+ """Test attaching attached volume to same server should fail
+
+ Test attaching the same volume to the same instance once
+ it's already attached. The nova/cinder validation for this differs
+ depending on whether or not cinder v3.27 is being used to attach
+ the volume to the instance.
+ """
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
@@ -59,6 +65,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce')
def test_attach_attached_volume_to_different_server(self):
+ """Test attaching attached volume to different server should fail"""
server1 = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index f3b7494..3c71ba9 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -20,6 +20,7 @@
class RolesNegativeTestJSON(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone roles via v2 API"""
def _get_role_params(self):
user = self.setup_test_user()
@@ -30,14 +31,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d5d5f1df-f8ca-4de0-b2ef-259c1cc67025')
def test_list_roles_by_unauthorized_user(self):
- # Non-administrator user should not be able to list roles
+ """Test Non-admin user should not be able to list roles via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_roles_client.list_roles)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('11a3c7da-df6c-40c2-abc2-badd682edf9f')
def test_list_roles_request_without_token(self):
- # Request to list roles without a valid token should fail
+ """Test listing roles without a valid token via v2 API should fail"""
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized, self.roles_client.list_roles)
@@ -46,14 +47,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c0b89e56-accc-4c73-85f8-9c0f866104c1')
def test_role_create_blank_name(self):
- # Should not be able to create a role with a blank name
+ """Test creating a role with a blank name via v2 API is not allowed"""
self.assertRaises(lib_exc.BadRequest, self.roles_client.create_role,
name='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('585c8998-a8a4-4641-a5dd-abef7a8ced00')
def test_create_role_by_unauthorized_user(self):
- # Non-administrator user should not be able to create role
+ """Test non-admin user should not be able to create role via v2 API"""
role_name = data_utils.rand_name(name='role')
self.assertRaises(lib_exc.Forbidden,
self.non_admin_roles_client.create_role,
@@ -62,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a7edd17a-e34a-4aab-8bb7-fa6f498645b8')
def test_create_role_request_without_token(self):
- # Request to create role without a valid token should fail
+ """Test creating role without a valid token via v2 API should fail"""
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
role_name = data_utils.rand_name(name='role')
@@ -73,7 +74,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c0cde2c8-81c1-4bb0-8fe2-cf615a3547a8')
def test_role_create_duplicate(self):
- # Role names should be unique
+ """Test role names should be unique via v2 API"""
role_name = data_utils.rand_name(name='role-dup')
body = self.roles_client.create_role(name=role_name)['role']
role1_id = body.get('id')
@@ -84,7 +85,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('15347635-b5b1-4a87-a280-deb2bd6d865e')
def test_delete_role_by_unauthorized_user(self):
- # Non-administrator user should not be able to delete role
+ """Test non-admin user should not be able to delete role via v2 API"""
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
self.addCleanup(self.roles_client.delete_role, body['id'])
@@ -95,7 +96,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('44b60b20-70de-4dac-beaf-a3fc2650a16b')
def test_delete_role_request_without_token(self):
- # Request to delete role without a valid token should fail
+ """Test deleting role without a valid token via v2 API should fail"""
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
self.addCleanup(self.roles_client.delete_role, body['id'])
@@ -110,7 +111,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('38373691-8551-453a-b074-4260ad8298ef')
def test_delete_role_non_existent(self):
- # Attempt to delete a non existent role should fail
+ """Test deleting a non existent role via v2 API should fail"""
non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role,
non_existent_role)
@@ -118,8 +119,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('391df5cf-3ec3-46c9-bbe5-5cb58dd4dc41')
def test_assign_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # assign a role to user
+ """Test non-admin user assigning a role to user via v2 API
+
+ Non-admin user should not be authorized to assign a role to user via
+ v2 API.
+ """
(user, tenant, role) = self._get_role_params()
self.assertRaises(
lib_exc.Forbidden,
@@ -129,7 +133,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f0d2683c-5603-4aee-95d7-21420e87cfd8')
def test_assign_user_role_request_without_token(self):
- # Request to assign a role to a user without a valid token
+ """Test assigning a role to a user without a valid token via v2 API
+
+ Assigning a role to a user without a valid token via v2 API should
+ fail.
+ """
(user, tenant, role) = self._get_role_params()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -142,7 +150,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('99b297f6-2b5d-47c7-97a9-8b6bb4f91042')
def test_assign_user_role_for_non_existent_role(self):
- # Attempt to assign a non existent role to user should fail
+ """Test assigning a non existent role to user via v2 API
+
+ Assigning a non existent role to user via v2 API should fail.
+ """
(user, tenant, _) = self._get_role_params()
non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
@@ -152,7 +163,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b2285aaa-9e76-4704-93a9-7a8acd0a6c8f')
def test_assign_user_role_for_non_existent_tenant(self):
- # Attempt to assign a role on a non existent tenant should fail
+ """Test assigning a role on a non existent tenant via v2 API
+
+ Assigning a role on a non existent tenant via v2 API should fail.
+ """
(user, _, role) = self._get_role_params()
non_existent_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
@@ -162,7 +176,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5c3132cd-c4c8-4402-b5ea-71eb44e97793')
def test_assign_duplicate_user_role(self):
- # Duplicate user role should not get assigned
+ """Test duplicate user role should not get assigned via v2 API"""
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -174,8 +188,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d0537987-0977-448f-a435-904c15de7298')
def test_remove_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # remove a user's role
+ """Test non-admin user removing a user's role via v2 API
+
+ Non-admin user should not be authorized to remove a user's role via
+ v2 API
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -188,7 +205,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('cac81cf4-c1d2-47dc-90d3-f2b7eb572286')
def test_remove_user_role_request_without_token(self):
- # Request to remove a user's role without a valid token
+ """Test removing a user's role without a valid token via v2 API
+
+ Removing a user's role without a valid token via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -203,7 +223,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ab32d759-cd16-41f1-a86e-44405fa9f6d2')
def test_remove_user_role_non_existent_role(self):
- # Attempt to delete a non existent role from a user should fail
+ """Test deleting a non existent role from a user via v2 API
+
+ Deleting a non existent role from a user via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -216,7 +239,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('67a679ec-03dd-4551-bbfc-d1c93284f023')
def test_remove_user_role_non_existent_tenant(self):
- # Attempt to remove a role from a non existent tenant should fail
+ """Test removing a role from a non existent tenant via v2 API
+
+ Removing a role from a non existent tenant via v2 API should fail.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -229,8 +255,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7391ab4c-06f3-477a-a64a-c8e55ce89837')
def test_list_user_roles_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to list
- # a user's roles
+ """Test non-admin user listing a user's roles via v2 API
+
+ Non-admin user should not be authorized to list a user's roles via v2
+ API.
+ """
(user, tenant, role) = self._get_role_params()
self.roles_client.create_user_role_on_project(tenant['id'],
user['id'],
@@ -243,7 +272,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('682adfb2-fd5f-4b0a-a9ca-322e9bebb907')
def test_list_user_roles_request_without_token(self):
- # Request to list user's roles without a valid token should fail
+ """Test listing user's roles without a valid token via v2 API
+
+ Listing user's roles without a valid token via v2 API should fail
+ """
(user, tenant, _) = self._get_role_params()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index 03543ac..182b24c 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -20,6 +20,7 @@
class ServicesTestJSON(base.BaseIdentityV2AdminTest):
+ """Test identity services via v2 API"""
def _del_service(self, service_id):
# Deleting the service created in this method
@@ -30,6 +31,7 @@
@decorators.idempotent_id('84521085-c6e6-491c-9a08-ec9f70f90110')
def test_create_get_delete_service(self):
+ """Test verifies the identity service create/get/delete via v2 API"""
# GET Service
# Creating a Service
name = data_utils.rand_name('service')
@@ -64,7 +66,10 @@
@decorators.idempotent_id('5d3252c8-e555-494b-a6c8-e11d7335da42')
def test_create_service_without_description(self):
- # Create a service only with name and type
+ """Test creating identity service without description via v2 API
+
+ Create a service only with name and type.
+ """
name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
service = self.services_client.create_service(
@@ -79,7 +84,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('34ea6489-012d-4a86-9038-1287cadd5eca')
def test_list_services(self):
- # Create, List, Verify and Delete Services
+ """Test Create/List/Verify/Delete of identity service via v2 API"""
services = []
for _ in range(3):
name = data_utils.rand_name('service')
diff --git a/tempest/api/identity/admin/v2/test_tenant_negative.py b/tempest/api/identity/admin/v2/test_tenant_negative.py
index 49bb949..792dad9 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -20,18 +20,22 @@
class TenantsNegativeTestJSON(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone tenants via v2 API"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ca9bb202-63dd-4240-8a07-8ef9c19c04bb')
def test_list_tenants_by_unauthorized_user(self):
- # Non-administrator user should not be able to list tenants
+ """Test Non-admin should not be able to list tenants via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.list_tenants)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('df33926c-1c96-4d8d-a762-79cc6b0c3cf4')
def test_list_tenant_request_without_token(self):
- # Request to list tenants without a valid token should fail
+ """Test listing tenants without a valid token via v2 API
+
+ Listing tenants without a valid token via v2 API should fail.
+ """
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized,
@@ -41,7 +45,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('162ba316-f18b-4987-8c0c-fd9140cd63ed')
def test_tenant_delete_by_unauthorized_user(self):
- # Non-administrator user should not be able to delete a tenant
+ """Test non-admin should not be able to delete a tenant via v2 API"""
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.delete_tenant,
@@ -50,7 +54,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e450db62-2e9d-418f-893a-54772d6386b1')
def test_tenant_delete_request_without_token(self):
- # Request to delete a tenant without a valid token should fail
+ """Test deleting a tenant without a valid token via v2 API
+
+ Deleting a tenant without a valid token via v2 API should fail.
+ """
tenant = self.setup_test_tenant()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -62,14 +69,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c9a2aed-6e3c-467a-8f5c-89da9d1b516b')
def test_delete_non_existent_tenant(self):
- # Attempt to delete a non existent tenant should fail
+ """Test deleting a non existent tenant via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.tenants_client.delete_tenant,
data_utils.rand_uuid_hex())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af16f44b-a849-46cb-9f13-a751c388f739')
def test_tenant_create_duplicate(self):
- # Tenant names should be unique
+ """Test tenant names should be unique via v2 API"""
tenant_name = data_utils.rand_name(name='tenant')
self.setup_test_tenant(name=tenant_name)
self.assertRaises(lib_exc.Conflict, self.tenants_client.create_tenant,
@@ -78,7 +85,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d26b278a-6389-4702-8d6e-5980d80137e0')
def test_create_tenant_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to create a tenant
+ """Test non-admin user creating a tenant via v2 API
+
+ Non-admin user should not be authorized to create a tenant via v2 API.
+ """
tenant_name = data_utils.rand_name(name='tenant')
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.create_tenant,
@@ -87,7 +97,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a3ee9d7e-6920-4dd5-9321-d4b2b7f0a638')
def test_create_tenant_request_without_token(self):
- # Create tenant request without a token should not be authorized
+ """Test creating tenant without a token via v2 API is not allowed"""
tenant_name = data_utils.rand_name(name='tenant')
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -99,7 +109,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5a2e4ca9-b0c0-486c-9c48-64a94fba2395')
def test_create_tenant_with_empty_name(self):
- # Tenant name should not be empty
+ """Test tenant name should not be empty via v2 API"""
self.assertRaises(lib_exc.BadRequest,
self.tenants_client.create_tenant,
name='')
@@ -107,7 +117,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2ff18d1e-dfe3-4359-9dc3-abf582c196b9')
def test_create_tenants_name_length_over_64(self):
- # Tenant name length should not be greater than 64 characters
+ """Test tenant name length should not exceed 64 via v2 API"""
tenant_name = 'a' * 65
self.assertRaises(lib_exc.BadRequest,
self.tenants_client.create_tenant,
@@ -116,14 +126,17 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bd20dc2a-9557-4db7-b755-f48d952ad706')
def test_update_non_existent_tenant(self):
- # Attempt to update a non existent tenant should fail
+ """Test updating a non existent tenant via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.tenants_client.update_tenant,
data_utils.rand_uuid_hex())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41704dc5-c5f7-4f79-abfa-76e6fedc570b')
def test_tenant_update_by_unauthorized_user(self):
- # Non-administrator user should not be able to update a tenant
+ """Test non-admin user updating a tenant via v2 API
+
+ Non-admin user should not be able to update a tenant via v2 API
+ """
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.update_tenant,
@@ -132,7 +145,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7a421573-72c7-4c22-a98e-ce539219c657')
def test_tenant_update_request_without_token(self):
- # Request to update a tenant without a valid token should fail
+ """Test updating a tenant without a valid token via v2 API
+
+ Updating a tenant without a valid token via v2 API should fail
+ """
tenant = self.setup_test_tenant()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index f68754e..5f73e1c 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -19,10 +19,14 @@
class TenantsTestJSON(base.BaseIdentityV2AdminTest):
+ """Test identity tenants via v2 API"""
@decorators.idempotent_id('16c6e05c-6112-4b0e-b83f-5e43f221b6b0')
def test_tenant_list_delete(self):
- # Create several tenants and delete them
+ """Test listing and deleting tenants via v2 API
+
+ Create several tenants and delete them
+ """
tenants = []
for _ in range(3):
tenant = self.setup_test_tenant()
@@ -41,7 +45,7 @@
@decorators.idempotent_id('d25e9f24-1310-4d29-b61b-d91299c21d6d')
def test_tenant_create_with_description(self):
- # Create tenant with a description
+ """Test creating tenant with a description via v2 API"""
tenant_desc = data_utils.rand_name(name='desc')
tenant = self.setup_test_tenant(description=tenant_desc)
tenant_id = tenant['id']
@@ -56,7 +60,7 @@
@decorators.idempotent_id('670bdddc-1cd7-41c7-b8e2-751cfb67df50')
def test_tenant_create_enabled(self):
- # Create a tenant that is enabled
+ """Test creating a tenant that is enabled via v2 API"""
tenant = self.setup_test_tenant(enabled=True)
tenant_id = tenant['id']
self.assertTrue(tenant['enabled'], 'Enable should be True in response')
@@ -66,7 +70,7 @@
@decorators.idempotent_id('3be22093-b30f-499d-b772-38340e5e16fb')
def test_tenant_create_not_enabled(self):
- # Create a tenant that is not enabled
+ """Test creating a tenant that is not enabled via v2 API"""
tenant = self.setup_test_tenant(enabled=False)
tenant_id = tenant['id']
self.assertFalse(tenant['enabled'],
@@ -78,7 +82,7 @@
@decorators.idempotent_id('781f2266-d128-47f3-8bdb-f70970add238')
def test_tenant_update_name(self):
- # Update name attribute of a tenant
+ """Test updating name attribute of a tenant via v2 API"""
t_name1 = data_utils.rand_name(name='tenant')
tenant = self.setup_test_tenant(name=t_name1)
t_id = tenant['id']
@@ -100,7 +104,7 @@
@decorators.idempotent_id('859fcfe1-3a03-41ef-86f9-b19a47d1cd87')
def test_tenant_update_desc(self):
- # Update description attribute of a tenant
+ """Test updating description attribute of a tenant via v2 API"""
t_desc = data_utils.rand_name(name='desc')
tenant = self.setup_test_tenant(description=t_desc)
t_id = tenant['id']
@@ -123,7 +127,7 @@
@decorators.idempotent_id('8fc8981f-f12d-4c66-9972-2bdcf2bc2e1a')
def test_tenant_update_enable(self):
- # Update the enabled attribute of a tenant
+ """Test updating the enabled attribute of a tenant via v2 API"""
t_en = False
tenant = self.setup_test_tenant(enabled=t_en)
t_id = tenant['id']
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index 6ce1a8b..5d89f9d 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -23,9 +23,11 @@
class TokensTestJSON(base.BaseIdentityV2AdminTest):
+ """Test keystone tokens via v2 API"""
@decorators.idempotent_id('453ad4d5-e486-4b2f-be72-cffc8149e586')
def test_create_check_get_delete_token(self):
+ """Test getting create/check/get/delete token for user via v2 API"""
# get a token by username and password
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
@@ -59,7 +61,7 @@
@decorators.idempotent_id('25ba82ee-8a32-4ceb-8f50-8b8c71e8765e')
def test_rescope_token(self):
- """An unscoped token can be requested
+ """Test an unscoped token can be requested via v2 API
That token can be used to request a scoped token.
"""
@@ -112,6 +114,7 @@
@decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31')
def test_list_endpoints_for_token(self):
+ """Test listing endpoints for token via v2 API"""
tempest_services = ['keystone', 'nova', 'neutron', 'swift', 'cinder',
'neutron']
# get a token for the user
diff --git a/tempest/api/identity/admin/v2/test_tokens_negative.py b/tempest/api/identity/admin/v2/test_tokens_negative.py
index eb3e365..f2e41ff 100644
--- a/tempest/api/identity/admin/v2/test_tokens_negative.py
+++ b/tempest/api/identity/admin/v2/test_tokens_negative.py
@@ -19,12 +19,17 @@
class TokensAdminTestNegative(base.BaseIdentityV2AdminTest):
+ """Negative tests of keystone tokens via v2 API"""
credentials = ['primary', 'admin', 'alt']
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a0a0a600-4292-4364-99c5-922c834fdf05')
def test_check_token_existence_negative(self):
+ """Test checking other tenant's token existence via v2 API
+
+ Checking other tenant's token existence via v2 API should fail.
+ """
creds = self.os_primary.credentials
creds_alt = self.os_alt.credentials
username = creds.username
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 0d98af5..57a321a 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -23,6 +23,7 @@
class UsersTestJSON(base.BaseIdentityV2AdminTest):
+ """Test keystone users via v2 API"""
@classmethod
def resource_setup(cls):
@@ -33,14 +34,14 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2d55a71e-da1d-4b43-9c03-d269fd93d905')
def test_create_user(self):
- # Create a user
+ """Test creating a user via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(name=self.alt_user, tenantId=tenant['id'])
self.assertEqual(self.alt_user, user['name'])
@decorators.idempotent_id('89d9fdb8-15c2-4304-a429-48715d0af33d')
def test_create_user_with_enabled(self):
- # Create a user with enabled : False
+ """Test creating a user with enabled : False via v2 API"""
tenant = self.setup_test_tenant()
name = data_utils.rand_name('test_user')
user = self.create_test_user(name=name,
@@ -53,7 +54,7 @@
@decorators.idempotent_id('39d05857-e8a5-4ed4-ba83-0b52d3ab97ee')
def test_update_user(self):
- # Test case to check if updating of user attributes is successful.
+ """Test updating user attributes via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(tenantId=tenant['id'])
@@ -75,14 +76,14 @@
@decorators.idempotent_id('29ed26f4-a74e-4425-9a85-fdb49fa269d2')
def test_delete_user(self):
- # Delete a user
+ """Test deleting a user via v2 API"""
tenant = self.setup_test_tenant()
user = self.create_test_user(tenantId=tenant['id'])
self.users_client.delete_user(user['id'])
@decorators.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1')
def test_user_authentication(self):
- # Valid user's token is authenticated
+ """Test that valid user's token is authenticated via v2 API"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -97,6 +98,7 @@
@decorators.idempotent_id('5d1fa498-4c2d-4732-a8fe-2b054598cfdd')
def test_authentication_request_without_token(self):
+ """Test authentication request without token via v2 API"""
# Request for token authentication with a valid token in header
password = data_utils.rand_password()
user = self.setup_test_user(password)
@@ -116,7 +118,10 @@
@decorators.idempotent_id('a149c02e-e5e0-4b89-809e-7e8faf33ccda')
def test_get_users(self):
- # Get a list of users and find the test user
+ """Test getting users via v2 API
+
+ Get a list of users and find the test user
+ """
user = self.setup_test_user()
users = self.users_client.list_users()['users']
self.assertThat([u['name'] for u in users],
@@ -125,7 +130,7 @@
@decorators.idempotent_id('6e317209-383a-4bed-9f10-075b7c82c79a')
def test_list_users_for_tenant(self):
- # Return a list of all users for a tenant
+ """Test returning a list of all users for a tenant via v2 API"""
tenant = self.setup_test_tenant()
user_ids = list()
fetched_user_ids = list()
@@ -147,7 +152,7 @@
@decorators.idempotent_id('a8b54974-40e1-41c0-b812-50fc90827971')
def test_list_users_with_roles_for_tenant(self):
- # Return list of users on tenant when roles are assigned to users
+ """Test listing users on tenant with roles assigned via v2 API"""
user = self.setup_test_user()
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
role = self.setup_test_role()
@@ -175,7 +180,7 @@
@decorators.idempotent_id('1aeb25ac-6ec5-4d8b-97cb-7ac3567a989f')
def test_update_user_password(self):
- # Test case to check if updating of user password is successful.
+ """Test updating of user password via v2 API"""
user = self.setup_test_user()
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
# Updating the user with new password
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 4f47e41..eda1fdd 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -20,6 +20,7 @@
class UsersNegativeTestJSON(base.BaseIdentityV2AdminTest):
+ """Negative tests of identity users via v2 API"""
@classmethod
def resource_setup(cls):
@@ -31,7 +32,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('60a1f5fa-5744-4cdf-82bf-60b7de2d29a4')
def test_create_user_by_unauthorized_user(self):
- # Non-administrator should not be authorized to create a user
+ """Non-admin should not be authorized to create a user via v2 API"""
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.create_user,
@@ -42,7 +43,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d80d0c2f-4514-4d1e-806d-0930dfc5a187')
def test_create_user_with_empty_name(self):
- # User with an empty name should not be created
+ """User with an empty name should not be created via v2 API"""
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
name='', password=self.alt_password,
@@ -52,7 +53,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7704b4f3-3b75-4b82-87cc-931d41c8f780')
def test_create_user_with_name_length_over_255(self):
- # Length of user name filed should be restricted to 255 characters
+ """Length of user name should not exceed 255 via v2 API"""
tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
name='a' * 256, password=self.alt_password,
@@ -62,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('57ae8558-120c-4723-9308-3751474e7ecf')
def test_create_user_with_duplicate_name(self):
- # Duplicate user should not be created
+ """Duplicate user should not be created via v2 API"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -75,7 +76,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0132cc22-7c4f-42e1-9e50-ac6aad31d59a')
def test_create_user_for_non_existent_tenant(self):
- # Attempt to create a user in a non-existent tenant should fail
+ """Creating a user in a non-existent tenant via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
name=self.alt_user,
password=self.alt_password,
@@ -85,7 +86,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('55bbb103-d1ae-437b-989b-bcdf8175c1f4')
def test_create_user_request_without_a_token(self):
- # Request to create a user without a valid token should fail
+ """Creating a user without a valid token via v2 API should fail"""
tenant = self.setup_test_tenant()
# Get the token of the current client
token = self.client.auth_provider.get_token()
@@ -103,7 +104,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
def test_create_user_with_enabled_non_bool(self):
- # Attempt to create a user with valid enabled para should fail
+ """Creating a user with invalid enabled para via v2 API should fail"""
tenant = self.setup_test_tenant()
name = data_utils.rand_name('test_user')
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
@@ -114,7 +115,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3d07e294-27a0-4144-b780-a2a1bf6fee19')
def test_update_user_for_non_existent_user(self):
- # Attempt to update a user non-existent user should fail
+ """Updating a non-existent user via v2 API should fail"""
user_name = data_utils.rand_name('user')
non_existent_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.users_client.update_user,
@@ -123,7 +124,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('3cc2a64b-83aa-4b02-88f0-d6ab737c4466')
def test_update_user_request_without_a_token(self):
- # Request to update a user without a valid token should fail
+ """Updating a user without a valid token via v2 API should fail"""
# Get the token of the current client
token = self.client.auth_provider.get_token()
@@ -139,7 +140,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac')
def test_update_user_by_unauthorized_user(self):
- # Non-administrator should not be authorized to update user
+ """Non-admin should not be authorized to update user via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.update_user,
self.alt_user)
@@ -147,7 +148,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d45195d5-33ed-41b9-a452-7d0d6a00f6e9')
def test_delete_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to delete a user
+ """Non-admin should not be authorized to delete a user via v2 API"""
user = self.setup_test_user()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.delete_user,
@@ -156,14 +157,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7cc82f7e-9998-4f89-abae-23df36495867')
def test_delete_non_existent_user(self):
- # Attempt to delete a non-existent user should fail
+ """Attempt to delete a non-existent user via v2 API should fail"""
self.assertRaises(lib_exc.NotFound, self.users_client.delete_user,
'junk12345123')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('57fe1df8-0aa7-46c0-ae9f-c2e785c7504a')
def test_delete_user_request_without_a_token(self):
- # Request to delete a user without a valid token should fail
+ """Deleting a user without a valid token via v2 API should fail"""
# Get the token of the current client
token = self.client.auth_provider.get_token()
@@ -179,7 +180,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829')
def test_authentication_for_disabled_user(self):
- # Disabled user's token should not get authenticated
+ """Disabled user's token should not get authenticated via v2 API"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -192,7 +193,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('440a7a8d-9328-4b7b-83e0-d717010495e4')
def test_authentication_when_tenant_is_disabled(self):
- # User's token for a disabled tenant should not be authenticated
+ """Test User's token for a disabled tenant via v2 API
+
+ User's token for a disabled tenant should not be authenticated via
+ v2 API.
+ """
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -205,7 +210,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('921f1ad6-7907-40b8-853f-637e7ee52178')
def test_authentication_with_invalid_tenant(self):
- # User's token for an invalid tenant should not be authenticated
+ """Test User's token for an invalid tenant via v2 API
+
+ User's token for an invalid tenant should not be authenticated via V2
+ API.
+ """
password = data_utils.rand_password()
user = self.setup_test_user(password)
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
@@ -216,7 +225,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bde9aecd-3b1c-4079-858f-beb5deaa5b5e')
def test_authentication_with_invalid_username(self):
- # Non-existent user's token should not get authenticated
+ """Non-existent user's token should not get authorized via v2 API"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
@@ -226,7 +235,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d5308b33-3574-43c3-8d87-1c090c5e1eca')
def test_authentication_with_invalid_password(self):
- # User's token with invalid password should not be authenticated
+ """Test User's token with invalid password via v2 API
+
+ User's token with invalid password should not be authenticated via V2
+ API.
+ """
user = self.setup_test_user()
tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
@@ -235,14 +248,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('284192ce-fb7c-4909-a63b-9a502e0ddd11')
def test_get_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to get user list
+ """Non-admin should not be authorized to get user list via v2 API"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.list_users)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a73591ec-1903-4ffe-be42-282b39fefc9d')
def test_get_users_request_without_token(self):
- # Request to get list of users without a valid token should fail
+ """Listing users without a valid token via v2 API should fail"""
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -254,8 +267,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f5d39046-fc5f-425c-b29e-bac2632da28e')
def test_list_users_with_invalid_tenant(self):
- # Should not be able to return a list of all
- # users for a non-existent tenant
+ """Listing users for a non-existent tenant via v2 API should fail"""
# Assign invalid tenant ids
invalid_id = list()
invalid_id.append(data_utils.rand_name('999'))
diff --git a/tempest/api/identity/admin/v3/test_application_credentials.py b/tempest/api/identity/admin/v3/test_application_credentials.py
index 7e802c6..c9cafd8 100644
--- a/tempest/api/identity/admin/v3/test_application_credentials.py
+++ b/tempest/api/identity/admin/v3/test_application_credentials.py
@@ -20,9 +20,11 @@
class ApplicationCredentialsV3AdminTest(base.BaseApplicationCredentialsV3Test,
base.BaseIdentityV3AdminTest):
+ """Test keystone application credentials"""
@decorators.idempotent_id('3b3dd48f-3388-406a-a9e6-4d078a552d0e')
def test_create_application_credential_with_roles(self):
+ """Test creating keystone application credential with roles"""
role = self.setup_test_role()
self.os_admin.roles_v3_client.create_user_role_on_project(
self.project_id,
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 23fe788..441f10f 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -20,6 +20,8 @@
class CredentialsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test keystone credentials"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -47,6 +49,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('7cd59bf9-bda4-4c72-9467-d21cab278355')
def test_credentials_create_get_update_delete(self):
+ """Test creating, getting, updating, deleting of credentials"""
blob = '{"access": "%s", "secret": "%s"}' % (
data_utils.rand_name('Access'), data_utils.rand_name('Secret'))
cred = self.creds_client.create_credential(
@@ -82,6 +85,7 @@
@decorators.idempotent_id('13202c00-0021-42a1-88d4-81b44d448aab')
def test_credentials_list_delete(self):
+ """Test listing credentials"""
created_cred_ids = list()
fetched_cred_ids = list()
diff --git a/tempest/api/identity/admin/v3/test_domain_configuration.py b/tempest/api/identity/admin/v3/test_domain_configuration.py
index c0b18ca..a246a36 100644
--- a/tempest/api/identity/admin/v3/test_domain_configuration.py
+++ b/tempest/api/identity/admin/v3/test_domain_configuration.py
@@ -21,6 +21,8 @@
class DomainConfigurationTestJSON(base.BaseIdentityV3AdminTest):
+ """Test domain configuration"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -51,10 +53,12 @@
@decorators.idempotent_id('11a02bf0-6f94-4380-b3b0-c8dc18fc0d22')
def test_show_default_group_config_and_options(self):
- # The API supports only the identity and ldap groups. For the ldap
- # group, a valid value is url or user_tree_dn. For the identity group,
- # a valid value is driver.
+ """Test showing default keystone group config and options
+ The API supports only the identity and ldap groups. For the ldap
+ group, a valid value is url or user_tree_dn. For the identity group,
+ a valid value is driver.
+ """
# Check that the default config has the identity and ldap groups.
config = self.client.show_default_config_settings()['config']
self.assertIsInstance(config, dict)
@@ -93,6 +97,7 @@
@decorators.idempotent_id('9e3ff13c-f597-4f01-9377-d6c06c2a1477')
def test_create_domain_config_and_show_config_groups_and_options(self):
+ """Test creating and showing keystone config groups and options"""
domain, created_config = self._create_domain_and_config(
self.custom_config)
@@ -117,6 +122,7 @@
@decorators.idempotent_id('7161023e-5dd0-4612-9da0-1bac6ac30b63')
def test_create_update_and_delete_domain_config(self):
+ """Test creating, updating and deleting keystone domain config"""
domain, created_config = self._create_domain_and_config(
self.custom_config)
@@ -140,6 +146,7 @@
@decorators.idempotent_id('c7510fa2-6661-4170-9c6b-4783a80651e9')
def test_create_update_and_delete_domain_config_groups_and_opts(self):
+ """Test create/update/delete keystone domain config groups and opts"""
domain, _ = self._create_domain_and_config(self.custom_config)
# Check that updating configuration groups work.
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 07175f4..32ccb9e 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -24,6 +24,7 @@
class DomainsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test identity domains"""
@classmethod
def resource_setup(cls):
@@ -37,7 +38,7 @@
@decorators.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
def test_list_domains(self):
- # Test to list domains
+ """Test listing domains"""
fetched_ids = list()
# List and Verify Domains
body = self.domains_client.list_domains()['domains']
@@ -49,7 +50,7 @@
@decorators.idempotent_id('c6aee07b-4981-440c-bb0b-eb598f58ffe9')
def test_list_domains_filter_by_name(self):
- # List domains filtering by name
+ """Test listing domains filtering by name"""
params = {'name': self.setup_domains[0]['name']}
fetched_domains = self.domains_client.list_domains(
**params)['domains']
@@ -61,7 +62,7 @@
@decorators.idempotent_id('3fd19840-65c1-43f8-b48c-51bdd066dff9')
def test_list_domains_filter_by_enabled(self):
- # List domains filtering by enabled domains
+ """Test listing domains filtering by enabled domains"""
params = {'enabled': True}
fetched_domains = self.domains_client.list_domains(
**params)['domains']
@@ -74,6 +75,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('f2f5b44a-82e8-4dad-8084-0661ea3b18cf')
def test_create_update_delete_domain(self):
+ """Test creating, updating and deleting domain"""
# Create domain
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
@@ -118,6 +120,7 @@
@decorators.idempotent_id('d8d318b7-d1b3-4c37-94c5-3c5ba0b121ea')
def test_domain_delete_cascades_content(self):
+ """Test deleting domain will delete its associated contents"""
# Create a domain with a user and a group in it
domain = self.setup_test_domain()
user = self.create_test_user(domain_id=domain['id'])
@@ -134,6 +137,7 @@
@decorators.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046')
def test_create_domain_with_disabled_status(self):
+ """Test creating domain with disabled status"""
# Create domain with enabled status as false
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
@@ -146,6 +150,7 @@
@decorators.idempotent_id('2abf8764-309a-4fa9-bc58-201b799817ad')
def test_create_domain_without_description(self):
+ """Test creating domain without description"""
# Create domain only with name
d_name = data_utils.rand_name('domain')
domain = self.domains_client.create_domain(name=d_name)['domain']
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index b3c68fb..c90206d 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -20,6 +20,8 @@
class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ """Negative tests of identity domains"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -28,6 +30,7 @@
@decorators.attr(type=['negative', 'gate'])
@decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
def test_delete_active_domain(self):
+ """Test deleting active domain should fail"""
domain = self.create_domain()
domain_id = domain['id']
@@ -40,14 +43,20 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9018461d-7d24-408d-b3fe-ae37e8cd5c9e')
def test_create_domain_with_empty_name(self):
- # Domain name should not be empty
+ """Test creating domain with empty name should fail
+
+ Domain name should not be empty
+ """
self.assertRaises(lib_exc.BadRequest,
self.domains_client.create_domain, name='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('37b1bbf2-d664-4785-9a11-333438586eae')
def test_create_domain_with_name_length_over_64(self):
- # Domain name length should not ne greater than 64 characters
+ """Test creating domain with name over length
+
+ Domain name length should not ne greater than 64 characters
+ """
d_name = 'a' * 65
self.assertRaises(lib_exc.BadRequest,
self.domains_client.create_domain,
@@ -56,13 +65,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('43781c07-764f-4cf2-a405-953c1916f605')
def test_delete_non_existent_domain(self):
- # Attempt to delete a non existent domain should fail
+ """Test attempting to delete a non existent domain should fail"""
self.assertRaises(lib_exc.NotFound, self.domains_client.delete_domain,
data_utils.rand_uuid_hex())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e6f9e4a2-4f36-4be8-bdbc-4e199ae29427')
def test_domain_create_duplicate(self):
+ """Test creating domain with duplicate name should fail"""
domain_name = data_utils.rand_name('domain-dup')
domain = self.domains_client.create_domain(name=domain_name)['domain']
domain_id = domain['id']
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
index 7d85dc9..2fa92e3 100644
--- a/tempest/api/identity/admin/v3/test_endpoint_groups.py
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -20,6 +20,8 @@
class EndPointGroupsTest(base.BaseIdentityV3AdminTest):
+ """Test endpoint groups"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -68,6 +70,7 @@
@decorators.idempotent_id('7c69e7a1-f865-402d-a2ea-44493017315a')
def test_create_list_show_check_delete_endpoint_group(self):
+ """Test create/list/show/check/delete of endpoint group"""
service_id = self._create_service()
self.addCleanup(self.services_client.delete_service, service_id)
name = data_utils.rand_name('service_group')
@@ -127,6 +130,7 @@
@decorators.idempotent_id('51c8fc38-fa84-4e76-b5b6-6fc37770fb26')
def test_update_endpoint_group(self):
+ """Test updating endpoint group"""
# Creating an endpoint group so as to check update endpoint group
# with new values
service1_id = self._create_service()
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 366d6a0..0199d73 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -20,6 +20,8 @@
class EndPointsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test keystone endpoints"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -71,6 +73,7 @@
@decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618')
def test_list_endpoints(self):
+ """Test listing keystone endpoints by filters"""
# Get the list of all the endpoints.
fetched_endpoints = self.client.list_endpoints()['endpoints']
fetched_endpoint_ids = [e['id'] for e in fetched_endpoints]
@@ -111,6 +114,7 @@
@decorators.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37')
def test_create_list_show_delete_endpoint(self):
+ """Test creating, listing, showing and deleting keystone endpoint"""
region_name = data_utils.rand_name('region')
url = data_utils.rand_url()
interface = 'public'
@@ -152,6 +156,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('37e8f15e-ee7c-4657-a1e7-f6b61e375eff')
def test_update_endpoint(self):
+ """Test updating keystone endpoint"""
# 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
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 164b577..9689d87 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -20,6 +20,8 @@
class EndpointsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ """Negative tests of endpoint"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -48,7 +50,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ac6c137e-4d3d-448f-8c83-4f13d0942651')
def test_create_with_enabled_False(self):
- # Enabled should be a boolean, not a string like 'False'
+ """Test creating endpoint with invalid enabled value 'False'
+
+ Enabled should be a boolean, not a string like 'False'
+ """
interface = 'public'
url = data_utils.rand_url()
region = data_utils.rand_name('region')
@@ -59,7 +64,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9c43181e-0627-484a-8c79-923e8a59598b')
def test_create_with_enabled_True(self):
- # Enabled should be a boolean, not a string like 'True'
+ """Test creating endpoint with invalid enabled value 'True'
+
+ Enabled should be a boolean, not a string like 'True'
+ """
interface = 'public'
url = data_utils.rand_url()
region = data_utils.rand_name('region')
@@ -88,11 +96,17 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('65e41f32-5eb7-498f-a92a-a6ccacf7439a')
def test_update_with_enabled_False(self):
- # Enabled should be a boolean, not a string like 'False'
+ """Test updating endpoint with invalid enabled value 'False'
+
+ Enabled should be a boolean, not a string like 'False'
+ """
self._assert_update_raises_bad_request('False')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('faba3587-f066-4757-a48e-b4a3f01803bb')
def test_update_with_enabled_True(self):
- # Enabled should be a boolean, not a string like 'True'
+ """Test updating endpoint with invalid enabled value 'True'
+
+ Enabled should be a boolean, not a string like 'True'
+ """
self._assert_update_raises_bad_request('True')
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 2dd1fe2..b2e3775 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -23,6 +23,8 @@
class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test keystone groups"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -35,6 +37,7 @@
@decorators.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
def test_group_create_update_get(self):
+ """Test creating, updating and getting keystone group"""
# Verify group creation works.
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
@@ -78,6 +81,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_group_users_add_list_delete(self):
+ """Test adding/listing/deleting group users"""
group = self.setup_test_group(domain_id=self.domain['id'])
# add user into group
users = []
@@ -104,6 +108,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_list_user_groups(self):
+ """Test listing user groups when the user is in two groups"""
# create a user
user = self.create_test_user()
# create two groups, and add user into them
@@ -127,7 +132,7 @@
@decorators.idempotent_id('cc9a57a5-a9ed-4f2d-a29f-4f979a06ec71')
def test_list_groups(self):
- # Test to list groups
+ """Test listing groups"""
group_ids = list()
fetched_ids = list()
for _ in range(3):
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 2672f71..cababc6 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -21,6 +21,8 @@
class InheritsV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test keystone inherits"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -72,6 +74,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_inherit_assign_list_check_revoke_roles_on_domains_user(self):
+ """Test assign/list/check/revoke inherited role on domain user"""
# Create role
src_role = self.setup_test_role()
# Assign role on domains user
@@ -96,6 +99,7 @@
@decorators.idempotent_id('c7a8dda2-be50-4fb4-9a9c-e830771078b1')
def test_inherit_assign_list_check_revoke_roles_on_domains_group(self):
+ """Test assign/list/check/revoke inherited role on domain group"""
# Create role
src_role = self.setup_test_role()
# Assign role on domains group
@@ -123,6 +127,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_inherit_assign_check_revoke_roles_on_projects_user(self):
+ """Test assign/list/check/revoke inherited role on project user"""
# Create role
src_role = self.setup_test_role()
# Assign role on projects user
@@ -138,6 +143,7 @@
@decorators.idempotent_id('26021436-d5a4-4256-943c-ded01e0d4b45')
def test_inherit_assign_check_revoke_roles_on_projects_group(self):
+ """Test assign/list/check/revoke inherited role on project group"""
# Create role
src_role = self.setup_test_role()
# Assign role on projects group
@@ -157,6 +163,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_inherit_assign_list_revoke_user_roles_on_domain(self):
+ """Test assign/list/check/revoke inherited role on domain"""
# Create role
src_role = self.setup_test_role()
@@ -204,6 +211,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_inherit_assign_list_revoke_user_roles_on_project_tree(self):
+ """Test assign/list/check/revoke inherited role on project tree"""
# Create role
src_role = self.setup_test_role()
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index cb8ea11..b33d8bd 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -40,6 +40,7 @@
class ListProjectsTestJSON(BaseListProjectsTestJSON):
+ """Test listing projects"""
@classmethod
def resource_setup(cls):
@@ -65,13 +66,13 @@
@decorators.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8')
def test_list_projects_with_enabled(self):
- # List the projects with enabled
+ """Test listing the projects with enabled"""
self._list_projects_with_params(
[self.p1], [self.p2, self.p3], {'enabled': False}, 'enabled')
@decorators.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac')
def test_list_projects_with_parent(self):
- # List projects with parent
+ """Test listing projects with parent"""
params = {'parent_id': self.p3['parent_id']}
fetched_projects = self.projects_client.list_projects(
params)['projects']
@@ -81,6 +82,11 @@
class ListProjectsStaticTestJSON(BaseListProjectsTestJSON):
+ """Test listing projects
+
+ These tests can be executed in clouds using the pre-provisioned users
+ """
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -102,7 +108,7 @@
@decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
def test_list_projects(self):
- # List projects
+ """Test listing projects"""
list_projects = self.projects_client.list_projects()['projects']
for p in [self.p1, self.p2]:
@@ -112,13 +118,13 @@
@decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
def test_list_projects_with_name(self):
- # List projects with name
+ """Test listing projects filtered by name"""
self._list_projects_with_params(
[self.p1], [self.p2], {'name': self.p1['name']}, 'name')
@decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
def test_list_projects_with_domains(self):
- # Verify project list filtered by domain
+ """Test listing projects filtered by domain"""
key = 'domain_id'
for p in [self.p1, self.p2]:
params = {key: p[key]}
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index e46145d..be1216a 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -23,6 +23,8 @@
class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test identity projects"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -30,7 +32,7 @@
@decorators.idempotent_id('0ecf465c-0dc4-4532-ab53-91ffeb74d12d')
def test_project_create_with_description(self):
- # Create project with a description
+ """Test creating project with a description"""
project_desc = data_utils.rand_name('desc')
project = self.setup_test_project(description=project_desc)
project_id = project['id']
@@ -44,7 +46,7 @@
@decorators.idempotent_id('5f50fe07-8166-430b-a882-3b2ee0abe26f')
def test_project_create_with_domain(self):
- # Create project with a domain
+ """Test creating project with a domain"""
domain = self.setup_test_domain()
project_name = data_utils.rand_name('project')
project = self.setup_test_project(
@@ -58,7 +60,7 @@
@decorators.idempotent_id('1854f9c0-70bc-4d11-a08a-1c789d339e3d')
def test_project_create_with_parent(self):
- # Create root project without providing a parent_id
+ """Test creating root project without providing a parent_id"""
domain = self.setup_test_domain()
domain_id = domain['id']
@@ -83,6 +85,7 @@
@decorators.idempotent_id('a7eb9416-6f9b-4dbb-b71b-7f73aaef59d5')
def test_create_is_domain_project(self):
+ """Test creating is_domain project"""
project = self.setup_test_project(domain_id=None, is_domain=True)
# To delete a domain, we need to disable it first
self.addCleanup(self.projects_client.update_project, project['id'],
@@ -103,7 +106,7 @@
@decorators.idempotent_id('1f66dc76-50cc-4741-a200-af984509e480')
def test_project_create_enabled(self):
- # Create a project that is enabled
+ """Test creating a project that is enabled"""
project = self.setup_test_project(enabled=True)
project_id = project['id']
self.assertTrue(project['enabled'],
@@ -113,7 +116,7 @@
@decorators.idempotent_id('78f96a9c-e0e0-4ee6-a3ba-fbf6dfd03207')
def test_project_create_not_enabled(self):
- # Create a project that is not enabled
+ """Test creating a project that is not enabled"""
project = self.setup_test_project(enabled=False)
self.assertFalse(project['enabled'],
'Enable should be False in response')
@@ -123,7 +126,7 @@
@decorators.idempotent_id('f608f368-048c-496b-ad63-d286c26dab6b')
def test_project_update_name(self):
- # Update name attribute of a project
+ """Test updating name attribute of a project"""
p_name1 = data_utils.rand_name('project')
project = self.setup_test_project(name=p_name1)
@@ -144,7 +147,7 @@
@decorators.idempotent_id('f138b715-255e-4a7d-871d-351e1ef2e153')
def test_project_update_desc(self):
- # Update description attribute of a project
+ """Test updating description attribute of a project"""
p_desc = data_utils.rand_name('desc')
project = self.setup_test_project(description=p_desc)
resp1_desc = project['description']
@@ -164,7 +167,7 @@
@decorators.idempotent_id('b6b25683-c97f-474d-a595-55d410b68100')
def test_project_update_enable(self):
- # Update the enabled attribute of a project
+ """Test updating the enabled attribute of a project"""
p_en = False
project = self.setup_test_project(enabled=p_en)
@@ -189,7 +192,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_associate_user_to_project(self):
- # Associate a user to a project
+ """Test associating a user to a project"""
# Create a Project
project = self.setup_test_project()
@@ -215,6 +218,7 @@
@decorators.idempotent_id('d1db68b6-aebe-4fa0-b79d-d724d2e21162')
def test_project_get_equals_list(self):
+ """Test the result of getting project equals that of listing"""
fields = ['parent_id', 'is_domain', 'description', 'links',
'name', 'enabled', 'domain_id', 'id', 'tags']
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 12f1d4a..79e3d29 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -20,11 +20,12 @@
class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ """Negative tests of projects"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
def test_project_delete_by_unauthorized_user(self):
- # Non-admin user should not be able to delete a project
+ """Non-admin user should not be able to delete a project"""
project = self.setup_test_project()
self.assertRaises(
lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
@@ -32,6 +33,11 @@
class ProjectsNegativeStaticTestJSON(base.BaseIdentityV3AdminTest):
+ """Negative tests of projects
+
+ These tests can be executed in clouds using the pre-provisioned users
+ """
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -40,14 +46,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('24c49279-45dd-4155-887a-cb738c2385aa')
def test_list_projects_by_unauthorized_user(self):
- # Non-admin user should not be able to list projects
+ """Non-admin user should not be able to list projects"""
self.assertRaises(lib_exc.Forbidden,
self.non_admin_projects_client.list_projects)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('874c3e84-d174-4348-a16b-8c01f599561b')
def test_project_create_duplicate(self):
- # Project names should be unique
+ """Project names should be unique"""
project_name = data_utils.rand_name('project-dup')
self.setup_test_project(name=project_name)
@@ -57,7 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8fba9de2-3e1f-4e77-812a-60cb68f8df13')
def test_create_project_by_unauthorized_user(self):
- # Non-admin user should not be authorized to create a project
+ """Non-admin user should not be authorized to create a project"""
project_name = data_utils.rand_name('project')
self.assertRaises(
lib_exc.Forbidden, self.non_admin_projects_client.create_project,
@@ -66,14 +72,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7828db17-95e5-475b-9432-9a51b4aa79a9')
def test_create_project_with_empty_name(self):
- # Project name should not be empty
+ """Project name should not be empty"""
self.assertRaises(lib_exc.BadRequest,
self.projects_client.create_project, name='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('502b6ceb-b0c8-4422-bf53-f08fdb21e2f0')
def test_create_projects_name_length_over_64(self):
- # Project name length should not be greater than 64 characters
+ """Project name length should not be greater than 64 characters"""
project_name = 'a' * 65
self.assertRaises(lib_exc.BadRequest,
self.projects_client.create_project, project_name)
@@ -81,7 +87,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb')
def test_delete_non_existent_project(self):
- # Attempt to delete a non existent project should fail
+ """Attempt to delete a non existent project should fail"""
self.assertRaises(lib_exc.NotFound,
self.projects_client.delete_project,
data_utils.rand_uuid_hex())
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index c8c0151..63e456e 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -20,6 +20,8 @@
class RegionsTestJSON(base.BaseIdentityV3AdminTest):
+ """Test regions"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -44,6 +46,7 @@
@decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
def test_create_update_get_delete_region(self):
+ """Test creating, updating, getting and updating region"""
# Create region
r_description = data_utils.rand_name('description')
region = self.client.create_region(
@@ -81,7 +84,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2c12c5b5-efcf-4aa5-90c5-bff1ab0cdbe2')
def test_create_region_with_specific_id(self):
- # Create a region with a specific id
+ """Test creating region with specific id"""
r_region_id = data_utils.rand_uuid()
r_description = data_utils.rand_name('description')
region = self.client.create_region(
@@ -93,7 +96,7 @@
@decorators.idempotent_id('d180bf99-544a-445c-ad0d-0c0d27663796')
def test_list_regions(self):
- # Get a list of regions
+ """Test getting a list of regions"""
fetched_regions = self.client.list_regions()['regions']
missing_regions =\
[e for e in self.setup_regions if e not in fetched_regions]
@@ -104,6 +107,7 @@
@decorators.idempotent_id('2d1057cb-bbde-413a-acdf-e2d265284542')
def test_list_regions_filter_by_parent_region_id(self):
+ """Test listing regions filtered by parent region id"""
# Add a sub-region to one of the existing test regions
r_description = data_utils.rand_name('description')
region = self.client.create_region(
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 5ba4c9f..dd7d5af 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,6 +25,8 @@
class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test roles"""
+
# NOTE: force_tenant_isolation is true in the base class by default but
# overridden to false here to allow test execution for clouds using the
# pre-provisioned credentials provider.
@@ -75,6 +77,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
def test_role_create_update_show_list(self):
+ """Test creating, updating, showing and listing a role"""
r_name = data_utils.rand_name('Role')
role = self.roles_client.create_role(name=r_name)['role']
self.addCleanup(self.roles_client.delete_role, role['id'])
@@ -101,6 +104,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_user_on_project(self):
+ """Test granting, listing, revoking role to user on project"""
self.roles_client.create_user_role_on_project(self.project['id'],
self.user_body['id'],
self.role['id'])
@@ -122,6 +126,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_user_on_domain(self):
+ """Test granting, listing, revoking role to user on domain"""
self.roles_client.create_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@@ -142,6 +147,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_grant_list_revoke_role_to_group_on_project(self):
+ """Test granting, listing, revoking role to group on project"""
# Grant role to group on project
self.roles_client.create_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
@@ -175,6 +181,7 @@
@decorators.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
def test_grant_list_revoke_role_to_group_on_domain(self):
+ """Test granting, listing, revoking role to group on domain"""
self.roles_client.create_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
@@ -192,6 +199,7 @@
@decorators.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
def test_list_roles(self):
+ """Test listing roles"""
# Return a list of all roles
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.roles]
@@ -215,6 +223,7 @@
@decorators.idempotent_id('c90c316c-d706-4728-bcba-eb1912081b69')
def test_implied_roles_create_check_show_delete(self):
+ """Test creating, checking, showing and deleting implied roles"""
prior_role_id = self.roles[0]['id']
implies_role_id = self.roles[1]['id']
@@ -248,6 +257,7 @@
@decorators.idempotent_id('dc6f5959-b74d-4e30-a9e5-a8255494ff00')
def test_roles_hierarchy(self):
+ """Test creating implied role and listing role inferences rules"""
# Create inference rule from "roles[0]" to "role[1]"
self._create_implied_role(
self.roles[0]['id'], self.roles[1]['id'])
@@ -280,6 +290,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_assignments_for_implied_roles_create_delete(self):
+ """Test assignments when implied roles are created and deleted"""
# Create a grant using "roles[0]"
self.roles_client.create_user_role_on_project(
self.project['id'], self.user_body['id'], self.roles[0]['id'])
@@ -321,6 +332,7 @@
@decorators.idempotent_id('d92a41d2-5501-497a-84bb-6e294330e8f8')
def test_domain_roles_create_delete(self):
+ """Test creating, listing and deleting domain roles"""
domain_role = self.roles_client.create_role(
name=data_utils.rand_name('domain_role'),
domain_id=self.domain['id'])['role']
@@ -341,6 +353,7 @@
@decorators.idempotent_id('eb1e1c24-1bc4-4d47-9748-e127a1852c82')
def test_implied_domain_roles(self):
+ """Test creating implied roles when roles are in domains"""
# Create two roles in the same domain
domain_role1 = self.setup_test_role(domain_id=self.domain['id'])
domain_role2 = self.setup_test_role(domain_id=self.domain['id'])
@@ -373,6 +386,7 @@
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
def test_assignments_for_domain_roles(self):
+ """Test assignments for domain roles"""
domain_role = self.setup_test_role(domain_id=self.domain['id'])
# Create a grant using "domain_role"
@@ -395,6 +409,7 @@
@decorators.idempotent_id('3748c316-c18f-4b08-997b-c60567bc6235')
def test_list_all_implied_roles(self):
+ """Test listing all implied roles"""
# Create inference rule from "roles[0]" to "roles[1]"
self._create_implied_role(
self.roles[0]['id'], self.roles[1]['id'])
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 5f1b58d..f3a7471 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -24,6 +24,7 @@
class TokensV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test tokens"""
credentials = ['primary', 'admin', 'alt']
@@ -123,6 +124,7 @@
@decorators.idempotent_id('08ed85ce-2ba8-4864-b442-bcc61f16ae89')
def test_get_available_project_scopes(self):
+ """Test getting available project scopes"""
manager_project_id = self.os_primary.credentials.project_id
admin_user_id = self.os_admin.credentials.user_id
admin_role_id = self.get_role_by_name(CONF.identity.admin_role)['id']
@@ -152,10 +154,13 @@
@decorators.idempotent_id('ec5ecb05-af64-4c04-ac86-4d9f6f12f185')
def test_get_available_domain_scopes(self):
- # Test for verifying that listing domain scopes for a user works if
- # the user has a domain role or belongs to a group that has a domain
- # role. For this test, admin client is used to add roles to alt user,
- # which performs API calls, to avoid 401 Unauthorized errors.
+ """Test getting available domain scopes
+
+ To verify that listing domain scopes for a user works if
+ the user has a domain role or belongs to a group that has a domain
+ role. For this test, admin client is used to add roles to alt user,
+ which performs API calls, to avoid 401 Unauthorized errors.
+ """
alt_user_id = self.os_alt.credentials.user_id
def _create_user_domain_role_for_alt_user():
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 78e3cce..580e304 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -27,6 +27,7 @@
class TrustsV3TestJSON(base.BaseIdentityV3AdminTest):
+ """Test keystone trusts"""
@classmethod
def skip_checks(cls):
@@ -195,8 +196,11 @@
@decorators.idempotent_id('5a0a91a4-baef-4a14-baba-59bf4d7fcace')
def test_trust_impersonate(self):
- # Test case to check we can create, get and delete a trust
- # updates are not supported for trusts
+ """Test keystone trust with impersonation enabled
+
+ To check we can create, get and delete a trust.
+ Updates are not supported for trusts
+ """
trust = self.create_trust()
self.validate_trust(trust)
@@ -207,8 +211,11 @@
@decorators.idempotent_id('ed2a8779-a7ac-49dc-afd7-30f32f936ed2')
def test_trust_noimpersonate(self):
- # Test case to check we can create, get and delete a trust
- # with impersonation=False
+ """Test keystone trust with impersonation disabled
+
+ To check we can create, get and delete a trust
+ with impersonation=False
+ """
trust = self.create_trust(impersonate=False)
self.validate_trust(trust, impersonate=False)
@@ -219,8 +226,11 @@
@decorators.idempotent_id('0ed14b66-cefd-4b5c-a964-65759453e292')
def test_trust_expire(self):
- # Test case to check we can create, get and delete a trust
- # with an expiry specified
+ """Test expire attribute of keystone trust
+
+ To check we can create, get and delete a trust
+ with an expiry specified
+ """
expires_at = timeutils.utcnow() + datetime.timedelta(hours=1)
# NOTE(ylobankov) In some cases the expiry time may be rounded up
# because of microseconds. In fact, it depends on database and its
@@ -246,8 +256,10 @@
@decorators.idempotent_id('3e48f95d-e660-4fa9-85e0-5a3d85594384')
def test_trust_expire_invalid(self):
- # Test case to check we can check an invalid expiry time
- # is rejected with the correct error
+ """Test invalid expire attribute of a keystone trust
+
+ To check an invalid expiry time is rejected with the correct error
+ """
# with an expiry specified
expires_str = 'bad.123Z'
self.assertRaises(lib_exc.BadRequest,
@@ -256,6 +268,7 @@
@decorators.idempotent_id('6268b345-87ca-47c0-9ce3-37792b43403a')
def test_get_trusts_query(self):
+ """Test getting keystone trusts"""
self.create_trust()
trusts_get = self.trustor_client.list_trusts(
trustor_user_id=self.trustor_user_id)['trusts']
@@ -265,7 +278,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
def test_get_trusts_all(self):
-
+ """Test getting all keystone trusts"""
# Simple function that can be used for cleanup
def set_scope(auth_provider, scope):
auth_provider.scope = scope
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 11dcdb0..1cba945 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -23,11 +23,12 @@
class UsersNegativeTest(base.BaseIdentityV3AdminTest):
+ """Negative tests of keystone users"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e75f006c-89cc-477b-874d-588e4eab4b17')
def test_create_user_for_non_existent_domain(self):
- # Attempt to create a user in a non-existent domain should fail
+ """Attempt to create a user in a non-existent domain should fail"""
u_name = data_utils.rand_name('user')
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
@@ -39,7 +40,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b3c9fccc-4134-46f5-b600-1da6fb0a3b1f')
def test_authentication_for_disabled_user(self):
- # Attempt to authenticate for disabled user should fail
+ """Attempt to authenticate for disabled user should fail"""
password = data_utils.rand_password()
user = self.setup_test_user(password)
self.disable_user(user['name'], user['domain_id'])
diff --git a/tempest/api/identity/v2/test_api_discovery.py b/tempest/api/identity/v2/test_api_discovery.py
index 5b9d38c..afda104 100644
--- a/tempest/api/identity/v2/test_api_discovery.py
+++ b/tempest/api/identity/v2/test_api_discovery.py
@@ -18,11 +18,12 @@
class TestApiDiscovery(base.BaseIdentityV2Test):
- """Tests for API discovery features."""
+ """Tests for identity v2 API discovery features."""
@decorators.attr(type='smoke')
@decorators.idempotent_id('ea889a68-a15f-4166-bfb1-c12456eae853')
def test_api_version_resources(self):
+ """Test showing identity v2 api version resources"""
descr = self.non_admin_client.show_api_description()['version']
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -34,6 +35,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('007a0be0-78fe-4fdb-bbee-e9216cc17bb2')
def test_api_media_types(self):
+ """Test showing identity v2 api version media type"""
descr = self.non_admin_client.show_api_description()['version']
# Get MIME type bases and descriptions
media_types = [(media_type['base'], media_type['type']) for
@@ -49,6 +51,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('77fd6be0-8801-48e6-b9bf-38cdd2f253ec')
def test_api_version_statuses(self):
+ """Test showing identity v2 api version status"""
descr = self.non_admin_client.show_api_description()['version']
status = descr['status'].lower()
supported_statuses = ['current', 'stable', 'experimental',
diff --git a/tempest/api/identity/v2/test_extension.py b/tempest/api/identity/v2/test_extension.py
index c538c14..13555bd 100644
--- a/tempest/api/identity/v2/test_extension.py
+++ b/tempest/api/identity/v2/test_extension.py
@@ -18,10 +18,11 @@
class ExtensionTestJSON(base.BaseIdentityV2Test):
+ """Test extensions in identity v2 API"""
@decorators.idempotent_id('85f3f661-f54c-4d48-b563-72ae952b9383')
def test_list_extensions(self):
- # List all the extensions
+ """List all the identity extensions via v2 API"""
body = self.non_admin_client.list_extensions()['extensions']['values']
self.assertNotEmpty(body)
keys = ['name', 'updated', 'alias', 'links',
diff --git a/tempest/api/identity/v2/test_tenants.py b/tempest/api/identity/v2/test_tenants.py
index b2a6d13..1752b65 100644
--- a/tempest/api/identity/v2/test_tenants.py
+++ b/tempest/api/identity/v2/test_tenants.py
@@ -19,11 +19,13 @@
class IdentityTenantsTest(base.BaseIdentityV2Test):
+ """Test listing tenants in identity v2 API"""
credentials = ['primary', 'alt']
@decorators.idempotent_id('ecae2459-243d-4ba1-ad02-65f15dc82b78')
def test_list_tenants_returns_only_authorized_tenants(self):
+ """Test listing tenants only returns authorized tenants via v2 API"""
alt_tenant_name = self.os_alt.credentials.tenant_name
resp = self.non_admin_tenants_client.list_tenants()
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index 64b81c2..a928ad9 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -20,10 +20,11 @@
class TokensTest(base.BaseIdentityV2Test):
+ """Test tokens in identity v2 API"""
@decorators.idempotent_id('65ae3b78-91ff-467b-a705-f6678863b8ec')
def test_create_token(self):
-
+ """Test creating token for user via v2 API"""
token_client = self.non_admin_token_client
# get a token for the user
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 2eea860..a63b45c 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -28,6 +28,7 @@
class IdentityUsersTest(base.BaseIdentityV2Test):
+ """Test user password in identity v2 API"""
@classmethod
def resource_setup(cls):
@@ -85,6 +86,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_user_update_own_password(self):
+ """test updating user's own password via v2 API"""
old_pass = self.creds.password
old_token = self.non_admin_users_client.token
new_pass = data_utils.rand_password()
diff --git a/tempest/api/identity/v3/test_api_discovery.py b/tempest/api/identity/v3/test_api_discovery.py
index e87d1cd..ebb96fd 100644
--- a/tempest/api/identity/v3/test_api_discovery.py
+++ b/tempest/api/identity/v3/test_api_discovery.py
@@ -22,10 +22,11 @@
class TestApiDiscovery(base.BaseIdentityV3Test):
- """Tests for API discovery features."""
+ """Tests for identity API discovery features."""
@decorators.idempotent_id('79aec9ae-710f-4c54-a4fc-3aa25b4feac3')
def test_identity_v3_existence(self):
+ """Test that identity v3 version should exist"""
versions = self.non_admin_versions_client.list_versions()
found = any(
"v3" in version.get('id')
@@ -35,9 +36,12 @@
@decorators.idempotent_id('721f480f-35b6-46c7-846e-047e6acea0dc')
@decorators.attr(type='smoke')
def test_list_api_versions(self):
- # NOTE: Actually this API doesn't depend on v3 API at all, because
- # the API operation is "GET /" without v3's endpoint. The reason of
- # this test path is just v3 API is CURRENT on Keystone side.
+ """Test listing identity api versions
+
+ NOTE: Actually this API doesn't depend on v3 API at all, because
+ the API operation is "GET /" without v3's endpoint. The reason of
+ this test path is just v3 API is CURRENT on Keystone side.
+ """
versions = self.non_admin_versions_client.list_versions()
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -49,6 +53,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('b9232f5e-d9e5-4d97-b96c-28d3db4de1bd')
def test_api_version_resources(self):
+ """Test showing identity v3 api version resources"""
descr = self.non_admin_client.show_api_description()['version']
expected_resources = ('id', 'links', 'media-types', 'status',
'updated')
@@ -60,6 +65,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('657c1970-4722-4189-8831-7325f3bc4265')
def test_api_media_types(self):
+ """Test showing identity v3 api version media type"""
descr = self.non_admin_client.show_api_description()['version']
# Get MIME type bases and descriptions
media_types = [(media_type['base'], media_type['type']) for
@@ -75,6 +81,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('8879a470-abfb-47bb-bb8d-5a7fd279ad1e')
def test_api_version_statuses(self):
+ """Test showing identity v3 api version status"""
descr = self.non_admin_client.show_api_description()['version']
status = descr['status'].lower()
supported_statuses = ['current', 'stable', 'experimental',
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index 1cee902..ef1bbdf 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -23,6 +23,7 @@
class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
+ """Test application credentials"""
def _list_app_creds(self, name=None):
kwargs = dict(user_id=self.user_id)
@@ -33,6 +34,7 @@
@decorators.idempotent_id('8080c75c-eddc-4786-941a-c2da7039ae61')
def test_create_application_credential(self):
+ """Test creating application credential"""
app_cred = self.create_application_credential()
# Check that the secret appears in the create response
@@ -55,6 +57,7 @@
@decorators.idempotent_id('852daf0c-42b5-4239-8466-d193d0543ed3')
def test_create_application_credential_expires(self):
+ """Test creating application credential with expire time"""
expires_at = timeutils.utcnow() + datetime.timedelta(hours=1)
app_cred = self.create_application_credential(expires_at=expires_at)
@@ -64,6 +67,7 @@
@decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
def test_list_application_credentials(self):
+ """Test listing application credentials"""
self.create_application_credential()
self.create_application_credential()
@@ -72,6 +76,7 @@
@decorators.idempotent_id('9bb5e5cc-5250-493a-8869-8b665f6aa5f6')
def test_query_application_credentials(self):
+ """Test listing application credentials filtered by name"""
self.create_application_credential()
app_cred_two = self.create_application_credential()
app_cred_two_name = app_cred_two['name']
diff --git a/tempest/api/identity/v3/test_domains.py b/tempest/api/identity/v3/test_domains.py
index 9f132dd..bb62ea6 100644
--- a/tempest/api/identity/v3/test_domains.py
+++ b/tempest/api/identity/v3/test_domains.py
@@ -21,6 +21,7 @@
class DefaultDomainTestJSON(base.BaseIdentityV3Test):
+ """Test identity default domains"""
@classmethod
def setup_clients(cls):
@@ -35,5 +36,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
def test_default_domain_exists(self):
+ """Test showing default domain"""
domain = self.domains_client.show_domain(self.domain_id)['domain']
self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/v3/test_projects.py b/tempest/api/identity/v3/test_projects.py
index bbb4013..338b57b 100644
--- a/tempest/api/identity/v3/test_projects.py
+++ b/tempest/api/identity/v3/test_projects.py
@@ -19,11 +19,13 @@
class IdentityV3ProjectsTest(base.BaseIdentityV3Test):
+ """Test identity projects"""
credentials = ['primary', 'alt']
@decorators.idempotent_id('86128d46-e170-4644-866a-cc487f699e1d')
def test_list_projects_returns_only_authorized_projects(self):
+ """Test listing projects only returns authorized projects"""
alt_project_name = self.os_alt.credentials.project_name
resp = self.non_admin_users_client.list_user_projects(
self.os_primary.credentials.user_id)
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index cb05f39..b201285 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -24,9 +24,11 @@
class TokensV3Test(base.BaseIdentityV3Test):
+ """Test identity tokens"""
@decorators.idempotent_id('a9512ac3-3909-48a4-b395-11f438e16260')
def test_validate_token(self):
+ """Test validating token for user"""
creds = self.os_primary.credentials
user_id = creds.user_id
username = creds.username
@@ -69,7 +71,7 @@
@decorators.idempotent_id('6f8e4436-fc96-4282-8122-e41df57197a9')
def test_create_token(self):
-
+ """Test creating token for user"""
creds = self.os_primary.credentials
user_id = creds.user_id
username = creds.username
@@ -120,9 +122,12 @@
@decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
def test_token_auth_creation_existence_deletion(self):
- # Tests basic token auth functionality in a way that is compatible with
- # pre-provisioned credentials. The default user is used for token
- # authentication.
+ """Test auth/check existence/delete token for user
+
+ Tests basic token auth functionality in a way that is compatible with
+ pre-provisioned credentials. The default user is used for token
+ authentication.
+ """
# Valid user's token is authenticated
user = self.os_primary.credentials
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index d4e7612..6425ea9 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -28,6 +28,7 @@
class IdentityV3UsersTest(base.BaseIdentityV3Test):
+ """Test identity user password"""
@classmethod
def resource_setup(cls):
@@ -82,6 +83,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_user_update_own_password(self):
+ """Test updating user's own password"""
old_pass = self.creds.password
old_token = self.non_admin_client.token
new_pass = data_utils.rand_password()
@@ -111,6 +113,7 @@
'immutable user source and solely '
'provides read-only access to users.')
def test_password_history_check_self_service_api(self):
+ """Test checking password changing history"""
old_pass = self.creds.password
new_pass1 = data_utils.rand_password()
new_pass2 = data_utils.rand_password()
@@ -141,6 +144,7 @@
'Security compliance not available.')
@decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
def test_user_account_lockout(self):
+ """Test locking out user account after failure attempts"""
if (CONF.identity.user_lockout_failure_attempts <= 0 or
CONF.identity.user_lockout_duration <= 0):
raise self.skipException(
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index c4a3e0e..c1a7211 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -29,6 +29,90 @@
LOG = logging.getLogger(__name__)
+class ImportImagesTest(base.BaseV2ImageTest):
+ """Here we test the import operations for image"""
+
+ @classmethod
+ def skip_checks(cls):
+ super(ImportImagesTest, cls).skip_checks()
+ if not CONF.image_feature_enabled.import_image:
+ skip_msg = (
+ "%s skipped as image import is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(ImportImagesTest, cls).resource_setup()
+ cls.available_import_methods = cls.client.info_import()[
+ 'import-methods']['value']
+ if not cls.available_import_methods:
+ raise cls.skipException('Server does not support '
+ 'any import method')
+
+ def _create_image(self):
+ # Create image
+ uuid = '00000000-1111-2222-3333-444455556666'
+ image_name = data_utils.rand_name('image')
+ container_format = CONF.image.container_formats[0]
+ disk_format = CONF.image.disk_formats[0]
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private',
+ ramdisk_id=uuid)
+ self.assertIn('name', image)
+ self.assertEqual(image_name, image['name'])
+ self.assertIn('visibility', image)
+ self.assertEqual('private', image['visibility'])
+ self.assertIn('status', image)
+ self.assertEqual('queued', image['status'])
+ return image
+
+ @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
+ def test_image_glance_direct_import(self):
+ """Test 'glance-direct' import functionalities
+
+ Create image, stage image data, import image and verify
+ that import succeeded.
+ """
+ if 'glance-direct' not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ 'glance-direct import method')
+ image = self._create_image()
+ # Stage image data
+ file_content = data_utils.random_bytes()
+ image_file = six.BytesIO(file_content)
+ self.client.stage_image_file(image['id'], image_file)
+ # Check image status is 'uploading'
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('uploading', body['status'])
+ # import image from staging to backend
+ self.client.image_import(image['id'], method='glance-direct')
+ self.client.wait_for_resource_activation(image['id'])
+
+ @decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62')
+ def test_image_web_download_import(self):
+ """Test 'web-download' import functionalities
+
+ Create image, import image and verify that import
+ succeeded.
+ """
+ if 'web-download' not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ 'web-download import method')
+ image = self._create_image()
+ # Now try to get image details
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('queued', body['status'])
+ # import image from web to backend
+ image_uri = CONF.image.http_image
+ self.client.image_import(image['id'], method='web-download',
+ image_uri=image_uri)
+ self.client.wait_for_resource_activation(image['id'])
+
+
class BasicOperationsImagesTest(base.BaseV2ImageTest):
"""Here we test the basic operations of images"""
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index 4631ea9..2506185 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -18,6 +18,7 @@
class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest):
+ """Test network DHCP agent scheduler extension"""
@classmethod
def skip_checks(cls):
@@ -37,11 +38,13 @@
@decorators.idempotent_id('5032b1fe-eb42-4a64-8f3b-6e189d8b5c7d')
def test_list_dhcp_agent_hosting_network(self):
+ """Test Listing DHCP agents hosting a network"""
self.admin_networks_client.list_dhcp_agents_on_hosting_network(
self.network['id'])
@decorators.idempotent_id('30c48f98-e45d-4ffb-841c-b8aad57c7587')
def test_list_networks_hosted_by_one_dhcp(self):
+ """Test Listing networks hosted by a DHCP agent"""
body = self.admin_networks_client.list_dhcp_agents_on_hosting_network(
self.network['id'])
agents = body['agents']
@@ -61,6 +64,7 @@
@decorators.idempotent_id('a0856713-6549-470c-a656-e97c8df9a14d')
def test_add_remove_network_from_dhcp_agent(self):
+ """Test adding and removing network from a DHCP agent"""
# The agent is now bound to the network, we can free the port
self.ports_client.delete_port(self.port['id'])
agent = dict()
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index 5bd3fce..0cec316 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -23,6 +23,7 @@
class ExternalNetworksTestJSON(base.BaseAdminNetworkTest):
+ """Test external networks"""
@classmethod
def resource_setup(cls):
@@ -42,8 +43,11 @@
@decorators.idempotent_id('462be770-b310-4df9-9c42-773217e4c8b1')
def test_create_external_network(self):
- # Create a network as an admin user specifying the
- # external network extension attribute
+ """Test creating external network
+
+ Create a network as an admin user specifying the
+ external network extension attribute
+ """
ext_network = self._create_network()
# Verifies router:external parameter
self.assertIsNotNone(ext_network['id'])
@@ -51,8 +55,11 @@
@decorators.idempotent_id('4db5417a-e11c-474d-a361-af00ebef57c5')
def test_update_external_network(self):
- # Update a network as an admin user specifying the
- # external network extension attribute
+ """Test updating external network
+
+ Update a network as an admin user specifying the
+ external network extension attribute
+ """
network = self._create_network(external=False)
self.assertFalse(network.get('router:external', False))
update_body = {'router:external': True}
@@ -64,6 +71,7 @@
@decorators.idempotent_id('39be4c9b-a57e-4ff9-b7c7-b218e209dfcc')
def test_list_external_networks(self):
+ """Test listing external networks"""
# Create external_net
external_network = self._create_network()
# List networks as a normal user and confirm the external
@@ -81,6 +89,7 @@
@decorators.idempotent_id('2ac50ab2-7ebd-4e27-b3ce-a9e399faaea2')
def test_show_external_networks_attribute(self):
+ """Test showing external network attribute"""
# Create external_net
external_network = self._create_network()
# Show an external network as a normal user and confirm the
@@ -101,9 +110,11 @@
@testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
'Floating ips are not availabled')
def test_delete_external_networks_with_floating_ip(self):
- # Verifies external network can be deleted while still holding
- # (unassociated) floating IPs
+ """Test deleting external network with unassociated floating ips
+ Verifies external network can be deleted while still holding
+ (unassociated) floating IPs
+ """
body = self.admin_networks_client.create_network(
**{'router:external': True})
external_network = body['network']
diff --git a/tempest/api/network/admin/test_external_networks_negative.py b/tempest/api/network/admin/test_external_networks_negative.py
index da32f2d..92731f6 100644
--- a/tempest/api/network/admin/test_external_networks_negative.py
+++ b/tempest/api/network/admin/test_external_networks_negative.py
@@ -25,16 +25,19 @@
class ExternalNetworksAdminNegativeTestJSON(base.BaseAdminNetworkTest):
+ """Negative tests of external network"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d402ae6c-0be0-4d8e-833b-a738895d98d0')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_create_port_with_precreated_floatingip_as_fixed_ip(self):
- # NOTE: External networks can be used to create both floating-ip as
- # well as instance-ip. So, creating an instance-ip with a value of a
- # pre-created floating-ip should be denied.
+ """Test creating port with precreated floating ip as fixed ip
+ NOTE: External networks can be used to create both floating-ip as
+ well as instance-ip. So, creating an instance-ip with a value of a
+ pre-created floating-ip should be denied.
+ """
# create a floating ip
body = self.admin_floating_ips_client.create_floatingip(
floating_network_id=CONF.network.public_network_id)
diff --git a/tempest/api/network/admin/test_floating_ips_admin_actions.py b/tempest/api/network/admin/test_floating_ips_admin_actions.py
index adc4dda..a8dae7c 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -23,6 +23,8 @@
class FloatingIPAdminTestJSON(base.BaseAdminNetworkTest):
+ """Test floating ips"""
+
credentials = ['primary', 'alt', 'admin']
@classmethod
@@ -55,6 +57,13 @@
@decorators.idempotent_id('64f2100b-5471-4ded-b46c-ddeeeb4f231b')
def test_list_floating_ips_from_admin_and_nonadmin(self):
+ """Test listing floating ips from admin and non admin users
+
+ This test performs below operations:
+ 1. Create couple floating ips for admin and non-admin users.
+ 2. Verify if admin can access all floating ips including other user
+ and non-admin user can only access its own floating ips.
+ """
# Create floating ip from admin user
floating_ip_admin = self.admin_floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id)
@@ -90,10 +99,11 @@
@decorators.idempotent_id('32727cc3-abe2-4485-a16e-48f2d54c14f2')
def test_create_list_show_floating_ip_with_tenant_id_by_admin(self):
+ """Verify if admin can create/list/show floating ip with tenant id"""
# Creates a floating IP
body = self.admin_floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id,
- tenant_id=self.network['tenant_id'],
+ project_id=self.network['project_id'],
port_id=self.port['id'])
created_floating_ip = body['floatingip']
self.addCleanup(
@@ -101,7 +111,7 @@
self.floating_ips_client.delete_floatingip,
created_floating_ip['id'])
self.assertIsNotNone(created_floating_ip['id'])
- self.assertIsNotNone(created_floating_ip['tenant_id'])
+ self.assertIsNotNone(created_floating_ip['project_id'])
self.assertIsNotNone(created_floating_ip['floating_ip_address'])
self.assertEqual(created_floating_ip['port_id'], self.port['id'])
self.assertEqual(created_floating_ip['floating_network_id'],
@@ -116,8 +126,8 @@
self.assertEqual(shown_floating_ip['id'], created_floating_ip['id'])
self.assertEqual(shown_floating_ip['floating_network_id'],
self.ext_net_id)
- self.assertEqual(shown_floating_ip['tenant_id'],
- self.network['tenant_id'])
+ self.assertEqual(shown_floating_ip['project_id'],
+ self.network['project_id'])
self.assertEqual(shown_floating_ip['floating_ip_address'],
created_floating_ip['floating_ip_address'])
self.assertEqual(shown_floating_ip['port_id'], self.port['id'])
diff --git a/tempest/api/network/admin/test_metering_extensions.py b/tempest/api/network/admin/test_metering_extensions.py
index 5063fef..a60cd48 100644
--- a/tempest/api/network/admin/test_metering_extensions.py
+++ b/tempest/api/network/admin/test_metering_extensions.py
@@ -92,13 +92,14 @@
@decorators.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
def test_list_metering_labels(self):
- # Verify label filtering
+ """Verify listing metering labels"""
body = self.admin_metering_labels_client.list_metering_labels(id=33)
metering_labels = body['metering_labels']
self.assertEmpty(metering_labels)
@decorators.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
def test_create_delete_metering_label_with_filters(self):
+ """Verifies creating and deleting metering label with filters"""
# Creates a label
name = data_utils.rand_name('metering-label-')
description = "label created by tempest"
@@ -115,19 +116,20 @@
@decorators.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
def test_show_metering_label(self):
- # Verifies the details of a label
+ """Verifies the details of a metering label"""
body = self.admin_metering_labels_client.show_metering_label(
self.metering_label['id'])
metering_label = body['metering_label']
self.assertEqual(self.metering_label['id'], metering_label['id'])
self.assertEqual(self.metering_label['tenant_id'],
- metering_label['tenant_id'])
+ metering_label['project_id'])
self.assertEqual(self.metering_label['name'], metering_label['name'])
self.assertEqual(self.metering_label['description'],
metering_label['description'])
@decorators.idempotent_id('cc832399-6681-493b-9d79-0202831a1281')
def test_list_metering_label_rules(self):
+ """Verifies listing metering label rules"""
client = self.admin_metering_label_rules_client
# Verify rule filtering
body = client.list_metering_label_rules(id=33)
@@ -136,6 +138,7 @@
@decorators.idempotent_id('f4d547cd-3aee-408f-bf36-454f8825e045')
def test_create_delete_metering_label_rule_with_filters(self):
+ """Verifies creating and deleting metering label rule with filters"""
# Creates a rule
remote_ip_prefix = ("10.0.1.0/24" if self._ip_version == 4
else "fd03::/64")
@@ -154,7 +157,7 @@
@decorators.idempotent_id('b7354489-96ea-41f3-9452-bace120fb4a7')
def test_show_metering_label_rule(self):
- # Verifies the details of a rule
+ """Verifies the metering details of a rule"""
client = self.admin_metering_label_rules_client
body = (client.show_metering_label_rule(
self.metering_label_rule['id']))
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 0db038d..190d9e3 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -53,17 +53,18 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
def test_network_quota_exceeding(self):
+ """Test creating network when exceeding network quota will fail"""
# Set the network quota to two
self.admin_quotas_client.update_quotas(self.project['id'], network=2)
# Create two networks
n1 = self.admin_networks_client.create_network(
- tenant_id=self.project['id'])
+ project_id=self.project['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_networks_client.delete_network,
n1['network']['id'])
n2 = self.admin_networks_client.create_network(
- tenant_id=self.project['id'])
+ project_id=self.project['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_networks_client.delete_network,
n2['network']['id'])
@@ -73,7 +74,7 @@
lib_exc.Conflict,
r"Quota exceeded for resources: \['network'\].*"):
n3 = self.admin_networks_client.create_network(
- tenant_id=self.project['id'])
+ project_id=self.project['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_networks_client.delete_network,
n3['network']['id'])
diff --git a/tempest/api/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
index 289e577..5f9f29f 100644
--- a/tempest/api/network/admin/test_ports.py
+++ b/tempest/api/network/admin/test_ports.py
@@ -24,6 +24,7 @@
class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
+ """Test extended attributes of ports"""
@classmethod
def setup_clients(cls):
@@ -41,11 +42,14 @@
@decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b')
@utils.services('compute')
def test_create_port_binding_ext_attr(self):
+ """Test creating port with extended attribute"""
post_body = {"network_id": self.network['id'],
"binding:host_id": self.host_id,
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -56,10 +60,13 @@
@decorators.idempotent_id('6f6c412c-711f-444d-8502-0ac30fbf5dd5')
@utils.services('compute')
def test_update_port_binding_ext_attr(self):
+ """Test updating port's extended attribute"""
post_body = {"network_id": self.network['id'],
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -73,11 +80,14 @@
@decorators.idempotent_id('1c82a44a-6c6e-48ff-89e1-abe7eaf8f9f8')
@utils.services('compute')
def test_list_ports_binding_ext_attr(self):
+ """Test updating and listing port's extended attribute"""
# Create a new port
post_body = {"network_id": self.network['id'],
"name": data_utils.rand_name(self.__class__.__name__)}
body = self.admin_ports_client.create_port(**post_body)
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
@@ -101,10 +111,13 @@
@decorators.idempotent_id('b54ac0ff-35fc-4c79-9ca3-c7dbd4ea4f13')
def test_show_port_binding_ext_attr(self):
+ """Test showing port's extended attribute"""
body = self.admin_ports_client.create_port(
name=data_utils.rand_name(self.__class__.__name__),
network_id=self.network['id'])
port = body['port']
+ self.addCleanup(self.admin_ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_ports_client.delete_port, port['id'])
body = self.admin_ports_client.show_port(port['id'])
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index ef5ebb6..d8db298 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -67,7 +67,7 @@
non_default_quotas = self.admin_quotas_client.list_quotas()
found = False
for qs in non_default_quotas['quotas']:
- if qs['tenant_id'] == project_id:
+ if qs['project_id'] == project_id:
found = True
self.assertTrue(found)
@@ -81,7 +81,7 @@
self.admin_quotas_client.reset_quotas(project_id)
non_default_quotas = self.admin_quotas_client.list_quotas()
for q in non_default_quotas['quotas']:
- self.assertNotEqual(project_id, q['tenant_id'])
+ self.assertNotEqual(project_id, q['project_id'])
quota_set = self.admin_quotas_client.show_quotas(project_id)['quota']
default_quotas = self.admin_quotas_client.show_default_quotas(
project_id)['quota']
@@ -89,6 +89,7 @@
@decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
def test_quotas(self):
+ """Test update/list/show/reset of network quotas"""
new_quotas = {'network': 0, 'port': 0}
self._check_quotas(new_quotas)
@@ -96,6 +97,7 @@
'quota_details', 'network'), 'Quota details extension not enabled.')
@decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288')
def test_show_quota_details(self):
+ """Test showing network quota details"""
# Show quota details for an existing project
quota_details = self.admin_quotas_client.show_quota_details(
self.admin_quotas_client.tenant_id)['quota']
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index 41f97d8..90e0917 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -27,6 +27,8 @@
class RoutersAdminTest(base.BaseAdminNetworkTest):
+ """Test routers operation supported by admin"""
+
# NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
# as some router operations, such as enabling or disabling SNAT
# require admin credentials by default
@@ -52,7 +54,7 @@
@decorators.idempotent_id('e54dd3a3-4352-4921-b09d-44369ae17397')
def test_create_router_setting_project_id(self):
- # Test creating router from admin user setting project_id.
+ """Test creating router from admin user setting project_id."""
project = data_utils.rand_name('test_tenant_')
description = data_utils.rand_name('desc_')
project = identity.identity_utils(self.os_admin).create_project(
@@ -63,18 +65,18 @@
name = data_utils.rand_name('router-')
create_body = self.admin_routers_client.create_router(
- name=name, tenant_id=project_id)
+ name=name, project_id=project_id)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_routers_client.delete_router,
create_body['router']['id'])
- self.assertEqual(project_id, create_body['router']['tenant_id'])
+ self.assertEqual(project_id, create_body['router']['project_id'])
@decorators.idempotent_id('847257cc-6afd-4154-b8fb-af49f5670ce8')
@utils.requires_ext(extension='ext-gw-mode', service='network')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_create_router_with_default_snat_value(self):
- # Create a router with default snat rule
+ """Create a router with default snat rule"""
router = self._create_router(
external_network_id=CONF.network.public_network_id)
self._verify_router_gateway(
@@ -86,6 +88,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_create_router_with_snat_explicit(self):
+ """Test creating router with specified enable_snat value"""
name = data_utils.rand_name('snat-router')
# Create a router enabling snat attributes
enable_snat_states = [False, True]
@@ -134,6 +137,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_update_router_set_gateway(self):
+ """Test updating router's gateway info"""
router = self._create_router()
self.routers_client.update_router(
router['id'],
@@ -150,6 +154,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_update_router_set_gateway_with_snat_explicit(self):
+ """Test setting router's gateway with snat enabled"""
router = self._create_router()
self.admin_routers_client.update_router(
router['id'],
@@ -167,6 +172,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_update_router_set_gateway_without_snat(self):
+ """Test setting router's gateway with snat not enabled"""
router = self._create_router()
self.admin_routers_client.update_router(
router['id'],
@@ -183,6 +189,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_update_router_unset_gateway(self):
+ """Test unsetting router's gateway"""
router = self._create_router(
external_network_id=CONF.network.public_network_id)
self.routers_client.update_router(router['id'],
@@ -199,6 +206,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_update_router_reset_gateway_without_snat(self):
+ """Test updating router's gateway to be with snat not enabled"""
router = self._create_router(
external_network_id=CONF.network.public_network_id)
self.admin_routers_client.update_router(
@@ -215,6 +223,7 @@
@decorators.idempotent_id('cbe42f84-04c2-11e7-8adb-fa163e4fa634')
@utils.requires_ext(extension='ext-gw-mode', service='network')
def test_create_router_set_gateway_with_fixed_ip(self):
+ """Test creating router setting gateway with fixed ip"""
# At first create an external network and then use that
# to create address and delete
network_name = data_utils.rand_name(self.__class__.__name__)
diff --git a/tempest/api/network/admin/test_routers_dvr.py b/tempest/api/network/admin/test_routers_dvr.py
index 270f802..291581c 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -106,14 +106,14 @@
attribute will be set to True
"""
name = data_utils.rand_name('router')
- tenant_id = self.routers_client.tenant_id
+ project_id = self.routers_client.project_id
# router needs to be in admin state down in order to be upgraded to DVR
# l3ha routers are not upgradable to dvr, make it explicitly non ha
router = self.admin_routers_client.create_router(name=name,
distributed=False,
admin_state_up=False,
ha=False,
- tenant_id=tenant_id)
+ project_id=project_id)
router_id = router['router']['id']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_routers_client.delete_router, router_id)
diff --git a/tempest/api/network/admin/test_routers_negative.py b/tempest/api/network/admin/test_routers_negative.py
index f605945..914c046 100644
--- a/tempest/api/network/admin/test_routers_negative.py
+++ b/tempest/api/network/admin/test_routers_negative.py
@@ -27,6 +27,7 @@
class RoutersAdminNegativeTest(base.BaseAdminNetworkTest):
+ """Admin negative tests of routers"""
@classmethod
def skip_checks(cls):
@@ -41,6 +42,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_router_set_gateway_used_ip_returns_409(self):
+ """Test creating router with gateway set to used ip should fail"""
# At first create a address from public_network_id
port = self.admin_ports_client.create_port(
name=data_utils.rand_name(self.__class__.__name__),
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 639defb..ff5026b 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -57,7 +57,7 @@
@decorators.idempotent_id('86c3529b-1231-40de-803c-00e40882f043')
def test_create_list_port_with_address_pair(self):
- # Create port with allowed address pair attribute
+ """Create and list port with allowed address pair attribute"""
allowed_address_pairs = [{'ip_address': self.ip_address,
'mac_address': self.mac_address}]
body = self.ports_client.create_port(
@@ -100,17 +100,17 @@
@decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
def test_update_port_with_address_pair(self):
- # Update port with allowed address pair
+ """Update port with allowed address pair"""
self._update_port_with_address(self.ip_address)
@decorators.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4')
def test_update_port_with_cidr_address_pair(self):
- # Update allowed address pair with cidr
+ """Update allowed address pair with cidr"""
self._update_port_with_address(str(self.cidr))
@decorators.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933')
def test_update_port_with_multiple_ip_mac_address_pair(self):
- # Create an ip _address and mac_address through port create
+ """Update allowed address pair port with multiple ip and mac"""
resp = self.ports_client.create_port(
network_id=self.network['id'],
name=data_utils.rand_name(self.__class__.__name__))
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index eb31ed3..fee6af5 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -104,9 +104,12 @@
@decorators.idempotent_id('e5517e62-6f16-430d-a672-f80875493d4c')
def test_dhcpv6_stateless_eui64(self):
- # NOTE: When subnets configured with RAs SLAAC (AOM=100) and DHCP
- # stateless (AOM=110) both for radvd and dnsmasq, port shall receive
- # IP address calculated from its MAC.
+ """Test eui64 ip when setting slaac and statelss for subnet
+
+ NOTE: When subnets configured with RAs SLAAC (AOM=100) and DHCP
+ stateless (AOM=110) both for radvd and dnsmasq, port shall receive
+ IP address calculated from its MAC.
+ """
for ra_mode, add_mode in (
('slaac', 'slaac'),
('dhcpv6-stateless', 'dhcpv6-stateless'),
@@ -122,9 +125,12 @@
@decorators.idempotent_id('ae2f4a5d-03ff-4c42-a3b0-ce2fcb7ea832')
def test_dhcpv6_stateless_no_ra(self):
- # NOTE: When subnets configured with dnsmasq SLAAC and DHCP stateless
- # and there is no radvd, port shall receive IP address calculated
- # from its MAC and mask of subnet.
+ """Test eui64 ip when setting stateless and no radvd for subnets
+
+ NOTE: When subnets configured with dnsmasq SLAAC and DHCP stateless
+ and there is no radvd, port shall receive IP address calculated
+ from its MAC and mask of subnet.
+ """
for ra_mode, add_mode in (
(None, 'slaac'),
(None, 'dhcpv6-stateless'),
@@ -161,8 +167,11 @@
@decorators.idempotent_id('21635b6f-165a-4d42-bf49-7d195e47342f')
def test_dhcpv6_stateless_no_ra_no_dhcp(self):
- # NOTE: If no radvd option and no dnsmasq option is configured
- # port shall receive IP from fixed IPs list of subnet.
+ """Test eui64 ip when setting no radvd and no dnsmasq for subnets
+
+ NOTE: If no radvd option and no dnsmasq option is configured
+ port shall receive IP from fixed IPs list of subnet.
+ """
real_ip, eui_ip = self._get_ips_from_subnet()
self._clean_network()
self.assertNotEqual(eui_ip, real_ip,
@@ -173,10 +182,13 @@
@decorators.idempotent_id('4544adf7-bb5f-4bdc-b769-b3e77026cef2')
def test_dhcpv6_two_subnets(self):
- # NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP
- # stateless and other IPv6 is with DHCP stateful, port shall receive
- # EUI-64 IP addresses from first subnet and DHCP address from second
- # one. Order of subnet creating should be unimportant.
+ """Test eui64 ip when creating port under network with two subnets
+
+ NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP
+ stateless and other IPv6 is with DHCP stateful, port shall receive
+ EUI-64 IP addresses from first subnet and DHCP address from second
+ one. Order of subnet creating should be unimportant.
+ """
for order in ("slaac_first", "dhcp_first"):
for ra_mode, add_mode in (
('slaac', 'slaac'),
@@ -225,10 +237,13 @@
@decorators.idempotent_id('4256c61d-c538-41ea-9147-3c450c36669e')
def test_dhcpv6_64_subnets(self):
- # NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP
- # stateless and other IPv4 is with DHCP of IPv4, port shall receive
- # EUI-64 IP addresses from first subnet and IPv4 DHCP address from
- # second one. Order of subnet creating should be unimportant.
+ """Test eui64 ip when setting slaac and stateless for subnets
+
+ NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP
+ stateless and other IPv4 is with DHCP of IPv4, port shall receive
+ EUI-64 IP addresses from first subnet and IPv4 DHCP address from
+ second one. Order of subnet creating should be unimportant.
+ """
for order in ("slaac_first", "dhcp_first"):
for ra_mode, add_mode in (
('slaac', 'slaac'),
@@ -271,8 +286,11 @@
@decorators.idempotent_id('4ab211a0-276f-4552-9070-51e27f58fecf')
def test_dhcp_stateful(self):
- # NOTE: With all options below, DHCPv6 shall allocate address from
- # subnet pool to port.
+ """Test creating port when setting stateful for subnets
+
+ NOTE: With all options below, DHCPv6 shall allocate address from
+ subnet pool to port.
+ """
for ra_mode, add_mode in (
('dhcpv6-stateful', 'dhcpv6-stateful'),
('dhcpv6-stateful', None),
@@ -294,9 +312,12 @@
@decorators.idempotent_id('51a5e97f-f02e-4e4e-9a17-a69811d300e3')
def test_dhcp_stateful_fixedips(self):
- # NOTE: With all options below, port shall be able to get
- # requested IP from fixed IP range not depending on
- # DHCP stateful (not SLAAC!) settings configured.
+ """Test creating port with fixed ip when setting stateful for subnets
+
+ NOTE: With all options below, port shall be able to get
+ requested IP from fixed IP range not depending on
+ DHCP stateful (not SLAAC!) settings configured.
+ """
for ra_mode, add_mode in (
('dhcpv6-stateful', 'dhcpv6-stateful'),
('dhcpv6-stateful', None),
@@ -324,8 +345,11 @@
@decorators.idempotent_id('98244d88-d990-4570-91d4-6b25d70d08af')
def test_dhcp_stateful_fixedips_outrange(self):
- # NOTE: When port gets IP address from fixed IP range it
- # shall be checked if it's from subnets range.
+ """Test creating port with fixed ip that is not in the range
+
+ NOTE: When port gets IP address from fixed IP range it
+ shall be checked if it's from subnets range.
+ """
kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
'ipv6_address_mode': 'dhcpv6-stateful'}
subnet = self.create_subnet(self.network, **kwargs)
@@ -342,8 +366,11 @@
@decorators.idempotent_id('57b8302b-cba9-4fbb-8835-9168df029051')
def test_dhcp_stateful_fixedips_duplicate(self):
- # NOTE: When port gets IP address from fixed IP range it
- # shall be checked if it's not duplicate.
+ """Test creating port with duplicate fixed ip
+
+ NOTE: When port gets IP address from fixed IP range it
+ shall be checked if it's not duplicate.
+ """
kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
'ipv6_address_mode': 'dhcpv6-stateful'}
subnet = self.create_subnet(self.network, **kwargs)
@@ -376,8 +403,11 @@
@decorators.idempotent_id('e98f65db-68f4-4330-9fea-abd8c5192d4d')
def test_dhcp_stateful_router(self):
- # NOTE: With all options below the router interface shall
- # receive DHCPv6 IP address from allocation pool.
+ """Test creating router with dhcp stateful
+
+ NOTE: With all options below the router interface shall
+ receive DHCPv6 IP address from allocation pool.
+ """
for ra_mode, add_mode in (
('dhcpv6-stateful', 'dhcpv6-stateful'),
('dhcpv6-stateful', None),
diff --git a/tempest/api/network/test_extensions.py b/tempest/api/network/test_extensions.py
index 4804ada..e116d7c 100644
--- a/tempest/api/network/test_extensions.py
+++ b/tempest/api/network/test_extensions.py
@@ -32,7 +32,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('ef28c7e6-e646-4979-9d67-deb207bc5564')
def test_list_show_extensions(self):
- # List available extensions for the project
+ """List available extensions and show the detail of each extension"""
expected_alias = ['security-group', 'l3_agent_scheduler',
'ext-gw-mode', 'binding', 'quotas',
'agent', 'dhcp_agent_scheduler', 'provider',
diff --git a/tempest/api/network/test_extra_dhcp_options.py b/tempest/api/network/test_extra_dhcp_options.py
index d363081..bc6418a 100644
--- a/tempest/api/network/test_extra_dhcp_options.py
+++ b/tempest/api/network/test_extra_dhcp_options.py
@@ -58,7 +58,7 @@
@decorators.idempotent_id('d2c17063-3767-4a24-be4f-a23dbfa133c9')
def test_create_list_port_with_extra_dhcp_options(self):
- # Create a port with Extra DHCP Options
+ """Test creating a port with Extra DHCP Options and list those"""
body = self.ports_client.create_port(
network_id=self.network['id'],
name=data_utils.rand_name(self.__class__.__name__),
@@ -76,7 +76,7 @@
@decorators.idempotent_id('9a6aebf4-86ee-4f47-b07a-7f7232c55607')
def test_update_show_port_with_extra_dhcp_options(self):
- # Update port with extra dhcp options
+ """Test updating port with extra DHCP options and show that port"""
name = data_utils.rand_name('new-port-name')
self.ports_client.update_port(
self.port['id'],
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index aaa5497..c32d3c1 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -73,6 +73,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e8718')
def test_create_list_show_update_delete_floating_ip(self):
+ """Test create/list/show/update/delete floating ip"""
# Creates a floating IP
body = self.floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id,
@@ -83,7 +84,7 @@
self.floating_ips_client.delete_floatingip,
created_floating_ip['id'])
self.assertIsNotNone(created_floating_ip['id'])
- self.assertIsNotNone(created_floating_ip['tenant_id'])
+ self.assertIsNotNone(created_floating_ip['project_id'])
self.assertIsNotNone(created_floating_ip['floating_ip_address'])
self.assertEqual(created_floating_ip['port_id'], self.ports[0]['id'])
self.assertEqual(created_floating_ip['floating_network_id'],
@@ -97,8 +98,8 @@
self.assertEqual(shown_floating_ip['id'], created_floating_ip['id'])
self.assertEqual(shown_floating_ip['floating_network_id'],
self.ext_net_id)
- self.assertEqual(shown_floating_ip['tenant_id'],
- created_floating_ip['tenant_id'])
+ self.assertEqual(shown_floating_ip['project_id'],
+ created_floating_ip['project_id'])
self.assertEqual(shown_floating_ip['floating_ip_address'],
created_floating_ip['floating_ip_address'])
self.assertEqual(shown_floating_ip['port_id'], self.ports[0]['id'])
@@ -133,6 +134,14 @@
@decorators.idempotent_id('e1f6bffd-442f-4668-b30e-df13f2705e77')
def test_floating_ip_delete_port(self):
+ """Test deleting floating ip's port
+
+ 1. Create a floating ip
+ 2. Create a port
+ 3. Update the floating ip's port_id to the created port
+ 4. Delete the port
+ 5. Verify that the port details are cleared from the floating ip
+ """
# Create a floating IP
body = self.floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id)
@@ -163,6 +172,7 @@
@decorators.idempotent_id('1bb2f731-fe5a-4b8c-8409-799ade1bed4d')
def test_floating_ip_update_different_router(self):
+ """Test associating a floating ip to a port on different router"""
# Associate a floating IP to a port on a router
body = self.floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id,
@@ -211,6 +221,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('36de4bd0-f09c-43e3-a8e1-1decc1ffd3a5')
def test_create_floating_ip_specifying_a_fixed_ip_address(self):
+ """Test creating floating ip with specified fixed ip"""
body = self.floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id,
port_id=self.ports[1]['id'],
@@ -230,6 +241,12 @@
@decorators.idempotent_id('45c4c683-ea97-41ef-9c51-5e9802f2f3d7')
def test_create_update_floatingip_with_port_multiple_ip_address(self):
+ """Test updating floating ip's fixed_ips to another ip of same port
+
+ First we create a port with 2 fixed ips, then we create a floating ip
+ with one of the fixed ips, and then we update the floating ip to
+ another fixed ip of that port.
+ """
# Find out ips that can be used for tests
list_ips = net_utils.get_unused_ip_addresses(
self.ports_client,
diff --git a/tempest/api/network/test_floating_ips_negative.py b/tempest/api/network/test_floating_ips_negative.py
index 1688c9d..80df5d6 100644
--- a/tempest/api/network/test_floating_ips_negative.py
+++ b/tempest/api/network/test_floating_ips_negative.py
@@ -58,6 +58,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('22996ea8-4a81-4b27-b6e1-fa5df92fa5e8')
def test_create_floatingip_with_port_ext_net_unreachable(self):
+ """Creating floating ip when port's external network is unreachable"""
self.assertRaises(
lib_exc.NotFound, self.floating_ips_client.create_floatingip,
floating_network_id=self.ext_net_id, port_id=self.port['id'],
@@ -67,6 +68,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('50b9aeb4-9f0b-48ee-aa31-fa955a48ff54')
def test_create_floatingip_in_private_network(self):
+ """Test creating floating in private network"""
self.assertRaises(lib_exc.BadRequest,
self.floating_ips_client.create_floatingip,
floating_network_id=self.network['id'],
@@ -77,6 +79,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6b3b8797-6d43-4191-985c-c48b773eb429')
def test_associate_floatingip_port_ext_net_unreachable(self):
+ """Associate floating ip to port with unreachable external network"""
# Create floating ip
body = self.floating_ips_client.create_floatingip(
floating_network_id=self.ext_net_id)
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index eba1f6c..7646b63 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -29,6 +29,7 @@
class BaseNetworkTestResources(base.BaseNetworkTest):
+ """Test networks"""
@classmethod
def resource_setup(cls):
@@ -158,6 +159,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('0e269138-0da6-4efc-a46d-578161e7b221')
def test_create_update_delete_network_subnet(self):
+ """Verify creating, updating and deleting network subnet"""
# Create a network
network = self.create_network()
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -183,7 +185,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2bf13842-c93f-4a69-83ed-717d2ec3b44e')
def test_show_network(self):
- # Verify the details of a network
+ """Verify the details of a network"""
body = self.networks_client.show_network(self.network['id'])
network = body['network']
for key in ['id', 'name']:
@@ -191,7 +193,7 @@
@decorators.idempotent_id('867819bb-c4b6-45f7-acf9-90edcf70aa5e')
def test_show_network_fields(self):
- # Verify specific fields of a network
+ """Verify specific fields of a network"""
fields = ['id', 'name']
if utils.is_extension_enabled('net-mtu', 'network'):
fields.append('mtu')
@@ -207,7 +209,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('f7ffdeda-e200-4a7a-bcbe-05716e86bf43')
def test_list_networks(self):
- # Verify the network exists in the list of all networks
+ """Verify the network exists in the list of all networks"""
body = self.networks_client.list_networks()
networks = [network['id'] for network in body['networks']
if network['id'] == self.network['id']]
@@ -215,7 +217,7 @@
@decorators.idempotent_id('6ae6d24f-9194-4869-9c85-c313cb20e080')
def test_list_networks_fields(self):
- # Verify specific fields of the networks
+ """Verify specific fields of the networks"""
fields = ['id', 'name']
if utils.is_extension_enabled('net-mtu', 'network'):
fields.append('mtu')
@@ -228,7 +230,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('bd635d81-6030-4dd1-b3b9-31ba0cfdf6cc')
def test_show_subnet(self):
- # Verify the details of a subnet
+ """Verify the details of a subnet"""
body = self.subnets_client.show_subnet(self.subnet['id'])
subnet = body['subnet']
self.assertNotEmpty(subnet, "Subnet returned has no fields")
@@ -238,7 +240,7 @@
@decorators.idempotent_id('270fff0b-8bfc-411f-a184-1e8fd35286f0')
def test_show_subnet_fields(self):
- # Verify specific fields of a subnet
+ """Verify specific fields of a subnet"""
fields = ['id', 'network_id']
body = self.subnets_client.show_subnet(self.subnet['id'],
fields=fields)
@@ -250,7 +252,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('db68ba48-f4ea-49e9-81d1-e367f6d0b20a')
def test_list_subnets(self):
- # Verify the subnet exists in the list of all subnets
+ """Verify the subnet exists in the list of all subnets"""
body = self.subnets_client.list_subnets()
subnets = [subnet['id'] for subnet in body['subnets']
if subnet['id'] == self.subnet['id']]
@@ -258,7 +260,7 @@
@decorators.idempotent_id('842589e3-9663-46b0-85e4-7f01273b0412')
def test_list_subnets_fields(self):
- # Verify specific fields of subnets
+ """Verify specific fields of subnets"""
fields = ['id', 'network_id']
body = self.subnets_client.list_subnets(fields=fields)
subnets = body['subnets']
@@ -268,6 +270,7 @@
@decorators.idempotent_id('f04f61a9-b7f3-4194-90b2-9bcf660d1bfe')
def test_delete_network_with_subnet(self):
+ """Verify deleting network with subnet"""
# Creates a network
network = self.create_network()
net_id = network['id']
@@ -287,34 +290,41 @@
@decorators.idempotent_id('d2d596e2-8e76-47a9-ac51-d4648009f4d3')
def test_create_delete_subnet_without_gateway(self):
+ """Verify creating and deleting subnet without gateway"""
self._create_verify_delete_subnet()
@decorators.idempotent_id('9393b468-186d-496d-aa36-732348cd76e7')
def test_create_delete_subnet_with_gw(self):
+ """Verify creating and deleting subnet with gateway"""
self._create_verify_delete_subnet(
**self.subnet_dict(['gateway']))
@decorators.idempotent_id('bec949c4-3147-4ba6-af5f-cd2306118404')
def test_create_delete_subnet_with_allocation_pools(self):
+ """Verify creating and deleting subnet with allocation pools"""
self._create_verify_delete_subnet(
**self.subnet_dict(['allocation_pools']))
@decorators.idempotent_id('8217a149-0c6c-4cfb-93db-0486f707d13f')
def test_create_delete_subnet_with_gw_and_allocation_pools(self):
+ """Verify create/delete subnet with gateway and allocation pools"""
self._create_verify_delete_subnet(**self.subnet_dict(
['gateway', 'allocation_pools']))
@decorators.idempotent_id('d830de0a-be47-468f-8f02-1fd996118289')
def test_create_delete_subnet_with_host_routes_and_dns_nameservers(self):
+ """Verify create/delete subnet with host routes and name servers"""
self._create_verify_delete_subnet(
**self.subnet_dict(['host_routes', 'dns_nameservers']))
@decorators.idempotent_id('94ce038d-ff0a-4a4c-a56b-09da3ca0b55d')
def test_create_delete_subnet_with_dhcp_enabled(self):
+ """Verify create/delete subnet with dhcp enabled"""
self._create_verify_delete_subnet(enable_dhcp=True)
@decorators.idempotent_id('3d3852eb-3009-49ec-97ac-5ce83b73010a')
def test_update_subnet_gw_dns_host_routes_dhcp(self):
+ """Verify updating subnet's gateway/nameserver/routes/dhcp"""
network = self.create_network()
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.networks_client.delete_network, network['id'])
@@ -349,6 +359,7 @@
@decorators.idempotent_id('a4d9ec4c-0306-4111-a75c-db01a709030b')
def test_create_delete_subnet_all_attributes(self):
+ """Verify create/delete subnet's all attributes"""
self._create_verify_delete_subnet(
enable_dhcp=True,
**self.subnet_dict(['gateway', 'host_routes', 'dns_nameservers']))
@@ -359,6 +370,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_external_network_visibility(self):
+ """Verify external network's visibility"""
public_network_id = CONF.network.public_network_id
# find external network matching public_network_id
@@ -394,6 +406,7 @@
@utils.requires_ext(extension="standard-attr-description",
service="network")
def test_create_update_network_description(self):
+ """Verify creating and updating network's description"""
body = self.create_network(description='d1')
self.assertEqual('d1', body['description'])
net_id = body['id']
@@ -454,6 +467,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('d4f9024d-1e28-4fc1-a6b1-25dbc6fa11e2')
def test_bulk_create_delete_network(self):
+ """Verify creating and deleting multiple networks in one request"""
# Creates 2 networks in one request
network_list = [{'name': data_utils.rand_name('network-')},
{'name': data_utils.rand_name('network-')}]
@@ -470,6 +484,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('8936533b-c0aa-4f29-8e53-6cc873aec489')
def test_bulk_create_delete_subnet(self):
+ """Verify creating and deleting multiple subnets in one request"""
networks = [self.create_network(), self.create_network()]
# Creates 2 subnets in one request
cidrs = [subnet_cidr
@@ -499,6 +514,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('48037ff2-e889-4c3b-b86a-8e3f34d2d060')
def test_bulk_create_delete_port(self):
+ """Verify creating and deleting multiple ports in one request"""
networks = [self.create_network(), self.create_network()]
# Creates 2 ports in one request
names = [data_utils.rand_name('port-') for i in range(len(networks))]
@@ -532,6 +548,7 @@
@decorators.idempotent_id('e41a4888-65a6-418c-a095-f7c2ef4ad59a')
def test_create_delete_subnet_with_gw(self):
+ """Verify creating and deleting subnet with gateway"""
net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
gateway = str(netaddr.IPAddress(net.first + 2))
network = self.create_network()
@@ -541,6 +558,7 @@
@decorators.idempotent_id('ebb4fd95-524f-46af-83c1-0305b239338f')
def test_create_delete_subnet_with_default_gw(self):
+ """Verify creating and deleting subnet without specified gateway"""
net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
gateway_ip = str(netaddr.IPAddress(net.first + 1))
network = self.create_network()
@@ -550,6 +568,12 @@
@decorators.idempotent_id('a9653883-b2a4-469b-8c3c-4518430a7e55')
def test_create_list_subnet_with_no_gw64_one_network(self):
+ """Verify subnets with and without gateway are in one network
+
+ First we create a network, then we create one ipv6 subnet with
+ gateway and one ipv4 subnet without gateway, the two subnets
+ should be in the same network
+ """
network = self.create_network()
ipv6_gateway = self.subnet_dict(['gateway'])['gateway']
subnet1 = self.create_subnet(network,
@@ -589,6 +613,7 @@
@decorators.idempotent_id('da40cd1b-a833-4354-9a85-cd9b8a3b74ca')
def test_create_delete_subnet_with_v6_attributes_stateful(self):
+ """Test create/delete subnet with ipv6 attributes stateful"""
self._create_verify_delete_subnet(
gateway=self._subnet_data[self._ip_version]['gateway'],
ipv6_ra_mode='dhcpv6-stateful',
@@ -596,12 +621,14 @@
@decorators.idempotent_id('176b030f-a923-4040-a755-9dc94329e60c')
def test_create_delete_subnet_with_v6_attributes_slaac(self):
+ """Test create/delete subnet with ipv6 attributes slaac"""
self._create_verify_delete_subnet(
ipv6_ra_mode='slaac',
ipv6_address_mode='slaac')
@decorators.idempotent_id('7d410310-8c86-4902-adf9-865d08e31adb')
def test_create_delete_subnet_with_v6_attributes_stateless(self):
+ """Test create/delete subnet with ipv6 attributes stateless"""
self._create_verify_delete_subnet(
ipv6_ra_mode='dhcpv6-stateless',
ipv6_address_mode='dhcpv6-stateless')
diff --git a/tempest/api/network/test_networks_negative.py b/tempest/api/network/test_networks_negative.py
index 3af67dd..0525484 100644
--- a/tempest/api/network/test_networks_negative.py
+++ b/tempest/api/network/test_networks_negative.py
@@ -21,10 +21,12 @@
class NetworksNegativeTestJSON(base.BaseNetworkTest):
+ """Negative tests of network"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9293e937-824d-42d2-8d5b-e985ea67002a')
def test_show_non_existent_network(self):
+ """Test showing non existent network"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.networks_client.show_network,
non_exist_id)
@@ -32,6 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d746b40c-5e09-4043-99f7-cba1be8b70df')
def test_show_non_existent_subnet(self):
+ """Test showing non existent subnet"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.subnets_client.show_subnet,
non_exist_id)
@@ -39,6 +42,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a954861d-cbfd-44e8-b0a9-7fab111f235d')
def test_show_non_existent_port(self):
+ """Test showing non existent port"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.ports_client.show_port,
non_exist_id)
@@ -46,6 +50,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('98bfe4e3-574e-4012-8b17-b2647063de87')
def test_update_non_existent_network(self):
+ """Test updating non existent network"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound, self.networks_client.update_network,
@@ -54,6 +59,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('03795047-4a94-4120-a0a1-bd376e36fd4e')
def test_delete_non_existent_network(self):
+ """Test deleting non existent network"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.networks_client.delete_network,
@@ -62,6 +68,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1cc47884-ac52-4415-a31c-e7ce5474a868')
def test_update_non_existent_subnet(self):
+ """Test updating non existent subnet"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.subnets_client.update_subnet,
non_exist_id, name='new_name')
@@ -69,6 +76,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a176c859-99fb-42ec-a208-8a85b552a239')
def test_delete_non_existent_subnet(self):
+ """Test deleting non existent subnet"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.subnets_client.delete_subnet, non_exist_id)
@@ -76,6 +84,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('13d3b106-47e6-4b9b-8d53-dae947f092fe')
def test_create_port_on_non_existent_network(self):
+ """Test creating port on non existent network"""
non_exist_net_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.ports_client.create_port,
@@ -85,6 +94,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('cf8eef21-4351-4f53-adcd-cc5cb1e76b92')
def test_update_non_existent_port(self):
+ """Test updating non existent port"""
non_exist_port_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.ports_client.update_port,
non_exist_port_id, name='new_name')
@@ -92,6 +102,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('49ec2bbd-ac2e-46fd-8054-798e679ff894')
def test_delete_non_existent_port(self):
+ """Test deleting non existent port"""
non_exist_port_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.ports_client.delete_port, non_exist_port_id)
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 10121de..479578d 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -70,12 +70,15 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('c72c1c0c-2193-4aca-aaa4-b1442640f51c')
def test_create_update_delete_port(self):
+ """Test creating, updating and deleting port"""
# Verify port creation
body = self.ports_client.create_port(
network_id=self.network['id'],
name=data_utils.rand_name(self.__class__.__name__))
port = body['port']
# Schedule port deletion with verification upon test completion
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(self._delete_port, port['id'])
self.assertTrue(port['admin_state_up'])
# Verify port update
@@ -89,6 +92,7 @@
@decorators.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c')
def test_create_bulk_port(self):
+ """Test creating multiple ports in a single request"""
network1 = self.network
network2 = self._create_network()
network_list = [network1['id'], network2['id']]
@@ -97,6 +101,10 @@
created_ports = body['ports']
port1 = created_ports[0]
port2 = created_ports[1]
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port1['id'])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port2['id'])
self.addCleanup(self._delete_port, port1['id'])
self.addCleanup(self._delete_port, port2['id'])
self.assertEqual(port1['network_id'], network1['id'])
@@ -107,6 +115,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('0435f278-40ae-48cb-a404-b8a087bc09b1')
def test_create_port_in_allowed_allocation_pools(self):
+ """Test creating port in allowed allocation pools"""
network = self._create_network()
net_id = network['id']
address = self.cidr
@@ -123,6 +132,8 @@
body = self.ports_client.create_port(
network_id=net_id,
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -136,7 +147,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('c9a685bd-e83f-499c-939f-9f7863ca259f')
def test_show_port(self):
- # Verify the details of port
+ """Verify the details of port"""
body = self.ports_client.show_port(self.port['id'])
port = body['port']
self.assertIn('id', port)
@@ -152,7 +163,7 @@
@decorators.idempotent_id('45fcdaf2-dab0-4c13-ac6c-fcddfb579dbd')
def test_show_port_fields(self):
- # Verify specific fields of a port
+ """Verify specific fields of a port"""
fields = ['id', 'mac_address']
body = self.ports_client.show_port(self.port['id'],
fields=fields)
@@ -164,7 +175,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('cf95b358-3e92-4a29-a148-52445e1ac50e')
def test_list_ports(self):
- # Verify the port exists in the list of all ports
+ """Verify the port exists in the list of all ports"""
body = self.ports_client.list_ports()
ports = [port['id'] for port in body['ports']
if port['id'] == self.port['id']]
@@ -172,6 +183,7 @@
@decorators.idempotent_id('e7fe260b-1e79-4dd3-86d9-bec6a7959fc5')
def test_port_list_filter_by_ip(self):
+ """Test listing ports filtered by ip"""
# Create network and subnet
network = self._create_network()
self._create_subnet(network)
@@ -179,11 +191,15 @@
port_1 = self.ports_client.create_port(
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_1['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_1['port']['id'])
port_2 = self.ports_client.create_port(
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_2['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_2['port']['id'])
# List ports filtered by fixed_ips
@@ -192,9 +208,9 @@
port_list = self.ports_client.list_ports(fixed_ips=fixed_ips)
# Check that we got the desired port
ports = port_list['ports']
- tenant_ids = set([port['tenant_id'] for port in ports])
- self.assertEqual(len(tenant_ids), 1,
- 'Ports from multiple tenants are in the list resp')
+ project_ids = set([port['project_id'] for port in ports])
+ self.assertEqual(len(project_ids), 1,
+ 'Ports from multiple projects are in the list resp')
port_ids = [port['id'] for port in ports]
fixed_ips = [port['fixed_ips'] for port in ports]
port_net_ids = [port['network_id'] for port in ports]
@@ -211,6 +227,7 @@
utils.is_extension_enabled('ip-substring-filtering', 'network'),
'ip-substring-filtering extension not enabled.')
def test_port_list_filter_by_ip_substr(self):
+ """Test listing ports filtered by part of ip address string"""
# Create network and subnet
network = self._create_network()
subnet = self._create_subnet(network)
@@ -236,6 +253,8 @@
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__),
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_1['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_1['port']['id'])
fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_2}]
@@ -243,6 +262,8 @@
network_id=network['id'],
name=data_utils.rand_name(self.__class__.__name__),
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port_2['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port_2['port']['id'])
@@ -289,6 +310,7 @@
@decorators.idempotent_id('5ad01ed0-0e6e-4c5d-8194-232801b15c72')
def test_port_list_filter_by_router_id(self):
+ """Test listing ports filtered by router id"""
# Create a router
network = self._create_network()
self._create_subnet(network)
@@ -301,6 +323,8 @@
# Add router interface to port created above
self.routers_client.add_router_interface(router['id'],
port_id=port['port']['id'])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.routers_client.remove_router_interface,
router['id'], port_id=port['port']['id'])
@@ -313,7 +337,7 @@
@decorators.idempotent_id('ff7f117f-f034-4e0e-abff-ccef05c454b4')
def test_list_ports_fields(self):
- # Verify specific fields of ports
+ """Verify specific fields of ports"""
fields = ['id', 'mac_address']
body = self.ports_client.list_ports(fields=fields)
ports = body['ports']
@@ -324,6 +348,7 @@
@decorators.idempotent_id('63aeadd4-3b49-427f-a3b1-19ca81f06270')
def test_create_update_port_with_second_ip(self):
+ """Test updating port from 2 fixed ips to 1 fixed ip and vice versa"""
# Create a network with two subnets
network = self._create_network()
subnet_1 = self._create_subnet(network)
@@ -336,6 +361,8 @@
# Create a port with multiple IP addresses
port = self.create_port(network,
fixed_ips=fixed_ips)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port['id'])
self.assertEqual(2, len(port['fixed_ips']))
@@ -379,6 +406,8 @@
"admin_state_up": True,
"fixed_ips": fixed_ip_1}
body = self.ports_client.create_port(**post_body)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -410,6 +439,12 @@
utils.is_extension_enabled('security-group', 'network'),
'security-group extension not enabled.')
def test_update_port_with_security_group_and_extra_attributes(self):
+ """Test updating port's security_group along with extra attributes
+
+ First we create a port with one security group, and then we update the
+ port's security_group, in the same update request we also change
+ the port's fixed ips.
+ """
self._update_port_with_security_groups(
[data_utils.rand_name('secgroup')])
@@ -418,12 +453,19 @@
utils.is_extension_enabled('security-group', 'network'),
'security-group extension not enabled.')
def test_update_port_with_two_security_groups_and_extra_attributes(self):
+ """Test updating port with two security_groups and extra attributes
+
+ First we create a port with one security group, and then we update the
+ port to two security_groups, in the same update request we also change
+ the port's fixed ips.
+ """
self._update_port_with_security_groups(
[data_utils.rand_name('secgroup'),
data_utils.rand_name('secgroup')])
@decorators.idempotent_id('13e95171-6cbd-489c-9d7c-3f9c58215c18')
def test_create_show_delete_port_user_defined_mac(self):
+ """Test creating port with user defined mac address"""
# Create a port for a legal mac
body = self.ports_client.create_port(
network_id=self.network['id'],
@@ -436,6 +478,8 @@
network_id=self.network['id'],
mac_address=free_mac_address,
name=data_utils.rand_name(self.__class__.__name__))
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ body['port']['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -450,9 +494,12 @@
utils.is_extension_enabled('security-group', 'network'),
'security-group extension not enabled.')
def test_create_port_with_no_securitygroups(self):
+ """Test creating port without security groups"""
network = self._create_network()
self._create_subnet(network)
port = self.create_port(network, security_groups=[])
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ port['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.ports_client.delete_port, port['id'])
self.assertIsNotNone(port['security_groups'])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 30423e3..c03a8a2 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -27,6 +27,7 @@
class RoutersTest(base.BaseNetworkTest):
+ """Test routers"""
def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
interface = self.routers_client.add_router_interface(
@@ -53,6 +54,7 @@
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
def test_create_show_list_update_delete_router(self):
+ """Test create/show/list/update/delete of a router"""
# Create a router
router_name = data_utils.rand_name(self.__class__.__name__ + '-router')
router = self.create_router(
@@ -87,6 +89,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('b42e6e39-2e37-49cc-a6f4-8467e940900a')
def test_add_remove_router_interface_with_subnet_id(self):
+ """Test adding and removing router interface with subnet id"""
network_name = data_utils.rand_name(self.__class__.__name__)
network = self.networks_client.create_network(
name=network_name)['network']
@@ -113,6 +116,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2b7d2f37-6748-4d78-92e5-1d590234f0d5')
def test_add_remove_router_interface_with_port_id(self):
+ """Test adding and removing router interface with port id"""
network_name = data_utils.rand_name(self.__class__.__name__)
network = self.networks_client.create_network(
name=network_name)['network']
@@ -145,6 +149,7 @@
@decorators.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c')
@utils.requires_ext(extension='extraroute', service='network')
def test_update_delete_extra_route(self):
+ """Test updating and deleting router with extra route"""
# Create different cidr for each subnet to avoid cidr duplicate
# The cidr starts from project_cidr
next_cidr = self.cidr
@@ -215,6 +220,7 @@
@decorators.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
def test_update_router_admin_state(self):
+ """Test updating router's admin state"""
router = self.create_router()
self.addCleanup(self.delete_router, router)
self.assertFalse(router['admin_state_up'])
@@ -228,6 +234,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('802c73c9-c937-4cef-824b-2191e24a6aab')
def test_add_multiple_router_interfaces(self):
+ """Test adding multiple router interfaces"""
network_name = data_utils.rand_name(self.__class__.__name__)
network01 = self.networks_client.create_network(
name=network_name)['network']
@@ -258,6 +265,7 @@
@decorators.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff')
def test_router_interface_port_update_with_fixed_ip(self):
+ """Test updating router interface port's fixed ip"""
network_name = data_utils.rand_name(self.__class__.__name__)
network = self.networks_client.create_network(
name=network_name)['network']
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 0b61860..10a2706 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -21,6 +21,7 @@
class RoutersNegativeTest(base.BaseNetworkTest):
+ """Negative tests of routers"""
@classmethod
def skip_checks(cls):
@@ -39,6 +40,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('37a94fc0-a834-45b9-bd23-9a81d2fd1e22')
def test_router_add_gateway_invalid_network_returns_404(self):
+ """Test adding gateway with invalid network for router"""
self.assertRaises(lib_exc.NotFound,
self.routers_client.update_router,
self.router['id'],
@@ -48,6 +50,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('11836a18-0b15-4327-a50b-f0d9dc66bddd')
def test_router_add_gateway_net_not_external_returns_400(self):
+ """Test adding gateway with not external network for router"""
alt_network = self.create_network()
sub_cidr = self.cidr.next()
self.create_subnet(alt_network, cidr=sub_cidr)
@@ -60,6 +63,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('957751a3-3c68-4fa2-93b6-eb52ea10db6e')
def test_add_router_interfaces_on_overlapping_subnets_returns_400(self):
+ """Test adding router interface which is on overlapping subnets"""
network01 = self.create_network(
network_name=data_utils.rand_name('router-network01-'))
network02 = self.create_network(
@@ -79,6 +83,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20')
def test_router_remove_interface_in_use_returns_409(self):
+ """Test removing in-use interface from router"""
self.routers_client.add_router_interface(self.router['id'],
subnet_id=self.subnet['id'])
self.addCleanup(self.routers_client.remove_router_interface,
@@ -90,6 +95,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c2a70d72-8826-43a7-8208-0209e6360c47')
def test_show_non_existent_router_returns_404(self):
+ """Test showing non existent router"""
router = data_utils.rand_name('non_exist_router')
self.assertRaises(lib_exc.NotFound, self.routers_client.show_router,
router)
@@ -97,6 +103,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b23d1569-8b0c-4169-8d4b-6abd34fad5c7')
def test_update_non_existent_router_returns_404(self):
+ """Test updating non existent router"""
router = data_utils.rand_name('non_exist_router')
self.assertRaises(lib_exc.NotFound, self.routers_client.update_router,
router, name="new_name")
@@ -104,6 +111,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c7edc5ad-d09d-41e6-a344-5c0c31e2e3e4')
def test_delete_non_existent_router_returns_404(self):
+ """Test deleting non existent router"""
router = data_utils.rand_name('non_exist_router')
self.assertRaises(lib_exc.NotFound, self.routers_client.delete_router,
router)
@@ -114,6 +122,7 @@
class DvrRoutersNegativeTest(base.BaseNetworkTest):
+ """Negative tests of DVR router"""
@classmethod
def skip_checks(cls):
@@ -125,5 +134,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4990b055-8fc7-48ab-bba7-aa28beaad0b9')
def test_router_create_tenant_distributed_returns_forbidden(self):
+ """Non admin user is not allowed to create distributed router"""
self.assertRaises(lib_exc.Forbidden, self.create_router,
distributed=True)
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index ef19122..d75acfc 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -21,6 +21,7 @@
class SecGroupTest(base.BaseSecGroupTest):
+ """Test security groups"""
@classmethod
def skip_checks(cls):
@@ -67,7 +68,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('e30abd17-fef9-4739-8617-dc26da88e686')
def test_list_security_groups(self):
- # Verify the security group belonging to project exist in list
+ """Verify that default security group exist"""
body = self.security_groups_client.list_security_groups()
security_groups = body['security_groups']
found = None
@@ -80,6 +81,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('bfd128e5-3c92-44b6-9d66-7fe29d22c802')
def test_create_list_update_show_delete_security_group(self):
+ """Verify create/list/update/show/delete of security group"""
group_create_body, _ = self._create_security_group()
# List security groups and verify if created group is there in response
@@ -111,6 +113,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('cfb99e0e-7410-4a3d-8a0c-959a63ee77e9')
def test_create_show_delete_security_group_rule(self):
+ """Test create/show/delete of security group rule"""
group_create_body, _ = self._create_security_group()
# Create rules for each protocol
@@ -191,7 +194,7 @@
@decorators.idempotent_id('c2ed2deb-7a0c-44d8-8b4c-a5825b5c310b')
def test_create_security_group_rule_with_remote_group_id(self):
- # Verify creating security group rule with remote_group_id works
+ """Verify creating security group rule with remote_group_id works"""
sg1_body, _ = self._create_security_group()
sg2_body, _ = self._create_security_group()
@@ -209,7 +212,7 @@
@decorators.idempotent_id('16459776-5da2-4634-bce4-4b55ee3ec188')
def test_create_security_group_rule_with_remote_ip_prefix(self):
- # Verify creating security group rule with remote_ip_prefix works
+ """Verify creating security group rule with remote_ip_prefix works"""
sg1_body, _ = self._create_security_group()
sg_id = sg1_body['security_group']['id']
@@ -226,9 +229,10 @@
@decorators.idempotent_id('0a307599-6655-4220-bebc-fd70c64f2290')
def test_create_security_group_rule_with_protocol_integer_value(self):
- # Verify creating security group rule with the
- # protocol as integer value
- # arguments : "protocol": 17
+ """Verify creating security group rule with the integer protocol value
+
+ arguments : "protocol": 17
+ """
group_create_body, _ = self._create_security_group()
direction = 'ingress'
protocol = 17
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index d054865..beaeb20 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -24,6 +24,7 @@
class NegativeSecGroupTest(base.BaseSecGroupTest):
+ """Negative tests of security groups"""
@classmethod
def skip_checks(cls):
@@ -35,6 +36,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('424fd5c3-9ddc-486a-b45f-39bf0c820fc6')
def test_show_non_existent_security_group(self):
+ """Test showing non existent security group"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound, self.security_groups_client.show_security_group,
@@ -43,6 +45,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4c094c09-000b-4e41-8100-9617600c02a6')
def test_show_non_existent_security_group_rule(self):
+ """Test showing non existent security group rule"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
@@ -52,6 +55,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1f1bb89d-5664-4956-9fcd-83ee0fa603df')
def test_delete_non_existent_security_group(self):
+ """Test deleting non existent security group"""
non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.security_groups_client.delete_security_group,
@@ -61,6 +65,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('981bdc22-ce48-41ed-900a-73148b583958')
def test_create_security_group_rule_with_bad_protocol(self):
+ """Test creating security group rule with bad protocol"""
group_create_body, _ = self._create_security_group()
# Create rule with bad protocol name
@@ -74,6 +79,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5f8daf69-3c5f-4aaa-88c9-db1d66f68679')
def test_create_security_group_rule_with_bad_remote_ip_prefix(self):
+ """Test creating security group rule with bad remote ip prefix"""
group_create_body, _ = self._create_security_group()
# Create rule with bad remote_ip_prefix
@@ -89,6 +95,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4bf786fd-2f02-443c-9716-5b98e159a49a')
def test_create_security_group_rule_with_non_existent_remote_groupid(self):
+ """Creating security group rule with non existent remote group id"""
group_create_body, _ = self._create_security_group()
non_exist_id = data_utils.rand_uuid()
@@ -105,6 +112,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b5c4b247-6b02-435b-b088-d10d45650881')
def test_create_security_group_rule_with_remote_ip_and_group(self):
+ """Test creating security group rule with remote ip and group"""
sg1_body, _ = self._create_security_group()
sg2_body, _ = self._create_security_group()
@@ -121,6 +129,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5666968c-fff3-40d6-9efc-df1c8bd01abb')
def test_create_security_group_rule_with_bad_ethertype(self):
+ """Test creating security group rule with bad bad ethertype"""
group_create_body, _ = self._create_security_group()
# Create rule with bad ethertype
@@ -134,6 +143,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0d9c7791-f2ad-4e2f-ac73-abf2373b0d2d')
def test_create_security_group_rule_with_invalid_ports(self):
+ """Test creating security group rule with invalid ports"""
group_create_body, _ = self._create_security_group()
# Create rule for tcp protocol with invalid ports
@@ -168,7 +178,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2323061e-9fbf-4eb0-b547-7e8fafc90849')
def test_create_additional_default_security_group_fails(self):
- # Create security group named 'default', it should be failed.
+ """Test creating additional default security group
+
+ Create security group named 'default', it should be failed.
+ """
name = 'default'
self.assertRaises(lib_exc.Conflict,
self.security_groups_client.create_security_group,
@@ -177,7 +190,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('966e2b96-023a-11e7-a9e4-fa163e4fa634')
def test_create_security_group_update_name_default(self):
- # Update security group name to 'default', it should be failed.
+ """Test updating security group's name to default
+
+ Update security group name to 'default', it should be failed.
+ """
group_create_body, _ = self._create_security_group()
self.assertRaises(lib_exc.Conflict,
self.security_groups_client.update_security_group,
@@ -187,7 +203,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8fde898f-ce88-493b-adc9-4e4692879fc5')
def test_create_duplicate_security_group_rule_fails(self):
- # Create duplicate security group rule, it should fail.
+ """Test creating duplicate security group rule
+
+ Create duplicate security group rule, it should fail.
+ """
body, _ = self._create_security_group()
min_port = 66
@@ -213,7 +232,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('be308db6-a7cf-4d5c-9baf-71bafd73f35e')
def test_create_security_group_rule_with_non_existent_security_group(self):
- # Create security group rules with not existing security group.
+ """Creating security group rules with not existing security group"""
non_existent_sg = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
@@ -228,6 +247,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7607439c-af73-499e-bf64-f687fd12a842')
def test_create_security_group_rule_wrong_ip_prefix_version(self):
+ """Test creating security group rule with wrong ip prefix version"""
group_create_body, _ = self._create_security_group()
# Create rule with bad remote_ip_prefix
diff --git a/tempest/api/network/test_service_providers.py b/tempest/api/network/test_service_providers.py
index 9ebcd89..5af5244 100644
--- a/tempest/api/network/test_service_providers.py
+++ b/tempest/api/network/test_service_providers.py
@@ -18,12 +18,14 @@
class ServiceProvidersTest(base.BaseNetworkTest):
+ """Test network service providers"""
@decorators.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6')
@testtools.skipUnless(
utils.is_extension_enabled('service-type', 'network'),
'service-type extension not enabled.')
def test_service_providers_list(self):
+ """Test listing network service providers"""
body = self.service_providers_client.list_service_providers()
self.assertIn('service_providers', body)
self.assertIsInstance(body['service_providers'], list)
diff --git a/tempest/api/network/test_subnetpools_extensions.py b/tempest/api/network/test_subnetpools_extensions.py
index bfc2609..48603ed 100644
--- a/tempest/api/network/test_subnetpools_extensions.py
+++ b/tempest/api/network/test_subnetpools_extensions.py
@@ -49,6 +49,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e9811')
def test_create_list_show_update_delete_subnetpools(self):
+ """Test create/list/show/update/delete of subnet pools"""
subnetpool_name = data_utils.rand_name('subnetpools')
# create subnet pool
prefix = CONF.network.default_network
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
index 2b9719a..5219c34 100644
--- a/tempest/api/network/test_tags.py
+++ b/tempest/api/network/test_tags.py
@@ -51,7 +51,7 @@
@decorators.idempotent_id('ee76bfaf-ac94-4d74-9ecc-4bbd4c583cb1')
def test_create_list_show_update_delete_tags(self):
- # Validate that creating a tag on a network resource works.
+ """Validate that creating a tag on a network resource works"""
tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
self.tags_client.create_tag('networks', self.network['id'], tag_name)
self.addCleanup(self.tags_client.delete_all_tags, 'networks',
@@ -158,6 +158,7 @@
@decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9')
def test_create_check_list_and_delete_tags(self):
+ """Test tag operations on subnets/ports/routers/subnetpools"""
tag_names = self._create_tags_for_each_resource()
for i, resource in enumerate(self.SUPPORTED_RESOURCES):
@@ -181,6 +182,7 @@
@decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791')
def test_update_and_delete_all_tags(self):
+ """Test update/delete all tags on subnets/ports/routers/subnetpools"""
self._create_tags_for_each_resource()
for resource in self.SUPPORTED_RESOURCES:
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 6599e43..687fe57 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -16,12 +16,12 @@
import tempfile
from tempest.api.object_storage import base
-from tempest.common import custom_matchers
from tempest.common import utils
from tempest.lib import decorators
class BulkTest(base.BaseObjectTest):
+ """Test bulk operation of archived file"""
def setUp(self):
super(BulkTest, self).setUp()
@@ -71,22 +71,12 @@
@decorators.idempotent_id('a407de51-1983-47cc-9f14-47c2b059413c')
@utils.requires_ext(extension='bulk_upload', service='object')
def test_extract_archive(self):
- # Test bulk operation of file upload with an archived file
+ """Test bulk operation of file upload with an archived file"""
filepath, container_name, object_name = self._create_archive()
resp = self._upload_archive(filepath)
self.containers.append(container_name)
- # When uploading an archived file with the bulk operation, the response
- # does not contain 'content-length' header. This is the special case,
- # therefore the existence of response headers is checked without
- # custom matcher.
- self.assertIn('transfer-encoding', resp.response)
- self.assertIn('content-type', resp.response)
- self.assertIn('x-trans-id', resp.response)
- self.assertIn('date', resp.response)
-
- # Check only the format of common headers with custom matcher
- self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
+ self.assertHeaders(resp.response, 'Account', 'PUT')
param = {'format': 'json'}
resp, body = self.account_client.list_account_containers(param)
@@ -106,24 +96,14 @@
@decorators.idempotent_id('c075e682-0d2a-43b2-808d-4116200d736d')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
data = '%s/%s\n%s' % (container_name, object_name, container_name)
resp = self.bulk_client.delete_bulk_data(data=data)
- # When deleting multiple files using the bulk operation, the response
- # does not contain 'content-length' header. This is the special case,
- # therefore the existence of response headers is checked without
- # custom matcher.
- self.assertIn('transfer-encoding', resp.response)
- self.assertIn('content-type', resp.response)
- self.assertIn('x-trans-id', resp.response)
- self.assertIn('date', resp.response)
-
- # Check only the format of common headers with custom matcher
- self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
+ self.assertHeaders(resp.response, 'Account', 'DELETE')
# Check if uploaded contents are completely deleted
self._check_contents_deleted(container_name)
@@ -131,7 +111,7 @@
@decorators.idempotent_id('dbea2bcb-efbb-4674-ac8a-a5a0e33d1d79')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete_by_POST(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files by HTTP POST"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
@@ -139,17 +119,7 @@
resp = self.bulk_client.delete_bulk_data_with_post(data=data)
- # When deleting multiple files using the bulk operation, the response
- # does not contain 'content-length' header. This is the special case,
- # therefore the existence of response headers is checked without
- # custom matcher.
- self.assertIn('transfer-encoding', resp.response)
- self.assertIn('content-type', resp.response)
- self.assertIn('x-trans-id', resp.response)
- self.assertIn('date', resp.response)
-
- # Check only the format of common headers with custom matcher
- self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
+ self.assertHeaders(resp.response, 'Account', 'POST')
# Check if uploaded contents are completely deleted
self._check_contents_deleted(container_name)
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 48f42ec..6854bbe 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -22,6 +22,7 @@
class AccountQuotasTest(base.BaseObjectTest):
+ """Test account quotas"""
credentials = [['operator', CONF.object_storage.operator_role],
['reseller', CONF.object_storage.reseller_admin_role]]
@@ -79,6 +80,7 @@
@decorators.idempotent_id('a22ef352-a342-4587-8f47-3bbdb5b039c4')
@utils.requires_ext(extension='account_quotas', service='object')
def test_upload_valid_object(self):
+ """Test uploading valid object"""
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index 3e664d7..8d2a501 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -21,6 +21,7 @@
class AccountNegativeTest(base.BaseObjectTest):
+ """Negative tests of account"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -33,7 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('070e6aca-6152-4867-868d-1118d68fb38c')
def test_list_containers_with_non_authorized_user(self):
- # list containers using non-authorized user
+ """Test listing containers using non-authorized user"""
test_auth_provider = self.os_operator.auth_provider
# Get auth for the test user
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index e9ca0b1..c8731fe 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -22,6 +22,7 @@
class ObjectTestACLs(base.BaseObjectTest):
+ """Test object ACLs"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -36,7 +37,7 @@
@decorators.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6')
def test_read_object_with_rights(self):
- # attempt to read object using authorized user
+ """Test reading object using authorized user"""
# update X-Container-Read metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
@@ -64,7 +65,7 @@
@decorators.idempotent_id('aa58bfa5-40d9-4bc3-82b4-d07f4a9e392a')
def test_write_object_with_rights(self):
- # attempt to write object using authorized user
+ """Test writing object using authorized user"""
# update X-Container-Write metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 90b24b4..73d7f27 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -22,6 +22,7 @@
class ObjectACLsNegativeTest(base.BaseObjectTest):
+ """Negative tests of object ACLs"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -48,6 +49,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af587587-0c24-4e15-9822-8352ce711013')
def test_write_object_without_using_creds(self):
+ """Test writing object without using credentials"""
# trying to create object with empty headers
# X-Auth-Token is not provided
object_name = data_utils.rand_name(name='Object')
@@ -62,6 +64,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af85af0b-a025-4e72-a90e-121babf55720')
def test_delete_object_without_using_creds(self):
+ """Test deleting object without using credentials"""
# create object
object_name = data_utils.rand_name(name='Object')
self.object_client.create_object(self.container_name, object_name,
@@ -79,7 +82,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('63d84e37-55a6-42e2-9e5f-276e60e26a00')
def test_write_object_with_non_authorized_user(self):
- # attempt to upload another file using non-authorized user
+ """Test writing object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
# trying to create object with non-authorized user
@@ -94,7 +97,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('abf63359-be52-4feb-87dd-447689fc77fd')
def test_read_object_with_non_authorized_user(self):
- # attempt to read object using non-authorized user
+ """Test reading object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
@@ -112,7 +115,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7343ac3d-cfed-4198-9bb0-00149741a492')
def test_delete_object_with_non_authorized_user(self):
- # attempt to delete object using non-authorized user
+ """Test deleting object with non-authorized user"""
# User provided token is forbidden. ACL are not set
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
@@ -130,7 +133,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ed01334-01e9-41ea-87ea-e6f465582823')
def test_read_object_without_rights(self):
- # attempt to read object using non-authorized user
+ """Test reading object without rights"""
# update X-Container-Read metadata ACL
cont_headers = {'X-Container-Read': 'badtenant:baduser'}
resp_meta, _ = (
@@ -155,7 +158,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a3a585a7-d8cf-4b65-a1a0-edc2b1204f85')
def test_write_object_without_rights(self):
- # attempt to write object using non-authorized user
+ """Test writing object without rights"""
# update X-Container-Write metadata ACL
cont_headers = {'X-Container-Write': 'badtenant:baduser'}
resp_meta, _ = (
@@ -177,7 +180,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8ba512ad-aa6e-444e-b882-2906a0ea2052')
def test_write_object_without_write_rights(self):
- # attempt to write object using non-authorized user
+ """Test writing object without write rights"""
# update X-Container-Read and X-Container-Write metadata ACL
tenant_name = self.os_operator.credentials.tenant_name
username = self.os_operator.credentials.username
@@ -203,7 +206,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b4e366f8-f185-47ab-b789-df4416f9ecdb')
def test_delete_object_without_write_rights(self):
- # attempt to delete object using non-authorized user
+ """Test deleting object without write rights"""
# update X-Container-Read and X-Container-Write metadata ACL
tenant_name = self.os_operator.credentials.tenant_name
username = self.os_operator.credentials.username
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index cdc420e..7ad6f6f 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -19,6 +19,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test containers"""
+
def tearDown(self):
self.delete_containers()
super(ContainerTest, self).tearDown()
@@ -26,6 +28,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
def test_create_container(self):
+ """Test creating container"""
container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -33,7 +36,7 @@
@decorators.idempotent_id('49f866ed-d6af-4395-93e7-4187eb56d322')
def test_create_container_overwrite(self):
- # overwrite container with the same name
+ """Test overwriting container with the same name"""
container_name = data_utils.rand_name(name='TestContainer')
self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -43,7 +46,7 @@
@decorators.idempotent_id('c2ac4d59-d0f5-40d5-ba19-0635056d48cd')
def test_create_container_with_metadata_key(self):
- # create container with the blank value of metadata
+ """Test creating container with the blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': ''}
resp, _ = self.container_client.update_container(
@@ -60,7 +63,7 @@
@decorators.idempotent_id('e1e8df32-7b22-44e1-aa08-ccfd8d446b58')
def test_create_container_with_metadata_value(self):
- # create container with metadata value
+ """Test creating container with metadata value"""
container_name = data_utils.rand_name(name='TestContainer')
# metadata name using underscores should be converted to hyphens
@@ -79,7 +82,7 @@
@decorators.idempotent_id('24d16451-1c0c-4e4f-b59c-9840a3aba40e')
def test_create_container_with_remove_metadata_key(self):
- # create container with the blank value of remove metadata
+ """Test creating container with the blank value of remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -97,7 +100,7 @@
@decorators.idempotent_id('8a21ebad-a5c7-4e29-b428-384edc8cd156')
def test_create_container_with_remove_metadata_value(self):
- # create container with remove metadata
+ """Test creating container with remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -114,6 +117,7 @@
@decorators.idempotent_id('95d3a249-b702-4082-a2c4-14bb860cf06a')
def test_delete_container(self):
+ """Test deleting container"""
# create a container
container_name = self.create_container()
# delete container, success asserted within
@@ -123,7 +127,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('312ff6bd-5290-497f-bda1-7c5fec6697ab')
def test_list_container_contents(self):
- # get container contents list
+ """Test getting container contents list"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -134,7 +138,7 @@
@decorators.idempotent_id('4646ac2d-9bfb-4c7d-a3c5-0f527402b3df')
def test_list_container_contents_with_no_object(self):
- # get empty container contents list
+ """Test getting empty container contents list"""
container_name = self.create_container()
resp, object_list = self.container_client.list_container_objects(
@@ -144,7 +148,7 @@
@decorators.idempotent_id('fe323a32-57b9-4704-a996-2e68f83b09bc')
def test_list_container_contents_with_delimiter(self):
- # get container contents list using delimiter param
+ """Test getting container contents list using delimiter param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject/')
self.create_object(container_name, object_name)
@@ -158,7 +162,7 @@
@decorators.idempotent_id('55b4fa5c-e12e-4ca9-8fcf-a79afe118522')
def test_list_container_contents_with_end_marker(self):
- # get container contents list using end_marker param
+ """Test getting container contents list using end_marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -171,7 +175,7 @@
@decorators.idempotent_id('196f5034-6ab0-4032-9da9-a937bbb9fba9')
def test_list_container_contents_with_format_json(self):
- # get container contents list using format_json param
+ """Test getting container contents list using format_json param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -190,7 +194,7 @@
@decorators.idempotent_id('655a53ca-4d15-408c-a377-f4c6dbd0a1fa')
def test_list_container_contents_with_format_xml(self):
- # get container contents list using format_xml param
+ """Test getting container contents list using format_xml param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -214,7 +218,7 @@
@decorators.idempotent_id('297ec38b-2b61-4ff4-bcd1-7fa055e97b61')
def test_list_container_contents_with_limit(self):
- # get container contents list using limit param
+ """Test getting container contents list using limit param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -227,7 +231,7 @@
@decorators.idempotent_id('c31ddc63-2a58-4f6b-b25c-94d2937e6867')
def test_list_container_contents_with_marker(self):
- # get container contents list using marker param
+ """Test getting container contents list using marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -240,7 +244,7 @@
@decorators.idempotent_id('58ca6cc9-6af0-408d-aaec-2a6a7b2f0df9')
def test_list_container_contents_with_path(self):
- # get container contents list using path param
+ """Test getting container contents list using path param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject')
object_name = 'Swift/' + object_name
@@ -255,7 +259,7 @@
@decorators.idempotent_id('77e742c7-caf2-4ec9-8aa4-f7d509a3344c')
def test_list_container_contents_with_prefix(self):
- # get container contents list using prefix param
+ """Test getting container contents list using prefix param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -270,7 +274,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('96e68f0e-19ec-4aa2-86f3-adc6a45e14dd')
def test_list_container_metadata(self):
- # List container metadata
+ """Test listing container metadata"""
container_name = self.create_container()
metadata = {'name': 'Pictures'}
@@ -286,7 +290,7 @@
@decorators.idempotent_id('a2faf936-6b13-4f8d-92a2-c2278355821e')
def test_list_no_container_metadata(self):
- # HEAD container without metadata
+ """Test listing container without metadata"""
container_name = self.create_container()
resp, _ = self.container_client.list_container_metadata(
@@ -296,7 +300,10 @@
@decorators.idempotent_id('cf19bc0b-7e16-4a5a-aaed-cb0c2fe8deef')
def test_update_container_metadata_with_create_and_delete_metadata(self):
- # Send one request of adding and deleting metadata
+ """Test updating container with adding and deleting metadata
+
+ Send one request of adding and deleting metadata.
+ """
container_name = data_utils.rand_name(name='TestContainer')
metadata_1 = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata_1)
@@ -319,7 +326,7 @@
@decorators.idempotent_id('2ae5f295-4bf1-4e04-bfad-21e54b62cec5')
def test_update_container_metadata_with_create_metadata(self):
- # update container metadata using add metadata
+ """Test updating container metadata using add metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': 'Meta1'}
@@ -337,7 +344,7 @@
@decorators.idempotent_id('3a5ce7d4-6e4b-47d0-9d87-7cd42c325094')
def test_update_container_metadata_with_delete_metadata(self):
- # update container metadata using delete metadata
+ """Test updating container metadata using delete metadata"""
container_name = data_utils.rand_name(name='TestContainer')
metadata = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata)
@@ -355,7 +362,7 @@
@decorators.idempotent_id('31f40a5f-6a52-4314-8794-cd89baed3040')
def test_update_container_metadata_with_create_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': ''}
@@ -371,7 +378,7 @@
@decorators.idempotent_id('a2e36378-6f1f-43f4-840a-ffd9cfd61914')
def test_update_container_metadata_with_delete_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **headers)
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index b8c83b7..31c33db 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -25,6 +25,7 @@
class ContainerNegativeTest(base.BaseObjectTest):
+ """Negative tests of containers"""
@classmethod
def resource_setup(cls):
@@ -41,7 +42,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_name_exceeds_max_length(self):
- # Attempts to create a container name that is longer than max
+ """Test creating container with name longer than max"""
max_length = self.constraints['max_container_name_length']
# create a container with long name
container_name = data_utils.arbitrary_string(size=max_length + 1)
@@ -58,8 +59,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_name_exceeds_max_length(self):
- # Attempts to create container with metadata name
- # that is longer than max.
+ """Test creating container with metadata name longer than max"""
max_length = self.constraints['max_meta_name_length']
container_name = data_utils.rand_name(name='TestContainer')
metadata_name = 'X-Container-Meta-' + data_utils.arbitrary_string(
@@ -77,8 +77,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_value_exceeds_max_length(self):
- # Attempts to create container with metadata value
- # that is longer than max.
+ """Test creating container with metadata value longer than max"""
max_length = self.constraints['max_meta_value_length']
container_name = data_utils.rand_name(name='TestContainer')
metadata_value = data_utils.arbitrary_string(size=max_length + 1)
@@ -95,8 +94,7 @@
CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_create_container_metadata_exceeds_overall_metadata_count(self):
- # Attempts to create container with metadata that exceeds the
- # default count
+ """Test creating container with metadata exceeding default count"""
max_count = self.constraints['max_meta_count']
container_name = data_utils.rand_name(name='TestContainer')
metadata = {}
@@ -113,8 +111,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('1a95ab2e-b712-4a98-8a4d-8ce21b7557d6')
def test_get_metadata_headers_with_invalid_container_name(self):
- # Attempts to retrieve metadata headers with an invalid
- # container name.
+ """Test getting metadata headers with invalid container name"""
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_metadata,
'invalid_container_name')
@@ -122,7 +119,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('125a24fa-90a7-4cfc-b604-44e49d788390')
def test_update_metadata_with_nonexistent_container_name(self):
- # Attempts to update metadata using a nonexistent container name.
+ """Test updating metadata using a nonexistent container name"""
metadata = {'animal': 'penguin'}
self.assertRaises(
@@ -133,7 +130,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba')
def test_delete_with_nonexistent_container_name(self):
- # Attempts to delete metadata using a nonexistent container name.
+ """Test deleting metadata using a non existent container name"""
metadata = {'animal': 'penguin'}
self.assertRaises(
@@ -144,8 +141,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5')
def test_list_all_container_objects_with_nonexistent_container(self):
- # Attempts to get a listing of all objects on a container
- # that doesn't exist.
+ """Test getting a list of all objects on a non existent container"""
params = {'limit': 9999, 'format': 'json'}
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_objects,
@@ -154,8 +150,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e')
def test_list_all_container_objects_on_deleted_container(self):
- # Attempts to get a listing of all objects on a container
- # that was deleted.
+ """Test getting a list of all objects on a deleted container"""
container_name = self.create_container()
# delete container
resp, _ = self.container_client.delete_container(container_name)
@@ -168,6 +163,7 @@
@decorators.attr(type=["negative"])
@decorators.idempotent_id('42da116e-1e8c-4c96-9e06-2f13884ed2b1')
def test_delete_non_empty_container(self):
+ """Test deleting a container with object in it"""
# create a container and an object within it
# attempt to delete a container that isn't empty.
container_name = self.create_container()
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 1243b83..ef98ed8 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -21,6 +21,7 @@
class StaticWebTest(base.BaseObjectTest):
+ """Test static web"""
@classmethod
def resource_setup(cls):
@@ -47,6 +48,7 @@
@decorators.idempotent_id('c1f055ab-621d-4a6a-831f-846fcb578b8b')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_index(self):
+ """Test web index"""
headers = {'web-index': self.object_name}
self.container_client.create_update_or_delete_container_metadata(
@@ -79,6 +81,7 @@
@decorators.idempotent_id('941814cf-db9e-4b21-8112-2b6d0af10ee5')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_listing(self):
+ """Test web listing"""
headers = {'web-listings': 'true'}
self.container_client.create_update_or_delete_container_metadata(
@@ -111,6 +114,7 @@
@decorators.idempotent_id('bc37ec94-43c8-4990-842e-0e5e02fc8926')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_listing_css(self):
+ """Test web listing css"""
headers = {'web-listings': 'true',
'web-listings-css': 'listings.css'}
@@ -134,6 +138,7 @@
@decorators.idempotent_id('f18b4bef-212e-45e7-b3ca-59af3a465f82')
@utils.requires_ext(extension='staticweb', service='object')
def test_web_error(self):
+ """Test web error"""
headers = {'web-listings': 'true',
'web-error': self.object_name}
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 322579c..bdcb5ae 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -123,7 +123,7 @@
self.assertEqual(object_content, obj_name[::-1].encode())
@decorators.attr(type='slow')
- @decorators.skip_because(bug='1317133')
+ @decorators.unstable_test(bug='1317133')
@decorators.idempotent_id('be008325-1bba-4925-b7dd-93b58f22ce9b')
@testtools.skipIf(
not CONF.object_storage_feature_enabled.container_sync,
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index 8e9e406..f5e2443 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -19,12 +19,14 @@
class HealthcheckTest(base.BaseObjectTest):
+ """Test healthcheck"""
def setUp(self):
super(HealthcheckTest, self).setUp()
@decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337')
def test_get_healthcheck(self):
+ """Test getting healthcheck"""
url = self.account_client._get_base_version_url() + "healthcheck"
resp, body = self.account_client.raw_request(url, "GET")
self.account_client._error_checker(resp, body)
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index cd834bf..d857d3b 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -25,6 +25,7 @@
class ObjectFormPostTest(base.BaseObjectTest):
+ """Test object post with form"""
metadata = {}
containers = []
@@ -110,6 +111,7 @@
@decorators.idempotent_id('80fac02b-6e54-4f7b-be0d-a965b5cbef76')
@utils.requires_ext(extension='formpost', service='object')
def test_post_object_using_form(self):
+ """Test posting object using form"""
body, content_type = self.get_multipart_form()
headers = {'Content-Type': content_type,
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index df6a0fd..0499eef 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -26,6 +26,7 @@
class ObjectFormPostNegativeTest(base.BaseObjectTest):
+ """Negative tests of object post with form"""
metadata = {}
containers = []
@@ -112,6 +113,7 @@
@utils.requires_ext(extension='formpost', service='object')
@decorators.attr(type=['negative'])
def test_post_object_using_form_expired(self):
+ """Test posting object using expired form"""
body, content_type = self.get_multipart_form(expires=1)
time.sleep(2)
@@ -129,6 +131,7 @@
@utils.requires_ext(extension='formpost', service='object')
@decorators.attr(type=['negative'])
def test_post_object_using_form_invalid_signature(self):
+ """Test posting object using form with invalid signature"""
self.key = "Wrong"
body, content_type = self.get_multipart_form()
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index c66776e..8bb2e6e 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -17,7 +17,6 @@
from oslo_serialization import jsonutils as json
from tempest.api.object_storage import base
-from tempest.common import custom_matchers
from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
@@ -160,17 +159,7 @@
object_name,
params=params_del)
- # When deleting SLO using multipart manifest, the response contains
- # not 'content-length' but 'transfer-encoding' header. This is the
- # special case, therefore the existence of response headers is checked
- # outside of custom matcher.
- self.assertIn('transfer-encoding', resp)
- self.assertIn('content-type', resp)
- self.assertIn('x-trans-id', resp)
- self.assertIn('date', resp)
-
- # Check only the format of common headers with custom matcher
- self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+ self.assertHeaders(resp, 'Object', 'DELETE')
resp, body = self.container_client.list_container_objects(
self.container_name)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index b99f93a..29354b6 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -25,6 +25,7 @@
class ObjectTempUrlTest(base.BaseObjectTest):
+ """Test object temp url"""
@classmethod
def resource_setup(cls):
@@ -90,6 +91,7 @@
@decorators.idempotent_id('f91c96d4-1230-4bba-8eb9-84476d18d991')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url(self):
+ """Test getting object using temp url"""
expires = self._get_expiry_date()
# get a temp URL for the created object
@@ -109,6 +111,7 @@
@decorators.idempotent_id('671f9583-86bd-4128-a034-be282a68c5d8')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url_key_2(self):
+ """Test getting object using metadata 'Temp-URL-Key-2'"""
key2 = 'Meta2-'
metadata = {'Temp-URL-Key-2': key2}
self.account_client.create_update_or_delete_account_metadata(
@@ -134,6 +137,7 @@
@decorators.idempotent_id('9b08dade-3571-4152-8a4f-a4f2a873a735')
@utils.requires_ext(extension='tempurl', service='object')
def test_put_object_using_temp_url(self):
+ """Test putting object using temp url"""
new_data = data_utils.random_bytes(size=len(self.object_name))
expires = self._get_expiry_date()
@@ -160,6 +164,7 @@
@decorators.idempotent_id('249a0111-5ad3-4534-86a7-1993d55f9185')
@utils.requires_ext(extension='tempurl', service='object')
def test_head_object_using_temp_url(self):
+ """Test HEAD operation of object using temp url"""
expires = self._get_expiry_date()
# get a temp URL for the created object
@@ -174,6 +179,7 @@
@decorators.idempotent_id('9d9cfd90-708b-465d-802c-e4a8090b823d')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url_with_inline_query_parameter(self):
+ """Test getting object using temp url with inline query parameter"""
expires = self._get_expiry_date()
# get a temp URL for the created object
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index 17ae6c1..bbb4827 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -26,6 +26,7 @@
class ObjectTempUrlNegativeTest(base.BaseObjectTest):
+ """Negative tests of object temp url"""
metadata = {}
containers = []
@@ -96,7 +97,7 @@
@decorators.idempotent_id('5a583aca-c804-41ba-9d9a-e7be132bdf0b')
@utils.requires_ext(extension='tempurl', service='object')
def test_get_object_after_expiration_time(self):
-
+ """Test getting object after expiration time"""
expires = self._get_expiry_date(1)
# get a temp URL for the created object
url = self._get_temp_url(self.container_name,
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 75111b6..b64b172 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,6 +24,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test versioned container"""
+
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
self.assertHeaders(resp, 'Container', 'HEAD')
@@ -39,6 +41,15 @@
not CONF.object_storage_feature_enabled.object_versioning,
'Object-versioning is disabled')
def test_versioned_container(self):
+ """Test versioned container
+
+ 1. create container1
+ 2. create container2, with container1 as 'X-versions-Location' header
+ 3. create object1 in container1
+ 4. create 2nd version of object1
+ 5. delete object version 2
+ 6. delete object version 1
+ """
# create container
vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, _ = self.container_client.update_container(vers_container_name)
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
index 1351704..3c76eca 100644
--- a/tempest/api/volume/admin/test_backends_capabilities.py
+++ b/tempest/api/volume/admin/test_backends_capabilities.py
@@ -20,6 +20,7 @@
class BackendsCapabilitiesAdminTestsJSON(base.BaseVolumeAdminTest):
+ """Test backends capabilities"""
@classmethod
def resource_setup(cls):
@@ -32,14 +33,16 @@
@decorators.idempotent_id('3750af44-5ea2-4cd4-bc3e-56e7e6caf854')
def test_get_capabilities_backend(self):
- # Test backend properties
+ """Test getting backend capabilities"""
# Check response schema
self.admin_capabilities_client.show_backend_capabilities(self.hosts[0])
@decorators.idempotent_id('a9035743-d46a-47c5-9cb7-3c80ea16dea0')
def test_compare_volume_stats_values(self):
- # Test values comparison between show_backend_capabilities
- # to show_pools
+ """Test comparing volume stats values
+
+ Compare volume stats between show_backend_capabilities and show_pools.
+ """
VOLUME_STATS = ('vendor_name',
'volume_backend_name',
'storage_protocol')
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index c57766e..0a8b56d 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -62,12 +62,26 @@
class GroupSnapshotsTest(BaseGroupSnapshotsTest):
+ """Test group snapshot"""
+
_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):
+ """Test create/show/list/delete group snapshot
+
+ 1. Create volume type "volume_type1"
+ 2. Create group type "group_type1"
+ 3. Create group "group1" with "group_type1" and "volume_type1"
+ 4. Create volume "volume1" with "volume_type1" and "group1"
+ 5. Create group snapshot "group_snapshot1" with "group1"
+ 6. Check snapshot created from "volume1" reaches available status
+ 7. Check the created group snapshot "group_snapshot1" is in the list
+ of all group snapshots
+ 8. Delete group snapshot "group_snapshot1"
+ """
# Create volume type
volume_type = self.create_volume_type()
@@ -118,6 +132,18 @@
@decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
def test_create_group_from_group_snapshot(self):
+ """Test creating group from group snapshot
+
+ 1. Create volume type "volume_type1"
+ 2. Create group type "group_type1"
+ 3. Create group "group1" with "group_type1" and "volume_type1"
+ 4. Create volume "volume1" with "volume_type1" and "group1"
+ 5. Create group snapshot "group_snapshot1" with "group1"
+ 6. Check snapshot created from "volume1" reaches available status
+ 7. Create group "group2" from "group_snapshot1"
+ 8. Check the volumes belonging to "group2" reach available status
+ 9. Check "group2" reaches available status
+ """
# Create volume type
volume_type = self.create_volume_type()
@@ -161,6 +187,20 @@
@decorators.idempotent_id('7d7fc000-0b4c-4376-a372-544116d2e127')
@decorators.related_bug('1739031')
def test_delete_group_snapshots_following_updated_volumes(self):
+ """Test deleting group snapshot following updated volumes
+
+ 1. Create volume type "volume_type1"
+ 2. Create group type "group_type1"
+ 3. Create group "group1" with "group_type1" and "volume_type1"
+ 4. Create 2 volumes "volume1" and "volume2"
+ with "volume_type1" and "group1"
+ 5. For each created volume, removing and then adding back to "group1"
+ 6. Create group snapshot "group_snapshot1" with "group1"
+ 7. Check snapshots created from "volume1" and "volume2" reach
+ available status
+ 8. Delete "group_snapshot1"
+ 9. Check snapshots created from "volume1" and "volume2" are deleted
+ """
volume_type = self.create_volume_type()
group_type = self.create_group_type()
@@ -211,6 +251,8 @@
class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
+ """Test group snapshot with volume microversion greater than 3.18"""
+
_api_version = 3
min_microversion = '3.19'
max_microversion = 'latest'
@@ -218,6 +260,7 @@
@decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
@decorators.skip_because(bug='1770179')
def test_reset_group_snapshot_status(self):
+ """Test resetting group snapshot status to creating/available/error"""
# Create volume type
volume_type = self.create_volume_type()
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index c5e6d1a..159c6fb 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -19,12 +19,15 @@
class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
+ """Test group type specs"""
+
_api_version = 3
min_microversion = '3.11'
max_microversion = 'latest'
@decorators.idempotent_id('bb4e30d0-de6e-4f4d-866c-dcc48d023b4e')
def test_group_type_specs_create_show_update_list_delete(self):
+ """Test create/show/update/list/delete group type specs"""
# Create new group type
group_type = self.create_group_type()
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 6723207..3993020 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -19,13 +19,15 @@
class GroupTypesTest(base.BaseVolumeAdminTest):
+ """Test group types"""
+
_api_version = 3
min_microversion = '3.11'
max_microversion = 'latest'
@decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
def test_group_type_create_list_update_show(self):
- # Create/list/show group type.
+ """Test create/list/update/show group type"""
name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
description = data_utils.rand_name("group-type-description")
group_specs = {"consistent_group_snapshot_enabled": "<is> False"}
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index 2f6eb6b..e67b985 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -23,12 +23,15 @@
class GroupsTest(base.BaseVolumeAdminTest):
+ """Tests of volume groups with microversion greater than 3.12"""
+
_api_version = 3
min_microversion = '3.13'
max_microversion = 'latest'
@decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
def test_group_create_show_list_delete(self):
+ """Test creating, showing, listing and deleting of volume group"""
# Create volume type
volume_type = self.create_volume_type()
@@ -95,6 +98,7 @@
@decorators.idempotent_id('4a8a6fd2-8b3b-4641-8f54-6a6f99320006')
def test_group_update(self):
+ """Test updating volume group"""
# Create volume type
volume_type = self.create_volume_type()
@@ -150,12 +154,15 @@
class GroupsV314Test(base.BaseVolumeAdminTest):
+ """Tests of volume groups with microversion greater than 3.13"""
+
_api_version = 3
min_microversion = '3.14'
max_microversion = 'latest'
@decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
def test_create_group_from_group(self):
+ """Test creating volume group from volume group"""
# Create volume type
volume_type = self.create_volume_type()
@@ -185,12 +192,15 @@
class GroupsV320Test(base.BaseVolumeAdminTest):
+ """Tests of volume groups with microversion greater than 3.19"""
+
_api_version = 3
min_microversion = '3.20'
max_microversion = 'latest'
@decorators.idempotent_id('b20c696b-0cbc-49a5-8b3a-b1fb9338f45c')
def test_reset_group_status(self):
+ """Test resetting volume group status to creating/available/error"""
# Create volume type
volume_type = self.create_volume_type()
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index c5c70d2..a5de987 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -21,6 +21,7 @@
class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
+ """Test volume multi backends"""
@classmethod
def skip_checks(cls):
@@ -78,24 +79,49 @@
@decorators.idempotent_id('c1a41f3f-9dad-493e-9f09-3ff197d477cc')
def test_backend_name_reporting(self):
- # get volume id which created by type without prefix
+ """Test backend name reporting for volume when type is without prefix
+
+ 1. Create volume type, with 'volume_backend_name' as extra spec key
+ 2. Create volume using the created volume type
+ 3. Check 'os-vol-host-attr:host' of the volume info, the value should
+ contain '@' character, like 'cinder@CloveStorage#tecs_backend'
+ """
for volume_id in self.volume_id_list_without_prefix:
self._test_backend_name_reporting_by_volume_id(volume_id)
@decorators.idempotent_id('f38e647f-ab42-4a31-a2e7-ca86a6485215')
def test_backend_name_reporting_with_prefix(self):
- # get volume id which created by type with prefix
+ """Test backend name reporting for volume when type is with prefix
+
+ 1. Create volume type, with 'capabilities:volume_backend_name' as
+ extra spec key
+ 2. Create volume using the created volume type
+ 3. Check 'os-vol-host-attr:host' of the volume info, the value should
+ contain '@' character, like 'cinder@CloveStorage#tecs_backend'
+ """
for volume_id in self.volume_id_list_with_prefix:
self._test_backend_name_reporting_by_volume_id(volume_id)
@decorators.idempotent_id('46435ab1-a0af-4401-8373-f14e66b0dd58')
def test_backend_name_distinction(self):
- # get volume ids which created by type without prefix
+ """Test volume backend distinction when type is without prefix
+
+ 1. For each backend, create volume type with 'volume_backend_name'
+ as extra spec key
+ 2. Create volumes using the created volume types
+ 3. Check 'os-vol-host-attr:host' of each created volume is different.
+ """
self._test_backend_name_distinction(self.volume_id_list_without_prefix)
@decorators.idempotent_id('4236305b-b65a-4bfc-a9d2-69cb5b2bf2ed')
def test_backend_name_distinction_with_prefix(self):
- # get volume ids which created by type without prefix
+ """Test volume backend distinction when type is with prefix
+
+ 1. For each backend, create volume type with
+ 'capabilities:volume_backend_name' as extra spec key
+ 2. Create volumes using the created volume types
+ 3. Check 'os-vol-host-attr:host' of each created volume is different.
+ """
self._test_backend_name_distinction(self.volume_id_list_with_prefix)
def _get_volume_host(self, volume_id):
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 37a47ec..ab0aa38 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -48,6 +48,7 @@
@decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
def test_unmanage_manage_snapshot(self):
+ """Test unmanaging and managing volume snapshot"""
# Create a volume
volume = self.create_volume()
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 41849bc..4fca240 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -22,6 +22,8 @@
class SnapshotsActionsTest(base.BaseVolumeAdminTest):
+ """Test volume snapshot actions"""
+
@classmethod
def skip_checks(cls):
super(SnapshotsActionsTest, cls).skip_checks()
@@ -65,7 +67,7 @@
@decorators.idempotent_id('3e13ca2f-48ea-49f3-ae1a-488e9180d535')
def test_reset_snapshot_status(self):
- # Reset snapshot status to creating
+ """Test resetting snapshot status to creating"""
status = 'creating'
self.admin_snapshots_client.reset_snapshot_status(
self.snapshot['id'], status)
@@ -74,6 +76,10 @@
@decorators.idempotent_id('41288afd-d463-485e-8f6e-4eea159413eb')
def test_update_snapshot_status(self):
+ """Test updating snapshot
+
+ Update snapshot status to 'error' and progress to '80%'.
+ """
# Reset snapshot status to creating
status = 'creating'
self.admin_snapshots_client.reset_snapshot_status(
@@ -95,20 +101,20 @@
@decorators.idempotent_id('05f711b6-e629-4895-8103-7ca069f2073a')
def test_snapshot_force_delete_when_snapshot_is_creating(self):
- # test force delete when status of snapshot is creating
+ """Test force delete when status of snapshot is creating"""
self._create_reset_and_force_delete_temp_snapshot('creating')
@decorators.idempotent_id('92ce8597-b992-43a1-8868-6316b22a969e')
def test_snapshot_force_delete_when_snapshot_is_deleting(self):
- # test force delete when status of snapshot is deleting
+ """Test force delete when status of snapshot is deleting"""
self._create_reset_and_force_delete_temp_snapshot('deleting')
@decorators.idempotent_id('645a4a67-a1eb-4e8e-a547-600abac1525d')
def test_snapshot_force_delete_when_snapshot_is_error(self):
- # test force delete when status of snapshot is error
+ """Test force delete when status of snapshot is error"""
self._create_reset_and_force_delete_temp_snapshot('error')
@decorators.idempotent_id('bf89080f-8129-465e-9327-b2f922666ba5')
def test_snapshot_force_delete_when_snapshot_is_error_deleting(self):
- # test force delete when status of snapshot is error_deleting
+ """Test force delete when status of snapshot is error_deleting"""
self._create_reset_and_force_delete_temp_snapshot('error_deleting')
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 8048017..096709c 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -22,6 +22,8 @@
class UserMessagesTest(base.BaseVolumeAdminTest):
+ """Test volume messages with microversion greater than 3.2"""
+
_api_version = 3
min_microversion = '3.3'
max_microversion = 'latest'
@@ -51,6 +53,7 @@
@decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
def test_list_show_messages(self):
+ """Test listing and showing volume messages"""
message_id = self._create_user_message()
self.addCleanup(self.messages_client.delete_message, message_id)
@@ -62,6 +65,7 @@
@decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
def test_delete_message(self):
+ """Test deleting volume messages"""
message_id = self._create_user_message()
self.messages_client.delete_message(message_id)
self.messages_client.wait_for_resource_deletion(message_id)
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index 83c27e1..e4e15c5 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -18,9 +18,11 @@
class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest):
+ """Test fetching volume hosts info by admin users"""
@decorators.idempotent_id('d5f3efa2-6684-4190-9ced-1c2f526352ad')
def test_list_hosts(self):
+ """Test listing volume hosts"""
hosts = self.admin_hosts_client.list_hosts()['hosts']
self.assertGreaterEqual(len(hosts), 2,
"The count of volume hosts is < 2, "
@@ -28,6 +30,7 @@
@decorators.idempotent_id('21168d57-b373-4b71-a3ac-f2c88f0c5d31')
def test_show_host(self):
+ """Test getting volume host details"""
hosts = self.admin_hosts_client.list_hosts()['hosts']
self.assertGreaterEqual(len(hosts), 2,
"The count of volume hosts is < 2, "
diff --git a/tempest/api/volume/admin/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
index 4b352e0..1e4e7cb 100644
--- a/tempest/api/volume/admin/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -24,6 +24,7 @@
class VolumeManageAdminTest(base.BaseVolumeAdminTest):
+ """Test volume manage by admin users"""
@classmethod
def skip_checks(cls):
@@ -39,6 +40,7 @@
@decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e')
def test_unmanage_manage_volume(self):
+ """Test unmanaging and managing volume"""
# Create original volume
org_vol_id = self.create_volume()['id']
org_vol_info = self.admin_volume_client.show_volume(
diff --git a/tempest/api/volume/admin/test_volume_pools.py b/tempest/api/volume/admin/test_volume_pools.py
index 744bc01..9424994 100644
--- a/tempest/api/volume/admin/test_volume_pools.py
+++ b/tempest/api/volume/admin/test_volume_pools.py
@@ -21,6 +21,8 @@
class VolumePoolsAdminTestsJSON(base.BaseVolumeAdminTest):
+ """Test getting volume pools by admin users"""
+
def _assert_pools(self, with_detail=False):
cinder_pools = self.admin_scheduler_stats_client.list_pools(
detail=with_detail)['pools']
@@ -33,8 +35,10 @@
@decorators.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
def test_get_pools_without_details(self):
+ """Test getting volume pools without detail"""
self._assert_pools()
@decorators.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced')
def test_get_pools_with_details(self):
+ """Test getting volume pools with detail"""
self._assert_pools(with_detail=True)
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
index ee52354..f482788 100644
--- a/tempest/api/volume/admin/test_volume_quota_classes.py
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -30,6 +30,7 @@
class VolumeQuotaClassesTest(base.BaseVolumeAdminTest):
+ """Test volume quota classes"""
def setUp(self):
# Note(jeremy.zhang): All test cases in this class need to externally
@@ -44,6 +45,7 @@
@decorators.idempotent_id('abb9198e-67d0-4b09-859f-4f4a1418f176')
def test_show_default_quota(self):
+ """Test showing default volume quota class set"""
# response body is validated by schema
default_quotas = self.admin_quota_classes_client.show_quota_class_set(
'default')['quota_class_set']
@@ -51,6 +53,11 @@
@decorators.idempotent_id('a7644c63-2669-467a-b00e-452dd5c5397b')
def test_update_default_quota(self):
+ """Test updating default volume quota class set
+
+ Check current project and new project's default quota are updated
+ to the provided one.
+ """
LOG.debug("Get the current default quota class values")
body = self.admin_quota_classes_client.show_quota_class_set(
'default')['quota_class_set']
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index b073604..5ab8e87 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -22,6 +22,8 @@
class VolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest):
+ """Test volume quotas with admin privilege"""
+
credentials = ['primary', 'alt', 'admin']
def setUp(self):
@@ -54,17 +56,19 @@
@decorators.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0')
def test_list_quotas(self):
+ """Test showing volume quota set"""
# Check response schema
self.admin_quotas_client.show_quota_set(self.demo_tenant_id)
@decorators.idempotent_id('2be020a2-5fdd-423d-8d35-a7ffbc36e9f7')
def test_list_default_quotas(self):
+ """Test showing volume default quota set"""
# Check response schema
self.admin_quotas_client.show_default_quota_set(self.demo_tenant_id)
@decorators.idempotent_id('3d45c99e-cc42-4424-a56e-5cbd212b63a6')
def test_update_all_quota_resources_for_tenant(self):
- # Admin can update all the resource quota limits for a tenant
+ """Test admin can update all the volume quota limits for a project"""
new_quota_set = {'gigabytes': 1009,
'volumes': 11,
'snapshots': 11,
@@ -87,14 +91,14 @@
@decorators.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed')
def test_show_quota_usage(self):
+ """Test showing volume quota usage"""
# Check response schema
self.admin_quotas_client.show_quota_set(
self.os_admin.credentials.tenant_id, params={'usage': True})
@decorators.idempotent_id('874b35a9-51f1-4258-bec5-cd561b6690d3')
def test_delete_quota(self):
- # Admin can delete the resource quota set for a project
-
+ """Test admin can delete the volume quota set for a project"""
self.addCleanup(self.admin_quotas_client.update_quota_set,
self.demo_tenant_id, **self.cleanup_quota_set)
@@ -112,6 +116,7 @@
@decorators.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f')
def test_quota_usage(self):
+ """Test volume quota usage is updated after creating volume"""
quota_usage = self.admin_quotas_client.show_quota_set(
self.demo_tenant_id, params={'usage': True})['quota_set']
@@ -131,6 +136,7 @@
@decorators.idempotent_id('8911036f-9d54-4720-80cc-a1c9796a8805')
def test_quota_usage_after_volume_transfer(self):
+ """Test volume quota usage is updated after transferring volume"""
# Create a volume for transfer
volume = self.create_volume()
self.addCleanup(self.delete_volume,
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 5c7ab15..937d28b 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -24,6 +24,7 @@
class VolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
+ """Negative tests of volume quotas"""
@classmethod
def setup_credentials(cls):
@@ -52,6 +53,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6')
def test_quota_volumes(self):
+ """Creating more volume than allowed quota will fail"""
self.admin_quotas_client.update_quota_set(self.demo_tenant_id,
volumes=1, gigabytes=-1)
self.assertRaises(lib_exc.OverLimit,
@@ -61,6 +63,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374')
def test_quota_volume_gigabytes(self):
+ """Creating volume with size larger than allowed quota will fail"""
self.admin_quotas_client.update_quota_set(
self.demo_tenant_id, gigabytes=CONF.volume.volume_size, volumes=-1)
self.assertRaises(lib_exc.OverLimit,
@@ -70,6 +73,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d321dc21-d8c6-401f-95fe-49f4845f1a6d')
def test_volume_extend_gigabytes_quota_deviation(self):
+ """Extending volume with size larger than allowed quota will fail"""
self.admin_quotas_client.update_quota_set(
self.demo_tenant_id, gigabytes=CONF.volume.volume_size)
self.assertRaises(lib_exc.OverLimit,
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 18e0b9b..5c14d52 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -88,6 +88,7 @@
class VolumeRetypeWithMigrationTest(VolumeRetypeTest):
+ """Test volume retype with migration"""
@classmethod
def skip_checks(cls):
@@ -134,11 +135,25 @@
@decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
def test_available_volume_retype_with_migration(self):
+ """Test volume retype with migration
+
+ 1. Create volume1 with volume_type1
+ 2. Retype volume1 to volume_type2 with migration_policy='on-demand'
+ 3. Check volume1's volume_type is changed to volume_type2, and
+ 'os-vol-host-attr:host' in the volume info is changed.
+ """
src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
self._retype_volume(src_vol, migration_policy='on-demand')
@decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
def test_volume_from_snapshot_retype_with_migration(self):
+ """Test volume created from snapshot retype with migration
+
+ 1. Create volume1 from snapshot with volume_type1
+ 2. Retype volume1 to volume_type2 with migration_policy='on-demand'
+ 3. Check volume1's volume_type is changed to volume_type2, and
+ 'os-vol-host-attr:host' in the volume info is changed.
+ """
src_vol = self._create_volume_from_snapshot()
# Migrate the volume from snapshot to the second backend
@@ -146,6 +161,7 @@
class VolumeRetypeWithoutMigrationTest(VolumeRetypeTest):
+ """Test volume retype without migration"""
@classmethod
def resource_setup(cls):
@@ -174,6 +190,13 @@
@decorators.idempotent_id('b90412ee-465d-46e9-b249-ec84a47d5f25')
def test_available_volume_retype(self):
+ """Test volume retype without migration
+
+ 1. Create volume1 with volume_type1
+ 2. Retype volume1 to volume_type2 with migration_policy='never'
+ 3. Check volume1's volume_type is changed to volume_type2, and
+ 'os-vol-host-attr:host' in the volume info is not changed.
+ """
src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
# Retype the volume from snapshot
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 293af81..1d12a73 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -39,12 +39,14 @@
@decorators.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d')
def test_list_services(self):
+ """Test listing volume services"""
services = (self.admin_volume_services_client.list_services()
['services'])
self.assertNotEmpty(services)
@decorators.idempotent_id('63a3e1ca-37ee-4983-826d-83276a370d25')
def test_get_service_by_service_binary_name(self):
+ """Test getting volume service by binary name"""
services = (self.admin_volume_services_client.list_services(
binary=self.binary_name)['services'])
self.assertNotEmpty(services)
@@ -53,6 +55,7 @@
@decorators.idempotent_id('178710e4-7596-4e08-9333-745cb8bc4f8d')
def test_get_service_by_host_name(self):
+ """Test getting volume service by service host name"""
services_on_host = [service for service in self.services if
_get_host(service['host']) == self.host_name]
@@ -69,6 +72,7 @@
@decorators.idempotent_id('67ec6902-f91d-4dec-91fa-338523208bbc')
def test_get_service_by_volume_host_name(self):
+ """Test getting volume service by volume host name"""
volume_id = self.create_volume()['id']
volume = self.admin_volume_client.show_volume(volume_id)['volume']
hostname = _get_host(volume['os-vol-host-attr:host'])
@@ -83,7 +87,7 @@
@decorators.idempotent_id('ffa6167c-4497-4944-a464-226bbdb53908')
def test_get_service_by_service_and_host_name(self):
-
+ """Test getting volume service by binary name and host name"""
services = (self.admin_volume_services_client.list_services(
host=self.host_name, binary=self.binary_name))['services']
diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py
index 3a863a1..bf39be5 100644
--- a/tempest/api/volume/admin/test_volume_services_negative.py
+++ b/tempest/api/volume/admin/test_volume_services_negative.py
@@ -19,6 +19,7 @@
class VolumeServicesNegativeTest(base.BaseVolumeAdminTest):
+ """Negative tests of volume services"""
@classmethod
def resource_setup(cls):
@@ -30,6 +31,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484')
def test_enable_service_with_invalid_host(self):
+ """Test enabling volume service with invalid host should fail"""
self.assertRaises(lib_exc.NotFound,
self.admin_volume_services_client.enable_service,
host='invalid_host', binary=self.binary)
@@ -37,6 +39,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('c571f179-c6e6-4c50-a0ab-368b628a8ac1')
def test_disable_service_with_invalid_binary(self):
+ """Test disabling volume service with invalid binary should fail"""
self.assertRaises(lib_exc.NotFound,
self.admin_volume_services_client.disable_service,
host=self.host, binary='invalid_binary')
@@ -44,6 +47,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('77767b36-5e8f-4c68-a0b5-2308cc21ec64')
def test_disable_log_reason_with_no_reason(self):
+ """Test disabling volume service with none reason should fail"""
self.assertRaises(lib_exc.BadRequest,
self.admin_volume_services_client.disable_log_reason,
host=self.host, binary=self.binary,
@@ -52,6 +56,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('712bfab8-1f44-4eb5-a632-fa70bf78f05e')
def test_freeze_host_with_invalid_host(self):
+ """Test freezing volume service with invalid host should fail"""
self.assertRaises(lib_exc.BadRequest,
self.admin_volume_services_client.freeze_host,
host='invalid_host')
@@ -59,6 +64,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('7c6287c9-d655-47e1-9a11-76f6657a6dce')
def test_thaw_host_with_invalid_host(self):
+ """Test thawing volume service with invalid host should fail"""
self.assertRaises(lib_exc.BadRequest,
self.admin_volume_services_client.thaw_host,
host='invalid_host')
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index ff5e7e2..10fd485 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -24,6 +24,7 @@
class VolumeSnapshotQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
+ """Negative tests of volume snapshot quotas"""
@classmethod
def skip_checks(cls):
@@ -67,6 +68,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('02bbf63f-6c05-4357-9d98-2926a94064ff')
def test_quota_volume_snapshots(self):
+ """Test creating snapshot exceeding snapshots quota should fail"""
self.assertRaises(lib_exc.OverLimit,
self.snapshots_client.create_snapshot,
volume_id=self.volume['id'])
@@ -74,6 +76,7 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('c99a1ca9-6cdf-498d-9fdf-25832babef27')
def test_quota_volume_gigabytes_snapshots(self):
+ """Test creating snapshot exceeding gigabytes quota should fail"""
self.addCleanup(self.admin_quotas_client.update_quota_set,
self.demo_tenant_id,
**self.shared_quota_set)
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index b64face..55ec428 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -24,11 +24,13 @@
class VolumeTypesAccessTest(base.BaseVolumeAdminTest):
+ """Test volume type access"""
credentials = ['primary', 'alt', 'admin']
@decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
def test_volume_type_access_add(self):
+ """Test adding volume type access for non-admin project"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
@@ -52,6 +54,7 @@
@decorators.idempotent_id('5220eb28-a435-43ce-baaf-ed46f0e95159')
def test_volume_type_access_list(self):
+ """Test listing volume type access"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ecc850e..ebcd3b7 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -23,17 +23,18 @@
class VolumeTypesTest(base.BaseVolumeAdminTest):
+ """Test volume types"""
@decorators.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54')
def test_volume_type_list(self):
- # List volume types.
+ """Test listing volume types"""
body = \
self.admin_volume_types_client.list_volume_types()['volume_types']
self.assertIsInstance(body, list)
@decorators.idempotent_id('c03cc62c-f4e9-4623-91ec-64ce2f9c1260')
def test_volume_crud_with_volume_type_and_extra_specs(self):
- # Create/update/get/delete volume with volume_type and extra spec.
+ """Test create/update/get/delete volume with volume_type"""
volume_types = list()
vol_name = data_utils.rand_name(self.__class__.__name__ + '-volume')
proto = CONF.volume.storage_protocol
@@ -80,7 +81,7 @@
@decorators.idempotent_id('4e955c3b-49db-4515-9590-0c99f8e471ad')
def test_volume_type_create_get_delete(self):
- # Create/get volume type.
+ """Test create/get/delete volume type"""
name = data_utils.rand_name(self.__class__.__name__ + '-volume-type')
description = data_utils.rand_name("volume-type-description")
proto = CONF.volume.storage_protocol
@@ -118,7 +119,7 @@
@decorators.idempotent_id('7830abd0-ff99-4793-a265-405684a54d46')
def test_volume_type_encryption_create_get_update_delete(self):
- # Create/get/update/delete encryption type.
+ """Test create/get/update/delete volume encryption type"""
create_kwargs = {'provider': 'LuksEncryptor',
'control_location': 'front-end'}
volume_type_id = self.create_volume_type()['id']
@@ -175,6 +176,7 @@
@decorators.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8')
def test_volume_type_update(self):
+ """Test updating volume type details"""
# Create volume type
volume_type = self.create_volume_type()
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 730acdf..852aa93 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -19,6 +19,7 @@
class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
+ """Test volume type extra specs"""
@classmethod
def resource_setup(cls):
@@ -27,7 +28,7 @@
@decorators.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
def test_volume_type_extra_specs_list(self):
- # List Volume types extra specs.
+ """Test listing volume type extra specs"""
extra_specs = {"spec1": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -40,7 +41,7 @@
@decorators.idempotent_id('0806db36-b4a0-47a1-b6f3-c2e7f194d017')
def test_volume_type_extra_specs_update(self):
- # Update volume type extra specs
+ """Test updating volume type extra specs"""
extra_specs = {"spec2": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -74,7 +75,7 @@
@decorators.idempotent_id('d4772798-601f-408a-b2a5-29e8a59d1220')
def test_volume_type_extra_spec_create_get_delete(self):
- # Create/Get/Delete volume type extra spec.
+ """Test Create/Get/Delete volume type extra specs"""
spec_key = "spec3"
extra_specs = {spec_key: "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index fe249d6..6b2a278 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -20,6 +20,7 @@
class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest):
+ """Negative tests of volume type extra specs"""
@classmethod
def resource_setup(cls):
@@ -30,7 +31,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
def test_update_no_body(self):
- # Should not update volume type extra specs with no body
+ """Test updating volume type extra specs with no body should fail"""
self.assertRaises(
lib_exc.BadRequest,
self.admin_volume_types_client.update_volume_type_extra_specs,
@@ -39,7 +40,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('25e5a0ee-89b3-4c53-8310-236f76c75365')
def test_update_nonexistent_extra_spec_id(self):
- # Should not update volume type extra specs with nonexistent id.
+ """Test updating volume type extra specs with non existent name
+
+ Updating volume type extra specs with non existent extra spec name
+ should fail.
+ """
extra_spec = {"spec1": "val2"}
self.assertRaises(
lib_exc.BadRequest,
@@ -50,7 +55,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9bf7a657-b011-4aec-866d-81c496fbe5c8')
def test_update_none_extra_spec_id(self):
- # Should not update volume type extra specs with none id.
+ """Test updating volume type extra specs without name
+
+ Updating volume type extra specs without extra spec name should fail.
+ """
extra_spec = {"spec1": "val2"}
self.assertRaises(
lib_exc.BadRequest,
@@ -60,8 +68,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a77dfda2-9100-448e-9076-ed1711f4bdfc')
def test_update_multiple_extra_spec(self):
- # Should not update volume type extra specs with multiple specs as
- # body.
+ """Test updating multiple volume type extra specs should fail"""
extra_spec = {"spec1": "val2", "spec2": "val1"}
self.assertRaises(
lib_exc.BadRequest,
@@ -72,8 +79,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('49d5472c-a53d-4eab-a4d3-450c4db1c545')
def test_create_nonexistent_type_id(self):
- # Should not create volume type extra spec for nonexistent volume
- # type id.
+ """Test creating volume type extra specs for non existent volume type
+
+ Creating volume type extra specs for non existent volume type should
+ fail.
+ """
extra_specs = {"spec2": "val1"}
self.assertRaises(
lib_exc.NotFound,
@@ -83,7 +93,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d')
def test_create_none_body(self):
- # Should not create volume type extra spec for none POST body.
+ """Test creating volume type extra spec with none POST body
+
+ Creating volume type extra spec with none POST body should fail.
+ """
self.assertRaises(
lib_exc.BadRequest,
self.admin_volume_types_client.create_volume_type_extra_specs,
@@ -92,7 +105,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bc772c71-1ed4-4716-b945-8b5ed0f15e87')
def test_create_invalid_body(self):
- # Should not create volume type extra spec for invalid POST body.
+ """Test creating volume type extra spec with invalid POST body
+
+ Creating volume type extra spec with invalid POST body should fail.
+ """
self.assertRaises(
lib_exc.BadRequest,
self.admin_volume_types_client.create_volume_type_extra_specs,
@@ -101,8 +117,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('031cda8b-7d23-4246-8bf6-bbe73fd67074')
def test_delete_nonexistent_volume_type_id(self):
- # Should not delete volume type extra spec for nonexistent
- # type id.
+ """Test deleting volume type extra spec for non existent volume type
+
+ Deleting volume type extra spec for non existent volume type should
+ fail.
+ """
self.assertRaises(
lib_exc.NotFound,
self.admin_volume_types_client.delete_volume_type_extra_specs,
@@ -111,7 +130,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb')
def test_list_nonexistent_volume_type_id(self):
- # Should not list volume type extra spec for nonexistent type id.
+ """Test listing volume type extra spec for non existent volume type
+
+ Listing volume type extra spec for non existent volume type should
+ fail.
+ """
self.assertRaises(
lib_exc.NotFound,
self.admin_volume_types_client.list_volume_types_extra_specs,
@@ -120,7 +143,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9')
def test_get_nonexistent_volume_type_id(self):
- # Should not get volume type extra spec for nonexistent type id.
+ """Test getting volume type extra spec for non existent volume type
+
+ Getting volume type extra spec for non existent volume type should
+ fail.
+ """
self.assertRaises(
lib_exc.NotFound,
self.admin_volume_types_client.show_volume_type_extra_specs,
@@ -129,8 +156,11 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753')
def test_get_nonexistent_extra_spec_name(self):
- # Should not get volume type extra spec for nonexistent extra spec
- # name.
+ """Test getting volume type extra spec for non existent spec name
+
+ Getting volume type extra spec for non existent extra spec name should
+ fail.
+ """
self.assertRaises(
lib_exc.NotFound,
self.admin_volume_types_client.show_volume_type_extra_specs,
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index ae29049..174cf9e 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -20,11 +20,12 @@
class VolumeTypesNegativeTest(base.BaseVolumeAdminTest):
+ """Negative tests of volume type"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('878b4e57-faa2-4659-b0d1-ce740a06ae81')
def test_create_with_empty_name(self):
- # Should not be able to create volume type with an empty name.
+ """Test creating volume type with an empty name will fail"""
self.assertRaises(
lib_exc.BadRequest,
self.admin_volume_types_client.create_volume_type, name='')
@@ -32,7 +33,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('994610d6-0476-4018-a644-a2602ef5d4aa')
def test_get_nonexistent_type_id(self):
- # Should not be able to get volume type with nonexistent type id.
+ """Test getting volume type with nonexistent type id will fail"""
self.assertRaises(lib_exc.NotFound,
self.admin_volume_types_client.show_volume_type,
data_utils.rand_uuid())
@@ -40,7 +41,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6b3926d2-7d73-4896-bc3d-e42dfd11a9f6')
def test_delete_nonexistent_type_id(self):
- # Should not be able to delete volume type with nonexistent type id.
+ """Test deleting volume type with nonexistent type id will fail"""
self.assertRaises(lib_exc.NotFound,
self.admin_volume_types_client.delete_volume_type,
data_utils.rand_uuid())
@@ -48,7 +49,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8c09f849-f225-4d78-ba87-bffd9a5e0c6f')
def test_create_volume_with_private_volume_type(self):
- # Should not be able to create volume with private volume type.
+ """Test creating volume with private volume type will fail"""
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
self.assertRaises(lib_exc.NotFound,
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 5bac3d8..ecddfba 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -23,6 +23,8 @@
class VolumesActionsTest(base.BaseVolumeAdminTest):
+ """Test volume actions"""
+
create_default_network = True
def _create_reset_and_force_delete_temp_volume(self, status=None):
@@ -38,7 +40,10 @@
@decorators.idempotent_id('d063f96e-a2e0-4f34-8b8a-395c42de1845')
def test_volume_reset_status(self):
- # test volume reset status : available->error->available->maintenance
+ """Test resetting volume status
+
+ Reset volume status to available->error->available->maintenance
+ """
volume = self.create_volume()
self.addCleanup(waiters.wait_for_volume_resource_status,
self.volumes_client, volume['id'], 'available')
@@ -52,27 +57,28 @@
@decorators.idempotent_id('21737d5a-92f2-46d7-b009-a0cc0ee7a570')
def test_volume_force_delete_when_volume_is_creating(self):
- # test force delete when status of volume is creating
+ """Test force deleting volume when its status is creating"""
self._create_reset_and_force_delete_temp_volume('creating')
@decorators.idempotent_id('db8d607a-aa2e-4beb-b51d-d4005c232011')
def test_volume_force_delete_when_volume_is_attaching(self):
- # test force delete when status of volume is attaching
+ """Test force deleting volume when its status is attaching"""
self._create_reset_and_force_delete_temp_volume('attaching')
@decorators.idempotent_id('3e33a8a8-afd4-4d64-a86b-c27a185c5a4a')
def test_volume_force_delete_when_volume_is_error(self):
- # test force delete when status of volume is error
+ """Test force deleting volume when its status is error"""
self._create_reset_and_force_delete_temp_volume('error')
@decorators.idempotent_id('b957cabd-1486-4e21-90cf-a9ed3c39dfb2')
def test_volume_force_delete_when_volume_is_maintenance(self):
- # test force delete when status of volume is maintenance
+ """Test force deleting volume when its status is maintenance"""
self._create_reset_and_force_delete_temp_volume('maintenance')
@decorators.idempotent_id('d38285d9-929d-478f-96a5-00e66a115b81')
@utils.services('compute')
def test_force_detach_volume(self):
+ """Test force detaching volume when its status is error"""
# Create a server and a volume
server_id = self.create_server()['id']
volume_id = self.create_volume()['id']
@@ -102,5 +108,4 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
volume_id, 'available')
vol_info = self.volumes_client.show_volume(volume_id)['volume']
- self.assertIn('attachments', vol_info)
self.assertEmpty(vol_info['attachments'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 45060d0..835cc1d 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -26,6 +26,7 @@
class VolumesBackupsAdminTest(base.BaseVolumeAdminTest):
+ """Test volume backups"""
@classmethod
def skip_checks(cls):
@@ -67,11 +68,8 @@
# Export Backup
export_backup = (self.admin_backups_client.export_backup(backup['id'])
['backup-record'])
- self.assertIn('backup_service', export_backup)
- self.assertIn('backup_url', export_backup)
self.assertTrue(export_backup['backup_service'].startswith(
'cinder.backup.drivers'))
- self.assertIsNotNone(export_backup['backup_url'])
# NOTE(geguileo): Backups are imported with the same backup id
# (important for incremental backups among other things), so we cannot
@@ -92,7 +90,6 @@
# deletions will delete data from the backup back-end because they
# were both pointing to the same backend data.
self.addCleanup(self._delete_backup, new_id)
- self.assertIn("id", import_backup)
self.assertEqual(new_id, import_backup['id'])
waiters.wait_for_volume_resource_status(self.admin_backups_client,
import_backup['id'],
@@ -122,6 +119,7 @@
@decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
def test_volume_backup_reset_status(self):
+ """Test resetting volume backup status to error"""
# Create a volume
volume = self.create_volume()
# Create a backup
diff --git a/tempest/api/volume/admin/test_volumes_list.py b/tempest/api/volume/admin/test_volumes_list.py
index 6ce4a85..c3229f0 100644
--- a/tempest/api/volume/admin/test_volumes_list.py
+++ b/tempest/api/volume/admin/test_volumes_list.py
@@ -24,6 +24,7 @@
class VolumesListAdminTestJSON(base.BaseVolumeAdminTest):
+ """Test listing volumes with admin privilege"""
@classmethod
def resource_setup(cls):
@@ -41,7 +42,7 @@
@decorators.idempotent_id('5866286f-3290-4cfd-a414-088aa6cdc469')
def test_volume_list_param_tenant(self):
- # Test to list volumes from single tenant
+ """Test admin can list volumes belonging to specified project"""
# Create a volume in admin tenant
adm_vol = self.admin_volume_client.create_volume(
size=CONF.volume.volume_size)['volume']
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index bcbcf43..7af5927 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -233,12 +233,15 @@
return group
def delete_group(self, group_id, delete_volumes=True):
- self.groups_client.delete_group(group_id, delete_volumes)
+ group_vols = []
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'])
+ group_vols.append(vol['id'])
+ self.groups_client.delete_group(group_id, delete_volumes)
+ for vol in group_vols:
+ self.volumes_client.wait_for_resource_deletion(vol)
self.groups_client.wait_for_resource_deletion(group_id)
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 0b6ee38..39369be 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -22,7 +22,7 @@
@decorators.idempotent_id('01f1ae88-eba9-4c6b-a011-6f7ace06b725')
def test_get_availability_zone_list(self):
- # List of availability zone
+ """Test listing volume available zones"""
availability_zone = (
self.availability_zone_client.list_availability_zones()
['availabilityZoneInfo'])
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index 39ce00c..acd9ca2 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -26,10 +26,11 @@
class ExtensionsTestJSON(base.BaseVolumeTest):
+ """Test volume extensions"""
@decorators.idempotent_id('94607eb0-43a5-47ca-82aa-736b41bd2e2c')
def test_list_extensions(self):
- # List of all extensions
+ """Test listing volume extensions"""
extensions = (self.volumes_extension_client.list_extensions()
['extensions'])
if not CONF.volume_feature_enabled.api_extensions:
diff --git a/tempest/api/volume/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
index 53b3acc..8f9bbd2 100644
--- a/tempest/api/volume/test_image_metadata.py
+++ b/tempest/api/volume/test_image_metadata.py
@@ -24,6 +24,7 @@
class VolumesImageMetadata(base.BaseVolumeTest):
+ """Test volume image metadata"""
@classmethod
def skip_checks(cls):
@@ -41,6 +42,7 @@
@decorators.idempotent_id('03efff0b-5c75-4822-8f10-8789ac15b13e')
@utils.services('image')
def test_update_show_delete_image_metadata(self):
+ """Test update/show/delete volume's image metadata"""
# Update image metadata
image_metadata = {'image_id': '5137a025-3c5f-43c1-bc64-5f41270040a5',
'image_name': 'image',
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index e6fe25d..ee1b5e5 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -23,6 +23,8 @@
class SnapshotMetadataTestJSON(base.BaseVolumeTest):
+ """Test snapshot metadata"""
+
@classmethod
def skip_checks(cls):
super(SnapshotMetadataTestJSON, cls).skip_checks()
@@ -45,6 +47,7 @@
@decorators.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c')
def test_crud_snapshot_metadata(self):
+ """Test create/get/update/delete snapshot metadata"""
# Create metadata for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
@@ -82,7 +85,7 @@
@decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
def test_update_show_snapshot_metadata_item(self):
- # Update metadata item for the snapshot
+ """Test update/show snapshot metadata item"""
metadata = {"key1": "value1",
"key2": "value2",
"key3": "value3"}
diff --git a/tempest/api/volume/test_versions.py b/tempest/api/volume/test_versions.py
index 1e5c9de..e065bdf 100644
--- a/tempest/api/volume/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -17,14 +17,14 @@
class VersionsTest(base.BaseVolumeTest):
- """Test cinder versions"""
+ """Test volume versions"""
_api_version = 3
@decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
@decorators.attr(type='smoke')
def test_list_versions(self):
- """Test listing cinder versions"""
+ """Test listing volume versions"""
# NOTE: The version data is checked on service client side
# with JSON-Schema validation. It is enough to just call
# the API here.
@@ -32,7 +32,7 @@
@decorators.idempotent_id('7f755ae2-caa9-4049-988c-331d8f7a579f')
def test_show_version(self):
- "Test getting cinder version details"
+ """Test getting volume version details"""
# NOTE: The version data is checked on service client side
# with JSON-Schema validation. So we will loop through each
# version and call show version.
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index 4d64a95..ccf0804 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -24,6 +24,7 @@
# it requires force_tenant_isolation=True, which need admin
# credentials to create non-admin users for the tests.
class AbsoluteLimitsTests(base.BaseVolumeAdminTest): # noqa: T115
+ """Test volume absolute limits"""
# avoid existing volumes of pre-defined tenant
force_tenant_isolation = True
@@ -43,7 +44,7 @@
@decorators.idempotent_id('8e943f53-e9d6-4272-b2e9-adcf2f7c29ad')
def test_get_volume_absolute_limits(self):
- # get volume limit for a tenant
+ """Test getting volume absolute limits"""
absolute_limits = \
self.volume_limits_client.show_limits(
)['limits']['absolute']
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 9edffc6..5b50bfa 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -25,6 +25,8 @@
class VolumesActionsTest(base.BaseVolumeTest):
+ """Test volume actions"""
+
create_default_network = True
@classmethod
@@ -38,6 +40,7 @@
@decorators.attr(type='smoke')
@utils.services('compute')
def test_attach_detach_volume_to_instance(self):
+ """Test attaching and detaching volume to instance"""
# Create a server
server = self.create_server()
# Volume is attached and detached successfully from an instance
@@ -53,7 +56,7 @@
@decorators.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
def test_volume_bootable(self):
- # Verify that a volume bootable flag is retrieved
+ """Test setting and retrieving bootable flag of a volume"""
for bool_bootable in [True, False]:
self.volumes_client.set_bootable_volume(self.volume['id'],
bootable=bool_bootable)
@@ -69,6 +72,11 @@
@decorators.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371')
@utils.services('compute')
def test_get_volume_attachment(self):
+ """Test getting volume attachments
+
+ Attach a volume to a server, and then retrieve volume's attachments
+ info.
+ """
# Create a server
server = self.create_server()
# Verify that a volume's attachment information is retrieved
@@ -84,7 +92,6 @@
self.volume['id'], 'available')
self.addCleanup(self.volumes_client.detach_volume, self.volume['id'])
volume = self.volumes_client.show_volume(self.volume['id'])['volume']
- self.assertIn('attachments', volume)
attachment = volume['attachments'][0]
self.assertEqual('/dev/%s' %
@@ -97,6 +104,7 @@
@decorators.idempotent_id('d8f1ca95-3d5b-44a3-b8ca-909691c9532d')
@utils.services('image')
def test_volume_upload(self):
+ """Test uploading volume to create an image"""
# NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
@@ -119,6 +127,7 @@
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
+ """Test reserving and unreserving volume"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# To get the volume info
@@ -132,6 +141,7 @@
@decorators.idempotent_id('fff74e1e-5bd3-4b33-9ea9-24c103bc3f59')
def test_volume_readonly_update(self):
+ """Test updating and retrieve volume's readonly flag"""
for readonly in [True, False]:
# Update volume readonly
self.volumes_client.update_volume_readonly(self.volume['id'],
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index c178272..2e78114 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -27,6 +27,7 @@
class VolumesBackupsTest(base.BaseVolumeTest):
+ """Test volumes backup"""
@classmethod
def skip_checks(cls):
@@ -54,6 +55,16 @@
'ceph does not support arbitrary container names')
@decorators.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
def test_volume_backup_create_get_detailed_list_restore_delete(self):
+ """Test create/get/list/restore/delete volume backup
+
+ 1. Create volume1 with metadata
+ 2. Create backup1 from volume1
+ 3. Show backup1
+ 4. List backups with detail
+ 5. Restore backup1
+ 6. Verify backup1 has been restored successfully with the metadata
+ of volume1
+ """
# Create a volume with metadata
metadata = {"vol-meta1": "value1",
"vol-meta2": "value2",
@@ -80,11 +91,7 @@
self.assertEqual('container', backup['container'])
# Get all backups with detail
- backups = self.backups_client.list_backups(
- detail=True)['backups']
- for backup_info in backups:
- self.assertIn('created_at', backup_info)
- self.assertIn('links', backup_info)
+ backups = self.backups_client.list_backups(detail=True)['backups']
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
@@ -93,7 +100,7 @@
restored_volume_metadata = self.volumes_client.show_volume(
restored_volume['volume_id'])['volume']['metadata']
- # Verify the backups has been restored successfully
+ # Verify the backup has been restored successfully
# with the metadata of the source volume.
self.assertThat(restored_volume_metadata.items(),
matchers.ContainsAll(metadata.items()))
@@ -124,6 +131,13 @@
@decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
@utils.services('image')
def test_bootable_volume_backup_and_restore(self):
+ """Test backuping and restoring a bootable volume
+
+ 1. Create volume1 from image
+ 2. Create backup1 from volume1
+ 3. Restore backup1
+ 4. Verify the restored backup volume is bootable
+ """
# Create volume from image
img_uuid = CONF.compute.image_ref
volume = self.create_volume(imageRef=img_uuid)
@@ -148,6 +162,7 @@
class VolumesBackupsV39Test(base.BaseVolumeTest):
+ """Test volumes backup with volume microversion greater than 3.8"""
_api_version = 3
min_microversion = '3.9'
@@ -161,6 +176,7 @@
@decorators.idempotent_id('9b374cbc-be5f-4d37-8848-7efb8a873dcc')
def test_update_backup(self):
+ """Test updating backup's name and description"""
# Create volume and backup
volume = self.create_volume()
backup = self.create_backup(volume_id=volume['id'])
@@ -176,7 +192,6 @@
backup['id'], **update_kwargs)['backup']
self.assertEqual(backup['id'], update_backup['id'])
self.assertEqual(update_kwargs['name'], update_backup['name'])
- self.assertIn('links', update_backup)
# Assert response body for show_backup method
retrieved_backup = self.backups_client.show_backup(
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index ade2deb..91728ab 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -37,11 +37,9 @@
kwargs['name'] = v_name
kwargs['metadata'] = metadata
volume = self.volumes_client.create_volume(**kwargs)['volume']
- self.assertIn('id', volume)
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
- self.assertIn('name', volume)
self.assertEqual(volume['name'], v_name,
"The created volume name is not equal "
"to the requested name")
@@ -101,7 +99,6 @@
'availability_zone': volume['availability_zone'],
'size': CONF.volume.volume_size}
new_volume = self.volumes_client.create_volume(**params)['volume']
- self.assertIn('id', new_volume)
self.addCleanup(self.delete_volume, self.volumes_client,
new_volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
@@ -153,7 +150,5 @@
@decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
def test_show_volume_summary(self):
"""Test showing volume summary"""
- volume_summary = \
- self.volumes_client.show_volume_summary()['volume-summary']
- for key in ['total_size', 'total_count']:
- self.assertIn(key, volume_summary)
+ # check response schema
+ self.volumes_client.show_volume_summary()
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 2345698..60f85a4 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -26,11 +26,14 @@
class VolumesListTestJSON(base.BaseVolumeTest):
- # NOTE: This test creates a number of 1G volumes. To run it successfully,
- # ensure that the backing file for the volume group that Cinder uses
- # has space for at least 3 1G volumes!
- # If you are running a Devstack environment, ensure that the
- # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
+ """Test listing volumes
+
+ NOTE: This test creates a number of 1G volumes. To run it successfully,
+ ensure that the backing file for the volume group that Cinder uses
+ has space for at least 3 1G volumes!
+ If you are running a Devstack environment, ensure that the
+ VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
+ """
VOLUME_FIELDS = ('id', 'name')
@@ -116,7 +119,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('0b6ddd39-b948-471f-8038-4787978747c4')
def test_volume_list(self):
- # Get a list of Volumes
+ """Test getting a list of volumes"""
# Fetch all volumes
fetched_list = self.volumes_client.list_volumes()['volumes']
self._assert_volumes_in(fetched_list, self.volume_list,
@@ -124,13 +127,14 @@
@decorators.idempotent_id('adcbb5a7-5ad8-4b61-bd10-5380e111a877')
def test_volume_list_with_details(self):
- # Get a list of Volumes with details
+ """Test getting a list of detailed volumes"""
# Fetch all Volumes
fetched_list = self.volumes_client.list_volumes(detail=True)['volumes']
self._assert_volumes_in(fetched_list, self.volume_list)
@decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
def test_volume_list_by_name(self):
+ """Test getting a list of volumes filtered by volume name"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
@@ -140,6 +144,7 @@
@decorators.idempotent_id('2de3a6d4-12aa-403b-a8f2-fdeb42a89623')
def test_volume_list_details_by_name(self):
+ """Test getting a list of detailed volumes filtered by volume name"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name']}
fetched_vol = self.volumes_client.list_volumes(
@@ -149,6 +154,7 @@
@decorators.idempotent_id('39654e13-734c-4dab-95ce-7613bf8407ce')
def test_volumes_list_by_status(self):
+ """Test getting a list of volumes filtered by volume status"""
params = {'status': 'available'}
fetched_list = self.volumes_client.list_volumes(
params=params)['volumes']
@@ -158,6 +164,7 @@
@decorators.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_status(self):
+ """Test getting a list of detailed volumes filtered by status"""
params = {'status': 'available'}
fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
@@ -181,6 +188,7 @@
@decorators.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_bootable(self):
+ """Test getting a list of detailed volumes filtered by bootable"""
params = {'bootable': 'false'}
fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
@@ -190,6 +198,7 @@
@decorators.idempotent_id('c0cfa863-3020-40d7-b587-e35f597d5d87')
def test_volumes_list_by_availability_zone(self):
+ """Test getting a list of volumes filtered by availability zone"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
@@ -201,6 +210,7 @@
@decorators.idempotent_id('e1b80d13-94f0-4ba2-a40e-386af29f8db1')
def test_volumes_list_details_by_availability_zone(self):
+ """Test getting a list of detailed volumes by availability zone"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
@@ -212,19 +222,19 @@
@decorators.idempotent_id('b5ebea1b-0603-40a0-bb41-15fcd0a53214')
def test_volume_list_with_param_metadata(self):
- # Test to list volumes when metadata param is given
+ """Test listing volumes when metadata param is given"""
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('1ca92d3c-4a8e-4b43-93f5-e4c7fb3b291d')
def test_volume_list_with_detail_param_metadata(self):
- # Test to list volumes details when metadata param is given
+ """Test listing volumes details when metadata param is given"""
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params, with_detail=True)
@decorators.idempotent_id('777c87c1-2fc4-4883-8b8e-5c0b951d1ec8')
def test_volume_list_param_display_name_and_status(self):
- # Test to list volume when display name and status param is given
+ """Test listing volume when display name and status param is given"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name'],
'status': 'available'}
@@ -232,7 +242,7 @@
@decorators.idempotent_id('856ab8ca-6009-4c37-b691-be1065528ad4')
def test_volume_list_with_detail_param_display_name_and_status(self):
- # Test to list volume when name and status param is given
+ """Test listing volume when name and status param is given"""
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name'],
'status': 'available'}
@@ -240,7 +250,7 @@
@decorators.idempotent_id('2a7064eb-b9c3-429b-b888-33928fc5edd3')
def test_volume_list_details_with_multiple_params(self):
- # List volumes detail using combined condition
+ """Test listing volumes detail using combined filtering condition"""
def _list_details_with_multiple_params(limit=2,
status='available',
sort_dir='asc',
@@ -375,14 +385,29 @@
@decorators.idempotent_id('e9138a2c-f67b-4796-8efa-635c196d01de')
def test_volume_list_details_pagination(self):
+ """Test listing volumes with details by pagination
+
+ All volumes will be returned by multiple requests, and the number of
+ 'limit' volumes will be returned at a time.
+ """
self._test_pagination('volumes', ids=self.volume_id_list, detail=True)
@decorators.idempotent_id('af55e775-8e4b-4feb-8719-215c43b0238c')
def test_volume_list_pagination(self):
+ """Test listing volumes by pagination
+
+ All volumes will be returned by multiple requests, and the number of
+ 'limit' volumes will be returned at a time.
+ """
self._test_pagination('volumes', ids=self.volume_id_list, detail=False)
@decorators.idempotent_id('46eff077-100b-427f-914e-3db2abcdb7e2')
def test_volume_list_with_detail_param_marker(self):
+ """Test listing volumes with details from the specified marker
+
+ Choose a volume id from all volumes as a marker, list volumes with
+ that marker, only volumes with id greater than marker will be returned.
+ """
# Choosing a random volume from a list of volumes for 'marker'
# parameter
marker = random.choice(self.volume_id_list)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 866bd87..76c22f0 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -28,6 +28,7 @@
class VolumesNegativeTest(base.BaseVolumeTest):
+ """Negative tests of volumes"""
@classmethod
def resource_setup(cls):
@@ -58,50 +59,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f131c586-9448-44a4-a8b0-54ca838aa43e')
def test_volume_get_nonexistent_volume_id(self):
- # Should not be able to get a non-existent volume
+ """Test getting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29')
def test_volume_delete_nonexistent_volume_id(self):
- # Should not be able to delete a non-existent Volume
+ """Test deleting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1ed83a8a-682d-4dfb-a30e-ee63ffd6c049')
def test_create_volume_with_invalid_size(self):
- # Should not be able to create volume with invalid size in request
+ """Test creating volume with invalid size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='#$%')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9387686f-334f-4d31-a439-33494b9e2683')
def test_create_volume_without_passing_size(self):
- # Should not be able to create volume without passing size
- # in request
+ """Test creating volume with empty size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41331caa-eaf4-4001-869d-bc18c1869360')
def test_create_volume_with_size_zero(self):
- # Should not be able to create volume with size zero
+ """Test creating volume with zero size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='0')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8b472729-9eba-446e-a83b-916bdb34bef7')
def test_create_volume_with_size_negative(self):
- # Should not be able to create volume with size negative
+ """Test creating volume with negative size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='-1')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('10254ed8-3849-454e-862e-3ab8e6aa01d2')
def test_create_volume_with_nonexistent_volume_type(self):
- # Should not be able to create volume with non-existent volume type
+ """Test creating volume with non existent volume type should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
volume_type=data_utils.rand_uuid())
@@ -109,7 +109,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9')
def test_create_volume_with_nonexistent_snapshot_id(self):
- # Should not be able to create volume with non-existent snapshot
+ """Test creating volume with non existent snapshot should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
snapshot_id=data_utils.rand_uuid())
@@ -117,7 +117,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344')
def test_create_volume_with_nonexistent_source_volid(self):
- # Should not be able to create volume with non-existent source volume
+ """Test creating volume with non existent source volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
source_volid=data_utils.rand_uuid())
@@ -125,46 +125,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
def test_update_volume_with_nonexistent_volume_id(self):
+ """Test updating non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
def test_update_volume_with_invalid_volume_id(self):
+ """Test updating volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
def test_update_volume_with_empty_volume_id(self):
+ """Test updating volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('30799cfd-7ee4-446c-b66c-45b383ed211b')
def test_get_invalid_volume_id(self):
- # Should not be able to get volume with invalid id
+ """Test getting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c6c3db06-29ad-4e91-beb0-2ab195fe49e3')
def test_get_volume_without_passing_volume_id(self):
- # Should not be able to get volume when empty ID is passed
+ """Test getting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.show_volume, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1f035827-7c32-4019-9240-b4ec2dbd9dfd')
def test_delete_invalid_volume_id(self):
- # Should not be able to delete volume when invalid ID is passed
+ """Test deleting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('441a1550-5d44-4b30-af0f-a6d402f52026')
def test_delete_volume_without_passing_volume_id(self):
- # Should not be able to delete volume when empty ID is passed
+ """Test deleting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.delete_volume, '')
@@ -172,6 +175,7 @@
@decorators.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@utils.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
+ """Test attaching non existent volume to server should fail"""
server = self.create_server()
self.assertRaises(lib_exc.NotFound,
@@ -183,6 +187,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a')
def test_detach_volumes_with_invalid_volume_id(self):
+ """Test detaching volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.detach_volume,
'xxx')
@@ -190,7 +195,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e0c75c74-ee34-41a9-9288-2a2051452854')
def test_volume_extend_with_size_smaller_than_original_size(self):
- # Extend volume with smaller size than original size.
+ """Test extending volume with decreasing size should fail"""
extend_size = 0
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -199,7 +204,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5d0b480d-e833-439f-8a5a-96ad2ed6f22f')
def test_volume_extend_with_non_number_size(self):
- # Extend volume when size is non number.
+ """Test extending volume with non-integer size should fail"""
extend_size = 'abc'
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -208,7 +213,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('355218f1-8991-400a-a6bb-971239287d92')
def test_volume_extend_with_None_size(self):
- # Extend volume with None size.
+ """Test extending volume with none size should fail"""
extend_size = None
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -217,7 +222,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb')
def test_volume_extend_with_nonexistent_volume_id(self):
- # Extend volume size when volume is nonexistent.
+ """Test extending non existent volume should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
data_utils.rand_uuid(), new_size=extend_size)
@@ -225,7 +230,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
def test_volume_extend_without_passing_volume_id(self):
- # Extend volume size when passing volume id is None.
+ """Test extending volume without passing volume id should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
None, new_size=extend_size)
@@ -233,6 +238,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2')
def test_reserve_volume_with_nonexistent_volume_id(self):
+ """Test reserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.reserve_volume,
data_utils.rand_uuid())
@@ -240,6 +246,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c')
def test_unreserve_volume_with_nonexistent_volume_id(self):
+ """Test unreserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.unreserve_volume,
data_utils.rand_uuid())
@@ -247,6 +254,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
def test_reserve_volume_with_negative_volume_status(self):
+ """Test reserving already reserved volume should fail"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
@@ -259,6 +267,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f')
def test_list_volumes_with_nonexistent_name(self):
+ """Test listing volumes with non existent name should get nothing"""
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = self.volumes_client.list_volumes(
@@ -268,6 +277,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359')
def test_list_volumes_detail_with_nonexistent_name(self):
+ """Test listing volume details with non existent name
+
+ Listing volume details with non existent name should get nothing.
+ """
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = \
@@ -278,6 +291,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c')
def test_list_volumes_with_invalid_status(self):
+ """Test listing volumes with invalid status should get nothing"""
params = {'status': 'null'}
fetched_volume = self.volumes_client.list_volumes(
params=params)['volumes']
@@ -286,6 +300,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ba94b27b-be3f-496c-a00e-0283b373fa75')
def test_list_volumes_detail_with_invalid_status(self):
+ """Test listing volume details with invalid status
+
+ Listing volume details with invalid status should get nothing
+ """
params = {'status': 'null'}
fetched_volume = \
self.volumes_client.list_volumes(detail=True,
@@ -296,6 +314,7 @@
@decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f')
@utils.services('image')
def test_create_volume_from_image_with_decreasing_size(self):
+ """Test creating volume from image with decreasing size should fail"""
# Create image
image = self.create_image()
@@ -311,6 +330,7 @@
@decorators.idempotent_id('d15e7f35-2cfc-48c8-9418-c8223a89bcbb')
@utils.services('image')
def test_create_volume_from_deactivated_image(self):
+ """Test creating volume from deactivated image should fail"""
# Create image
image = self.create_image()
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index bf221e8..fd2e7c4 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -25,6 +25,8 @@
class VolumesSnapshotTestJSON(base.BaseVolumeTest):
+ """Test volume snapshots"""
+
create_default_network = True
@classmethod
@@ -41,6 +43,7 @@
@decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
@utils.services('compute')
def test_snapshot_create_delete_with_volume_in_use(self):
+ """Test create/delete snapshot from volume attached to server"""
# Create a test instance
server = self.create_server()
# NOTE(zhufl) Here we create volume from self.image_ref for adding
@@ -66,7 +69,13 @@
@decorators.idempotent_id('5210a1de-85a0-11e6-bb21-641c676a5d61')
@utils.services('compute')
def test_snapshot_create_offline_delete_online(self):
+ """Test creating snapshots when volume is detached and attached
+ 1. Create snapshot1 from volume1(not attached to any server)
+ 2. Attach volume1 to server1
+ 3. Create snapshot2 and snapshot3 from volume1
+ 4. Delete snapshot3, snapshot1, snapshot2
+ """
# Create a snapshot while it is not attached
snapshot1 = self.create_snapshot(self.volume_origin['id'])
@@ -74,7 +83,7 @@
server = self.create_server()
self.attach_volume(server['id'], self.volume_origin['id'])
- # Now that the volume is attached, create another snapshots
+ # Now that the volume is attached, create other snapshots
snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True)
@@ -86,6 +95,7 @@
@decorators.idempotent_id('2a8abbe4-d871-46db-b049-c41f5af8216e')
def test_snapshot_create_get_list_update_delete(self):
+ """Test create/get/list/update/delete snapshot"""
# Create a snapshot with metadata
metadata = {"snap-meta1": "value1",
"snap-meta2": "value2",
@@ -156,19 +166,25 @@
@decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
def test_volume_from_snapshot(self):
- # Creates a volume from a snapshot passing a size
- # different from the source
+ """Test creating volume from snapshot with extending size"""
self._create_volume_from_snapshot(extra_size=1)
@decorators.idempotent_id('053d8870-8282-4fff-9dbb-99cb58bb5e0a')
def test_volume_from_snapshot_no_size(self):
- # Creates a volume from a snapshot defaulting to original size
+ """Test creating volume from snapshot with original size"""
self._create_volume_from_snapshot()
@decorators.idempotent_id('bbcfa285-af7f-479e-8c1a-8c34fc16543c')
@testtools.skipUnless(CONF.volume_feature_enabled.backup,
"Cinder backup is disabled")
def test_snapshot_backup(self):
+ """Test creating backup from snapshot and volume
+
+ 1. Create snapshot1 from volume1
+ 2. Create backup from volume1 and snapshot1
+ 3. Check the created backup's volume is volume1 and snapshot
+ is snapshot1
+ """
# Create a snapshot
snapshot = self.create_snapshot(volume_id=self.volume_origin['id'])
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index f4f039c..77627bc 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -18,6 +18,7 @@
class VolumesSnapshotListTestJSON(base.BaseVolumeTest):
+ """Test listing volume snapshots"""
@classmethod
def skip_checks(cls):
@@ -50,6 +51,7 @@
def _list_snapshots_by_param_limit(self, limit, expected_elements):
"""list snapshots by limit param"""
+
# Get snapshots list using limit parameter
fetched_snap_list = self.snapshots_client.list_snapshots(
limit=limit)['snapshots']
@@ -58,7 +60,8 @@
@decorators.idempotent_id('59f41f43-aebf-48a9-ab5d-d76340fab32b')
def test_snapshots_list_with_params(self):
- """list snapshots with params."""
+ """Test listing snapshots with params"""
+
# Verify list snapshots by display_name filter
params = {'name': self.snapshot['name']}
self._list_by_param_values_and_assert(**params)
@@ -74,7 +77,8 @@
@decorators.idempotent_id('220a1022-1fcd-4a74-a7bd-6b859156cda2')
def test_snapshots_list_details_with_params(self):
- """list snapshot details with params."""
+ """Test listing snapshot details with params"""
+
# Verify list snapshot details by display_name filter
params = {'name': self.snapshot['name']}
self._list_by_param_values_and_assert(with_detail=True, **params)
@@ -88,24 +92,29 @@
@decorators.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
def test_snapshot_list_param_limit(self):
- # List returns limited elements
+ """Test listing snapshot with limit returns the limited elements
+
+ If listing snapshots with limit=1, then 1 snapshot is returned.
+ """
self._list_snapshots_by_param_limit(limit=1, expected_elements=1)
@decorators.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400')
def test_snapshot_list_param_limit_equals_infinite(self):
- # List returns all elements when request limit exceeded
- # snapshots number
+ """Test listing snapshot with infinite limit
+
+ If listing snapshots with limit greater than the count of all
+ snapshots, then all snapshots are returned.
+ """
snap_list = self.snapshots_client.list_snapshots()['snapshots']
self._list_snapshots_by_param_limit(limit=100000,
expected_elements=len(snap_list))
@decorators.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
def test_snapshot_list_param_limit_equals_zero(self):
- # List returns zero elements
+ """Test listing snapshot with zero limit should return empty list"""
self._list_snapshots_by_param_limit(limit=0, expected_elements=0)
def _list_snapshots_param_sort(self, sort_key, sort_dir):
- """list snapshots by sort param"""
snap_list = self.snapshots_client.list_snapshots(
sort_key=sort_key, sort_dir=sort_dir)['snapshots']
self.assertNotEmpty(snap_list)
@@ -122,33 +131,42 @@
@decorators.idempotent_id('c5513ada-64c1-4d28-83b9-af3307ec1388')
def test_snapshot_list_param_sort_id_asc(self):
+ """Test listing snapshots sort by id ascendingly"""
self._list_snapshots_param_sort(sort_key='id', sort_dir='asc')
@decorators.idempotent_id('8a7fe058-0b41-402a-8afd-2dbc5a4a718b')
def test_snapshot_list_param_sort_id_desc(self):
+ """Test listing snapshots sort by id descendingly"""
self._list_snapshots_param_sort(sort_key='id', sort_dir='desc')
@decorators.idempotent_id('4052c3a0-2415-440a-a8cc-305a875331b0')
def test_snapshot_list_param_sort_created_at_asc(self):
+ """Test listing snapshots sort by created_at ascendingly"""
self._list_snapshots_param_sort(sort_key='created_at', sort_dir='asc')
@decorators.idempotent_id('dcbbe24a-f3c0-4ec8-9274-55d48db8d1cf')
def test_snapshot_list_param_sort_created_at_desc(self):
+ """Test listing snapshots sort by created_at descendingly"""
self._list_snapshots_param_sort(sort_key='created_at', sort_dir='desc')
@decorators.idempotent_id('d58b5fed-0c37-42d3-8c5d-39014ac13c00')
def test_snapshot_list_param_sort_name_asc(self):
+ """Test listing snapshots sort by display_name ascendingly"""
self._list_snapshots_param_sort(sort_key='display_name',
sort_dir='asc')
@decorators.idempotent_id('96ba6f4d-1f18-47e1-b4bc-76edc6c21250')
def test_snapshot_list_param_sort_name_desc(self):
+ """Test listing snapshots sort by display_name descendingly"""
self._list_snapshots_param_sort(sort_key='display_name',
sort_dir='desc')
@decorators.idempotent_id('05489dde-44bc-4961-a1f5-3ce7ee7824f7')
def test_snapshot_list_param_marker(self):
- # The list of snapshots should end before the provided marker
+ """Test listing snapshots with marker
+
+ The list of snapshots should end before the provided marker
+ """
snap_list = self.snapshots_client.list_snapshots()['snapshots']
# list_snapshots will take the reverse order as they are created.
snapshot_id_list = [snap['id'] for snap in snap_list][::-1]
@@ -163,6 +181,13 @@
@decorators.idempotent_id('ca96d551-17c6-4e11-b0e8-52d3bb8a63c7')
def test_snapshot_list_param_offset(self):
+ """Test listing snapshots with offset and limit
+
+ If listing snapshots with offset=2 and limit=3, then at most 3(limit)
+ snapshots located in the position 2(offset) in the all snapshots list
+ should be returned.
+ (The items in the all snapshots list start from position 0.)
+ """
params = {'offset': 2, 'limit': 3}
snap_list = self.snapshots_client.list_snapshots(**params)['snapshots']
# Verify the list of snapshots skip offset=2 from the first element
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 0453c0a..9c36dc6 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -20,6 +20,7 @@
class VolumesSnapshotNegativeTestJSON(base.BaseVolumeTest):
+ """Negative tests of volume snapshot"""
@classmethod
def skip_checks(cls):
@@ -30,7 +31,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e3e466af-70ab-4f4b-a967-ab04e3532ea7')
def test_create_snapshot_with_nonexistent_volume_id(self):
- # Create a snapshot with nonexistent volume id
+ """Test creating snapshot from non existent volume should fail"""
s_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
self.assertRaises(lib_exc.NotFound,
self.snapshots_client.create_snapshot,
@@ -40,6 +41,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bb9da53e-d335-4309-9c15-7e76fd5e4d6d')
def test_create_snapshot_without_passing_volume_id(self):
+ """Test creating snapshot without passing volume_id should fail"""
# Create a snapshot without passing volume id
s_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
self.assertRaises(lib_exc.NotFound,
@@ -49,6 +51,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('677863d1-34f9-456d-b6ac-9924f667a7f4')
def test_volume_from_snapshot_decreasing_size(self):
+ """Test creating volume from snapshot with decreasing size
+
+ creating volume from snapshot with decreasing size should fail.
+ """
# Creates a volume a snapshot passing a size different from the source
src_size = CONF.volume.volume_size * 2
@@ -64,6 +70,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8fd92339-e22f-4591-86b4-1e2215372a40')
def test_list_snapshot_invalid_param_limit(self):
+ """Test listing snapshots with invalid limit param should fail"""
self.assertRaises(lib_exc.BadRequest,
self.snapshots_client.list_snapshots,
limit='invalid')
@@ -71,6 +78,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8')
def test_list_snapshots_invalid_param_sort(self):
+ """Test listing snapshots with invalid sort key should fail"""
self.assertRaises(lib_exc.BadRequest,
self.snapshots_client.list_snapshots,
sort_key='invalid')
@@ -78,6 +86,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a')
def test_list_snapshots_invalid_param_marker(self):
+ """Test listing snapshots with invalid marker should fail"""
self.assertRaises(lib_exc.NotFound,
self.snapshots_client.list_snapshots,
marker=data_utils.rand_uuid())
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index b230615..ff552a1 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -97,6 +97,7 @@
[OPTIONS] <accounts_file.yaml> -h``.
"""
+import argparse
import os
import traceback
@@ -199,6 +200,14 @@
LOG.info('%s generated successfully!', account_file)
+def positive_int(number):
+ number = int(number)
+ if number <= 0:
+ raise argparse.ArgumentTypeError("Concurrency value should be a "
+ "positive number")
+ return number
+
+
def _parser_add_args(parser):
parser.add_argument('-c', '--config-file',
metavar='/etc/tempest.conf',
@@ -228,7 +237,7 @@
help='Resources tag')
parser.add_argument('-r', '--concurrency',
default=1,
- type=int,
+ type=positive_int,
required=False,
dest='concurrency',
help='Concurrency count')
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index e029538..172fbaa 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -30,6 +30,8 @@
* ``--ports, -p``: (Optional) The path to a JSON file describing the ports
being used by different services
* ``--verbose, -v``: (Optional) Print Request and Response Headers and Body
+ data to stdout in the non cliff deprecated CLI
+* ``--all-stdout, -a``: (Optional) Print Request and Response Headers and Body
data to stdout
@@ -278,7 +280,7 @@
return url_parser
-def output(url_parser, output_file, verbose):
+def output(url_parser, output_file, all_stdout):
if output_file is not None:
with open(output_file, "w") as outfile:
outfile.write(json.dumps(url_parser.test_logs))
@@ -294,7 +296,7 @@
sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
item.get('status_code'), item.get('verb'),
item.get('service'), item.get('url')))
- if verbose:
+ if all_stdout:
sys.stdout.write('\t\t- request headers: {0}\n'.format(
item.get('request_headers')))
sys.stdout.write('\t\t- request body: {0}\n'.format(
@@ -313,7 +315,7 @@
"please use: 'tempest subunit-describe-calls'")
cl_args = ArgumentParser().parse_args()
parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports)
- output(parser, cl_args.output_file, cl_args.verbose)
+ output(parser, cl_args.output_file, cl_args.all_stdout)
def _parser_add_args(parser):
@@ -339,9 +341,23 @@
help="A JSON file describing the ports for each service."
)
- parser.add_argument(
- "-v", "--verbose", action='store_true', default=False,
- help="Add Request and Response header and body data to stdout."
+ group = parser.add_mutually_exclusive_group()
+ # the -v and --verbose command are for the old subunit-describe-calls
+ # main() CLI interface. It does not work with the new
+ # tempest subunit-describe-callss CLI. So when the main CLI approach is
+ # deleted this argument is not needed.
+ group.add_argument(
+ "-v", "--verbose", action='store_true', dest='all_stdout',
+ help='Add Request and Response header and body data to stdout print.'
+ ' NOTE: This argument deprecated and does not work with'
+ ' tempest subunit-describe-calls CLI.'
+ ' Use new option: "-a", "--all-stdout"'
+ )
+ group.add_argument(
+ "-a", "--all-stdout", action='store_true',
+ help="Add Request and Response header and body data to stdout print."
+ " Note: this argument work with the subunit-describe-calls and"
+ " tempest subunit-describe-calls CLI commands."
)
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 8d5bdbd..235d8e3 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -60,16 +60,16 @@
"""
import argparse
+import configparser
import os
import re
import sys
import traceback
+from urllib import parse as urlparse
from cliff import command
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
-from six import moves
-from six.moves.urllib import parse as urlparse
from tempest import clients
from tempest.common import credentials_factory as credentials
@@ -439,9 +439,9 @@
if update:
conf_file = _get_config_file()
- CONF_PARSER = moves.configparser.ConfigParser()
+ CONF_PARSER = configparser.ConfigParser()
CONF_PARSER.optionxform = str
- CONF_PARSER.readfp(conf_file)
+ CONF_PARSER.read_file(conf_file)
# Indicate not to create network resources as part of getting credentials
net_resources = {
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index c702d88..b0bf5b2 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -62,8 +62,9 @@
# [1] https://bugs.launchpad.net/swift/+bug/1537811
# [2] http://tracker.ceph.com/issues/13582
if ('content-length' not in actual and
+ 'transfer-encoding' not in actual and
self._content_length_required(actual)):
- return NonExistentHeader('content-length')
+ return NonExistentHeaders(['content-length', 'transfer-encoding'])
if 'content-type' not in actual:
return NonExistentHeader('content-type')
if 'x-trans-id' not in actual:
@@ -75,8 +76,6 @@
if self.method == 'GET' or self.method == 'HEAD':
if 'x-timestamp' not in actual:
return NonExistentHeader('x-timestamp')
- if 'accept-ranges' not in actual:
- return NonExistentHeader('accept-ranges')
if self.target == 'Account':
if 'x-account-bytes-used' not in actual:
return NonExistentHeader('x-account-bytes-used')
@@ -192,6 +191,19 @@
return {}
+class NonExistentHeaders(object):
+ """Informs an error message in the case of missing certain headers"""
+
+ def __init__(self, headers):
+ self.headers = headers
+
+ def describe(self):
+ return "none of these headers exist: %s" % self.headers
+
+ def get_details(self):
+ return {}
+
+
class InvalidHeaderValue(object):
"""Informs an error message when a header contains a bad value"""
diff --git a/tempest/config.py b/tempest/config.py
index 204d977..a632dee 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from __future__ import print_function
-
import os
import tempfile
@@ -483,6 +481,12 @@
'MIN_LIBVIRT_VERSION is >= 1.2.17 on all '
'branches from stable/rocky and will be '
'removed in a future release.'),
+ cfg.BoolOpt('can_migrate_between_any_hosts',
+ default=True,
+ help="Does the test environment support migrating between "
+ "any hosts? In environments with non-homogeneous compute "
+ "nodes you can set this to False so that it will select "
+ "destination host for migrating automatically"),
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
@@ -594,6 +598,18 @@
help='Does the test environment support attaching a volume to '
'more than one instance? This depends on hypervisor and '
'volume backend/type and compute API version 2.60.'),
+ cfg.BoolOpt('xenapi_apis',
+ default=False,
+ help='Does the test environment support the XenAPI-specific '
+ 'APIs: os-agents, writeable server metadata and the '
+ 'resetNetwork server action? '
+ 'These were removed in Victoria alongside the XenAPI '
+ 'virt driver.',
+ deprecated_for_removal=True,
+ deprecated_reason="On Nova side, XenAPI virt driver and the "
+ "APIs that only worked with that driver "
+ "have been removed and there's nothing to "
+ "test after Ussuri."),
]
@@ -658,6 +674,12 @@
'are current one. In future, Tempest will '
'test v2 APIs only so this config option '
'will be removed.'),
+ # Image import feature is setup in devstack victoria onwards.
+ # Once all stable branches setup the same via glance standalone
+ # mode or with uwsgi, we can remove this config option.
+ cfg.BoolOpt('import_image',
+ default=False,
+ help="Is image import feature enabled"),
]
network_group = cfg.OptGroup(name='network',
@@ -1021,7 +1043,7 @@
help="Number of seconds to wait while looping to check the "
"status of a container to container synchronization"),
cfg.StrOpt('operator_role',
- default='Member',
+ default='member',
help="Role to add to users created for swift tests to "
"enable creating containers"),
cfg.StrOpt('reseller_admin_role',
@@ -1068,11 +1090,13 @@
cfg.StrOpt('img_dir',
default='/opt/stack/new/devstack/files/images/'
'cirros-0.3.1-x86_64-uec',
- help='Directory containing image files',
+ help='Directory containing image files, this has been '
+ 'deprecated - img_file option contains a full path now.',
deprecated_for_removal=True),
cfg.StrOpt('img_file', deprecated_name='qcow2_img_file',
- default='cirros-0.3.1-x86_64-disk.img',
- help='Image file name'),
+ default='/opt/stack/new/devstack/files/images'
+ '/cirros-0.3.1-x86_64-disk.img',
+ help='Image full path.'),
cfg.StrOpt('img_disk_format',
default='qcow2',
help='Image disk format'),
@@ -1081,18 +1105,6 @@
help='Image container format'),
cfg.DictOpt('img_properties', help='Glance image properties. '
'Use for custom images which require them'),
- cfg.StrOpt('ami_img_file',
- default='cirros-0.3.1-x86_64-blank.img',
- help='AMI image file name',
- deprecated_for_removal=True),
- cfg.StrOpt('ari_img_file',
- default='cirros-0.3.1-x86_64-initrd',
- help='ARI image file name',
- deprecated_for_removal=True),
- cfg.StrOpt('aki_img_file',
- default='cirros-0.3.1-x86_64-vmlinuz',
- help='AKI image file name',
- deprecated_for_removal=True),
# TODO(yfried): add support for dhcpcd
cfg.StrOpt('dhcp_client',
default='udhcpc',
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
index 28ed816..8aed37d 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -120,3 +120,10 @@
# 7: SUSPENDED
'enum': [0, 1, 3, 4, 6, 7]
}
+
+uuid_or_null = {
+ 'anyOf': [
+ {'type': 'string', 'format': 'uuid'},
+ {'type': 'null'}
+ ]
+}
diff --git a/tempest/lib/api_schema/response/volume/backups.py b/tempest/lib/api_schema/response/volume/backups.py
new file mode 100644
index 0000000..cba7981
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/backups.py
@@ -0,0 +1,229 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+common_show_backup = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'object_count': {'type': 'integer'},
+ 'container': {'type': ['string', 'null']},
+ 'description': {'type': ['string', 'null']},
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': parameter_types.date_time,
+ 'updated_at': parameter_types.date_time_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'has_dependent_backups': {'type': 'boolean'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'fail_reason': {'type': ['string', 'null']},
+ 'size': {'type': 'integer'},
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'is_incremental': {'type': 'boolean'},
+ 'data_timestamp': parameter_types.date_time_or_null,
+ 'snapshot_id': {'type': ['string', 'null']},
+ # TODO(zhufl): os-backup-project-attr:project_id is added
+ # in 3.18, we should move it to the 3.18 schema file when
+ # microversion is supported in volume interfaces.
+ 'os-backup-project-attr:project_id': {
+ 'type': 'string', 'format': 'uuid'},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ # TODO(zhufl): user_id is added in 3.56, we should move it
+ # to the 3.56 schema file when microversion is supported
+ # in volume interfaces.
+ 'user_id': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'object_count', 'fail_reason', 'links',
+ 'created_at', 'updated_at', 'name', 'volume_id', 'size', 'id',
+ 'data_timestamp']
+}
+
+create_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': ['string', 'null']},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+update_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': ['string', 'null']},
+ 'metadata': {'^.+$': {'type': 'string'}}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+restore_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'restore': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_id', 'volume_id', 'volume_name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['restore']
+ }
+}
+
+delete_backup = {'status_code': [202]}
+
+show_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': common_show_backup
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+list_backups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'links': parameter_types.links,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': ['string', 'null']},
+ # TODO(zhufl): count is added in 3.45, we should move
+ # it to the 3.45 schema file when microversion is
+ # supported in volume interfaces
+ 'count': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['links', 'id', 'name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+list_backups_detail = copy.deepcopy(common_show_backup)
+# TODO(zhufl): count is added in 3.45, we should move it to the 3.45 schema
+# file when microversion is supported in volume interfaces
+list_backups_detail['properties'].update({'count': {'type': 'integer'}})
+list_backups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ 'type': 'array',
+ 'items': list_backups_detail
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+export_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup-record': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_service': {'type': 'string'},
+ 'backup_url': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_service', 'backup_url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup-record']
+ }
+}
+
+import_backup = {
+ 'status_code': [201],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+reset_backup_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/group_snapshots.py b/tempest/lib/api_schema/response/volume/group_snapshots.py
new file mode 100644
index 0000000..c75c3ba
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/group_snapshots.py
@@ -0,0 +1,106 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_group_snapshot = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshot': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ 'group_type_id': {'type': 'string', 'format': 'uuid'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'group_type_id']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshot']
+ }
+}
+
+delete_group_snapshot = {'status_code': [202]}
+
+common_show_group_snapshot = {
+ 'type': 'object',
+ 'properties': {
+ 'created_at': parameter_types.date_time,
+ 'group_id': {'type': 'string', 'format': 'uuid'},
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'group_type_id': {'type': 'string', 'format': 'uuid'},
+ },
+ 'additionalProperties': False,
+ 'required': ['created_at', 'group_id', 'id', 'name',
+ 'status', 'description', 'group_type_id']
+}
+
+show_group_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshot': common_show_group_snapshot
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshot']
+ }
+}
+
+list_group_snapshots_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshots': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshots'],
+ }
+}
+
+list_group_snapshots_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_snapshots': {
+ 'type': 'array',
+ 'items': common_show_group_snapshot
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_snapshots'],
+ }
+}
+
+reset_group_snapshot_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/group_types.py b/tempest/lib/api_schema/response/volume/group_types.py
index bcfa32e..4fc9ae8 100644
--- a/tempest/lib/api_schema/response/volume/group_types.py
+++ b/tempest/lib/api_schema/response/volume/group_types.py
@@ -73,6 +73,18 @@
}
}
+show_default_group_type = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_type': common_show_group_type
+ },
+ 'additionalProperties': False,
+ 'required': ['group_type']
+ }
+}
+
update_group_type = {
'status_code': [200],
'response_body': {
diff --git a/tempest/lib/api_schema/response/volume/groups.py b/tempest/lib/api_schema/response/volume/groups.py
new file mode 100644
index 0000000..cb31269
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/groups.py
@@ -0,0 +1,164 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_group = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+delete_group = {'status_code': [202]}
+
+show_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should move it
+ # to the 3.25 schema file when microversion is supported
+ # in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'replication_status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+list_groups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+list_groups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should
+ # move it to the 3.25 schema file when microversion
+ # is supported in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+create_group_from_source = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+update_group = {'status_code': [202]}
+reset_group_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/manage_volume.py b/tempest/lib/api_schema/response/volume/manage_volume.py
new file mode 100644
index 0000000..d3acfd9
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/manage_volume.py
@@ -0,0 +1,27 @@
+# Copyright 2018 ZTE Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.volume import volumes
+
+
+manage_volume = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': volumes.common_show_volume},
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
diff --git a/tempest/lib/api_schema/response/volume/snapshots.py b/tempest/lib/api_schema/response/volume/snapshots.py
index e9aeb64..9d52801 100644
--- a/tempest/lib/api_schema/response/volume/snapshots.py
+++ b/tempest/lib/api_schema/response/volume/snapshots.py
@@ -28,7 +28,7 @@
'status': {'type': 'string'},
'description': {'type': ['string', 'null']},
'created_at': parameter_types.date_time,
- 'name': {'type': 'string'},
+ 'name': {'type': ['string', 'null']},
'volume_id': {'type': 'string', 'format': 'uuid'},
'metadata': metadata,
'id': {'type': 'string', 'format': 'uuid'},
diff --git a/tempest/lib/api_schema/response/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
new file mode 100644
index 0000000..ffcf488
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -0,0 +1,368 @@
+# Copyright 2018 ZTE Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+attachments = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'server_id': {'type': 'string', 'format': 'uuid'},
+ 'attachment_id': {'type': 'string', 'format': 'uuid'},
+ 'attached_at': parameter_types.date_time_or_null,
+ 'host_name': {'type': ['string', 'null']},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'device': {'type': ['string', 'null']},
+ 'id': {'type': 'string', 'format': 'uuid'}
+ },
+ 'additionalProperties': False,
+ 'required': ['server_id', 'attachment_id', 'host_name',
+ 'volume_id', 'device', 'id']
+ }
+}
+
+common_show_volume = {
+ 'type': 'object',
+ 'properties': {
+ 'migration_status': {'type': ['string', 'null']},
+ 'attachments': attachments,
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'os-vol-host-attr:host': {
+ 'type': ['string', 'null'], 'pattern': '.+@.+#.+'},
+ 'encrypted': {'type': 'boolean'},
+ 'updated_at': parameter_types.date_time_or_null,
+ 'replication_status': {'type': ['string', 'null']},
+ 'snapshot_id': parameter_types.uuid_or_null,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'size': {'type': 'integer'},
+ 'user_id': {'type': 'string', 'format': 'uuid'},
+ 'os-vol-tenant-attr:tenant_id': {'type': 'string',
+ 'format': 'uuid'},
+ 'os-vol-mig-status-attr:migstat': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'status': {'type': 'string'},
+ 'volume_image_metadata': {'type': ['object', 'null']},
+ 'description': {'type': ['string', 'null']},
+ 'multiattach': {'type': 'boolean'},
+ 'source_volid': parameter_types.uuid_or_null,
+ 'consistencygroup_id': parameter_types.uuid_or_null,
+ 'os-vol-mig-status-attr:name_id': parameter_types.uuid_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'bootable': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'volume_type': {'type': ['string', 'null']},
+ # TODO(zhufl): group_id is added in 3.13, we should move it to the
+ # 3.13 schema file when microversion is supported in volume interfaces
+ 'group_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): provider_id is added in 3.21, we should move it to the
+ # 3.21 schema file when microversion is supported in volume interfaces
+ 'provider_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): service_uuid and shared_targets are added in 3.48,
+ # we should move them to the 3.48 schema file when microversion
+ # is supported in volume interfaces.
+ 'service_uuid': parameter_types.uuid_or_null,
+ 'shared_targets': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['attachments', 'links', 'encrypted',
+ 'updated_at', 'replication_status', 'id',
+ 'size', 'user_id', 'availability_zone',
+ 'metadata', 'status', 'description',
+ 'multiattach', 'consistencygroup_id',
+ 'name', 'bootable', 'created_at',
+ 'volume_type', 'snapshot_id', 'source_volid']
+}
+
+list_volumes_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'links': parameter_types.links,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': ['string', 'null']},
+ # TODO(zhufl): count is added in 3.45, we should move
+ # it to the 3.45 schema file when microversion is
+ # supported in volume interfaces
+ # 'count': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['links', 'id', 'name']
+ }
+ },
+ 'volumes_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+show_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': common_show_volume
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+list_volumes_detail = copy.deepcopy(common_show_volume)
+# TODO(zhufl): count is added in 3.45, we should move it to the 3.45 schema
+# file when microversion is supported in volume interfaces
+# list_volumes_detail['properties'].update({'count': {'type': 'integer'}})
+list_volumes_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': list_volumes_detail
+ },
+ 'volumes_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+create_volume = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': {
+ 'type': 'object',
+ 'properties': {
+ 'migration_status': {'type': ['string', 'null']},
+ 'attachments': attachments,
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'encrypted': {'type': 'boolean'},
+ 'updated_at': parameter_types.date_time_or_null,
+ 'replication_status': {'type': ['string', 'null']},
+ 'snapshot_id': parameter_types.uuid_or_null,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'size': {'type': 'integer'},
+ 'user_id': {'type': 'string', 'format': 'uuid'},
+ 'metadata': {'type': 'object'},
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'multiattach': {'type': 'boolean'},
+ 'source_volid': parameter_types.uuid_or_null,
+ 'consistencygroup_id': parameter_types.uuid_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'bootable': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'volume_type': {'type': ['string', 'null']},
+ # TODO(zhufl): group_id is added in 3.13, we should move
+ # it to the 3.13 schema file when microversion is
+ # supported in volume interfaces.
+ 'group_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): provider_id is added in 3.21, we should
+ # move it to the 3.21 schema file when microversion is
+ # supported in volume interfaces
+ 'provider_id': parameter_types.uuid_or_null,
+ # TODO(zhufl): service_uuid and shared_targets are added
+ # in 3.48, we should move them to the 3.48 schema file
+ # when microversion is supported in volume interfaces.
+ 'service_uuid': parameter_types.uuid_or_null,
+ 'shared_targets': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['attachments', 'links', 'encrypted',
+ 'updated_at', 'replication_status', 'id',
+ 'size', 'user_id', 'availability_zone',
+ 'metadata', 'status', 'description',
+ 'multiattach', 'consistencygroup_id',
+ 'name', 'bootable', 'created_at',
+ 'volume_type', 'snapshot_id', 'source_volid']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+update_volume = copy.deepcopy(create_volume)
+update_volume.update({'status_code': [200]})
+
+delete_volume = {'status_code': [202]}
+
+show_volume_summary = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume-summary': {
+ 'type': 'object',
+ 'properties': {
+ 'total_size': {'type': 'integer'},
+ 'total_count': {'type': 'integer'},
+ # TODO(zhufl): metadata is added in 3.36, we should move
+ # it to the 3.36 schema file when microversion is
+ # supported in volume interfaces
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['total_size', 'total_count']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume-summary']
+ }
+}
+
+# TODO(zhufl): This is under discussion, so will be merged in a seperate patch.
+# https://bugs.launchpad.net/cinder/+bug/1880566
+# upload_volume = {
+# 'status_code': [202],
+# 'response_body': {
+# 'type': 'object',
+# 'properties': {
+# 'os-volume_upload_image': {
+# 'type': 'object',
+# 'properties': {
+# 'status': {'type': 'string'},
+# 'image_name': {'type': 'string'},
+# 'disk_format': {'type': 'string'},
+# 'container_format': {'type': 'string'},
+# 'is_public': {'type': 'boolean'},
+# 'visibility': {'type': 'string'},
+# 'protected': {'type': 'boolean'},
+# 'updated_at': parameter_types.date_time_or_null,
+# 'image_id': {'type': 'string', 'format': 'uuid'},
+# 'display_description': {'type': ['string', 'null']},
+# 'id': {'type': 'string', 'format': 'uuid'},
+# 'size': {'type': 'integer'},
+# 'volume_type': {
+# 'type': ['object', 'null'],
+# 'properties': {
+# 'created_at': parameter_types.date_time,
+# 'deleted': {'type': 'boolean'},
+# 'deleted_at': parameter_types.date_time_or_null,
+# 'description': {'type': ['string', 'null']},
+# 'extra_specs': {
+# 'type': 'object',
+# 'patternProperties': {
+# '^.+$': {'type': 'string'}
+# }
+# },
+# 'id': {'type': 'string', 'format': 'uuid'},
+# 'is_public': {'type': 'boolean'},
+# 'name': {'type': ['string', 'null']},
+# 'qos_specs_id': parameter_types.uuid_or_null,
+# 'updated_at': parameter_types.date_time_or_null
+# },
+# }
+# },
+# 'additionalProperties': False,
+# 'required': ['status', 'image_name', 'updated_at',
+# 'image_id',
+# 'display_description', 'id', 'size',
+# 'volume_type', 'disk_format',
+# 'container_format']
+# }
+# },
+# 'additionalProperties': False,
+# 'required': ['os-volume_upload_image']
+# }
+# }
+
+attach_volume = {'status_code': [202]}
+set_bootable_volume = {'status_code': [200]}
+detach_volume = {'status_code': [202]}
+reserve_volume = {'status_code': [202]}
+unreserve_volume = {'status_code': [202]}
+extend_volume = {'status_code': [202]}
+reset_volume_status = {'status_code': [202]}
+update_volume_readonly = {'status_code': [202]}
+force_delete_volume = {'status_code': [202]}
+retype_volume = {'status_code': [202]}
+force_detach_volume = {'status_code': [202]}
+
+create_volume_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+show_volume_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+update_volume_metadata = copy.deepcopy(show_volume_metadata)
+
+show_volume_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+update_volume_metadata_item = copy.deepcopy(show_volume_metadata_item)
+delete_volume_metadata_item = {'status_code': [200]}
+
+update_volume_image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {'metadata': {'type': 'object'}},
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+delete_volume_image_metadata = {'status_code': [200]}
+show_volume_image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'},
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+unmanage_volume = {'status_code': [202]}
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index f27e926..8b82391 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -207,10 +207,10 @@
# our newly created user has a role on the newly created project.
if self.identity_version == 'v3' and not role_assigned:
try:
- self.creds_client.create_user_role('Member')
+ self.creds_client.create_user_role('member')
except lib_exc.Conflict:
- LOG.warning('Member role already exists, ignoring conflict.')
- self.creds_client.assign_user_role(user, project, 'Member')
+ LOG.warning('member role already exists, ignoring conflict.')
+ self.creds_client.assign_user_role(user, project, 'member')
creds = self.creds_client.get_credentials(user, project, user_password)
return cred_provider.TestResources(creds)
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 431a0a0..0513e90 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -21,6 +21,7 @@
import jsonschema
from oslo_log import log as logging
+from oslo_log import versionutils
from oslo_serialization import jsonutils as json
import six
from six.moves import urllib
@@ -177,13 +178,27 @@
return self.auth_provider.credentials.tenant_name
@property
+ def project_id(self):
+ """The project id being used for requests
+
+ :rtype: string
+ :return: The project id being used for requests
+ """
+ return self.auth_provider.credentials.tenant_id
+
+ @property
def tenant_id(self):
"""The tenant/project id being used for requests
:rtype: string
:return: The tenant/project id being used for requests
"""
- return self.auth_provider.credentials.tenant_id
+ # NOTE(ralonsoh): this property should be deprecated, reference
+ # blueprint adopt-oslo-versioned-objects-for-db.
+ versionutils.report_deprecated_feature(
+ self.LOG, '"tenant_id" property is deprecated for removal, use '
+ '"project_id" instead')
+ return self.project_id
@property
def password(self):
@@ -899,12 +914,44 @@
raise exceptions.TimeoutException(message)
time.sleep(self.build_interval)
+ def wait_for_resource_activation(self, id):
+ """Waits for a resource to become active
+
+ This method will loop over is_resource_active until either
+ is_resource_active returns True or the build timeout is reached. This
+ depends on is_resource_active being implemented
+
+ :param str id: The id of the resource to check
+ :raises TimeoutException: If the build_timeout has elapsed and the
+ resource still hasn't been active
+ """
+ start_time = int(time.time())
+ while True:
+ if self.is_resource_active(id):
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ message = ('Failed to reach active state %(resource_type)s '
+ '%(id)s within the required time (%(timeout)s s).' %
+ {'resource_type': self.resource_type, 'id': id,
+ 'timeout': self.build_timeout})
+ caller = test_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
+ time.sleep(self.build_interval)
+
def is_resource_deleted(self, id):
"""Subclasses override with specific deletion detection."""
message = ('"%s" does not implement is_resource_deleted'
% self.__class__.__name__)
raise NotImplementedError(message)
+ def is_resource_active(self, id):
+ """Subclasses override with specific active detection."""
+ message = ('"%s" does not implement is_resource_active'
+ % self.__class__.__name__)
+ raise NotImplementedError(message)
+
@property
def resource_type(self):
"""Returns the primary type of resource this client works with."""
diff --git a/tempest/lib/common/thread.py b/tempest/lib/common/thread.py
index 510fc36..b47d40d 100644
--- a/tempest/lib/common/thread.py
+++ b/tempest/lib/common/thread.py
@@ -13,10 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-# This make disable relative module import
-from __future__ import absolute_import
-
-
import six
if six.PY2:
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 8ac1d38..71fed02 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -11,7 +11,6 @@
# under the License.
import functools
-import re
import sys
import netaddr
@@ -134,9 +133,8 @@
This method will not unmount the config drive, so unmount_config_drive
must be used for cleanup.
"""
- cmd_blkid = 'blkid | grep -i config-2'
- result = self.exec_command(cmd_blkid)
- dev_name = re.match('([^:]+)', result).group()
+ cmd_blkid = 'blkid -L config-2 -o device'
+ dev_name = self.exec_command(cmd_blkid).strip()
try:
self.exec_command('sudo mount %s /mnt' % dev_name)
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index 808e0fb..ebe2d61 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -124,7 +124,7 @@
def decorator(f):
f = testtools.testcase.attr('id-%s' % id)(f)
if f.__doc__:
- f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
+ f.__doc__ = 'Test idempotent id: %s\n\n%s' % (id, f.__doc__)
else:
f.__doc__ = 'Test idempotent id: %s' % id
return f
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 3ceecda..6723516 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -206,7 +206,10 @@
def action(self, server_id, action_name,
schema=schema.server_actions_common_schema,
**kwargs):
- post_body = json.dumps({action_name: kwargs})
+ if 'body' in kwargs:
+ post_body = json.dumps(kwargs['body'])
+ else:
+ post_body = json.dumps({action_name: kwargs})
resp, body = self.post('servers/%s/action' % server_id,
post_body)
if body:
@@ -608,6 +611,15 @@
API reference:
https://docs.openstack.org/api-ref/compute/#unshelve-restore-shelved-server-unshelve-action
"""
+ # NOTE(gmann): pass None as request body if nothing is requested.
+ # Nova started the request body check since 2.77 microversion and only
+ # accept AZ or None as valid req body and reject the empty dict {}.
+ # Before 2.77 microverison anything is valid body as Nova does not
+ # check the request body but as per api-ref None is valid request
+ # body to pass so we do not need to check the requested microversion
+ # here and always default req body to None.
+ if not kwargs:
+ kwargs['body'] = {'unshelve': None}
return self.action(server_id, 'unshelve', **kwargs)
def shelve_offload_server(self, server_id, **kwargs):
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 90778da..4713cce 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -128,6 +128,15 @@
return True
return False
+ def is_resource_active(self, id):
+ try:
+ image = self.show_image(id)
+ if image['status'] != 'active':
+ return False
+ except lib_exc.NotFound:
+ return False
+ return True
+
@property
def resource_type(self):
"""Returns the primary type of resource this client works with."""
@@ -152,6 +161,83 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def stage_image_file(self, image_id, data):
+ """Upload binary image data to staging area.
+
+ For a full list of available parameters, please refer to the official
+ API reference (stage API:
+ https://docs.openstack.org/api-ref/image/v2/#interoperable-image-import
+ """
+ url = 'images/%s/stage' % image_id
+
+ # We are going to do chunked transfer, so split the input data
+ # info fixed-sized chunks.
+ headers = {'Content-Type': 'application/octet-stream'}
+ data = iter(functools.partial(data.read, CHUNKSIZE), b'')
+
+ resp, body = self.request('PUT', url, headers=headers,
+ body=data, chunked=True)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def info_import(self):
+ """Return information about server-supported import methods."""
+ url = 'info/import'
+ resp, body = self.get(url)
+
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def info_stores(self):
+ """Return information about server-supported stores."""
+ url = 'info/stores'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def image_import(self, image_id, method='glance-direct',
+ all_stores_must_succeed=None, all_stores=True,
+ stores=None, image_uri=None):
+ """Import data from staging area to glance store.
+
+ For a full list of available parameters, please refer to the official
+ API reference (stage API:
+ https://docs.openstack.org/api-ref/image/v2/#interoperable-image-import
+
+ :param method: The import method (i.e. glance-direct) to use
+ :param all_stores_must_succeed: Boolean indicating if all store imports
+ must succeed for the import to be
+ considered successful. Must be None if
+ server does not support multistore.
+ :param all_stores: Boolean indicating if image should be imported to
+ all available stores (incompatible with stores)
+ :param stores: A list of destination store names for the import. Must
+ be None if server does not support multistore.
+ :param image_uri: A URL to be used with the web-download method
+ """
+ url = 'images/%s/import' % image_id
+ data = {
+ "method": {
+ "name": method
+ },
+ }
+ if stores is not None:
+ data["stores"] = stores
+ else:
+ data["all_stores"] = all_stores
+
+ if all_stores_must_succeed is not None:
+ data['all_stores_must_succeed'] = all_stores_must_succeed
+ if image_uri:
+ data['method']['uri'] = image_uri
+ data = json.dumps(data)
+ headers = {'Content-Type': 'application/json'}
+ resp, _ = self.post(url, data, headers=headers)
+
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp)
+
def show_image_file(self, image_id):
"""Download binary image data.
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index 997d201..96cc65d 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -12,11 +12,33 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
+
from tempest.lib.services.network import base
+def _warning_deprecate_tenant_id(func):
+ @functools.wraps(func)
+ def inner(*args, **kwargs):
+ _self = args[0]
+ # check length of arg to know whether 'tenant_id' is passed as
+ # positional arg or kwargs.
+ if len(args) < 2:
+ if 'tenant_id' in kwargs:
+ _self.LOG.warning(
+ 'positional arg name "tenant_id" is deprecated, for '
+ 'removal, please start using "project_id" instead')
+ elif 'project_id' in kwargs:
+ # fallback to deprecated name till deprecation phase.
+ kwargs['tenant_id'] = kwargs.pop('project_id')
+
+ return func(*args, **kwargs)
+ return inner
+
+
class QuotasClient(base.BaseNetworkClient):
+ @_warning_deprecate_tenant_id
def update_quotas(self, tenant_id, **kwargs):
"""Update quota for a project.
@@ -28,12 +50,14 @@
uri = '/quotas/%s' % tenant_id
return self.update_resource(uri, put_body)
+ @_warning_deprecate_tenant_id
def reset_quotas(self, tenant_id): # noqa
# NOTE: This noqa is for passing T111 check and we cannot rename
# to keep backwards compatibility.
uri = '/quotas/%s' % tenant_id
return self.delete_resource(uri)
+ @_warning_deprecate_tenant_id
def show_quotas(self, tenant_id, **fields):
"""Show quota for a project.
@@ -54,11 +78,13 @@
uri = '/quotas'
return self.list_resources(uri, **filters)
+ @_warning_deprecate_tenant_id
def show_default_quotas(self, tenant_id):
"""List default quotas for a project."""
uri = '/quotas/%s/default' % tenant_id
return self.show_resource(uri)
+ @_warning_deprecate_tenant_id
def show_quota_details(self, tenant_id):
"""Show quota details for a project."""
uri = '/quotas/%s/details.json' % tenant_id
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 970471e..1df45fa 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import backups as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -34,7 +35,7 @@
post_body = json.dumps({'backup': kwargs})
resp, body = self.post('backups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def update_backup(self, backup_id, **kwargs):
@@ -47,7 +48,7 @@
put_body = json.dumps({'backup': kwargs})
resp, body = self.put('backups/%s' % backup_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def restore_backup(self, backup_id, **kwargs):
@@ -60,13 +61,13 @@
post_body = json.dumps({'restore': kwargs})
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.restore_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % backup_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
@@ -74,7 +75,7 @@
url = "backups/%s" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def list_backups(self, detail=False, **params):
@@ -86,13 +87,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail
"""
url = "backups"
+ list_backups_schema = schema.list_backups_no_detail
if detail:
url += "/detail"
+ list_backups_schema = schema.list_backups_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_backups_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def export_backup(self, backup_id):
@@ -100,7 +103,7 @@
url = "backups/%s/export_record" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.export_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def import_backup(self, **kwargs):
@@ -113,14 +116,14 @@
post_body = json.dumps({'backup-record': kwargs})
resp, body = self.post("backups/import_record", post_body)
body = json.loads(body)
- self.expected_success(201, resp.status)
+ self.validate_response(schema.import_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_backup_status(self, backup_id, status):
"""Reset the specified backup's status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('backups/%s/action' % backup_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_backup_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index e425a3f..4051c06 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import group_snapshots as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -34,7 +35,7 @@
post_body = json.dumps({'group_snapshot': kwargs})
resp, body = self.post('group_snapshots', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group_snapshot(self, group_snapshot_id):
@@ -44,7 +45,7 @@
https://docs.openstack.org/api-ref/block-storage/v3/#delete-group-snapshot
"""
resp, body = self.delete('group_snapshots/%s' % group_snapshot_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group_snapshot(self, group_snapshot_id):
@@ -56,7 +57,7 @@
url = "group_snapshots/%s" % str(group_snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def list_group_snapshots(self, detail=False, **params):
@@ -67,13 +68,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/#list-group-snapshots-with-details
"""
url = "group_snapshots"
+ list_group_snapshots = schema.list_group_snapshots_no_detail
if detail:
url += "/detail"
+ list_group_snapshots = schema.list_group_snapshots_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_group_snapshots, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_group_snapshot_status(self, group_snapshot_id, status_to_set):
@@ -85,7 +88,7 @@
post_body = json.dumps({'reset_status': {'status': status_to_set}})
resp, body = self.post('group_snapshots/%s/action' % group_snapshot_id,
post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_group_snapshot_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index e0bf5e2..1dcd508 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -73,7 +73,7 @@
url = 'group_types/default'
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_default_group_type, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group_type(self, group_type_id):
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index ffae232..3d8523d 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import groups as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -23,6 +24,7 @@
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
+ api_version = 'v3'
def create_group(self, **kwargs):
"""Creates a group.
@@ -35,7 +37,7 @@
post_body = json.dumps({'group': kwargs})
resp, body = self.post('groups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id, delete_volumes=True):
@@ -49,7 +51,7 @@
post_body = json.dumps({'delete': post_body})
resp, body = self.post('groups/%s/action' % group_id,
post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
@@ -62,7 +64,7 @@
url = "groups/%s" % str(group_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group, resp, body)
return rest_client.ResponseBody(resp, body)
def list_groups(self, detail=False, **params):
@@ -74,13 +76,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/#list-groups-with-details
"""
url = "groups"
+ schema_list_groups = schema.list_groups_no_detail
if detail:
url += "/detail"
+ schema_list_groups = schema.list_groups_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema_list_groups, resp, body)
return rest_client.ResponseBody(resp, body)
def create_group_from_source(self, **kwargs):
@@ -93,7 +97,7 @@
post_body = json.dumps({'create-from-src': kwargs})
resp, body = self.post('groups/action', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_from_source, resp, body)
return rest_client.ResponseBody(resp, body)
def update_group(self, group_id, **kwargs):
@@ -105,7 +109,7 @@
"""
put_body = json.dumps({'group': kwargs})
resp, body = self.put('groups/%s' % group_id, put_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_group, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_group_status(self, group_id, status_to_set):
@@ -116,7 +120,7 @@
"""
post_body = json.dumps({'reset_status': {'status': status_to_set}})
resp, body = self.post('groups/%s/action' % group_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_group_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index aa6c867..4ac4112 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -54,8 +54,9 @@
"""
version_url = urljoin(self._get_base_version_url(), version + '/')
- resp, body = self.raw_request(version_url, 'GET',
- {'X-Auth-Token': self.token})
+ headers = self.get_headers()
+ headers['X-Auth-Token'] = self.token
+ resp, body = self.raw_request(version_url, 'GET', headers=headers)
self._error_checker(resp, body)
body = json.loads(body)
self.validate_response(schema.volume_api_version_details, resp, body)
diff --git a/tempest/lib/services/volume/v3/volume_manage_client.py b/tempest/lib/services/volume/v3/volume_manage_client.py
index 85b1b82..f6642c5 100644
--- a/tempest/lib/services/volume/v3/volume_manage_client.py
+++ b/tempest/lib/services/volume/v3/volume_manage_client.py
@@ -15,6 +15,7 @@
from oslo_serialization import jsonutils as json
+from tempest.lib.api_schema.response.volume import manage_volume as schema
from tempest.lib.common import rest_client
@@ -30,6 +31,6 @@
"""
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('os-volume-manage', post_body)
- self.expected_success(202, resp.status)
body = json.loads(body)
+ self.validate_response(schema.manage_volume, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 4fb6d2e..b8535d8 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -17,6 +17,7 @@
import six
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import volumes as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -55,14 +56,16 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes
"""
url = 'volumes'
+ list_schema = schema.list_volumes_no_detail
if detail:
+ list_schema = schema.list_volumes_with_detail
url += '/detail'
if params:
url += '?%s' % self._prepare_params(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def migrate_volume(self, volume_id, **kwargs):
@@ -83,7 +86,7 @@
url = "volumes/%s" % volume_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume(self, **kwargs):
@@ -96,7 +99,7 @@
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('volumes', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume(self, volume_id, **kwargs):
@@ -109,7 +112,7 @@
put_body = json.dumps({'volume': kwargs})
resp, body = self.put('volumes/%s' % volume_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume(self, volume_id, **params):
@@ -123,7 +126,7 @@
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.delete(url)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_summary(self, **params):
@@ -138,7 +141,7 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_summary, resp, body)
return rest_client.ResponseBody(resp, body)
def upload_volume(self, volume_id, **kwargs):
@@ -152,6 +155,10 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
body = json.loads(body)
+ # TODO(zhufl): This is under discussion, so will be merged
+ # in a seperate patch.
+ # https://bugs.launchpad.net/cinder/+bug/1880566
+ # self.validate_response(schema.upload_volume, resp, body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -165,7 +172,7 @@
post_body = json.dumps({'os-attach': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.attach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
@@ -178,7 +185,7 @@
post_body = json.dumps({'os-set_bootable': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.set_bootable_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def detach_volume(self, volume_id):
@@ -186,7 +193,7 @@
post_body = json.dumps({'os-detach': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def reserve_volume(self, volume_id):
@@ -194,7 +201,7 @@
post_body = json.dumps({'os-reserve': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reserve_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def unreserve_volume(self, volume_id):
@@ -202,7 +209,7 @@
post_body = json.dumps({'os-unreserve': {}})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unreserve_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
@@ -219,7 +226,7 @@
if volume["volume"]["status"] == "error_deleting":
raise lib_exc.DeleteErrorException(
"Volume %s failed to delete and is in error_deleting status" %
- volume['id'])
+ volume['volume']['id'])
return False
@property
@@ -237,7 +244,7 @@
post_body = json.dumps({'os-extend': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.extend_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_volume_status(self, volume_id, **kwargs):
@@ -249,7 +256,7 @@
"""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_volume_status, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_readonly(self, volume_id, **kwargs):
@@ -262,14 +269,14 @@
post_body = json.dumps({'os-update_readonly_flag': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_volume_readonly, resp, body)
return rest_client.ResponseBody(resp, body)
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.force_delete_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
@@ -283,7 +290,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.post(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.create_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_metadata(self, volume_id):
@@ -291,7 +298,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
@@ -305,7 +312,7 @@
url = "volumes/%s/metadata" % volume_id
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_metadata_item(self, volume_id, id):
@@ -313,7 +320,7 @@
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_metadata_item(self, volume_id, id, meta_item):
@@ -322,14 +329,14 @@
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.delete(url)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.delete_volume_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
@@ -341,7 +348,7 @@
"""
post_body = json.dumps({'os-retype': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.retype_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def force_detach_volume(self, volume_id, **kwargs):
@@ -354,7 +361,7 @@
post_body = json.dumps({'os-force_detach': kwargs})
url = 'volumes/%s/action' % volume_id
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.force_detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_image_metadata(self, volume_id, **kwargs):
@@ -368,7 +375,7 @@
url = "volumes/%s/action" % (volume_id)
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_image_metadata(self, volume_id, key_name):
@@ -376,7 +383,7 @@
post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
url = "volumes/%s/action" % (volume_id)
resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.delete_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_image_metadata(self, volume_id):
@@ -385,7 +392,7 @@
url = "volumes/%s/action" % volume_id
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_volume_image_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def unmanage_volume(self, volume_id):
@@ -397,5 +404,5 @@
"""
post_body = json.dumps({'os-unmanage': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unmanage_volume, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/manager.py b/tempest/manager.py
index e3174d4..b485ef2 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -33,7 +33,7 @@
def __init__(self, credentials, scope='project'):
msg = ("tempest.manager.Manager is not a stable interface and as such "
- "it should not imported directly. It will be removed as "
+ "it should not be imported directly. It will be removed as "
"soon as the client manager becomes available in tempest.lib.")
LOG.warning(msg)
dscv = CONF.identity.disable_ssl_certificate_validation
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index efdfe8e..ff860d5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import subprocess
import netaddr
@@ -90,10 +91,32 @@
volume_microversion=self.volume_request_microversion,
placement_microversion=self.placement_request_microversion))
+ def setup_compute_client(cls):
+ """Compute and Compute security groups client"""
+ cls.compute_images_client = cls.os_primary.compute_images_client
+ cls.keypairs_client = cls.os_primary.keypairs_client
+ cls.compute_security_groups_client = (
+ cls.os_primary.compute_security_groups_client)
+ cls.compute_security_group_rules_client = (
+ cls.os_primary.compute_security_group_rules_client)
+ cls.servers_client = cls.os_primary.servers_client
+ cls.interface_client = cls.os_primary.interfaces_client
+
+ def setup_network_client(cls):
+ """Neutron network client"""
+ cls.networks_client = cls.os_primary.networks_client
+ cls.ports_client = cls.os_primary.ports_client
+ cls.routers_client = cls.os_primary.routers_client
+ cls.subnets_client = cls.os_primary.subnets_client
+ cls.floating_ips_client = cls.os_primary.floating_ips_client
+ cls.security_groups_client = cls.os_primary.security_groups_client
+ cls.security_group_rules_client = (
+ cls.os_primary.security_group_rules_client)
+
@classmethod
def setup_clients(cls):
+ """This setup the service clients for the tests"""
super(ScenarioTest, cls).setup_clients()
- # Clients (in alphabetical order)
cls.flavors_client = cls.os_primary.flavors_client
cls.compute_floating_ips_client = (
cls.os_primary.compute_floating_ips_client)
@@ -107,37 +130,20 @@
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
- # Compute image client
- cls.compute_images_client = cls.os_primary.compute_images_client
- cls.keypairs_client = cls.os_primary.keypairs_client
- # Nova security groups client
- cls.compute_security_groups_client = (
- cls.os_primary.compute_security_groups_client)
- cls.compute_security_group_rules_client = (
- cls.os_primary.compute_security_group_rules_client)
- cls.servers_client = cls.os_primary.servers_client
- cls.interface_client = cls.os_primary.interfaces_client
- # Neutron network client
- cls.networks_client = cls.os_primary.networks_client
- cls.ports_client = cls.os_primary.ports_client
- cls.routers_client = cls.os_primary.routers_client
- cls.subnets_client = cls.os_primary.subnets_client
- cls.floating_ips_client = cls.os_primary.floating_ips_client
- cls.security_groups_client = cls.os_primary.security_groups_client
- cls.security_group_rules_client = (
- cls.os_primary.security_group_rules_client)
- # Use the latest available volume clients
+
+ cls.setup_compute_client(cls)
+ cls.setup_network_client(cls)
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
cls.snapshots_client = cls.os_primary.snapshots_client_latest
cls.backups_client = cls.os_primary.backups_client_latest
# ## Test functions library
- #
# The create_[resource] functions only return body and discard the
# resp part which is not used in scenario tests
def create_port(self, network_id, client=None, **kwargs):
+ """Creates port"""
if not client:
client = self.ports_client
name = data_utils.rand_name(self.__class__.__name__)
@@ -155,6 +161,13 @@
return port
def create_keypair(self, client=None):
+ """Creates keypair
+
+ Keypair is a public key of OpenSSH key pair used for accessing
+ and create servers
+ Keypair can also be created by a private key for the same purpose
+ Here, the keys are randomly generated[public/private]
+ """
if not client:
client = self.keypairs_client
name = data_utils.rand_name(self.__class__.__name__)
@@ -294,6 +307,13 @@
def create_volume(self, size=None, name=None, snapshot_id=None,
imageRef=None, volume_type=None):
+ """Creates volume
+
+ This wrapper utility creates volume and waits for volume to be
+ in 'available' state.
+ This method returns the volume's full representation by GET request.
+ """
+
if size is None:
size = CONF.volume.volume_size
if imageRef:
@@ -333,6 +353,11 @@
def create_backup(self, volume_id, name=None, description=None,
force=False, snapshot_id=None, incremental=False,
container=None):
+ """Creates backup
+
+ This wrapper utility creates backup and waits for backup to be
+ in 'available' state.
+ """
name = name or data_utils.rand_name(
self.__class__.__name__ + "-backup")
@@ -350,6 +375,12 @@
return backup
def restore_backup(self, backup_id):
+ """Restore backup
+
+ This wrapper utility restores backup and waits for backup to be
+ in 'available' state.
+ """
+
restore = self.backups_client.restore_backup(backup_id)['restore']
self.addCleanup(self.volumes_client.delete_volume,
restore['volume_id'])
@@ -361,8 +392,31 @@
self.assertEqual(backup_id, restore['backup_id'])
return restore
+ def rebuild_server(self, server_id, image=None,
+ preserve_ephemeral=False, wait=True,
+ rebuild_kwargs=None):
+ if image is None:
+ image = CONF.compute.image_ref
+ rebuild_kwargs = rebuild_kwargs or {}
+ LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
+ server_id, image, preserve_ephemeral)
+ self.servers_client.rebuild_server(
+ server_id=server_id,
+ image_ref=image,
+ preserve_ephemeral=preserve_ephemeral,
+ **rebuild_kwargs)
+ if wait:
+ waiters.wait_for_server_status(self.servers_client,
+ server_id, 'ACTIVE')
+
def create_volume_snapshot(self, volume_id, name=None, description=None,
metadata=None, force=False):
+ """Creates volume
+
+ This wrapper utility creates volume snapshot and waits for backup
+ to be in 'available' state.
+ """
+
name = name or data_utils.rand_name(
self.__class__.__name__ + '-snapshot')
snapshot = self.snapshots_client.create_snapshot(
@@ -371,6 +425,7 @@
display_name=name,
description=description,
metadata=metadata)['snapshot']
+
self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
snapshot['id'])
self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
@@ -400,6 +455,23 @@
admin_volume_type_client.delete_volume_type(volume_type['id'])
def create_volume_type(self, client=None, name=None, backend_name=None):
+ """Creates volume type
+
+ In a multiple-storage back-end configuration,
+ each back end has a name (volume_backend_name).
+ The name of the back end is declared as an extra-specification
+ of a volume type (such as, volume_backend_name=LVM).
+ When a volume is created, the scheduler chooses an
+ appropriate back end to handle the request, according
+ to the volume type specified by the user.
+ The scheduler uses volume types to explicitly create volumes on
+ specific back ends.
+
+ Before using volume type, a volume type has to be declared
+ to Block Storage. In addition to that, an extra-specification
+ has to be created to link the volume type to a back end name.
+ """
+
if not client:
client = self.os_admin.volume_types_client_latest
if not name:
@@ -415,6 +487,7 @@
volume_type = client.create_volume_type(
name=randomized_name, extra_specs=extra_specs)['volume_type']
+ self.assertIn('id', volume_type)
self.addCleanup(self._cleanup_volume_type, volume_type)
return volume_type
@@ -455,7 +528,7 @@
return rules
def _create_security_group(self):
- # Create security group
+ """Create security group and add rules to security group"""
sg_name = data_utils.rand_name(self.__class__.__name__)
sg_desc = sg_name + " description"
secgroup = self.compute_security_groups_client.create_security_group(
@@ -469,7 +542,6 @@
# Add rules to the security group
self._create_loginable_secgroup_rule(secgroup['id'])
-
return secgroup
def get_remote_client(self, ip_address, username=None, private_key=None,
@@ -501,68 +573,60 @@
linux_client.validate_authentication()
return linux_client
- def _image_create(self, name, fmt, path,
- disk_format=None, properties=None):
- if properties is None:
- properties = {}
- name = data_utils.rand_name('%s-' % name)
- params = {
- 'name': name,
- 'container_format': fmt,
- 'disk_format': disk_format or fmt,
- }
- if CONF.image_feature_enabled.api_v1:
- params['is_public'] = 'False'
- params['properties'] = properties
- params = {'headers': common_image.image_meta_to_headers(**params)}
- else:
- params['visibility'] = 'private'
- # Additional properties are flattened out in the v2 API.
- params.update(properties)
- body = self.image_client.create_image(**params)
- image = body['image'] if 'image' in body else body
- self.addCleanup(self.image_client.delete_image, image['id'])
- self.assertEqual("queued", image['status'])
- with open(path, 'rb') as image_file:
- if CONF.image_feature_enabled.api_v1:
- self.image_client.update_image(image['id'], data=image_file)
- else:
- self.image_client.store_image_file(image['id'], image_file)
- return image['id']
-
- def glance_image_create(self):
- img_path = CONF.scenario.img_dir + "/" + CONF.scenario.img_file
- aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
- ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
- ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
+ def image_create(self, name='scenario-img'):
+ img_path = CONF.scenario.img_file
+ if not os.path.exists(img_path):
+ # TODO(kopecmartin): replace LOG.warning for rasing
+ # InvalidConfiguration exception after tempest 25.0.0 is
+ # released - there will be one release which accepts both
+ # behaviors in order to avoid many failures across CIs and etc.
+ LOG.warning(
+ 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
+ 'a full path for the image. CONF.scenario.img_dir was '
+ 'deprecated and will be removed in the next release. Till '
+ 'Tempest 25.0.0, old behavior is maintained and keep working '
+ 'but starting Tempest 26.0.0, you need to specify the full '
+ 'path in CONF.scenario.img_file config option.')
+ img_path = os.path.join(CONF.scenario.img_dir, img_path)
img_container_format = CONF.scenario.img_container_format
img_disk_format = CONF.scenario.img_disk_format
img_properties = CONF.scenario.img_properties
LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
- "properties: %s, ami: %s, ari: %s, aki: %s",
+ "properties: %s",
img_path, img_container_format, img_disk_format,
- img_properties, ami_img_path, ari_img_path, aki_img_path)
- try:
- image = self._image_create('scenario-img',
- img_container_format,
- img_path,
- disk_format=img_disk_format,
- properties=img_properties)
- except IOError:
- LOG.warning(
- "A(n) %s image was not found. Retrying with uec image.",
- img_disk_format)
- kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
- ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
- properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
- image = self._image_create('scenario-ami', 'ami',
- path=ami_img_path,
- properties=properties)
- LOG.debug("image:%s", image)
-
- return image
+ img_properties)
+ if img_properties is None:
+ img_properties = {}
+ name = data_utils.rand_name('%s-' % name)
+ params = {
+ 'name': name,
+ 'container_format': img_container_format,
+ 'disk_format': img_disk_format or img_container_format,
+ }
+ if CONF.image_feature_enabled.api_v1:
+ params['is_public'] = 'False'
+ if img_properties:
+ params['properties'] = img_properties
+ params = {'headers': common_image.image_meta_to_headers(**params)}
+ else:
+ params['visibility'] = 'private'
+ # Additional properties are flattened out in the v2 API.
+ if img_properties:
+ params.update(img_properties)
+ body = self.image_client.create_image(**params)
+ image = body['image'] if 'image' in body else body
+ self.addCleanup(self.image_client.delete_image, image['id'])
+ self.assertEqual("queued", image['status'])
+ with open(img_path, 'rb') as image_file:
+ if CONF.image_feature_enabled.api_v1:
+ self.image_client.update_image(image['id'], data=image_file)
+ else:
+ self.image_client.store_image_file(image['id'], image_file)
+ LOG.debug("image:%s", image['id'])
+ return image['id']
def _log_console_output(self, servers=None, client=None):
+ """Console log output"""
if not CONF.compute_feature_enabled.console_output:
LOG.debug('Console output not supported, cannot log')
return
@@ -581,11 +645,12 @@
"for the console log", server['id'])
def _log_net_info(self, exc):
- # network debug is called as part of ssh init
+ """network debug is called as part of ssh init"""
if not isinstance(exc, lib_exc.SSHTimeout):
LOG.debug('Network information on a devstack host')
def create_server_snapshot(self, server, name=None):
+ """Creates server snapshot"""
# Glance client
_image_client = self.image_client
# Compute client
@@ -603,7 +668,7 @@
_image_client.delete_image, image_id)
if CONF.image_feature_enabled.api_v1:
- # In glance v1 the additional properties are stored in the headers.
+ # In glance v1 the additional properties are stored in the headers
resp = _image_client.check_image(image_id)
snapshot_image = common_image.get_image_meta_from_headers(resp)
image_props = snapshot_image.get('properties', {})
@@ -633,22 +698,34 @@
return snapshot_image
def nova_volume_attach(self, server, volume_to_attach):
+ """Compute volume attach
+
+ This utility attaches volume from compute and waits for the
+ volume status to be 'in-use' state.
+ """
volume = self.servers_client.attach_volume(
server['id'], volumeId=volume_to_attach['id'])['volumeAttachment']
self.assertEqual(volume_to_attach['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
-
# Return the updated volume after the attachment
return self.volumes_client.show_volume(volume['id'])['volume']
def nova_volume_detach(self, server, volume):
+ """Compute volume detach
+
+ This utility detaches volume from compute and check whether the
+ volume status is 'available' state, and if not, an exception
+ will be thrown.
+ """
self.servers_client.detach_volume(server['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
def ping_ip_address(self, ip_address, should_succeed=True,
ping_timeout=None, mtu=None, server=None):
+ """ping ip address"""
timeout = ping_timeout or CONF.validation.ping_timeout
cmd = ['ping', '-c1', '-w1']
@@ -709,6 +786,7 @@
:raises: AssertError if the result of the connectivity check does
not match the value of the should_connect param
"""
+
LOG.debug('checking network connections to IP %s with user: %s',
ip_address, username)
if should_connect:
@@ -732,7 +810,7 @@
LOG.exception(extra_msg)
raise
- def create_floating_ip(self, thing, pool_name=None):
+ def create_floating_ip(self, server, pool_name=None):
"""Create a floating IP and associates to a server on Nova"""
if not pool_name:
@@ -743,11 +821,17 @@
self.compute_floating_ips_client.delete_floating_ip,
floating_ip['id'])
self.compute_floating_ips_client.associate_floating_ip_to_server(
- floating_ip['ip'], thing['id'])
+ floating_ip['ip'], server['id'])
return floating_ip
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
private_key=None, server=None):
+ """Creates timestamp
+
+ This wrapper utility does ssh, creates timestamp and returns the
+ created timestamp.
+ """
+
ssh_client = self.get_remote_client(ip_address,
private_key=private_key,
server=server)
@@ -765,6 +849,11 @@
def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
private_key=None, server=None):
+ """Returns timestamp
+
+ This wrapper utility does ssh and returns the timestamp.
+ """
+
ssh_client = self.get_remote_client(ip_address,
private_key=private_key,
server=server)
@@ -782,6 +871,7 @@
Based on the configuration we're in, return a correct ip
address for validating that a guest is up.
"""
+
if CONF.validation.connect_method == 'floating':
# The tests calling this method don't have a floating IP
# and can't make use of the validation resources. So the
@@ -807,6 +897,8 @@
@classmethod
def get_host_for_server(cls, server_id):
+ """Gets host of server"""
+
server_details = cls.os_admin.servers_client.show_server(server_id)
return server_details['server']['OS-EXT-SRV-ATTR:host']
@@ -825,6 +917,12 @@
security_group=None,
delete_on_termination=False,
name=None):
+ """Boot instance from resource
+
+ This wrapper utility boots instance from resource with block device
+ mapping with source info passed in arguments
+ """
+
create_kwargs = dict()
if keypair:
create_kwargs['key_name'] = keypair['name']
@@ -841,6 +939,7 @@
return self.create_server(image_id='', **create_kwargs)
def create_volume_from_image(self):
+ """Create volume from image"""
img_uuid = CONF.compute.image_ref
vol_name = data_utils.rand_name(
self.__class__.__name__ + '-volume-origin')
@@ -868,15 +967,15 @@
raise cls.skipException('Neutron not available')
def _create_network(self, networks_client=None,
- tenant_id=None,
+ project_id=None,
namestart='network-smoke-',
port_security_enabled=True, **net_dict):
if not networks_client:
networks_client = self.networks_client
- if not tenant_id:
- tenant_id = networks_client.tenant_id
+ if not project_id:
+ project_id = networks_client.project_id
name = data_utils.rand_name(namestart)
- network_kwargs = dict(name=name, tenant_id=tenant_id)
+ network_kwargs = dict(name=name, project_id=project_id)
if net_dict:
network_kwargs.update(net_dict)
# Neutron disables port security by default so we have to check the
@@ -896,19 +995,28 @@
namestart='subnet-smoke', **kwargs):
"""Create a subnet for the given network
+ This utility creates subnet for the given network
within the cidr block configured for tenant networks.
+
+ :param **kwargs:
+ See extra parameters below
+
+ :Keyword Arguments:
+
+ * *ip_version = ip version of the given network,
"""
+
if not subnets_client:
subnets_client = self.subnets_client
- def cidr_in_use(cidr, tenant_id):
+ def cidr_in_use(cidr, project_id):
"""Check cidr existence
:returns: True if subnet with cidr already exist in tenant
False else
"""
cidr_in_use = self.os_admin.subnets_client.list_subnets(
- tenant_id=tenant_id, cidr=cidr)['subnets']
+ project_id=project_id, cidr=cidr)['subnets']
return len(cidr_in_use) != 0
ip_version = kwargs.pop('ip_version', 4)
@@ -927,13 +1035,13 @@
# blocks until an unallocated block is found.
for subnet_cidr in tenant_cidr.subnet(num_bits):
str_cidr = str(subnet_cidr)
- if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
+ if cidr_in_use(str_cidr, project_id=network['project_id']):
continue
subnet = dict(
name=data_utils.rand_name(namestart),
network_id=network['id'],
- tenant_id=network['tenant_id'],
+ project_id=network['project_id'],
cidr=str_cidr,
ip_version=ip_version,
**kwargs
@@ -1000,22 +1108,23 @@
"Unable to get network by name: %s" % network_name)
return net[0]
- def create_floating_ip(self, thing, external_network_id=None,
+ def create_floating_ip(self, server, external_network_id=None,
port_id=None, client=None):
"""Create a floating IP and associates to a resource/port on Neutron"""
+
if not external_network_id:
external_network_id = CONF.network.public_network_id
if not client:
client = self.floating_ips_client
if not port_id:
- port_id, ip4 = self._get_server_port_id_and_ip4(thing)
+ port_id, ip4 = self._get_server_port_id_and_ip4(server)
else:
ip4 = None
kwargs = {
'floating_network_id': external_network_id,
'port_id': port_id,
- 'tenant_id': thing['tenant_id'],
+ 'tenant_id': server.get('project_id') or server['tenant_id'],
'fixed_ip_address': ip4,
}
if CONF.network.subnet_id:
@@ -1035,12 +1144,16 @@
:param status: target status
:raises: AssertionError if status doesn't match
"""
+
floatingip_id = floating_ip['id']
def refresh():
- result = (self.floating_ips_client.
- show_floatingip(floatingip_id)['floatingip'])
- return status == result['status']
+ floating_ip = (self.floating_ips_client.
+ show_floatingip(floatingip_id)['floatingip'])
+ if status == floating_ip['status']:
+ LOG.info("FloatingIP: {fp} is at status: {st}"
+ .format(fp=floating_ip, st=status))
+ return status == floating_ip['status']
if not test_utils.call_until_true(refresh,
CONF.network.build_timeout,
@@ -1052,14 +1165,13 @@
"failed to reach status: {st}"
.format(fp=floating_ip, cst=floating_ip['status'],
st=status))
- LOG.info("FloatingIP: {fp} is at status: {st}"
- .format(fp=floating_ip, st=status))
def check_tenant_network_connectivity(self, server,
username,
private_key,
should_connect=True,
servers_for_debug=None):
+ """Checks tenant network connectivity"""
if not CONF.network.project_networks_reachable:
msg = 'Tenant networks not configured to be reachable.'
LOG.info(msg)
@@ -1092,6 +1204,7 @@
:returns: True, if the connection succeeded and it was expected to
succeed. False otherwise.
"""
+
method_name = '%s_check' % protocol
connectivity_checker = getattr(source, method_name)
@@ -1121,18 +1234,18 @@
self.fail(msg)
def _create_security_group(self, security_group_rules_client=None,
- tenant_id=None,
+ project_id=None,
namestart='secgroup-smoke',
security_groups_client=None):
if security_group_rules_client is None:
security_group_rules_client = self.security_group_rules_client
if security_groups_client is None:
security_groups_client = self.security_groups_client
- if tenant_id is None:
- tenant_id = security_groups_client.tenant_id
+ if project_id is None:
+ project_id = security_groups_client.project_id
secgroup = self._create_empty_security_group(
namestart=namestart, client=security_groups_client,
- tenant_id=tenant_id)
+ project_id=project_id)
# Add rules to the security group
rules = self._create_loginable_secgroup_rule(
@@ -1140,11 +1253,11 @@
secgroup=secgroup,
security_groups_client=security_groups_client)
for rule in rules:
- self.assertEqual(tenant_id, rule['tenant_id'])
+ self.assertEqual(project_id, rule['project_id'])
self.assertEqual(secgroup['id'], rule['security_group_id'])
return secgroup
- def _create_empty_security_group(self, client=None, tenant_id=None,
+ def _create_empty_security_group(self, client=None, project_id=None,
namestart='secgroup-smoke'):
"""Create a security group without rules.
@@ -1152,23 +1265,24 @@
- IPv4 egress to any
- IPv6 egress to any
- :param tenant_id: secgroup will be created in this tenant
+ :param project_id: secgroup will be created in this project
:returns: the created security group
"""
+
if client is None:
client = self.security_groups_client
- if not tenant_id:
- tenant_id = client.tenant_id
+ if not project_id:
+ project_id = client.project_id
sg_name = data_utils.rand_name(namestart)
sg_desc = sg_name + " description"
sg_dict = dict(name=sg_name,
description=sg_desc)
- sg_dict['tenant_id'] = tenant_id
+ sg_dict['project_id'] = project_id
result = client.create_security_group(**sg_dict)
secgroup = result['security_group']
self.assertEqual(secgroup['name'], sg_name)
- self.assertEqual(tenant_id, secgroup['tenant_id'])
+ self.assertEqual(project_id, secgroup['project_id'])
self.assertEqual(secgroup['description'], sg_desc)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -1177,15 +1291,15 @@
def _create_security_group_rule(self, secgroup=None,
sec_group_rules_client=None,
- tenant_id=None,
+ project_id=None,
security_groups_client=None, **kwargs):
"""Create a rule from a dictionary of rule parameters.
Create a rule in a secgroup. if secgroup not defined will search for
- default secgroup in tenant_id.
+ default secgroup in project_id.
:param secgroup: the security group.
- :param tenant_id: if secgroup not passed -- the tenant in which to
+ :param project_id: if secgroup not passed -- the tenant in which to
search for default secgroup
:param kwargs: a dictionary containing rule parameters:
for example, to allow incoming ssh:
@@ -1196,22 +1310,23 @@
port_range_max: 22
}
"""
+
if sec_group_rules_client is None:
sec_group_rules_client = self.security_group_rules_client
if security_groups_client is None:
security_groups_client = self.security_groups_client
- if not tenant_id:
- tenant_id = security_groups_client.tenant_id
+ if not project_id:
+ project_id = security_groups_client.project_id
if secgroup is None:
- # Get default secgroup for tenant_id
+ # Get default secgroup for project_id
default_secgroups = security_groups_client.list_security_groups(
- name='default', tenant_id=tenant_id)['security_groups']
- msg = "No default security group for tenant %s." % (tenant_id)
+ name='default', project_id=project_id)['security_groups']
+ msg = "No default security group for project %s." % (project_id)
self.assertNotEmpty(default_secgroups, msg)
secgroup = default_secgroups[0]
ruleset = dict(security_group_id=secgroup['id'],
- tenant_id=secgroup['tenant_id'])
+ project_id=secgroup['project_id'])
ruleset.update(kwargs)
sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
@@ -1277,7 +1392,7 @@
return rules
- def _get_router(self, client=None, tenant_id=None):
+ def _get_router(self, client=None, project_id=None):
"""Retrieve a router for the given tenant id.
If a public router has been configured, it will be returned.
@@ -1286,10 +1401,11 @@
network has, a tenant router will be created and returned that
routes traffic to the public network.
"""
+
if not client:
client = self.routers_client
- if not tenant_id:
- tenant_id = client.tenant_id
+ if not project_id:
+ project_id = client.project_id
router_id = CONF.network.public_router_id
network_id = CONF.network.public_network_id
if router_id:
@@ -1299,7 +1415,7 @@
router = client.create_router(
name=data_utils.rand_name(self.__class__.__name__ + '-router'),
admin_state_up=True,
- tenant_id=tenant_id,
+ project_id=project_id,
external_gateway_info=dict(network_id=network_id))['router']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
client.delete_router, router['id'])
@@ -1310,14 +1426,14 @@
def create_networks(self, networks_client=None,
routers_client=None, subnets_client=None,
- tenant_id=None, dns_nameservers=None,
+ project_id=None, dns_nameservers=None,
port_security_enabled=True, **net_dict):
"""Create a network with a subnet connected to a router.
The baremetal driver is a special case since all nodes are
on the same shared network.
- :param tenant_id: id of tenant to create resources in.
+ :param project_id: id of project to create resources in.
:param dns_nameservers: list of dns servers to send to subnet.
:param port_security_enabled: whether or not port_security is enabled
:param net_dict: a dict containing experimental network information in
@@ -1326,6 +1442,7 @@
'provider:segmentation_id': '42'}
:returns: network, subnet, router
"""
+
if CONF.network.shared_physical_network:
# NOTE(Shrews): This exception is for environments where tenant
# credential isolation is available, but network separation is
@@ -1342,11 +1459,11 @@
else:
network = self._create_network(
networks_client=networks_client,
- tenant_id=tenant_id,
+ project_id=project_id,
port_security_enabled=port_security_enabled,
**net_dict)
router = self._get_router(client=routers_client,
- tenant_id=tenant_id)
+ project_id=project_id)
subnet_kwargs = dict(network=network,
subnets_client=subnets_client)
# use explicit check because empty list is a valid option
@@ -1382,6 +1499,7 @@
def create_encryption_type(self, client=None, type_id=None, provider=None,
key_size=None, cipher=None,
control_location=None):
+ """Creates an encryption type for volume"""
if not client:
client = self.admin_encryption_types_client
if not type_id:
@@ -1395,6 +1513,7 @@
def create_encrypted_volume(self, encryption_provider, volume_type,
key_size=256, cipher='aes-xts-plain64',
control_location='front-end'):
+ """Creates an encrypted volume"""
volume_type = self.create_volume_type(name=volume_type)
self.create_encryption_type(type_id=volume_type['id'],
provider=encryption_provider,
@@ -1435,11 +1554,12 @@
cls.object_client = cls.os_operator.object_client
def get_swift_stat(self):
- """get swift status for our user account."""
+ """Get swift status for our user account."""
self.account_client.list_account_containers()
LOG.debug('Swift status information obtained successfully')
def create_container(self, container_name=None):
+ """Creates container"""
name = container_name or data_utils.rand_name(
'swift-scenario-container')
self.container_client.update_container(name)
@@ -1452,10 +1572,12 @@
return name
def delete_container(self, container_name):
+ """Deletes container"""
self.container_client.delete_container(container_name)
LOG.debug('Container %s deleted', container_name)
def upload_object_to_container(self, container_name, obj_name=None):
+ """Uploads object to container"""
obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
obj_data = data_utils.random_bytes()
self.object_client.create_object(container_name, obj_name, obj_data)
@@ -1466,6 +1588,7 @@
return obj_name, obj_data
def delete_object(self, container_name, filename):
+ """Deletes object"""
self.object_client.delete_object(container_name, filename)
self.list_and_check_container_objects(container_name,
not_present_obj=[filename])
@@ -1473,8 +1596,13 @@
def list_and_check_container_objects(self, container_name,
present_obj=None,
not_present_obj=None):
- # List objects for a given container and assert which are present and
- # which are not.
+ """List and verify objects for a given container
+
+ This utility lists objects for a given container
+ and asserts which are present and
+ which are not
+ """
+
if present_obj is None:
present_obj = []
if not_present_obj is None:
@@ -1489,5 +1617,6 @@
self.assertNotIn(obj, object_list)
def download_and_verify(self, container_name, obj_name, expected_data):
+ """Asserts the object and expected data to verify if they are same"""
_, obj = self.object_client.get_object(container_name, obj_name)
self.assertEqual(obj, expected_data)
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 008d1ae..fc93a5e 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -44,7 +44,7 @@
raise cls.skipException('Encrypted volume attach is not supported')
def launch_instance(self):
- image = self.glance_image_create()
+ image = self.image_create()
keypair = self.create_keypair()
return self.create_server(image_id=image, key_name=keypair['name'])
diff --git a/tempest/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
index e7085f6..5eab1da 100644
--- a/tempest/scenario/test_minbw_allocation_placement.py
+++ b/tempest/scenario/test_minbw_allocation_placement.py
@@ -124,8 +124,11 @@
resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
self.SMALLEST_POSSIBLE_BW))
if len(alloc_candidates['provider_summaries']) == 0:
- self.fail('No allocation candidates are available for %s:%s' %
- (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
+ # Skip if the backend does not support QoS minimum bandwidth
+ # allocation in Placement API
+ raise self.skipException(
+ 'No allocation candidates are available for %s:%s' %
+ (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
# Just to be sure check with impossible high (placement max_int),
# allocation
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 4cd860d..fe42583 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -106,7 +106,7 @@
@decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
@utils.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
- image = self.glance_image_create()
+ image = self.image_create()
keypair = self.create_keypair()
server = self.create_server(image_id=image, key_name=keypair['name'])
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index b1919d4..e26dc9d 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -262,7 +262,7 @@
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
- @decorators.skip_because(bug='1836595')
+ @decorators.unstable_test(bug='1836595')
@decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration is not available.')
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index d8584ec..6c1b3fa 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -297,9 +297,19 @@
ip_mask = CONF.network.project_network_mask_bits
# check if the address is not already in use, if not, set it
if ' ' + ip_address + '/' + str(ip_mask) not in ip_output:
- ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % (
- ip_address, ip_mask, new_nic))
- ssh_client.exec_command("sudo ip link set %s up" % new_nic)
+ try:
+ ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % (
+ ip_address, ip_mask, new_nic))
+ ssh_client.exec_command("sudo ip link set %s up" % new_nic)
+ except exceptions.SSHExecCommandFailed as exc:
+ if 'RTNETLINK answers: File exists' in str(exc):
+ LOG.debug(
+ 'IP address %(ip_address)s is already set in device '
+ '%(device)s\nPrevious "ip a" output: %(ip_output)s',
+ {'ip_address': ip_address, 'device': new_nic,
+ 'ip_output': ip_output})
+ else:
+ raise exc
def _get_server_nics(self, ssh_client):
reg = re.compile(r'(?P<num>\d+): (?P<nic_name>\w+)[@]?.*:')
@@ -321,7 +331,7 @@
internal_ips = (
p['fixed_ips'][0]['ip_address'] for p in
self.os_admin.ports_client.list_ports(
- tenant_id=server['tenant_id'],
+ project_id=server['tenant_id'],
network_id=network['id'])['ports']
if p['device_owner'].startswith('network') or
p['device_owner'].startswith('compute')
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 8de6614..14f24c7 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -130,7 +130,7 @@
key_name=self.keypair['name'],
security_groups=[{'name': self.sec_grp['name']}],
networks=[{'uuid': n['id']} for n in networks])
- fip = self.create_floating_ip(thing=srv)
+ fip = self.create_floating_ip(server=srv)
ips = self.define_server_ips(srv=srv)
ssh = self.get_remote_client(
ip_address=fip['floating_ip_address'],
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 9cbd831..3fc93e4 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -176,7 +176,7 @@
cls.primary_tenant = cls.TenantProperties(cls.os_primary)
cls.alt_tenant = cls.TenantProperties(cls.os_alt)
for tenant in [cls.primary_tenant, cls.alt_tenant]:
- cls.tenants[tenant.creds.tenant_id] = tenant
+ cls.tenants[tenant.creds.project_id] = tenant
cls.floating_ip_access = not CONF.network.public_router_id
@@ -199,14 +199,14 @@
def _create_tenant_security_groups(self, tenant):
access_sg = self._create_empty_security_group(
namestart='secgroup_access-',
- tenant_id=tenant.creds.tenant_id,
+ project_id=tenant.creds.project_id,
client=tenant.manager.security_groups_client
)
# don't use default secgroup since it allows in-project traffic
def_sg = self._create_empty_security_group(
namestart='secgroup_general-',
- tenant_id=tenant.creds.tenant_id,
+ project_id=tenant.creds.project_id,
client=tenant.manager.security_groups_client
)
tenant.security_groups.update(access=access_sg, default=def_sg)
@@ -536,7 +536,7 @@
# Create empty security group and add icmp rule in it
new_sg = self._create_empty_security_group(
namestart='secgroup_new-',
- tenant_id=new_tenant.creds.tenant_id,
+ project_id=new_tenant.creds.project_id,
client=new_tenant.manager.security_groups_client)
icmp_rule = dict(
protocol='icmp',
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 1593464..74d2625 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from oslo_utils import uuidutils
import six
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index 0b53b45..e8b2c98 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
from oslotest import base
diff --git a/tempest/tests/cmd/sample_streams/calls.subunit b/tempest/tests/cmd/subunit_describe_calls_data/calls.subunit
similarity index 100%
rename from tempest/tests/cmd/sample_streams/calls.subunit
rename to tempest/tests/cmd/subunit_describe_calls_data/calls.subunit
Binary files differ
diff --git a/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json
new file mode 100644
index 0000000..53976ee
--- /dev/null
+++ b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json
@@ -0,0 +1,87 @@
+{"bar":[
+ {
+ "name":"AgentsAdminTestJSON:setUp",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\", \"agent_id\": 1}}",
+ "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_create_agent",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\", \"agent_id\": 2}}",
+ "response_headers":"{'status': '200', 'content-length': '195', 'x-compute-request-id': 'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:tearDown",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/1",
+ "verb":"DELETE"
+},
+ {
+ "name":"AgentsAdminTestJSON:_run_cleanups",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/2",
+ "verb":"DELETE"
+}], "foo":[
+ {
+ "name":"AgentsAdminTestJSON:setUp",
+ "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\"}}",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\", \"agent_id\": 3}}",
+ "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"POST"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_delete_agent",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"",
+ "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents/3",
+ "verb":"DELETE"
+},
+ {
+ "name":"AgentsAdminTestJSON:test_delete_agent",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_body":"{\"agents\": []}",
+ "response_headers":"{'status': '200', 'content-length': '14', 'content-location': 'http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents', 'x-compute-request-id': 'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}",
+ "service":"Nova",
+ "status_code":"200",
+ "url":"v2.1/<id>/os-agents",
+ "verb":"GET"
+},
+ {
+ "name":"AgentsAdminTestJSON:tearDown",
+ "request_body":"None",
+ "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ "response_headers":"{'status': '404', 'content-length': '82', 'x-compute-request-id': 'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': 'application/json; charset=UTF-8'}",
+ "service":"Nova",
+ "status_code":"404",
+ "url":"v2.1/<id>/os-agents/3",
+ "verb":"DELETE"
+}]}
\ No newline at end of file
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index a962e37..7d764be 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from unittest import mock
+
import fixtures
-import mock
from oslo_config import cfg
from tempest.cmd import account_generator
@@ -336,3 +337,24 @@
def setUp(self):
self.mock_domains()
super(TestDumpAccountsV3, self).setUp()
+
+
+class TestAccountGeneratorCliCheck(base.TestCase):
+
+ def setUp(self):
+ super(TestAccountGeneratorCliCheck, self).setUp()
+ self.account_generator = account_generator.TempestAccountGenerator(
+ app=mock.Mock(), app_args=mock.Mock())
+ self.parser = self.account_generator.get_parser("generator")
+
+ def test_account_generator_zero_concurrency(self):
+ error = self.assertRaises(
+ SystemExit, lambda: self.parser.parse_args(
+ ['-r', '0', 'accounts_file.yaml']))
+ self.assertTrue(error.code != 0)
+
+ def test_account_generator_negative_concurrency(self):
+ error = self.assertRaises(
+ SystemExit, lambda: self.parser.parse_args(
+ ['-r', '-1', 'accounts_file.yaml']))
+ self.assertTrue(error.code != 0)
diff --git a/tempest/tests/cmd/test_cleanup.py b/tempest/tests/cmd/test_cleanup.py
index 1618df9..69e735b 100644
--- a/tempest/tests/cmd/test_cleanup.py
+++ b/tempest/tests/cmd/test_cleanup.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import mock
+from unittest import mock
from tempest.cmd import cleanup
from tempest.tests import base
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index 7bf7315..fc44793 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -511,7 +511,8 @@
},
{
"id": "aa77asdf-1234",
- "name": "saved-volume"
+ "name": "saved-volume",
+ "links": [],
}
]
}
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index e9bbcc2..5d9ddfa 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -18,9 +18,9 @@
import shutil
import subprocess
import tempfile
+from unittest import mock
import fixtures
-import mock
import six
from tempest.cmd import run
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index cb34ba6..4fed84a 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -14,220 +14,422 @@
# License for the specific language governing permissions and limitations
# under the License.
+import argparse
+from io import StringIO
import os
+import shutil
import subprocess
+import sys
import tempfile
+from unittest import mock
+from unittest.mock import patch
+
+from oslo_serialization import jsonutils as json
from tempest.cmd import subunit_describe_calls
from tempest.tests import base
-class TestSubunitDescribeCalls(base.TestCase):
- def test_return_code(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file,
- '-o', tempfile.mkstemp()[1]], stdin=subprocess.PIPE)
- p.communicate()
- self.assertEqual(0, p.returncode)
+class TestArgumentParser(base.TestCase):
+ def test_init(self):
+ test_object = subunit_describe_calls.ArgumentParser()
+ self.assertEqual("subunit-describe-calls", test_object.prog)
+ self.assertEqual(subunit_describe_calls.DESCRIPTION,
+ test_object.description)
- def test_verbose(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file,
- '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- stdout = p.communicate()
- self.assertEqual(0, p.returncode)
- self.assertIn(b'- request headers:', stdout[0])
- self.assertIn(b'- request body:', stdout[0])
- self.assertIn(b'- response headers:', stdout[0])
- self.assertIn(b'- response body:', stdout[0])
- def test_return_code_no_output(self):
- subunit_file = os.path.join(
+class TestUrlParser(base.TestCase):
+ services_custom_ports = {
+ "18776": "Block Storage",
+ "18774": "Nova",
+ "18773": "Nova-API",
+ "18386": "Sahara",
+ "35358": "Keystone",
+ "19292": "Glance",
+ "19696": "Neutron",
+ "16000": "Swift",
+ "18004": "Heat",
+ "18777": "Ceilometer",
+ "10080": "Horizon",
+ "18080": "Swift",
+ "1873": "rsync",
+ "13260": "iSCSI",
+ "13306": "MySQL",
+ "15672": "AMQP",
+ "18082": "murano"}
+
+ def setUp(self):
+ super(TestUrlParser, self).setUp()
+ self.test_object = subunit_describe_calls.UrlParser()
+
+ def test_get_service_default_ports(self):
+ base_url = "http://site.something.com:"
+ for port in self.test_object.services:
+ url = base_url + port + "/v2/action"
+ service = self.test_object.services[port]
+ self.assertEqual(service, self.test_object.get_service(url))
+
+ def test_get_service_custom_ports(self):
+ self.test_object = subunit_describe_calls.\
+ UrlParser(services=self.services_custom_ports)
+ base_url = "http://site.something.com:"
+ for port in self.services_custom_ports:
+ url = base_url + port + "/v2/action"
+ service = self.services_custom_ports[port]
+ self.assertEqual(service, self.test_object.get_service(url))
+
+ def test_get_service_port_not_found(self):
+ url = "https://site.somewhere.com:1234/v2/action"
+ self.assertEqual("Unknown", self.test_object.get_service(url))
+ self.assertEqual("Unknown", self.test_object.get_service(""))
+
+ def test_parse_details_none(self):
+ self.assertIsNone(self.test_object.parse_details(None))
+
+ def test_url_path_ports(self):
+ uuid_sample1 = "3715e0bb-b1b3-4291-aa13-2c86c3b9ec93"
+ uuid_sample2 = "2715e0bb-b1b4-4291-aa13-2c86c3b9ec88"
+
+ # test http url
+ host = "http://host.company.com"
+ url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \
+ uuid_sample2 + "/extra_specs"
+ self.assertEqual("v3/<uuid>/types/<uuid>/extra_specs",
+ self.test_object.url_path(url))
+ url = host + ":8774/v2.1/servers/" + uuid_sample1
+ self.assertEqual("v2.1/servers/<uuid>",
+ self.test_object.url_path(url))
+ # test https url
+ host = "https://host.company.com"
+ url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \
+ uuid_sample2 + "/extra_specs"
+ self.assertEqual("v3/<uuid>/types/<uuid>/extra_specs",
+ self.test_object.url_path(url))
+ url = host + ":8774/v2.1/servers/" + uuid_sample1
+ self.assertEqual("v2.1/servers/<uuid>",
+ self.test_object.url_path(url))
+
+ def test_url_path_no_match(self):
+ host_port = 'https://host.company.com:1234/'
+ url = 'v2/action/no/special/data'
+ self.assertEqual(url, self.test_object.url_path(host_port + url))
+ url = 'data'
+ self.assertEqual(url, self.test_object.url_path(url))
+
+
+class TestCliBase(base.TestCase):
+ """Base class for share code on all CLI sub-process testing"""
+
+ def setUp(self):
+ super(TestCliBase, self).setUp()
+ self._subunit_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
+ 'subunit_describe_calls_data', 'calls.subunit')
+
+ def _bytes_to_string(self, data):
+ if isinstance(data, (bytes, bytearray)):
+ data = str(data, 'utf-8')
+ return data
+
+ def _assert_cli_message(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn("Running subunit_describe_calls ...", data)
+
+ def _assert_deprecated_warning(self, stdout):
+ self.assertIn(
+ b"Use of: 'subunit-describe-calls' is deprecated, "
+ b"please use: 'tempest subunit-describe-calls'", stdout)
+
+ def _assert_expect_json(self, json_data):
+ expected_file_name = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'subunit_describe_calls_data', 'calls_subunit_expected.json')
+ with open(expected_file_name, "rb") as read_file:
+ expected_result = json.load(read_file)
+ self.assertDictEqual(expected_result, json_data)
+
+ def _assert_headers_and_bodies(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn('- request headers:', data)
+ self.assertIn('- request body:', data)
+ self.assertIn('- response headers:', data)
+ self.assertIn('- response body:', data)
+
+ def _assert_methods_details(self, data):
+ data = self._bytes_to_string(data)
+ self.assertIn('foo', data)
+ self.assertIn('- 200 POST request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 200 DELETE request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 200 GET request for Nova to v2.1/<id>/',
+ data)
+ self.assertIn('- 404 DELETE request for Nova to v2.1/<id>/',
+ data)
+
+ def _assert_mutual_exclusive_message(self, stderr):
+ self.assertIn(b"usage: subunit-describe-calls "
+ b"[-h] [-s [<subunit file>]]", stderr)
+ self.assertIn(b"[-n <non subunit name>] [-o <output file>]",
+ stderr)
+ self.assertIn(b"[-p <ports file>] [-v | -a]", stderr)
+ self.assertIn(
+ b"subunit-describe-calls: error: argument -v/--verbose: "
+ b"not allowed with argument -a/--all-stdout", stderr)
+
+ def _assert_no_headers_and_bodies(self, data):
+ data = self._bytes_to_string(data)
+ self.assertNotIn('- request headers:', data)
+ self.assertNotIn('- request body:', data)
+ self.assertNotIn('- response headers:', data)
+ self.assertNotIn('- response body:', data)
+
+
+class TestMainCli(TestCliBase):
+ """Test cases that use subunit_describe_calls module main interface
+
+ via subprocess calls to make sure the total user experience
+ is well defined and tested. This interface is deprecated.
+ Note: these test do not affect code coverage percentages.
+ """
+
+ def test_main_output_file(self):
+ temp_file = tempfile.mkstemp()[1]
p = subprocess.Popen([
- 'subunit-describe-calls', '-s', subunit_file],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- stdout = p.communicate()
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-o', temp_file], stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
self.assertEqual(0, p.returncode)
- self.assertIn(b'foo', stdout[0])
- self.assertIn(b'- 200 POST request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 200 DELETE request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 200 GET request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertIn(b'- 404 DELETE request for Nova to v2.1/<id>/',
- stdout[0])
- self.assertNotIn(b'- request headers:', stdout[0])
- self.assertNotIn(b'- request body:', stdout[0])
- self.assertNotIn(b'- response headers:', stdout[0])
- self.assertNotIn(b'- response body:', stdout[0])
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ with open(temp_file, 'r') as file:
+ data = json.loads(file.read())
+ self._assert_expect_json(data)
+
+ def test_main_verbose(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_main_all_stdout(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '--all-stdout'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_main(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+
+ def test_main_verbose_and_all_stdout(self):
+ p = subprocess.Popen([
+ 'subunit-describe-calls', '-s', self._subunit_file,
+ '-a', '-v'],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(2, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_deprecated_warning(stdout)
+ self._assert_mutual_exclusive_message(stderr)
+
+
+class TestCli(TestCliBase):
+ """Test cases that use tempest subunit_describe_calls cliff interface
+
+ via subprocess calls to make sure the total user experience
+ is well defined and tested.
+ Note: these test do not affect code coverage percentages.
+ """
+
+ def _assert_cliff_verbose(self, stdout):
+ self.assertIn(b'tempest initialize_app', stdout)
+ self.assertIn(b'prepare_to_run_command TempestSubunitDescribeCalls',
+ stdout)
+ self.assertIn(b'tempest clean_up TempestSubunitDescribeCalls',
+ stdout)
+
+ def test_run_all_stdout(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-a'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_headers_and_bodies(stdout)
+
+ def test_run_verbose(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-v'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+ self._assert_cliff_verbose(stderr)
+
+ def test_run_min(self):
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_methods_details(stdout)
+ self._assert_no_headers_and_bodies(stdout)
+
+ def test_run_verbose_all_stdout(self):
+ """Test Cliff -v argument
+
+ Since Cliff framework has a argument at the
+ abstract command level the -v or --verbose for
+ this command is not processed as a boolean.
+ So the use of verbose only exists for the
+ deprecated main CLI interface. When the
+ main is deleted this test would not be needed.
+ """
+ p = subprocess.Popen(['tempest', 'subunit-describe-calls',
+ '-s', self._subunit_file, '-a', '-v'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ self.assertEqual(0, p.returncode)
+ self._assert_cli_message(stdout)
+ self._assert_cliff_verbose(stderr)
+ self._assert_methods_details(stdout)
+
+
+class TestSubunitDescribeCalls(TestCliBase):
+ """Test cases use the subunit_describe_calls module interface
+
+ and effect code coverage reporting
+ """
+
+ def setUp(self):
+ super(TestSubunitDescribeCalls, self).setUp()
+ self.test_object = subunit_describe_calls.TempestSubunitDescribeCalls(
+ app=mock.Mock(),
+ app_args=mock.Mock(spec=argparse.Namespace))
def test_parse(self):
- subunit_file = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'sample_streams/calls.subunit')
- parser = subunit_describe_calls.parse(
- open(subunit_file), "pythonlogging", None)
- expected_result = {
- 'bar': [{
- 'name': 'AgentsAdminTestJSON:setUp',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-424013832", "os": "linux"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-424013832", "os": "linux", '
- '"agent_id": 1}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'203', 'x-compute-request-id': "
- "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:test_create_agent',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "kvm", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86-252246646", "os": "win"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "kvm", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86-252246646", "os": "win", '
- '"agent_id": 2}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'195', 'x-compute-request-id': "
- "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:tearDown',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '',
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/1',
- 'verb': 'DELETE'}, {
- 'name': 'AgentsAdminTestJSON:_run_cleanups',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/2',
- 'verb': 'DELETE'}],
- 'foo': [{
- 'name': 'AgentsAdminTestJSON:setUp',
- 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-948635295", "os": "linux"}}',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
- '"hypervisor": "common", "md5hash": '
- '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
- '"architecture": "tempest-x86_64-948635295", "os": "linux", '
- '"agent_id": 3}}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'203', 'x-compute-request-id': "
- "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'}, {
- 'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '',
- 'response_headers': "{'status': '200', 'content-length': "
- "'0', 'x-compute-request-id': "
- "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'}, {
- 'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_body': '{"agents": []}',
- 'response_headers': "{'status': '200', 'content-length': "
- "'14', 'content-location': "
- "'http://23.253.76.97:8774/v2.1/"
- "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', "
- "'x-compute-request-id': "
- "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
- "'application/json'}",
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'GET'}, {
- 'name': 'AgentsAdminTestJSON:tearDown',
- 'request_body': 'None',
- 'request_headers': "{'Content-Type': 'application/json', "
- "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
- 'response_headers': "{'status': '404', 'content-length': "
- "'82', 'x-compute-request-id': "
- "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': "
- "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
- "'x-openstack-nova-api-version': '2.1', 'date': "
- "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': "
- "'application/json; charset=UTF-8'}",
- 'service': 'Nova',
- 'status_code': '404',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'}]}
+ with open(self._subunit_file, 'r') as read_file:
+ parser = subunit_describe_calls.parse(
+ read_file, "pythonlogging", None)
+ self._assert_expect_json(parser.test_logs)
- self.assertEqual(expected_result, parser.test_logs)
+ def test_get_description(self):
+ self.assertEqual(subunit_describe_calls.DESCRIPTION,
+ self.test_object.get_description())
+
+ def test_get_parser_default_min(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args([])
+ self.assertIsNone(parsed_args.output_file)
+ self.assertIsNone(parsed_args.ports)
+ self.assertFalse(parsed_args.all_stdout)
+ self.assertEqual(parsed_args.subunit, sys.stdin)
+
+ def test_get_parser_default_max(self):
+ temp_dir = tempfile.mkdtemp(prefix="parser")
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+ outfile_name = os.path.join(temp_dir, 'output.json')
+ open(outfile_name, 'a').close()
+ portfile_name = os.path.join(temp_dir, 'ports.json')
+ open(portfile_name, 'a').close()
+
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-a", "-o " + outfile_name,
+ "-p " + portfile_name])
+
+ self.assertIsNotNone(parsed_args.output_file)
+ self.assertIsNotNone(parsed_args.ports)
+ self.assertTrue(parsed_args.all_stdout)
+ self.assertEqual(parsed_args.subunit, sys.stdin)
+
+ def test_take_action_min(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-s" + self._subunit_file],)
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+
+ stdout_data = mock_stdout.getvalue()
+ self._assert_methods_details(stdout_data)
+ self._assert_no_headers_and_bodies(stdout_data)
+
+ def test_take_action_all_stdout(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-as" + self._subunit_file],)
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+
+ stdout_data = mock_stdout.getvalue()
+ self._assert_methods_details(stdout_data)
+ self._assert_headers_and_bodies(stdout_data)
+
+ def test_take_action_outfile_files(self):
+ temp_file = tempfile.mkstemp()[1]
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(
+ ["-as" + self._subunit_file, '-o', temp_file], )
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+ stdout_data = mock_stdout.getvalue()
+ self._assert_cli_message(stdout_data)
+ with open(temp_file, 'r') as file:
+ data = json.loads(file.read())
+ self._assert_expect_json(data)
+
+ def test_take_action_no_items(self):
+ temp_file = tempfile.mkstemp()[1]
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(
+ ["-as" + temp_file], )
+ with patch('sys.stdout', new=StringIO()) as mock_stdout:
+ self.test_object.take_action(parsed_args)
+ stdout_data = mock_stdout.getvalue()
+ self._assert_cli_message(stdout_data)
+
+ def test_take_action_exception(self):
+ parser = self.test_object.get_parser('NAME')
+ parsed_args = parser.parse_args(["-s" + self._subunit_file],)
+ with patch('sys.stderr', new=StringIO()) as mock_stderr:
+ with patch('tempest.cmd.subunit_describe_calls.entry_point') \
+ as mock_method:
+ mock_method.side_effect = OSError()
+ self.assertRaises(OSError, self.test_object.take_action,
+ parsed_args)
+ stderr_data = mock_stderr.getvalue()
+
+ self.assertIn("Traceback (most recent call last):", stderr_data)
+ self.assertIn("entry_point(parsed_args)", stderr_data)
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 8dbba38..721fd76 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -13,9 +13,9 @@
# under the License.
import os
+from unittest import mock
import fixtures
-import mock
from oslo_serialization import jsonutils as json
from tempest import clients
@@ -629,3 +629,23 @@
def test_contains_version_negative_data(self):
self.assertFalse(
verify_tempest_config.contains_version('v5.', ['v1.0', 'v2.0']))
+
+ def test_check_service_availability(self):
+ class FakeAuthProvider:
+ def get_auth(self):
+ return ('token',
+ {'serviceCatalog': [{'type': 'compute'},
+ {'type': 'image'},
+ {'type': 'volumev3'},
+ {'type': 'network'},
+ {'type': 'object-store'}]})
+
+ class Fake_os:
+ auth_provider = FakeAuthProvider()
+ auth_version = 'v2'
+ verify_tempest_config.CONF._config = fake_config.FakePrivate()
+ services = verify_tempest_config.check_service_availability(
+ Fake_os(), True)
+ self.assertEqual(
+ sorted(['nova', 'glance', 'neutron', 'swift', 'cinder']),
+ sorted(services))
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index 7a6b576..eae6202 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -16,12 +16,12 @@
import shutil
import subprocess
import tempfile
-
-from mock import patch
+from unittest.mock import patch
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
+
from tempest.cmd import workspace
from tempest.lib.common.utils import data_utils
from tempest.tests import base
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
index c108be9..45a439c 100644
--- a/tempest/tests/common/test_compute.py
+++ b/tempest/tests/common/test_compute.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+from unittest import mock
+
from six.moves.urllib import parse as urlparse
-import mock
from tempest.common import compute
from tempest.tests import base
diff --git a/tempest/tests/common/test_credentials_factory.py b/tempest/tests/common/test_credentials_factory.py
index 7cf87f8..0ef3742 100644
--- a/tempest/tests/common/test_credentials_factory.py
+++ b/tempest/tests/common/test_credentials_factory.py
@@ -13,7 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
from oslo_config import cfg
import testtools
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index e3bb836..32d6498 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -13,8 +13,8 @@
# under the License.
import time
+from unittest import mock
-import mock
from oslo_utils.fixture import uuidsentinel as uuids
from tempest.common import waiters
@@ -249,9 +249,9 @@
waiters.wait_for_volume_attachment_remove(client, uuids.volume_id,
uuids.attachment_id)
# Assert that show volume is called until the attachment is removed.
- show_volume.assert_has_calls = [mock.call(uuids.volume_id),
- mock.call(uuids.volume_id),
- mock.call(uuids.volume_id)]
+ show_volume.assert_has_calls([mock.call(uuids.volume_id),
+ mock.call(uuids.volume_id),
+ mock.call(uuids.volume_id)])
def test_wait_for_volume_attachment_timeout(self):
show_volume = mock.MagicMock(return_value={
diff --git a/tempest/tests/common/utils/test_net_utils.py b/tempest/tests/common/utils/test_net_utils.py
index 83c6bcc..51d86d1 100644
--- a/tempest/tests/common/utils/test_net_utils.py
+++ b/tempest/tests/common/utils/test_net_utils.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.common.utils import net_utils
from tempest.lib import exceptions as lib_exc
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index c069af5..a10e3bb 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -12,8 +12,8 @@
# under the License.
import subprocess
+from unittest import mock
-import mock
from tempest.lib.cli import base as cli_base
from tempest.lib import exceptions
diff --git a/tempest/tests/lib/cmd/__init__.py b/tempest/tests/lib/cmd/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/cmd/__init__.py
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
new file mode 100644
index 0000000..28ebca1
--- /dev/null
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -0,0 +1,181 @@
+# 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 importlib
+import os
+import sys
+import tempfile
+from unittest import mock
+
+import fixtures
+
+from tempest.lib.cmd import check_uuid
+from tempest.tests import base
+
+
+class TestCLInterface(base.TestCase):
+ CODE = "import unittest\n" \
+ "class TestClass(unittest.TestCase):\n" \
+ " def test_tests(self):\n" \
+ " pass"
+
+ def create_tests_file(self, directory):
+ with open(directory + "/__init__.py", "w"):
+ pass
+
+ tests_file = directory + "/tests.py"
+ with open(tests_file, "w") as fake_file:
+ fake_file.write(TestCLInterface.CODE)
+
+ return tests_file
+
+ def test_fix_argument_no(self):
+ temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
+ tests_file = self.create_tests_file(temp_dir.path)
+
+ sys.argv = [sys.argv[0]] + ["--package",
+ os.path.relpath(temp_dir.path)]
+
+ self.assertRaises(SystemExit, check_uuid.run)
+ with open(tests_file, "r") as f:
+ self.assertTrue(TestCLInterface.CODE == f.read())
+
+ def test_fix_argument_yes(self):
+ temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
+ tests_file = self.create_tests_file(temp_dir.path)
+
+ sys.argv = [sys.argv[0]] + ["--fix", "--package",
+ os.path.relpath(temp_dir.path)]
+
+ check_uuid.run()
+ with open(tests_file, "r") as f:
+ self.assertTrue(TestCLInterface.CODE != f.read())
+
+
+class TestSourcePatcher(base.TestCase):
+ def test_add_patch(self):
+ patcher = check_uuid.SourcePatcher()
+ fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+ file_contents = 'first_line\nsecond_line'
+ fake_file.write(file_contents)
+ fake_file.close()
+ patcher.add_patch(fake_file.name, 'patch', 2)
+
+ source_file = patcher.source_files[fake_file.name]
+ self.assertEqual(1, len(patcher.patches))
+ (patch_id, patch), = patcher.patches.items()
+ self.assertEqual(patcher._quote('patch\n'), patch)
+ self.assertEqual('first_line\n{%s:s}second_line' % patch_id,
+ patcher._unquote(source_file))
+
+ def test_apply_patches(self):
+ fake_file = tempfile.NamedTemporaryFile("w+t")
+ patcher = check_uuid.SourcePatcher()
+ patcher.patches = {'fake-uuid': patcher._quote('patch\n')}
+ patcher.source_files = {
+ fake_file.name: patcher._quote('first_line\n') +
+ '{fake-uuid:s}second_line'}
+ with mock.patch('sys.stdout'):
+ patcher.apply_patches()
+
+ lines = fake_file.read().split('\n')
+ fake_file.close()
+ self.assertEqual(['first_line', 'patch', 'second_line'], lines)
+ self.assertFalse(patcher.patches)
+ self.assertFalse(patcher.source_files)
+
+
+class TestTestChecker(base.TestCase):
+ def _test_add_uuid_to_test(self, source_file):
+ class Fake_test_node():
+ lineno = 1
+ col_offset = 4
+ patcher = check_uuid.SourcePatcher()
+ checker = check_uuid.TestChecker(importlib.import_module('tempest'))
+ fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+ fake_file.write(source_file)
+ fake_file.close()
+ checker._add_uuid_to_test(patcher, Fake_test_node(), fake_file.name)
+
+ self.assertEqual(1, len(patcher.patches))
+ self.assertEqual(1, len(patcher.source_files))
+ (patch_id, patch), = patcher.patches.items()
+ changed_source_file, = patcher.source_files.values()
+ self.assertEqual('{%s:s}%s' % (patch_id, patcher._quote(source_file)),
+ changed_source_file)
+ expected_patch_start = patcher._quote(
+ ' ' + check_uuid.DECORATOR_TEMPLATE.split('(')[0])
+ self.assertTrue(patch.startswith(expected_patch_start))
+
+ def test_add_uuid_to_test_def(self):
+ source_file = (" def test_test():\n"
+ " pass")
+ self._test_add_uuid_to_test(source_file)
+
+ def test_add_uuid_to_test_decorator(self):
+ source_file = (" @decorators.idempotent_id\n"
+ " def test_test():\n"
+ " pass")
+ self._test_add_uuid_to_test(source_file)
+
+ def test_add_import_for_test_uuid_no_tempest(self):
+ patcher = check_uuid.SourcePatcher()
+ checker = check_uuid.TestChecker(importlib.import_module('tempest'))
+ fake_file = tempfile.NamedTemporaryFile("w+t")
+
+ class Fake_src_parsed():
+ body = ['test_node']
+ checker._import_name = mock.Mock(return_value='fake_module')
+
+ checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+ fake_file.name)
+ (patch_id, patch), = patcher.patches.items()
+ self.assertEqual(patcher._quote('\n' + check_uuid.IMPORT_LINE + '\n'),
+ patch)
+ self.assertEqual('{%s:s}' % patch_id,
+ patcher.source_files[fake_file.name])
+
+ def test_add_import_for_test_uuid_tempest(self):
+ patcher = check_uuid.SourcePatcher()
+ checker = check_uuid.TestChecker(importlib.import_module('tempest'))
+ fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+ test1 = (" def test_test():\n"
+ " pass\n")
+ test2 = (" def test_another_test():\n"
+ " pass\n")
+ source_code = test1 + test2
+ fake_file.write(source_code)
+ fake_file.close()
+
+ def fake_import_name(node):
+ return node.name
+ checker._import_name = fake_import_name
+
+ class Fake_node():
+ def __init__(self, lineno, col_offset, name):
+ self.lineno = lineno
+ self.col_offset = col_offset
+ self.name = name
+
+ class Fake_src_parsed():
+ body = [Fake_node(1, 4, 'tempest.a_fake_module'),
+ Fake_node(3, 4, 'another_fake_module')]
+
+ checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+ fake_file.name)
+ (patch_id, patch), = patcher.patches.items()
+ self.assertEqual(patcher._quote(check_uuid.IMPORT_LINE + '\n'),
+ patch)
+ expected_source = patcher._quote(test1) + '{' + patch_id + ':s}' +\
+ patcher._quote(test2)
+ self.assertEqual(expected_source,
+ patcher.source_files[fake_file.name])
diff --git a/tempest/tests/lib/common/test_cred_client.py b/tempest/tests/lib/common/test_cred_client.py
index 3dff16f..860a465 100644
--- a/tempest/tests/lib/common/test_cred_client.py
+++ b/tempest/tests/lib/common/test_cred_client.py
@@ -11,7 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.lib.common import cred_client
from tempest.tests import base
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index 4723458..e9073cc 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from unittest import mock
+
import fixtures
-import mock
from oslo_config import cfg
from tempest.common import credentials_factory as credentials
@@ -110,7 +111,7 @@
(200,
{'roles': [{'id': id, 'name': name},
{'id': '1', 'name': 'FakeRole'},
- {'id': '2', 'name': 'Member'}]}))))
+ {'id': '2', 'name': 'member'}]}))))
return roles_fix
def _mock_list_2_roles(self):
@@ -139,7 +140,7 @@
return_value=(rest_client.ResponseBody
(200, {'roles': [
{'id': '1', 'name': 'FakeRole'},
- {'id': '2', 'name': 'Member'}]}))))
+ {'id': '2', 'name': 'member'}]}))))
return roles_fix
def _mock_list_ec2_credentials(self, user_id, tenant_id):
@@ -664,6 +665,6 @@
with mock.patch('tempest.lib.common.dynamic_creds.LOG') as log_mock:
creds._create_creds()
log_mock.warning.assert_called_once_with(
- "Member role already exists, ignoring conflict.")
+ "member role already exists, ignoring conflict.")
creds.creds_client.assign_user_role.assert_called_once_with(
- mock.ANY, mock.ANY, 'Member')
+ mock.ANY, mock.ANY, 'member')
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 25df2a7..579363e 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -15,8 +15,8 @@
import hashlib
import os
import shutil
+from unittest import mock
-import mock
import six
import testtools
diff --git a/tempest/tests/lib/common/test_profiler.py b/tempest/tests/lib/common/test_profiler.py
index 59fa0364..166d831 100644
--- a/tempest/tests/lib/common/test_profiler.py
+++ b/tempest/tests/lib/common/test_profiler.py
@@ -10,7 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
import testtools
from tempest.lib.common import profiler
diff --git a/tempest/tests/lib/common/test_validation_resources.py b/tempest/tests/lib/common/test_validation_resources.py
index d5139f4..d50fd89 100644
--- a/tempest/tests/lib/common/test_validation_resources.py
+++ b/tempest/tests/lib/common/test_validation_resources.py
@@ -11,8 +11,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from unittest import mock
+
import fixtures
-import mock
import testtools
from tempest.lib.common import validation_resources as vr
diff --git a/tempest/tests/lib/common/utils/linux/test_remote_client.py b/tempest/tests/lib/common/utils/linux/test_remote_client.py
index 7a21a5f..df23e63 100644
--- a/tempest/tests/lib/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/lib/common/utils/linux/test_remote_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.lib.common import ssh
from tempest.lib.common.utils.linux import remote_client
diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
index 865767b..bdc0ea4 100644
--- a/tempest/tests/lib/common/utils/test_test_utils.py
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -14,8 +14,8 @@
# under the License.
import time
+from unittest import mock
-import mock
from tempest.lib.common import thread
from tempest.lib.common.utils import test_utils
diff --git a/tempest/tests/lib/services/compute/test_base_compute_client.py b/tempest/tests/lib/services/compute/test_base_compute_client.py
index 69e8542..5841ae4 100644
--- a/tempest/tests/lib/services/compute/test_base_compute_client.py
+++ b/tempest/tests/lib/services/compute/test_base_compute_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.lib.common import rest_client
from tempest.lib import exceptions
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index 86f6ad5..a82b255 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -14,8 +14,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import servers_client
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index ba432e3..0c513cc 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -13,8 +13,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import services_client
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
index 5b4e210..dc14a50 100644
--- a/tempest/tests/lib/services/identity/v2/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
from oslo_serialization import jsonutils as json
from tempest.lib.common import rest_client
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
index 656e10a..1c2295d 100644
--- a/tempest/tests/lib/services/identity/v3/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
from oslo_serialization import jsonutils as json
from tempest.lib.common import rest_client
diff --git a/tempest/tests/lib/services/network/test_base_network_client.py b/tempest/tests/lib/services/network/test_base_network_client.py
index e121cec..a426397 100644
--- a/tempest/tests/lib/services/network/test_base_network_client.py
+++ b/tempest/tests/lib/services/network/test_base_network_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.lib.services.network import base as base_network_client
from tempest.tests.lib import fake_auth_provider
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
index aa6c1a1..7dce4e1 100644
--- a/tempest/tests/lib/services/network/test_quotas_client.py
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -52,7 +52,7 @@
}
}
- FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
+ FAKE_QUOTA_PROJECT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
FAKE_QUOTA_DETAILS = {
"quota": {
@@ -115,7 +115,7 @@
self.FAKE_PROJECT_QUOTAS,
bytes_body,
200,
- tenant_id=self.FAKE_QUOTA_TENANT_ID)
+ project_id=self.FAKE_QUOTA_PROJECT_ID)
def _test_show_default_quotas(self, bytes_body=False):
self.check_service_client_function(
@@ -124,7 +124,7 @@
self.FAKE_PROJECT_QUOTAS,
bytes_body,
200,
- tenant_id=self.FAKE_QUOTA_TENANT_ID)
+ project_id=self.FAKE_QUOTA_PROJECT_ID)
def _test_update_quotas(self, bytes_body=False):
self.check_service_client_function(
@@ -133,7 +133,7 @@
self.FAKE_PROJECT_QUOTAS,
bytes_body,
200,
- tenant_id=self.FAKE_QUOTA_TENANT_ID)
+ project_id=self.FAKE_QUOTA_PROJECT_ID)
def _test_show_quota_details(self, bytes_body=False):
self.check_service_client_function(
@@ -142,7 +142,7 @@
self.FAKE_QUOTA_DETAILS,
bytes_body,
200,
- tenant_id=self.FAKE_QUOTA_TENANT_ID)
+ project_id=self.FAKE_QUOTA_PROJECT_ID)
def test_reset_quotas(self):
self.check_service_client_function(
@@ -150,7 +150,7 @@
"tempest.lib.common.rest_client.RestClient.delete",
{},
status=204,
- tenant_id=self.FAKE_QUOTA_TENANT_ID)
+ project_id=self.FAKE_QUOTA_PROJECT_ID)
def test_list_quotas_with_str_body(self):
self._test_list_quotas()
diff --git a/tempest/tests/lib/services/network/test_security_group_rules_client.py b/tempest/tests/lib/services/network/test_security_group_rules_client.py
index b9c17a1..2ecc996 100644
--- a/tempest/tests/lib/services/network/test_security_group_rules_client.py
+++ b/tempest/tests/lib/services/network/test_security_group_rules_client.py
@@ -14,8 +14,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from oslo_serialization import jsonutils as json
from tempest.lib.services.network import base as network_base
diff --git a/tempest/tests/lib/services/network/test_security_groups_client.py b/tempest/tests/lib/services/network/test_security_groups_client.py
index f96805f..501883b 100644
--- a/tempest/tests/lib/services/network/test_security_groups_client.py
+++ b/tempest/tests/lib/services/network/test_security_groups_client.py
@@ -14,8 +14,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from oslo_serialization import jsonutils as json
from tempest.lib.services.network import base as network_base
diff --git a/tempest/tests/lib/services/object_storage/test_object_client.py b/tempest/tests/lib/services/object_storage/test_object_client.py
index 1749b03..c646d61 100644
--- a/tempest/tests/lib/services/object_storage/test_object_client.py
+++ b/tempest/tests/lib/services/object_storage/test_object_client.py
@@ -14,7 +14,7 @@
# under the License.
-import mock
+from unittest import mock
from tempest.lib import exceptions
from tempest.lib.services.object_storage import object_client
diff --git a/tempest/tests/lib/services/test_clients.py b/tempest/tests/lib/services/test_clients.py
index 43fd88f..f83064a 100644
--- a/tempest/tests/lib/services/test_clients.py
+++ b/tempest/tests/lib/services/test_clients.py
@@ -13,9 +13,9 @@
# the License.
import types
+from unittest import mock
import fixtures
-import mock
import six
import testtools
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index 97e1132..ca7918a 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -45,6 +45,8 @@
"availability_zone": "az1",
"container": "volumebackups",
"created_at": "2013-04-02T10:35:27.000000",
+ "updated_at": "2013-04-02T10:39:27.000000",
+ "data_timestamp": "2013-04-02T10:35:27.000000",
"description": None,
"fail_reason": None,
"id": "2ef47aee-8844-490c-804d-2a8efe561c65",
@@ -64,7 +66,6 @@
"user_id": "515ba0dd59f84f25a6a084a45d8d93b2",
"size": 1,
"status": "available",
- "updated_at": "2013-04-02T10:35:27.000000",
"volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
"is_incremental": True,
"has_dependent_backups": False
diff --git a/tempest/tests/lib/services/volume/v3/test_services_client.py b/tempest/tests/lib/services/volume/v3/test_services_client.py
index f65228f..c807bc2 100644
--- a/tempest/tests/lib/services/volume/v3/test_services_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_services_client.py
@@ -14,8 +14,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from oslo_serialization import jsonutils as json
from tempest.lib.services.volume.v3 import services_client
diff --git a/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
index 1b88020..8309f7a 100644
--- a/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from oslo_serialization import jsonutils as json
diff --git a/tempest/tests/lib/services/volume/v3/test_transfers_client.py b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
index d631fe7..1dfe2df 100644
--- a/tempest/tests/lib/services/volume/v3/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
@@ -14,8 +14,8 @@
# under the License.
import copy
+from unittest import mock
-import mock
from oslo_serialization import jsonutils as json
from tempest.lib.services.volume.v3 import transfers_client
diff --git a/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
index 902f027..3d47caf 100644
--- a/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from oslo_serialization import jsonutils as json
@@ -54,7 +54,6 @@
}
],
"availability_zone": "nova",
- "os-vol-host-attr:host": "controller1@rbd#rbd",
"encrypted": False,
"updated_at": None,
"replication_status": None,
@@ -62,15 +61,12 @@
"id": "c07cd4a4-b52b-4511-a176-fbaa2011a227",
"size": 0,
"user_id": "142d8663efce464c89811c63e45bd82e",
- "os-vol-tenant-attr:tenant_id": "f21a9c86d7114bf99c711f4874d80474",
- "os-vol-mig-status-attr:migstat": None,
"metadata": {},
"status": "creating",
"description": "volume-manage-description",
"multiattach": False,
"source_volid": None,
"consistencygroup_id": None,
- "os-vol-mig-status-attr:name_id": None,
"name": "volume-managed",
"bootable": "false",
"created_at": "2017-07-11T09:14:01.000000",
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index 56c1a35..6bd75d9 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -26,10 +26,6 @@
"volume-summary": {
"total_size": 4,
"total_count": 4,
- "metadata": {
- "key1": ["value1", "value2"],
- "key2": ["value2"]
- }
}
}
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index 9c6cac7..e3c17e8 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -14,8 +14,8 @@
# under the License.
import abc
+from unittest import mock
-import mock
import six
import testtools
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index c849231..85048fb 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -13,8 +13,8 @@
# under the License.
import socket
+from unittest import mock
-import mock
import six
from six import StringIO
import testtools
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index 2b5a947..b154cd5 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
+
from oslo_config import cfg
from tempest import clients
diff --git a/tempest/tests/test_imports.py b/tempest/tests/test_imports.py
index 6f1cfca..ad7bebb 100644
--- a/tempest/tests/test_imports.py
+++ b/tempest/tests/test_imports.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from unittest import mock
from tempest.tests import base
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 49fd010..72e8b6d 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -15,8 +15,8 @@
import os
import sys
+from unittest import mock
-import mock
from oslo_config import cfg
import testtools
diff --git a/test-requirements.txt b/test-requirements.txt
index 17a7d2a..17fa9f1 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,7 +2,6 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=3.0.1,<3.1.0;python_version>='3.5' # Apache-2.0
-mock>=2.0.0 # BSD
coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
pycodestyle>=2.0.0,<2.6.0 # MIT
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 5ffef3e..618c388 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -41,7 +41,7 @@
'x/intel-nfv-ci-tests', # https://review.opendev.org/#/c/634640/
'openstack/networking-generic-switch',
# https://review.opendev.org/#/c/634846/
- 'openstack/networking-l2gw-tempest-plugin',
+ 'x/networking-l2gw-tempest-plugin',
# https://review.opendev.org/#/c/635093/
'openstack/networking-midonet', # https://review.opendev.org/#/c/635096/
'x/networking-plumgrid', # https://review.opendev.org/#/c/635096/
@@ -52,6 +52,8 @@
'x/tap-as-a-service', # To avoid sanity-job failure
'x/valet', # https://review.opendev.org/#/c/638339/
'x/kingbird', # https://bugs.launchpad.net/kingbird/+bug/1869722
+ # vmware-nsx is blacklisted since https://review.opendev.org/#/c/736952
+ 'x/vmware-nsx-tempest-plugin',
]
url = 'https://review.opendev.org/projects/'
diff --git a/tools/tempest-integrated-gate-compute-blacklist.txt b/tools/tempest-integrated-gate-compute-blacklist.txt
index 8805262..2290751 100644
--- a/tools/tempest-integrated-gate-compute-blacklist.txt
+++ b/tools/tempest-integrated-gate-compute-blacklist.txt
@@ -11,3 +11,9 @@
tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops
tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download
tempest.scenario.test_volume_backup_restore.TestVolumeBackupRestore.test_volume_backup_restore
+
+# Skip test scenario when creating second image from instance
+# https://bugs.launchpad.net/tripleo/+bug/1881592
+# The test is most likely wrong and may fail if the fists image is create quickly.
+# FIXME: Either fix the test so it won't race or consider if we should cover the scenario at all.
+tempest.api.compute.images.test_images_oneserver_negative.ImagesOneServerNegativeTestJSON.test_create_second_image_when_first_image_is_being_saved
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 2ff4aea..c983da9 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -66,7 +66,7 @@
# function to create virtualenv to perform sanity operation
function prepare_workspace {
SANITY_DIR=$(pwd)
- virtualenv -p python3 --clear "$SANITY_DIR"/.venv
+ python3 -m venv "$SANITY_DIR"/.venv
export TVENV="$SANITY_DIR/tools/with_venv.sh"
cd "$SANITY_DIR"
diff --git a/tox.ini b/tox.ini
index 0477d6f..031a400 100644
--- a/tox.ini
+++ b/tox.ini
@@ -282,15 +282,31 @@
-r{toxinidir}/requirements.txt
-r{toxinidir}/doc/requirements.txt
commands =
+ sphinx-apidoc -f -o doc/source/tests/compute tempest/api/compute
+ sphinx-apidoc -f -o doc/source/tests/identity tempest/api/identity
+ sphinx-apidoc -f -o doc/source/tests/image tempest/api/image
+ sphinx-apidoc -f -o doc/source/tests/network tempest/api/network
+ sphinx-apidoc -f -o doc/source/tests/object_storage tempest/api/object_storage
+ sphinx-apidoc -f -o doc/source/tests/scenario tempest/scenario
+ sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
rm -rf doc/build
sphinx-build -W -b html doc/source doc/build/html
-whitelist_externals = rm
+whitelist_externals =
+ rm
[testenv:pdf-docs]
deps = {[testenv:docs]deps}
whitelist_externals =
+ rm
make
commands =
+ sphinx-apidoc -f -o doc/source/tests/compute tempest/api/compute
+ sphinx-apidoc -f -o doc/source/tests/identity tempest/api/identity
+ sphinx-apidoc -f -o doc/source/tests/image tempest/api/image
+ sphinx-apidoc -f -o doc/source/tests/network tempest/api/network
+ sphinx-apidoc -f -o doc/source/tests/object_storage tempest/api/object_storage
+ sphinx-apidoc -f -o doc/source/tests/scenario tempest/scenario
+ sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
sphinx-build -W -b latex doc/source doc/build/pdf
make -C doc/build/pdf