Merge "Adding description for testcases - identity part3"
diff --git a/.gitignore b/.gitignore
index 9767e52..8b6222e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,7 +31,7 @@
!.coveragerc
cover/
doc/source/_static/tempest.conf.sample
-doc/source/plugin-registry.rst
+doc/source/plugins/plugin-registry.rst
# Files created by releasenotes build
releasenotes/build
diff --git a/.zuul.yaml b/.zuul.yaml
index 80d49d8..9c53ba9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -154,8 +154,11 @@
Base integration test with Neutron networking.
It includes all scenarios as it was in the past.
This job runs all scenario tests in parallel!
+ timeout: 9000
vars:
tox_envlist: full-parallel
+ run_tempest_cleanup: true
+ run_tempest_dry_cleanup: true
devstack_localrc:
USE_PYTHON3: True
@@ -177,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
@@ -267,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
@@ -447,6 +452,11 @@
USE_PYTHON3: true
- job:
+ name: tempest-full-ussuri-py3
+ parent: tempest-full-py3
+ override-checkout: stable/ussuri
+
+- job:
name: tempest-full-train-py3
parent: tempest-full-py3
override-checkout: stable/train
@@ -457,12 +467,6 @@
override-checkout: stable/stein
- job:
- name: tempest-full-rocky-py3
- parent: tempest-full-py3
- nodeset: openstack-single-node-xenial
- override-checkout: stable/rocky
-
-- job:
name: tempest-tox-plugin-sanity-check
parent: tox
description: |
@@ -532,11 +536,11 @@
run on neutron gate only.
check:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-networking
gate:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-networking
- project-template:
@@ -548,11 +552,11 @@
run on Nova gate only.
check:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-compute
gate:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-compute
- project-template:
@@ -564,11 +568,11 @@
run on Placement gate only.
check:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-placement
gate:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-placement
- project-template:
@@ -580,11 +584,11 @@
run on Cinder and Glance gate only.
check:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-storage
gate:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-storage
- project-template:
@@ -596,11 +600,11 @@
run on swift gate only.
check:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-object-storage
gate:
jobs:
- - grenade-py3
+ - grenade
- tempest-integrated-object-storage
- project:
@@ -608,7 +612,7 @@
- check-requirements
- integrated-gate-py3
- openstack-cover-jobs
- - openstack-python3-ussuri-jobs
+ - openstack-python3-victoria-jobs
- publish-openstack-docs-pti
- release-notes-jobs-python3
check:
@@ -644,12 +648,12 @@
- tempest-full-py3-ipv6:
voting: false
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-ussuri-py3:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-train-py3:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-stein-py3:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-rocky-py3:
- irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
irrelevant-files: *tempest-irrelevant-files
- tempest-tox-plugin-sanity-check:
@@ -674,11 +678,10 @@
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
- - grenade-py3:
+ - grenade:
irrelevant-files: *tempest-irrelevant-files
- puppet-openstack-integration-4-scenario001-tempest-centos-7:
voting: false
@@ -712,23 +715,23 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
irrelevant-files: *tempest-irrelevant-files
- - grenade-py3:
+ - grenade:
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
@@ -736,9 +739,9 @@
irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
+ - tempest-full-ussuri-py3
- tempest-full-train-py3
- tempest-full-stein-py3
- - tempest-full-rocky-py3
periodic:
jobs:
- tempest-all
diff --git a/HACKING.rst b/HACKING.rst
index f8be0ad..95bcbb5 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -59,7 +59,7 @@
`relevant plugin projects`_.
.. _External Plugin Interface: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/tempest-external-plugin-interface.html
-.. _relevant plugin projects: https://docs.openstack.org/tempest/latest/plugin-registry.html#detected-plugins
+.. _relevant plugin projects: https://docs.openstack.org/tempest/latest/plugins/plugin-registry.html#detected-plugins
Exception Handling
------------------
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 9f38ada..30394e8 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,7 +1,7 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-openstackdocstheme>=1.20.0 # Apache-2.0
-reno>=2.5.0 # Apache-2.0
-sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD
+openstackdocstheme>=2.2.0 # Apache-2.0
+reno>=3.1.0 # Apache-2.0
+sphinx>=2.0.0,!=2.1.0 # BSD
sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD
diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess
index 7745594..5422af7 100644
--- a/doc/source/_extra/.htaccess
+++ b/doc/source/_extra/.htaccess
@@ -1 +1,4 @@
redirectmatch 301 ^/developer/tempest/(.*) /tempest/latest/$1
+redirectmatch 301 ^/tempest/latest/plugin.html /tempest/latest/plugins/plugin.html
+redirectmatch 301 ^/tempest/latest/plugin-registry.html /tempest/latest/plugins/plugin-registry
+redirectmatch 301 ^/tempest/latest/#support-policy /tempest/latest/#stable-branch-support-policy
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 7ce431e..ded713d 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -63,9 +63,10 @@
todo_include_todos = True
# openstackdocstheme options
-repository_name = 'openstack/tempest'
-bug_project = 'tempest'
-bug_tag = 'doc'
+openstackdocs_repo_name = 'openstack/tempest'
+openstackdocs_bug_project = 'tempest'
+openstackdocs_bug_tag = 'doc'
+openstackdocs_pdf_link = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -111,7 +112,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.']
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 93fcc3a..d4dc166 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -80,6 +80,7 @@
microversion_testing
test_removal
write_tests
+ requirement_upper_constraint_for_tempest
Plugins
-------
@@ -87,8 +88,7 @@
.. toctree::
:maxdepth: 2
- plugin
- plugin-registry
+ plugins/index
Tempest & Plugins Compatible Version Policy
-------------------------------------------
@@ -98,6 +98,22 @@
tempest_and_plugins_compatible_version_policy
+Stable Branch Support Policy
+----------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ stable_branch_support_policy
+
+Stable Branch Testing Policy
+----------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ stable_branch_testing_policy
+
Library
-------
@@ -106,14 +122,6 @@
library
-Support Policy
---------------
-
-.. toctree::
- :maxdepth: 2
-
- stable_branch_support_policy
-
Search
======
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 50fd4f2..5bc0eac 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -394,6 +394,10 @@
.. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
+ * `2.59`_
+
+ .. _2.59: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+
* `2.60`_
.. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens
diff --git a/doc/source/plugins/index.rst b/doc/source/plugins/index.rst
new file mode 100644
index 0000000..f961ac7
--- /dev/null
+++ b/doc/source/plugins/index.rst
@@ -0,0 +1,40 @@
+=====================
+Tempest Plugins Guide
+=====================
+
+.. toctree::
+ :maxdepth: 2
+
+ plugin
+
+Stable Branch Support Policy
+----------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ ../stable_branch_support_policy
+
+Stable Branch Testing Policy
+----------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ ../stable_branch_testing_policy
+
+Tempest & Plugins Compatible Version Policy
+-------------------------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ ../tempest_and_plugins_compatible_version_policy
+
+Plugins Registry
+----------------
+
+.. toctree::
+ :maxdepth: 2
+
+ plugin-registry
diff --git a/doc/source/plugin.rst b/doc/source/plugins/plugin.rst
similarity index 99%
rename from doc/source/plugin.rst
rename to doc/source/plugins/plugin.rst
index a9e2059..ab1b0b1 100644
--- a/doc/source/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/stable_branch_testing_policy.rst b/doc/source/stable_branch_testing_policy.rst
new file mode 100644
index 0000000..02c5338
--- /dev/null
+++ b/doc/source/stable_branch_testing_policy.rst
@@ -0,0 +1,33 @@
+Stable Branch Testing Policy
+============================
+
+Tempest and its plugins need to support the stable branches
+as per :doc:`Stable Branch Support Policy </stable_branch_support_policy>`.
+
+Because of branchless model of Tempest and plugins, all the supported
+stable branches use the Tempest and plugins master version for their
+testing. That is done in devstack by using the `master branch
+<https://opendev.org/openstack/devstack/src/commit/c104afec7dd72edfd909847bee9c14eaf077a28b/stackrc#L314>`_
+for the Tempest installation. To make sure the master version of Tempest or
+plugins (for any changes or adding new tests) is compatible for all
+the supported stable branches testing, Tempest and its plugins need to
+add the stable branches job on the master gate. That way can test the stable
+branches against master code and can avoid breaking supported branches
+accidentally.
+
+Example:
+
+* `Stable jobs on Tempest master
+ <https://opendev.org/openstack/tempest/src/commit/e8f1876aa6772077f85f380677b30251c2454505/.zuul.yaml#L646-L651>`_.
+
+* `Stable job on neutron tempest plugins
+ <https://opendev.org/openstack/neutron-tempest-plugin/src/commit/4bc1b00213cf660648cad1916fe6497ac29b2e78/.zuul.yaml#L1427-L1428>`_
+
+Once any stable branch is moved to the `Extended Maintenance Phases`_
+and devstack start using the Tempest older version for that stable
+branch testing then we can remove that stable branch job from master
+gate.
+
+Example: https://review.opendev.org/#/c/722183/
+
+.. _Extended Maintenance Phases: https://docs.openstack.org/project-team-guide/stable-branches.html#extended-maintenance
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index 4f65fd4..388b4cd 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -9,9 +9,9 @@
Tempest master supports the below OpenStack Releases:
+* Ussuri
* Train
* Stein
-* Rocky
For older OpenStack Release:
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index 5f87abd..7ee7411 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -12,8 +12,41 @@
# job provided by the gabbi-tempest plugin. It can be safely ignored
# if that plugin is not being used.
GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}"
- roles:
- - setup-tempest-run-dir
- - setup-tempest-data-dir
- - acl-devstack-files
- - run-tempest
+ tasks:
+ - name: Setup Tempest Run Directory
+ include_role:
+ name: setup-tempest-run-dir
+
+ - name: Setup Tempest Data Directory
+ include_role:
+ name: setup-tempest-data-dir
+
+ - name: ACL devstack files
+ include_role:
+ name: acl-devstack-files
+
+ - name: Run tempest cleanup init-saved-state
+ include_role:
+ name: tempest-cleanup
+ vars:
+ init_saved_state: true
+ when:
+ - run_tempest_dry_cleanup is defined
+ - run_tempest_cleanup is defined
+
+ - name: Run Tempest
+ include_role:
+ name: run-tempest
+
+ - name: Run tempest cleanup dry-run
+ include_role:
+ name: tempest-cleanup
+ vars:
+ dry_run: true
+ when:
+ - run_tempest_dry_cleanup is defined
+
+ - name: Run tempest cleanup
+ include_role:
+ name: tempest-cleanup
+ when: run_tempest_cleanup is defined
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/account_generator-6eb03f664a448c35.yaml b/releasenotes/notes/account_generator-6eb03f664a448c35.yaml
new file mode 100644
index 0000000..ade632f
--- /dev/null
+++ b/releasenotes/notes/account_generator-6eb03f664a448c35.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - |
+ Remove the deprecated CLI ``tempest-account-generator`` in favor of
+ ``tempest account-generator`` command.
+ You can use ``tempest account-generator`` CLI to generate the accounts
+ yaml file.
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/source/conf.py b/releasenotes/source/conf.py
index 92df4c4..9a9de43 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -42,9 +42,9 @@
]
# openstackdocstheme options
-repository_name = 'openstack/tempest'
-bug_project = 'tempest'
-bug_tag = ''
+openstackdocs_repo_name = 'openstack/tempest'
+openstackdocs_bug_project = 'tempest'
+openstackdocs_bug_tag = ''
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -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/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
index 3724e0e..e3a0a0e 100644
--- a/roles/process-stackviz/tasks/main.yaml
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -17,13 +17,18 @@
when: not subunit_input.stat.exists
- name: Install stackviz
- pip:
- name: "file://{{ stackviz_archive.stat.path }}"
- virtualenv: /tmp/stackviz
- extra_args: -U
when:
- stackviz_archive.stat.exists
- subunit_input.stat.exists
+ block:
+ - include_role:
+ name: ensure-pip
+
+ - pip:
+ name: "file://{{ stackviz_archive.stat.path }}"
+ virtualenv: /tmp/stackviz
+ virtualenv_command: '{{ ensure_pip_virtualenv_command }}'
+ extra_args: -U
- name: Deploy stackviz static html+js
command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 91b0b5f..3643edb 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -1,5 +1,8 @@
Run Tempest
+The result of the tempest run is stored in the `tempest_run_result`
+variable (through the `register` statement).
+
**Role Variables**
.. zuul:rolevar:: devstack_base_dir
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 82bc265..1de3725 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -56,6 +56,7 @@
--black-regex={{tempest_black_regex|quote}}
args:
chdir: "{{devstack_base_dir}}/tempest"
+ register: tempest_run_result
become: true
become_user: tempest
environment: "{{ tempest_tox_environment }}"
diff --git a/roles/tempest-cleanup/README.rst b/roles/tempest-cleanup/README.rst
new file mode 100644
index 0000000..70719ca
--- /dev/null
+++ b/roles/tempest-cleanup/README.rst
@@ -0,0 +1,33 @@
+Tempest cleanup
+===============
+
+Documentation regarding tempest cleanup can be found at the following
+link:
+https://docs.openstack.org/tempest/latest/cleanup.html
+
+When init_saved_state and dry_run variables are set to false, the role
+execution will run tempest cleanup which deletes resources not present in the
+saved_state.json file.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+ :default: /opt/stack
+
+ The devstack base directory.
+
+.. zuul:rolevar:: init_saved_state
+ :default: false
+
+ When true, tempest cleanup --init-saved-state will be executed which
+ initializes the saved state of the OpenStack deployment and will output
+ a saved_state.json file containing resources from the deployment that will
+ be preserved from the cleanup command. This should be done prior to running
+ Tempest tests.
+
+.. zuul:rolevar:: dry_run
+ :default: false
+
+ When true, tempest cleanup creates a report (./dry_run.json) of the
+ resources that would be cleaned up if the role was ran with dry_run option
+ set to false.
diff --git a/roles/tempest-cleanup/defaults/main.yaml b/roles/tempest-cleanup/defaults/main.yaml
new file mode 100644
index 0000000..fc1948a
--- /dev/null
+++ b/roles/tempest-cleanup/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+init_saved_state: false
+dry_run: false
diff --git a/roles/tempest-cleanup/tasks/main.yaml b/roles/tempest-cleanup/tasks/main.yaml
new file mode 100644
index 0000000..5444afc
--- /dev/null
+++ b/roles/tempest-cleanup/tasks/main.yaml
@@ -0,0 +1,31 @@
+- when: init_saved_state
+ block:
+ - name: Run tempest cleanup init-saved-state
+ become: yes
+ become_user: tempest
+ command: tox -evenv-tempest -- tempest cleanup --init-saved-state --debug
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
+
+ - name: Cat saved_state.json
+ command: cat "{{ devstack_base_dir }}/tempest/saved_state.json"
+
+- when: dry_run
+ block:
+ - name: Run tempest cleanup dry-run
+ become: yes
+ become_user: tempest
+ command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
+
+ - name: Cat dry_run.json
+ command: cat "{{ devstack_base_dir }}/tempest/dry_run.json"
+
+- name: Run tempest cleanup
+ become: yes
+ become_user: tempest
+ command: tox -evenv-tempest -- tempest cleanup --debug
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
+ when: not dry_run and not init_saved_state
diff --git a/setup.cfg b/setup.cfg
index 04511e1..18427a2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -17,6 +17,7 @@
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
@@ -28,7 +29,6 @@
[entry_points]
console_scripts =
- tempest-account-generator = tempest.cmd.account_generator:main
tempest = tempest.cmd.main:main
skip-tracker = tempest.lib.cmd.skip_tracker:main
check-uuid = tempest.lib.cmd.check_uuid:run
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_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_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_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/base.py b/tempest/api/compute/base.py
index eab2a8d..74570ce 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -109,6 +109,7 @@
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
cls.attachments_client = cls.os_primary.attachments_client_latest
+ cls.snapshots_client = cls.os_primary.snapshots_client_latest
if CONF.service_available.glance:
if CONF.image_feature_enabled.api_v1:
cls.images_client = cls.os_primary.image_client
@@ -578,6 +579,25 @@
volume['id'], 'in-use')
return attachment
+ def create_volume_snapshot(self, volume_id, name=None, description=None,
+ metadata=None, force=False):
+ name = name or data_utils.rand_name(
+ self.__class__.__name__ + '-snapshot')
+ snapshot = self.snapshots_client.create_snapshot(
+ volume_id=volume_id,
+ force=force,
+ 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'])
+ waiters.wait_for_volume_resource_status(self.snapshots_client,
+ snapshot['id'], 'available')
+ snapshot = self.snapshots_client.show_snapshot(
+ snapshot['id'])['snapshot']
+ return snapshot
+
def assert_flavor_equal(self, flavor_id, server_flavor):
"""Check whether server_flavor equals to flavor.
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 2c1ad80..1247494 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -106,24 +106,33 @@
name=sg['name'])
-class ServerStableDeviceRescueTest(base.BaseV2ComputeTest):
+class BaseServerStableDeviceRescueTest(base.BaseV2ComputeTest):
create_default_network = True
@classmethod
- def skip_checks(self):
- super(ServerStableDeviceRescueTest, self).skip_checks()
+ def skip_checks(cls):
+ super(BaseServerStableDeviceRescueTest, cls).skip_checks()
if not CONF.compute_feature_enabled.rescue:
msg = "Server rescue not available."
- raise self.skipException(msg)
+ raise cls.skipException(msg)
if not CONF.compute_feature_enabled.stable_rescue:
msg = "Stable rescue not available."
- raise self.skipException(msg)
+ raise cls.skipException(msg)
def _create_server_and_rescue_image(self, hw_rescue_device=None,
- hw_rescue_bus=None):
- server_id = self.create_test_server(wait_until='ACTIVE')['id']
- image_id = self.create_image_from_server(server_id,
- wait_until='ACTIVE')['id']
+ hw_rescue_bus=None,
+ block_device_mapping_v2=None):
+
+ server_id = self.create_test_server(
+ wait_until='ACTIVE')['id']
+ image_id = self.create_image_from_server(
+ server_id, wait_until='ACTIVE')['id']
+
+ if block_device_mapping_v2:
+ server_id = self.create_test_server(
+ wait_until='ACTIVE',
+ block_device_mapping_v2=block_device_mapping_v2)['id']
+
if hw_rescue_bus:
self.images_client.update_image(
image_id, [dict(add='/hw_rescue_bus',
@@ -143,6 +152,9 @@
waiters.wait_for_server_status(
self.servers_client, server_id, 'ACTIVE')
+
+class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
+
@decorators.idempotent_id('947004c3-e8ef-47d9-9f00-97b74f9eaf96')
def test_stable_device_rescue_cdrom_ide(self):
server_id, rescue_image_id = self._create_server_and_rescue_image(
@@ -177,3 +189,35 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
self._test_stable_device_rescue(server_id, rescue_image_id)
+
+
+class ServerBootFromVolumeStableRescueTest(BaseServerStableDeviceRescueTest):
+
+ 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):
+ block_device_mapping_v2 = [{
+ "boot_index": "0",
+ "source_type": "blank",
+ "volume_size": CONF.volume.volume_size,
+ "destination_type": "volume"}]
+ server_id, rescue_image_id = self._create_server_and_rescue_image(
+ hw_rescue_device='disk', hw_rescue_bus='virtio',
+ block_device_mapping_v2=block_device_mapping_v2)
+ self._test_stable_device_rescue(server_id, rescue_image_id)
+
+ @decorators.attr(type='slow')
+ @decorators.idempotent_id('e4636333-c928-40fc-98b7-70a23eef4224')
+ def test_stable_device_rescue_bfv_image_volume(self):
+ block_device_mapping_v2 = [{
+ "boot_index": "0",
+ "source_type": "image",
+ "volume_size": CONF.volume.volume_size,
+ "uuid": CONF.compute.image_ref,
+ "destination_type": "volume"}]
+ server_id, rescue_image_id = self._create_server_and_rescue_image(
+ hw_rescue_device='disk', hw_rescue_bus='virtio',
+ block_device_mapping_v2=block_device_mapping_v2)
+ self._test_stable_device_rescue(server_id, rescue_image_id)
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_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_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_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/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index fa1c47f..cb05f39 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import operator
+
from oslo_utils import timeutils
import six
@@ -40,6 +42,15 @@
authenticated_token = self.non_admin_client.show_token(
subject_token)['token']
# sanity checking to make sure they are indeed the same token
+ # If there are roles in the token, sort the roles
+ authenticated_token_roles = authenticated_token.get("roles")
+ if authenticated_token_roles:
+ authenticated_token["roles"] = authenticated_token_roles.sort(
+ key=operator.itemgetter('id'))
+ token_body_roles = token_body.get("roles")
+ if token_body_roles:
+ token_body["roles"] = token_body_roles.sort(
+ key=operator.itemgetter('id'))
self.assertEqual(authenticated_token, token_body)
# test to see if token has been properly authenticated
self.assertEqual(authenticated_token['user']['id'], user_id)
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index bf2e510..5e2c8af 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -19,9 +19,11 @@
class ImageMembersTest(base.BaseV1ImageMembersTest):
+ """Test image members"""
@decorators.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
def test_add_image_member(self):
+ """Test adding member for image"""
image = self._create_image()
self.image_member_client.create_image_member(image, self.alt_tenant_id)
body = self.image_member_client.list_image_members(image)
@@ -33,6 +35,7 @@
@decorators.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
def test_get_shared_images(self):
+ """Test getting shared images"""
image = self._create_image()
self.image_member_client.create_image_member(image, self.alt_tenant_id)
share_image = self._create_image()
@@ -47,6 +50,7 @@
@decorators.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
def test_remove_member(self):
+ """Test removing member from image"""
image_id = self._create_image()
self.image_member_client.create_image_member(image_id,
self.alt_tenant_id)
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 2748bd5..4e3c27c 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -19,11 +19,12 @@
class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
+ """Negative tests of image members"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('147a9536-18e3-45da-91ea-b037a028f364')
def test_add_member_with_non_existing_image(self):
- # Add member with non existing image.
+ """Add member with non existing image"""
non_exist_image = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.image_member_client.create_image_member,
@@ -32,7 +33,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e1559f05-b667-4f1b-a7af-518b52dc0c0f')
def test_delete_member_with_non_existing_image(self):
- # Delete member with non existing image.
+ """Delete member with non existing image"""
non_exist_image = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.image_member_client.delete_image_member,
@@ -41,7 +42,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f5720333-dd69-4194-bb76-d2f048addd56')
def test_delete_member_with_non_existing_tenant(self):
- # Delete member with non existing tenant.
+ """Delete member from image with non existing tenant"""
image_id = self._create_image()
non_exist_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
@@ -51,7 +52,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f25f89e4-0b6c-453b-a853-1f80b9d7ef26')
def test_get_image_without_membership(self):
- # Image is hidden from another tenants.
+ """Get image without membership
+
+ Image is hidden from another tenants.
+ """
image_id = self._create_image()
self.assertRaises(lib_exc.NotFound,
self.alt_img_cli.show_image,
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 2432c8b..595717e 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -57,7 +57,7 @@
@decorators.idempotent_id('3027f8e6-3492-4a11-8575-c3293017af4d')
def test_register_then_upload(self):
- # Register, then upload an image
+ """Register, then upload an image"""
properties = {'prop1': 'val1'}
container_format, disk_format = get_container_and_disk_format()
image = self.create_image(name='New Name',
@@ -79,7 +79,7 @@
@decorators.idempotent_id('69da74d9-68a9-404b-9664-ff7164ccb0f5')
def test_register_remote_image(self):
- # Register a new remote image
+ """Register a new remote image"""
container_format, disk_format = get_container_and_disk_format()
body = self.create_image(name='New Remote Image',
container_format=container_format,
@@ -96,6 +96,7 @@
@decorators.idempotent_id('6d0e13a7-515b-460c-b91f-9f4793f09816')
def test_register_http_image(self):
+ """Register a new image from an http image path url"""
container_format, disk_format = get_container_and_disk_format()
image = self.create_image(name='New Http Image',
container_format=container_format,
@@ -108,7 +109,7 @@
@decorators.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
def test_register_image_with_min_ram(self):
- # Register an image with min ram
+ """Register an image with min ram"""
container_format, disk_format = get_container_and_disk_format()
properties = {'prop1': 'val1'}
body = self.create_image(name='New_image_with_min_ram',
@@ -213,7 +214,7 @@
@decorators.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d')
def test_index_no_params(self):
- # Simple test to see all fixture images returned
+ """Simple test to see all fixture images returned"""
images_list = self.client.list_images()['images']
image_list = [image['id'] for image in images_list]
for image_id in self.created_images:
@@ -221,6 +222,7 @@
@decorators.idempotent_id('f1755589-63d6-4468-b098-589820eb4031')
def test_index_disk_format(self):
+ """Test listing images by disk format"""
images_list = self.client.list_images(
disk_format=self.disk_format_alt)['images']
for image in images_list:
@@ -232,6 +234,7 @@
@decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
def test_index_container_format(self):
+ """Test listing images by container format"""
images_list = self.client.list_images(
container_format=self.container_format)['images']
for image in images_list:
@@ -243,6 +246,7 @@
@decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
def test_index_max_size(self):
+ """Test listing images by max size"""
images_list = self.client.list_images(size_max=42)['images']
for image in images_list:
self.assertLessEqual(image['size'], 42)
@@ -252,6 +256,7 @@
@decorators.idempotent_id('6ffc16d0-4cbf-4401-95c8-4ac63eac34d8')
def test_index_min_size(self):
+ """Test listing images by min size"""
images_list = self.client.list_images(size_min=142)['images']
for image in images_list:
self.assertGreaterEqual(image['size'], 142)
@@ -261,6 +266,7 @@
@decorators.idempotent_id('e5dc26d9-9aa2-48dd-bda5-748e1445da98')
def test_index_status_active_detail(self):
+ """Test listing active images sorting by size in descending order"""
images_list = self.client.list_images(detail=True,
status='active',
sort_key='size',
@@ -274,6 +280,7 @@
@decorators.idempotent_id('097af10a-bae8-4342-bff4-edf89969ed2a')
def test_index_name(self):
+ """Test listing images by its name"""
images_list = self.client.list_images(
detail=True,
name='New Remote Image dup')['images']
@@ -285,6 +292,8 @@
class UpdateImageMetaTest(base.BaseV1ImageTest):
+ """Test image metadata"""
+
@classmethod
def resource_setup(cls):
super(UpdateImageMetaTest, cls).resource_setup()
@@ -308,6 +317,7 @@
@decorators.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
def test_list_image_metadata(self):
+ """Test listing image metadata"""
# All metadata key/value pairs for an image should be returned
resp = self.client.check_image(self.image_id)
resp_metadata = common_image.get_image_meta_from_headers(resp)
@@ -316,6 +326,7 @@
@decorators.idempotent_id('d6d7649c-08ce-440d-9ea7-e3dda552f33c')
def test_update_image_metadata(self):
+ """Test updating image metadata"""
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
resp = self.client.check_image(self.image_id)
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index 690b8da..2af1288 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -26,7 +26,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('036ede36-6160-4463-8c01-c781eee6369d')
def test_register_with_invalid_container_format(self):
- # Negative tests for invalid data supplied to POST /images
+ """Create image with invalid container format
+
+ Negative tests for invalid data supplied to POST /images
+ """
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
headers={'x-image-meta-name': 'test',
'x-image-meta-container_format': 'wrong',
@@ -35,6 +38,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67')
def test_register_with_invalid_disk_format(self):
+ """Create image with invalid disk format"""
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
headers={'x-image-meta-name': 'test',
'x-image-meta-container_format': 'bare',
@@ -43,7 +47,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ec652588-7e3c-4b67-a2f2-0fa96f57c8fc')
def test_delete_non_existent_image(self):
- # Return an error while trying to delete a non-existent image
+ """Return an error while trying to delete a non-existent image"""
non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
@@ -52,13 +56,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('04f72aa3-fcec-45a3-81a3-308ef7cc82bc')
def test_delete_image_blank_id(self):
- # Return an error while trying to delete an image with blank Id
+ """Return an error while trying to delete an image with blank Id"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('950e5054-a3c7-4dee-ada5-e576f1087abd')
def test_delete_image_non_hex_string_id(self):
- # Return an error while trying to delete an image with non hex id
+ """Return an error while trying to delete an image with non hex id"""
invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
invalid_image_id)
@@ -66,13 +70,13 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4ed757cd-450c-44b1-9fd1-c819748c650d')
def test_delete_image_negative_image_id(self):
- # Return an error while trying to delete an image with negative id
+ """Return an error while trying to delete an image with negative id"""
self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a4a448ab-3db2-4d2d-b9b2-6a1271241dfe')
def test_delete_image_id_over_character_limit(self):
- # Return an error while trying to delete image with id over limit
+ """Return an error while trying to delete image with id over limit"""
overlimit_image_id = data_utils.rand_uuid() + "1"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
overlimit_image_id)
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index dbb8c58..7e13d7f 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -19,10 +19,12 @@
class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
+ """"Test image operations about image owner"""
@decorators.related_bug('1420008')
@decorators.idempotent_id('646a6eaa-135f-4493-a0af-12583021224e')
def test_create_image_owner_param(self):
+ """Test creating image with specified owner"""
# NOTE: Create image with owner different from tenant owner by
# using "owner" parameter requires an admin privileges.
random_id = data_utils.rand_uuid_hex()
@@ -35,6 +37,7 @@
@decorators.related_bug('1420008')
@decorators.idempotent_id('525ba546-10ef-4aad-bba1-1858095ce553')
def test_update_image_owner_param(self):
+ """Test updating image owner"""
random_id_1 = data_utils.rand_uuid_hex()
image = self.admin_client.create_image(
container_format='bare', disk_format='raw', owner=random_id_1)
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 5a27a43..3e72b34 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -29,6 +29,64 @@
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)
+
+ @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
+ def test_image_import(self):
+ """Here we test these functionalities
+
+ Create image, stage image data, import image and verify
+ that import succeeded.
+ """
+
+ body = self.client.info_import()
+ if 'glance-direct' not in body['import-methods']['value']:
+ raise self.skipException('Server does not support '
+ 'glance-direct import method')
+
+ # 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'])
+
+ # Stage image data
+ file_content = data_utils.random_bytes()
+ image_file = six.BytesIO(file_content)
+ self.client.stage_image_file(image['id'], image_file)
+
+ # Now try to get image details
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual(image_name, body['name'])
+ self.assertEqual(uuid, body['ramdisk_id'])
+ self.assertEqual('uploading', body['status'])
+
+ # import image from staging to backend
+ self.client.image_import(image['id'])
+ self.client.wait_for_resource_activation(image['id'])
+
+
class BasicOperationsImagesTest(base.BaseV2ImageTest):
"""Here we test the basic operations of images"""
@@ -88,8 +146,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('f848bb94-1c6e-45a4-8726-39e3a5b23535')
def test_delete_image(self):
- # Deletes an image by image_id
-
+ """Test deleting an image by image_id"""
# Create image
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
@@ -110,8 +167,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('f66891a7-a35c-41a8-b590-a065c2a1caa6')
def test_update_image(self):
- # Updates an image by image_id
-
+ """Test updating an image by image_id"""
# Create image
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
@@ -135,6 +191,7 @@
@decorators.idempotent_id('951ebe01-969f-4ea9-9898-8a3f1f442ab0')
def test_deactivate_reactivate_image(self):
+ """Test deactivating and reactivating an image"""
# Create image
image_name = data_utils.rand_name('image')
image = self.create_image(name=image_name,
@@ -235,7 +292,7 @@
@decorators.idempotent_id('1e341d7a-90a9-494c-b143-2cdf2aeb6aee')
def test_list_no_params(self):
- # Simple test to see all fixture images returned
+ """Simple test to see all fixture images returned"""
images_list = self.client.list_images()['images']
image_list = [image['id'] for image in images_list]
@@ -244,25 +301,25 @@
@decorators.idempotent_id('9959ca1d-1aa7-4b7a-a1ea-0fff0499b37e')
def test_list_images_param_container_format(self):
- # Test to get all images with a specific container_format
+ """Test to get all images with a specific container_format"""
params = {"container_format": self.test_data['container_format']}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('4a4735a7-f22f-49b6-b0d9-66e1ef7453eb')
def test_list_images_param_disk_format(self):
- # Test to get all images with disk_format = raw
+ """Test to get all images with disk_format = raw"""
params = {"disk_format": "raw"}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('7a95bb92-d99e-4b12-9718-7bc6ab73e6d2')
def test_list_images_param_visibility(self):
- # Test to get all images with visibility = private
+ """Test to get all images with visibility = private"""
params = {"visibility": "private"}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('cf1b9a48-8340-480e-af7b-fe7e17690876')
def test_list_images_param_size(self):
- # Test to get all images by size
+ """Test to get all images by size"""
image_id = self.created_images[0]
# Get image metadata
image = self.client.show_image(image_id)
@@ -272,7 +329,7 @@
@decorators.idempotent_id('4ad8c157-971a-4ba8-aa84-ed61154b1e7f')
def test_list_images_param_min_max_size(self):
- # Test to get all images with size between 2000 to 3000
+ """Test to get all images with min size and max size"""
image_id = self.created_images[0]
# Get image metadata
image = self.client.show_image(image_id)
@@ -290,13 +347,13 @@
@decorators.idempotent_id('7fc9e369-0f58-4d05-9aa5-0969e2d59d15')
def test_list_images_param_status(self):
- # Test to get all active images
+ """Test to get all active images"""
params = {"status": "active"}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('e914a891-3cc8-4b40-ad32-e0a39ffbddbb')
def test_list_images_param_limit(self):
- # Test to get images by limit
+ """Test to get images by limit"""
params = {"limit": 1}
images_list = self.client.list_images(params=params)['images']
@@ -305,7 +362,7 @@
@decorators.idempotent_id('e9a44b91-31c8-4b40-a332-e0a39ffb4dbb')
def test_list_image_param_owner(self):
- # Test to get images by owner
+ """Test to get images by owner"""
image_id = self.created_images[0]
# Get image metadata
image = self.client.show_image(image_id)
@@ -315,13 +372,13 @@
@decorators.idempotent_id('55c8f5f5-bfed-409d-a6d5-4caeda985d7b')
def test_list_images_param_name(self):
- # Test to get images by name
+ """Test to get images by name"""
params = {'name': self.test_data['name']}
self._list_by_param_value_and_assert(params)
@decorators.idempotent_id('aa8ac4df-cff9-418b-8d0f-dd9c67b072c9')
def test_list_images_param_tag(self):
- # Test to get images matching a tag
+ """Test to get images matching a tag"""
params = {'tag': self.test_data['tags'][0]}
images_list = self.client.list_images(params=params)['images']
# Validating properties of fetched images
@@ -336,24 +393,26 @@
@decorators.idempotent_id('eeadce49-04e0-43b7-aec7-52535d903e7a')
def test_list_images_param_sort(self):
+ """Test listing images sorting in descending order"""
params = {'sort': 'size:desc'}
self._list_sorted_by_image_size_and_assert(params, desc=True)
@decorators.idempotent_id('9faaa0c2-c3a5-43e1-8f61-61c54b409a49')
def test_list_images_param_sort_key_dir(self):
+ """Test listing images sorting by size in descending order"""
params = {'sort_key': 'size', 'sort_dir': 'desc'}
self._list_sorted_by_image_size_and_assert(params, desc=True)
@decorators.idempotent_id('622b925c-479f-4736-860d-adeaf13bc371')
def test_get_image_schema(self):
- # Test to get image schema
+ """Test to get image schema"""
schema = "image"
body = self.schemas_client.show_schema(schema)
self.assertEqual("image", body['name'])
@decorators.idempotent_id('25c8d7b2-df21-460f-87ac-93130bcdc684')
def test_get_images_schema(self):
- # Test to get images schema
+ """Test to get images schema"""
schema = "images"
body = self.schemas_client.show_schema(schema)
self.assertEqual("images", body['name'])
@@ -372,6 +431,7 @@
@decorators.idempotent_id('3fa50be4-8e38-4c02-a8db-7811bb780122')
def test_list_images_param_member_status(self):
+ """Test listing images by member_status and visibility"""
# Create an image to be shared using default visibility
image_file = six.BytesIO(data_utils.random_bytes(2048))
container_format = CONF.image.container_formats[0]
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index e19d8c8..bc67859 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -15,9 +15,11 @@
class ImagesMemberTest(base.BaseV2MemberImageTest):
+ """Test image members"""
@decorators.idempotent_id('5934c6ea-27dc-4d6e-9421-eeb5e045494a')
def test_image_share_accept(self):
+ """Test sharing and accepting an image"""
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
@@ -41,6 +43,7 @@
@decorators.idempotent_id('d9e83e5f-3524-4b38-a900-22abcb26e90e')
def test_image_share_reject(self):
+ """Test sharing and rejecting an image"""
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
@@ -57,6 +60,7 @@
@decorators.idempotent_id('a6ee18b9-4378-465e-9ad9-9a6de58a3287')
def test_get_image_member(self):
+ """Test getting image members after the image is accepted"""
image_id = self._create_image()
self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
@@ -75,6 +79,7 @@
@decorators.idempotent_id('72989bc7-2268-48ed-af22-8821e835c914')
def test_remove_image_member(self):
+ """Test removing image members after the image is accepted"""
image_id = self._create_image()
self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
@@ -89,10 +94,12 @@
@decorators.idempotent_id('634dcc3f-f6e2-4409-b8fd-354a0bb25d83')
def test_get_image_member_schema(self):
+ """Test getting image member schema"""
body = self.schemas_client.show_schema("member")
self.assertEqual("member", body['name'])
@decorators.idempotent_id('6ae916ef-1052-4e11-8d36-b3ae14853cbb')
def test_get_image_members_schema(self):
+ """Test getting image members schema"""
body = self.schemas_client.show_schema("members")
self.assertEqual("members", body['name'])
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index caa90f9..5f6f1ae 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -16,10 +16,12 @@
class ImagesMemberNegativeTest(base.BaseV2MemberImageTest):
+ """Negative tests of image members"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b79efb37-820d-4cf0-b54c-308b00cf842c')
def test_image_share_invalid_status(self):
+ """Test updating image member status to invalid status should fail"""
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
@@ -32,6 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('27002f74-109e-4a37-acd0-f91cd4597967')
def test_image_share_owner_cannot_accept(self):
+ """Test that image owner can't accept image shared to other member"""
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py b/tempest/api/image/v2/test_images_metadefs_namespace_objects.py
index 80f8112..32b81b1 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_objects.py
@@ -30,6 +30,7 @@
@decorators.idempotent_id('b1a3775e-3b5c-4f6a-a3b4-1ba3574ae718')
def test_create_update_delete_meta_namespace_objects(self):
+ """Test creating/updating/deleting image metadata namespace objects"""
# Create a namespace
namespace = self.create_namespace()
# Create a namespace object
@@ -52,6 +53,7 @@
@decorators.idempotent_id('a2a3615e-3b5c-3f6a-a2b1-1ba3574ae738')
def test_list_meta_namespace_objects(self):
+ """Test listing image metadata namespace objects"""
# Create a namespace object
namespace = self.create_namespace()
meta_namespace_object = self._create_namespace_object(namespace)
@@ -64,6 +66,7 @@
@decorators.idempotent_id('b1a3674e-3b4c-3f6a-a3b4-1ba3573ca768')
def test_show_meta_namespace_objects(self):
+ """Test showing image metadata namespace object"""
# Create a namespace object
namespace = self.create_namespace()
namespace_object = self._create_namespace_object(namespace)
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py b/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
index ed91726..1d4f0a6 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
@@ -20,6 +20,7 @@
@decorators.idempotent_id('b1a3765e-3a5d-4f6d-a3a7-3ca3476ae768')
def test_basic_meta_def_namespace_property(self):
+ """Test operations of image metadata definition namespace property"""
# Get the available resource types and use one resource_type
body = self.resource_types_client.list_resource_types()
resource_name = body['resource_types'][0]['name']
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
index 482e808..dc64185 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -43,6 +43,7 @@
@decorators.idempotent_id('a2a3765e-3a6d-4f6d-a3a7-3cc3476aa876')
def test_create_list_delete_namespace_tags(self):
+ """Test creating/listing/deleting image metadata namespace tags"""
# Create a namespace
namespace = self.create_namespace()
self._create_namespace_tags(namespace)
@@ -62,6 +63,7 @@
@decorators.idempotent_id('a2a3765e-1a2c-3f6d-a3a7-3cc3466ab875')
def test_create_update_delete_tag(self):
+ """Test creating/updating/deleting image metadata namespace tag"""
# Create a namespace
namespace = self.create_namespace()
self._create_namespace_tags(namespace)
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index f71b16c..502949f 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -25,6 +25,7 @@
@decorators.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768')
def test_basic_metadata_definition_namespaces(self):
+ """Test operations of image metadata definition namespaces"""
# get the available resource types and use one resource_type
body = self.resource_types_client.list_resource_types()
resource_name = body['resource_types'][0]['name']
diff --git a/tempest/api/image/v2/test_images_metadefs_resource_types.py b/tempest/api/image/v2/test_images_metadefs_resource_types.py
index c60b3f7..6867f2d 100644
--- a/tempest/api/image/v2/test_images_metadefs_resource_types.py
+++ b/tempest/api/image/v2/test_images_metadefs_resource_types.py
@@ -22,6 +22,7 @@
@decorators.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606')
def test_basic_meta_def_resource_type_association(self):
+ """Test image resource type associations"""
# Get the available resource types and use one resource_type
body = self.resource_types_client.list_resource_types()
resource_name = body['resource_types'][0]['name']
diff --git a/tempest/api/image/v2/test_images_metadefs_schema.py b/tempest/api/image/v2/test_images_metadefs_schema.py
index 95cc310..7dd36d2 100644
--- a/tempest/api/image/v2/test_images_metadefs_schema.py
+++ b/tempest/api/image/v2/test_images_metadefs_schema.py
@@ -18,64 +18,64 @@
class MetadataSchemaTest(base.BaseV2ImageTest):
- """Test to get metadata schema"""
+ """Test to get image metadata schema"""
@decorators.idempotent_id('e9e44891-3cb8-3b40-a532-e0a39fea3dab')
def test_get_metadata_namespace_schema(self):
- # Test to get namespace schema
+ """Test to get image namespace schema"""
body = self.schemas_client.show_schema("metadefs/namespace")
self.assertEqual("namespace", body['name'])
@decorators.idempotent_id('ffe44891-678b-3ba0-a3e2-e0a3967b3aeb')
def test_get_metadata_namespaces_schema(self):
- # Test to get namespaces schema
+ """Test to get image namespaces schema"""
body = self.schemas_client.show_schema("metadefs/namespaces")
self.assertEqual("namespaces", body['name'])
@decorators.idempotent_id('fde34891-678b-3b40-ae32-e0a3e67b6beb')
def test_get_metadata_resource_type_schema(self):
- # Test to get resource_type schema
+ """Test to get image resource_type schema"""
body = self.schemas_client.show_schema("metadefs/resource_type")
self.assertEqual("resource_type_association", body['name'])
@decorators.idempotent_id('dfe4a891-b38b-3bf0-a3b2-e03ee67b3a3a')
def test_get_metadata_resources_types_schema(self):
- # Test to get resource_types schema
+ """Test to get image resource_types schema"""
body = self.schemas_client.show_schema("metadefs/resource_types")
self.assertEqual("resource_type_associations", body['name'])
@decorators.idempotent_id('dff4a891-b38b-3bf0-a3b2-e03ee67b3a3b')
def test_get_metadata_object_schema(self):
- # Test to get object schema
+ """Test to get image object schema"""
body = self.schemas_client.show_schema("metadefs/object")
self.assertEqual("object", body['name'])
@decorators.idempotent_id('dee4a891-b38b-3bf0-a3b2-e03ee67b3a3c')
def test_get_metadata_objects_schema(self):
- # Test to get objects schema
+ """Test to get image objects schema"""
body = self.schemas_client.show_schema("metadefs/objects")
self.assertEqual("objects", body['name'])
@decorators.idempotent_id('dae4a891-b38b-3bf0-a3b2-e03ee67b3a3d')
def test_get_metadata_property_schema(self):
- # Test to get property schema
+ """Test to get image property schema"""
body = self.schemas_client.show_schema("metadefs/property")
self.assertEqual("property", body['name'])
@decorators.idempotent_id('dce4a891-b38b-3bf0-a3b2-e03ee67b3a3e')
def test_get_metadata_properties_schema(self):
- # Test to get properties schema
+ """Test to get image properties schema"""
body = self.schemas_client.show_schema("metadefs/properties")
self.assertEqual("properties", body['name'])
@decorators.idempotent_id('dde4a891-b38b-3bf0-a3b2-e03ee67b3a3e')
def test_get_metadata_tag_schema(self):
- # Test to get tag schema
+ """Test to get image tag schema"""
body = self.schemas_client.show_schema("metadefs/tag")
self.assertEqual("tag", body['name'])
@decorators.idempotent_id('cde4a891-b38b-3bf0-a3b2-e03ee67b3a3a')
def test_get_metadata_tags_schema(self):
- # Test to get tags schema
+ """Test to get image tags schema"""
body = self.schemas_client.show_schema("metadefs/tags")
self.assertEqual("tags", body['name'])
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index b4baf05..dc2bb96 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -36,7 +36,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('668743d5-08ad-4480-b2b8-15da34f81d9f')
def test_get_non_existent_image(self):
- # get the non-existent image
+ """Get the non-existent image"""
non_existent_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.show_image,
non_existent_id)
@@ -44,14 +44,14 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ef45000d-0a72-4781-866d-4cb7bf2562ad')
def test_get_image_null_id(self):
- # get image with image_id = NULL
+ """Get image with image_id = NULL"""
image_id = ""
self.assertRaises(lib_exc.NotFound, self.client.show_image, image_id)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e57fc127-7ba0-4693-92d7-1d8a05ebcba9')
def test_get_delete_deleted_image(self):
- # get and delete the deleted image
+ """Get and delete the deleted image"""
# create and delete image
image = self.client.create_image(name='test',
container_format='bare',
@@ -70,7 +70,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6fe40f1c-57bd-4918-89cc-8500f850f3de')
def test_delete_non_existing_image(self):
- # delete non-existent image
+ """Delete non-existent image"""
non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
non_existent_image_id)
@@ -78,7 +78,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('32248db1-ab88-4821-9604-c7c369f1f88c')
def test_delete_image_null_id(self):
- # delete image with image_id=NULL
+ """Delete image with image_id=NULL"""
image_id = ""
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
image_id)
@@ -86,7 +86,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('292bd310-369b-41c7-a7a3-10276ef76753')
def test_register_with_invalid_container_format(self):
- # Negative tests for invalid data supplied to POST /images
+ """Create image with invalid container format
+
+ Negative tests for invalid data supplied to POST /images
+ """
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
name='test', container_format='wrong',
disk_format='vhd')
@@ -94,6 +97,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('70c6040c-5a97-4111-9e13-e73665264ce1')
def test_register_with_invalid_disk_format(self):
+ """Create image with invalid disk format"""
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
name='test', container_format='bare',
disk_format='wrong')
@@ -101,7 +105,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ab980a34-8410-40eb-872b-f264752f46e5')
def test_delete_protected_image(self):
- # Create a protected image
+ """Create a protected image"""
image = self.create_image(protected=True)
self.addCleanup(self.client.update_image, image['id'],
[dict(replace="/protected", value=False)])
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 601826e..163063c 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -18,9 +18,11 @@
class ImagesTagsTest(base.BaseV2ImageTest):
+ """Test image tags"""
@decorators.idempotent_id('10407036-6059-4f95-a2cd-cbbbee7ed329')
def test_update_delete_tags_for_image(self):
+ """Test adding and deleting image tags"""
image = self.create_image(container_format='bare',
disk_format='raw',
visibility='private')
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index 440fa36..2db4a74 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -19,11 +19,12 @@
class ImagesTagsNegativeTest(base.BaseV2ImageTest):
+ """Negative tests of image tags"""
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8cd30f82-6f9a-4c6e-8034-c1b51fba43d9')
def test_update_tags_for_non_existing_image(self):
- # Update tag with non existing image.
+ """Update image tag with non existing image"""
tag = data_utils.rand_name('tag')
non_exist_image = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.add_image_tag,
@@ -32,7 +33,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('39c023a2-325a-433a-9eea-649bf1414b19')
def test_delete_non_existing_tag(self):
- # Delete non existing tag.
+ """Delete non existing image tag"""
image = self.create_image(container_format='bare',
disk_format='raw',
visibility='private'
diff --git a/tempest/api/image/v2/test_versions.py b/tempest/api/image/v2/test_versions.py
index 84f1068..ef91354 100644
--- a/tempest/api/image/v2/test_versions.py
+++ b/tempest/api/image/v2/test_versions.py
@@ -17,10 +17,12 @@
class VersionsTest(base.BaseV2ImageTest):
+ """Test image versions"""
@decorators.idempotent_id('659ea30a-a17c-4317-832c-0f68ed23c31d')
@decorators.attr(type='smoke')
def test_list_versions(self):
+ """Test listing image versions"""
versions = self.versions_client.list_versions()['versions']
expected_resources = ('id', 'links', 'status')
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..ad7dfb3 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..51f2857 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,6 +42,7 @@
@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__)}
@@ -56,6 +58,7 @@
@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)
@@ -73,6 +76,7 @@
@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__)}
@@ -101,6 +105,7 @@
@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'])
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 a4a057c..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(
@@ -212,6 +220,43 @@
'enable_snat': False})
self._verify_gateway_port(router['id'])
+ @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__)
+ network_1 = self.admin_networks_client.create_network(
+ name=network_name, **{'router:external': True})['network']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.admin_networks_client.delete_network,
+ network_1['id'])
+ subnet = self.create_subnet(
+ network_1, client=self.admin_subnets_client, enable_dhcp=False)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.admin_subnets_client.delete_subnet, subnet['id'])
+ port = self.admin_ports_client.create_port(
+ name=data_utils.rand_name(self.__class__.__name__),
+ network_id=network_1['id'])['port']
+ self.admin_ports_client.delete_port(port_id=port['id'])
+ fixed_ip = {
+ 'subnet_id': port['fixed_ips'][0]['subnet_id'],
+ 'ip_address': port['fixed_ips'][0]['ip_address']
+ }
+ external_gateway_info = {
+ 'network_id': network_1['id'],
+ 'external_fixed_ips': [fixed_ip]
+ }
+ # Create a router and set gateway to fixed_ip
+ router = self.admin_routers_client.create_router(
+ external_gateway_info=external_gateway_info)['router']
+ self.admin_routers_client.delete_router(router['id'])
+ # Examine router's gateway is equal to fixed_ip
+ self.assertEqual(router['external_gateway_info'][
+ 'external_fixed_ips'][0]['ip_address'],
+ fixed_ip['ip_address'])
+
class RoutersIpV6AdminTest(RoutersAdminTest):
_ip_version = 6
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_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_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_ports.py b/tempest/api/network/test_ports.py
index 10121de..c6d049a 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -70,6 +70,7 @@
@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'],
@@ -89,6 +90,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']]
@@ -107,6 +109,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
@@ -136,7 +139,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 +155,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 +167,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 +175,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)
@@ -192,9 +196,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 +215,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)
@@ -289,6 +294,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)
@@ -313,7 +319,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 +330,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)
@@ -410,6 +417,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 +431,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'],
@@ -450,6 +470,7 @@
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=[])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index ad316d1..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']
@@ -142,42 +146,10 @@
self.routers_client.remove_router_interface(
router['id'], port_id=port_body['port']['id'])
- @decorators.idempotent_id('cbe42f84-04c2-11e7-8adb-fa163e4fa634')
- @utils.requires_ext(extension='ext-gw-mode', service='network')
- @testtools.skipUnless(CONF.network.public_network_id,
- 'The public_network_id option must be specified.')
- @decorators.skip_because(bug='1676207')
- def test_create_router_set_gateway_with_fixed_ip(self):
- # Don't know public_network_address, so at first create address
- # from public_network and delete
- port = self.admin_ports_client.create_port(
- name=data_utils.rand_name(self.__class__.__name__),
- network_id=CONF.network.public_network_id)['port']
- self.admin_ports_client.delete_port(port_id=port['id'])
-
- fixed_ip = {
- 'subnet_id': port['fixed_ips'][0]['subnet_id'],
- 'ip_address': port['fixed_ips'][0]['ip_address']
- }
- external_gateway_info = {
- 'network_id': CONF.network.public_network_id,
- 'external_fixed_ips': [fixed_ip]
- }
-
- # Create a router and set gateway to fixed_ip
- router = self.admin_routers_client.create_router(
- external_gateway_info=external_gateway_info)['router']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.admin_routers_client.delete_router,
- router_id=router['id'])
- # Examine router's gateway is equal to fixed_ip
- self.assertEqual(router['external_gateway_info'][
- 'external_fixed_ips'][0]['ip_address'],
- fixed_ip['ip_address'])
-
@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
@@ -248,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'])
@@ -261,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']
@@ -291,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_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..80f790f 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -16,7 +16,6 @@
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
@@ -76,17 +75,7 @@
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)
@@ -113,17 +102,7 @@
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)
@@ -139,17 +118,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_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_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/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_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_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_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_types.py b/tempest/api/volume/admin/test_volume_types.py
index c1ceeb7..ecc850e 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -127,7 +127,6 @@
encryption_type = \
self.admin_encryption_types_client.create_encryption_type(
volume_type_id, **create_kwargs)['encryption']
- self.assertIn('volume_type_id', encryption_type)
for key in create_kwargs:
self.assertEqual(create_kwargs[key], encryption_type[key],
'The created encryption_type %s is different '
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..33e503f 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']
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/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 1535786..ff552a1 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -96,6 +96,7 @@
To see help on specific argument, please do: ``tempest account-generator
[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')
@@ -248,21 +257,6 @@
help='Output accounts yaml file')
-def get_options():
- usage_string = ('tempest account-generator [-h] <ARG> ...\n\n'
- 'To see help on specific argument, do:\n'
- 'tempest account-generator <ARG> -h')
- parser = argparse.ArgumentParser(
- description=DESCRIPTION,
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- usage=usage_string
- )
-
- _parser_add_args(parser)
- opts = parser.parse_args()
- return opts
-
-
class TempestAccountGenerator(command.Command):
def get_parser(self, prog_name):
@@ -272,7 +266,19 @@
def take_action(self, parsed_args):
try:
- main(parsed_args)
+ if parsed_args.config_file:
+ config.CONF.set_config_path(parsed_args.config_file)
+ setup_logging()
+ resources = []
+ for count in range(parsed_args.concurrency):
+ # Use N different cred_providers to obtain different
+ # sets of creds
+ cred_provider = get_credential_provider(parsed_args)
+ resources.extend(generate_resources(cred_provider,
+ parsed_args.admin))
+ dump_accounts(resources, parsed_args.identity_version,
+ parsed_args.accounts)
+
except Exception:
LOG.exception("Failure generating test accounts.")
traceback.print_exc()
@@ -280,26 +286,3 @@
def get_description(self):
return DESCRIPTION
-
-
-def main(opts=None):
- log_warning = False
- if not opts:
- log_warning = True
- opts = get_options()
- if opts.config_file:
- config.CONF.set_config_path(opts.config_file)
- setup_logging()
- if log_warning:
- LOG.warning("Use of: 'tempest-account-generator' is deprecated, "
- "please use: 'tempest account-generator'")
- resources = []
- for count in range(opts.concurrency):
- # Use N different cred_providers to obtain different sets of creds
- cred_provider = get_credential_provider(opts)
- resources.extend(generate_resources(cred_provider, opts.admin))
- dump_accounts(resources, opts.identity_version, opts.accounts)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index c54b16b..0b96d9e 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -123,6 +123,16 @@
raise Exception(self.GOT_EXCEPTIONS)
def init(self, parsed_args):
+ # set new handler for logging to stdout, by default only INFO messages
+ # are logged to stdout
+ stdout_handler = logging.logging.StreamHandler()
+ # debug argument is defined in cliff already
+ if self.app_args.debug:
+ stdout_handler.level = logging.DEBUG
+ else:
+ stdout_handler.level = logging.INFO
+ LOG.handlers.append(stdout_handler)
+
cleanup_service.init_conf()
self.options = parsed_args
self.admin_mgr = clients.Manager(
@@ -149,7 +159,7 @@
self._load_json()
def _cleanup(self):
- print("Begin cleanup")
+ LOG.info("Begin cleanup")
is_dry_run = self.options.dry_run
is_preserve = not self.options.delete_tempest_conf_objects
is_save_state = False
@@ -167,7 +177,7 @@
'is_save_state': is_save_state}
project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
projects = project_service.list()
- print("Process %s projects" % len(projects))
+ LOG.info("Processing %s projects", len(projects))
# Loop through list of projects and clean them up.
for project in projects:
@@ -179,10 +189,12 @@
'is_preserve': is_preserve,
'is_save_state': is_save_state,
'got_exceptions': self.GOT_EXCEPTIONS}
+ LOG.info("Processing global services")
for service in self.global_services:
svc = service(admin_mgr, **kwargs)
svc.run()
+ LOG.info("Processing services")
for service in self.resource_cleanup_services:
svc = service(self.admin_mgr, **kwargs)
svc.run()
@@ -193,7 +205,7 @@
indent=2, separators=(',', ': ')))
def _clean_project(self, project):
- print("Cleaning project: %s " % project['name'])
+ LOG.debug("Cleaning project: %s ", project['name'])
is_dry_run = self.options.dry_run
dry_run_data = self.dry_run_data
is_preserve = not self.options.delete_tempest_conf_objects
@@ -263,7 +275,7 @@
return 'Cleanup after tempest run'
def _init_state(self):
- print("Initializing saved state.")
+ LOG.info("Initializing saved state.")
data = {}
admin_mgr = self.admin_mgr
kwargs = {'data': data,
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 469b214..84d2492 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -23,7 +23,7 @@
from tempest import config
from tempest.lib import exceptions
-LOG = logging.getLogger(__name__)
+LOG = logging.getLogger('tempest.cmd.cleanup')
CONF = config.CONF
CONF_FLAVORS = None
@@ -167,6 +167,7 @@
client = self.client
for snap in snaps:
try:
+ LOG.debug("Deleting Snapshot with id %s", snap['id'])
client.delete_snapshot(snap['id'])
except Exception:
LOG.exception("Delete Snapshot %s exception.", snap['id'])
@@ -204,6 +205,7 @@
servers = self.list()
for server in servers:
try:
+ LOG.debug("Deleting Server with id %s", server['id'])
client.delete_server(server['id'])
except Exception:
LOG.exception("Delete Server %s exception.", server['id'])
@@ -236,6 +238,7 @@
sgs = self.list()
for sg in sgs:
try:
+ LOG.debug("Deleting Server Group with id %s", sg['id'])
client.delete_server_group(sg['id'])
except Exception:
LOG.exception("Delete Server Group %s exception.", sg['id'])
@@ -273,6 +276,7 @@
for k in keypairs:
name = k['keypair']['name']
try:
+ LOG.debug("Deleting keypair %s", name)
client.delete_keypair(name)
except Exception:
LOG.exception("Delete Keypair %s exception.", name)
@@ -309,6 +313,7 @@
vols = self.list()
for v in vols:
try:
+ LOG.debug("Deleting volume with id %s", v['id'])
client.delete_volume(v['id'])
except Exception:
LOG.exception("Delete Volume %s exception.", v['id'])
@@ -332,6 +337,8 @@
def delete(self):
client = self.client
try:
+ LOG.debug("Deleting Volume Quotas for project with id %s",
+ self.project_id)
client.delete_quota_set(self.project_id)
except Exception:
LOG.exception("Delete Volume Quotas exception for 'project %s'.",
@@ -352,9 +359,11 @@
def delete(self):
client = self.client
try:
+ LOG.debug("Deleting Nova Quotas for project with id %s",
+ self.project_id)
client.delete_quota_set(self.project_id)
except Exception:
- LOG.exception("Delete Quotas exception for 'project %s'.",
+ LOG.exception("Delete Nova Quotas exception for 'project %s'.",
self.project_id)
def dry_run(self):
@@ -371,6 +380,8 @@
def delete(self):
client = self.client
try:
+ LOG.debug("Deleting Network Quotas for project with id %s",
+ self.project_id)
client.reset_quotas(self.project_id)
except Exception:
LOG.exception("Delete Network Quotas exception for 'project %s'.",
@@ -419,7 +430,7 @@
if self.is_preserve:
networks = [network for network in networks
if network['id'] not in CONF_NETWORKS]
- LOG.debug("List count, %s Networks", networks)
+ LOG.debug("List count, %s Networks", len(networks))
return networks
def delete(self):
@@ -427,6 +438,7 @@
networks = self.list()
for n in networks:
try:
+ LOG.debug("Deleting Network with id %s", n['id'])
client.delete_network(n['id'])
except Exception:
LOG.exception("Delete Network %s exception.", n['id'])
@@ -461,6 +473,8 @@
flips = self.list()
for flip in flips:
try:
+ LOG.debug("Deleting Network Floating IP with id %s",
+ flip['id'])
client.delete_floatingip(flip['id'])
except Exception:
LOG.exception("Delete Network Floating IP %s exception.",
@@ -506,11 +520,14 @@
if net_info.is_router_interface_port(port)]
for port in ports:
try:
+ LOG.debug("Deleting port with id %s of router with id %s",
+ port['id'], rid)
client.remove_router_interface(rid, port_id=port['id'])
except Exception:
LOG.exception("Delete Router Interface exception for "
"'port %s' of 'router %s'.", port['id'], rid)
try:
+ LOG.debug("Deleting Router with id %s", rid)
client.delete_router(rid)
except Exception:
LOG.exception("Delete Router %s exception.", rid)
@@ -546,6 +563,8 @@
rules = self.list()
for rule in rules:
try:
+ LOG.debug("Deleting Metering Label Rule with id %s",
+ rule['id'])
client.delete_metering_label_rule(rule['id'])
except Exception:
LOG.exception("Delete Metering Label Rule %s exception.",
@@ -582,6 +601,7 @@
labels = self.list()
for label in labels:
try:
+ LOG.debug("Deleting Metering Label with id %s", label['id'])
client.delete_metering_label(label['id'])
except Exception:
LOG.exception("Delete Metering Label %s exception.",
@@ -622,6 +642,7 @@
ports = self.list()
for port in ports:
try:
+ LOG.debug("Deleting port with id %s", port['id'])
client.delete_port(port['id'])
except Exception:
LOG.exception("Delete Port %s exception.", port['id'])
@@ -663,6 +684,7 @@
secgroups = self.list()
for secgroup in secgroups:
try:
+ LOG.debug("Deleting security_group with id %s", secgroup['id'])
client.delete_security_group(secgroup['id'])
except Exception:
LOG.exception("Delete security_group %s exception.",
@@ -699,6 +721,7 @@
subnets = self.list()
for subnet in subnets:
try:
+ LOG.debug("Deleting subnet with id %s", subnet['id'])
client.delete_subnet(subnet['id'])
except Exception:
LOG.exception("Delete Subnet %s exception.", subnet['id'])
@@ -734,6 +757,7 @@
pools = self.list()
for pool in pools:
try:
+ LOG.debug("Deleting Subnet Pool with id %s", pool['id'])
client.delete_subnetpool(pool['id'])
except Exception:
LOG.exception("Delete Subnet Pool %s exception.", pool['id'])
@@ -762,8 +786,10 @@
if not self.is_save_state:
regions = [region for region in regions['regions'] if region['id']
not in self.saved_state_json['regions'].keys()]
+ LOG.debug("List count, %s Regions", len(regions))
return regions
else:
+ LOG.debug("List count, %s Regions", len(regions['regions']))
return regions['regions']
def delete(self):
@@ -771,6 +797,7 @@
regions = self.list()
for region in regions:
try:
+ LOG.debug("Deleting region with id %s", region['id'])
client.delete_region(region['id'])
except Exception:
LOG.exception("Delete Region %s exception.", region['id'])
@@ -812,6 +839,7 @@
flavors = self.list()
for flavor in flavors:
try:
+ LOG.debug("Deleting flavor with id %s", flavor['id'])
client.delete_flavor(flavor['id'])
except Exception:
LOG.exception("Delete Flavor %s exception.", flavor['id'])
@@ -857,6 +885,7 @@
images = self.list()
for image in images:
try:
+ LOG.debug("Deleting image with id %s", image['id'])
client.delete_image(image['id'])
except Exception:
LOG.exception("Delete Image %s exception.", image['id'])
@@ -900,6 +929,7 @@
users = self.list()
for user in users:
try:
+ LOG.debug("Deleting user with id %s", user['id'])
self.client.delete_user(user['id'])
except Exception:
LOG.exception("Delete User %s exception.", user['id'])
@@ -940,6 +970,7 @@
roles = self.list()
for role in roles:
try:
+ LOG.debug("Deleting role with id %s", role['id'])
self.client.delete_role(role['id'])
except Exception:
LOG.exception("Delete Role %s exception.", role['id'])
@@ -982,6 +1013,7 @@
projects = self.list()
for project in projects:
try:
+ LOG.debug("Deleting project with id %s", project['id'])
self.client.delete_project(project['id'])
except Exception:
LOG.exception("Delete project %s exception.", project['id'])
@@ -1018,6 +1050,7 @@
domains = self.list()
for domain in domains:
try:
+ LOG.debug("Deleting domain with id %s", domain['id'])
client.update_domain(domain['id'], enabled=False)
client.delete_domain(domain['id'])
except Exception:
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 1699c7d..eca2023 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
@@ -658,6 +656,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',
@@ -833,7 +837,8 @@
help="User name used to authenticate to an instance."),
cfg.StrOpt('image_ssh_password',
default="password",
- help="Password used to authenticate to an instance."),
+ help="Password used to authenticate to an instance.",
+ secret=True),
cfg.StrOpt('ssh_shell_prologue',
default="set -eu -o pipefail; PATH=$$PATH:/sbin:/usr/sbin;",
help="Shell fragments to use before executing a command "
@@ -1020,7 +1025,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',
@@ -1067,11 +1072,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'),
@@ -1080,18 +1087,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_59/__init__.py b/tempest/lib/api_schema/response/compute/v2_59/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_59/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_59/migrations.py b/tempest/lib/api_schema/response/compute/v2_59/migrations.py
new file mode 100644
index 0000000..a37c0f1
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_59/migrations.py
@@ -0,0 +1,36 @@
+# 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_23 import migrations
+
+###########################################################################
+#
+# 2.59:
+#
+# The uuid value is now returned in the response body in addition to the
+# migration id for the following API responses:
+#
+# - GET /os-migrations
+# - GET /servers/{server_id}/migrations/{migration_id}
+# - GET /servers/{server_id}/migrations
+#
+###########################################################################
+
+uuid = {'type': 'string', 'format': 'uuid'}
+
+list_migrations = copy.deepcopy(migrations.list_migrations)
+list_migrations['response_body']['properties']['migrations']['items'][
+ 'properties'].update({'uuid': uuid})
+list_migrations['response_body']['properties']['migrations']['items'][
+ 'required'].append('uuid')
diff --git a/tempest/lib/api_schema/response/volume/encryption_types.py b/tempest/lib/api_schema/response/volume/encryption_types.py
new file mode 100755
index 0000000..7e7ca4a
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/encryption_types.py
@@ -0,0 +1,95 @@
+# Copyright 2019 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.compute.v2_1 import parameter_types
+
+show_encryption_type = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': ['object', 'null'],
+ 'properties': {
+ 'volume_type_id': {'type': 'string', 'format': 'uuid'},
+ 'encryption_id': {'type': 'string', 'format': 'uuid'},
+ 'key_size': {'type': ['integer', 'null']},
+ 'provider': {'type': 'string'},
+ 'control_location': {'enum': ['front-end', 'back-end']},
+ 'cipher': {'type': ['string', 'null']},
+ 'deleted': {'type': 'boolean'},
+ 'created_at': parameter_types.date_time,
+ 'updated_at': parameter_types.date_time_or_null,
+ 'deleted_at': parameter_types.date_time_or_null
+ },
+ # result of show_encryption_type may be empty list,
+ # so no required fields.
+ 'additionalProperties': False,
+ }
+}
+
+show_encryption_specs_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+ }
+}
+
+create_encryption_type = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'encryption': {
+ 'type': 'object',
+ 'properties': {
+ 'volume_type_id': {'type': 'string', 'format': 'uuid'},
+ 'encryption_id': {'type': 'string', 'format': 'uuid'},
+ 'key_size': {'type': ['integer', 'null']},
+ 'provider': {'type': 'string'},
+ 'control_location': {'enum': ['front-end', 'back-end']},
+ 'cipher': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['volume_type_id', 'encryption_id']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['encryption']
+ }
+}
+
+delete_encryption_type = {'status_code': [202]}
+
+update_encryption_type = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'encryption': {
+ 'type': 'object',
+ 'properties': {
+ 'key_size': {'type': ['integer', 'null']},
+ 'provider': {'type': 'string'},
+ 'control_location': {'enum': ['front-end', 'back-end']},
+ 'cipher': {'type': ['string', 'null']},
+ },
+ # all fields are optional
+ 'additionalProperties': False,
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['encryption']
+ }
+}
diff --git a/tempest/lib/api_schema/response/volume/group_types.py b/tempest/lib/api_schema/response/volume/group_types.py
new file mode 100644
index 0000000..4fc9ae8
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/group_types.py
@@ -0,0 +1,134 @@
+# 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.
+
+group_specs = {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+}
+
+common_show_group_type = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'is_public': {'type': 'boolean'},
+ 'group_specs': group_specs,
+ 'description': {'type': ['string', 'null']},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'is_public', 'description', 'name']
+}
+
+create_group_type = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_type': common_show_group_type
+ },
+ 'additionalProperties': False,
+ 'required': ['group_type']
+ }
+}
+
+delete_group_type = {'status_code': [202]}
+
+list_group_types = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_types': {
+ 'type': 'array',
+ 'items': common_show_group_type
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group_types'],
+ }
+}
+
+show_group_type = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_type': common_show_group_type
+ },
+ 'additionalProperties': False,
+ 'required': ['group_type']
+ }
+}
+
+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': {
+ 'type': 'object',
+ 'properties': {
+ 'group_type': common_show_group_type
+ },
+ 'additionalProperties': False,
+ 'required': ['group_type']
+ }
+}
+
+create_or_update_group_type_specs = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_specs': group_specs,
+ },
+ 'additionalProperties': False,
+ 'required': ['group_specs']
+ }
+}
+
+list_group_type_specs = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group_specs': group_specs,
+ },
+ 'additionalProperties': False,
+ 'required': ['group_specs']
+ }
+}
+
+show_group_type_specs_item = {
+ 'status_code': [200],
+ 'response_body': group_specs
+}
+
+update_group_type_specs_item = {
+ 'status_code': [200],
+ 'response_body': group_specs
+}
+
+delete_group_type_specs_item = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/limits.py b/tempest/lib/api_schema/response/volume/limits.py
new file mode 100644
index 0000000..99af180
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/limits.py
@@ -0,0 +1,55 @@
+# 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.
+
+show_limits = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'limits': {
+ 'type': 'object',
+ 'properties': {
+ 'rate': {'type': 'array'},
+ 'absolute': {
+ 'type': 'object',
+ 'properties': {
+ 'totalSnapshotsUsed': {'type': 'integer'},
+ 'maxTotalBackups': {'type': 'integer'},
+ 'maxTotalVolumeGigabytes': {'type': 'integer'},
+ 'maxTotalSnapshots': {'type': 'integer'},
+ 'maxTotalBackupGigabytes': {'type': 'integer'},
+ 'totalBackupGigabytesUsed': {'type': 'integer'},
+ 'maxTotalVolumes': {'type': 'integer'},
+ 'totalVolumesUsed': {'type': 'integer'},
+ 'totalBackupsUsed': {'type': 'integer'},
+ 'totalGigabytesUsed': {'type': 'integer'},
+ },
+ 'additionalProperties': False,
+ 'required': ['totalSnapshotsUsed', 'maxTotalBackups',
+ 'maxTotalVolumeGigabytes',
+ 'maxTotalSnapshots',
+ 'maxTotalBackupGigabytes',
+ 'totalBackupGigabytesUsed',
+ 'maxTotalVolumes', 'totalVolumesUsed',
+ 'totalBackupsUsed', 'totalGigabytesUsed']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['rate', 'absolute'],
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['limits']
+ }
+}
diff --git a/tempest/lib/api_schema/response/volume/scheduler_stats.py b/tempest/lib/api_schema/response/volume/scheduler_stats.py
new file mode 100644
index 0000000..b5d7d2c
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/scheduler_stats.py
@@ -0,0 +1,79 @@
+# 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.compute.v2_1 import parameter_types
+
+get_pools_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'pools': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['pools'],
+ }
+}
+
+get_pools_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'pools': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'capabilities': {
+ 'type': ['object', 'null'],
+ 'properties': {
+ 'updated': parameter_types.date_time_or_null,
+ 'QoS_support': {'type': 'boolean'},
+ 'total_capacity_gb': {
+ 'type': ['number', 'string']
+ },
+ 'volume_backend_name': {'type': 'string'},
+ 'free_capacity_gb': {
+ 'type': ['number', 'string']
+ },
+ 'driver_version': {'type': 'string'},
+ 'reserved_percentage': {'type': 'integer'},
+ 'storage_protocol': {'type': 'string'},
+ 'vendor_name': {'type': 'string'},
+ 'timestamp': parameter_types.date_time_or_null
+ },
+ # Because some legacy volumes or backends may not
+ # support pools, so no required fields here.
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['name', 'capabilities']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['pools'],
+ }
+}
diff --git a/tempest/lib/api_schema/response/volume/snapshots.py b/tempest/lib/api_schema/response/volume/snapshots.py
new file mode 100644
index 0000000..9d52801
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/snapshots.py
@@ -0,0 +1,198 @@
+# 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
+
+metadata = {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+}
+
+common_snapshot_schema = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'created_at': parameter_types.date_time,
+ 'name': {'type': ['string', 'null']},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'metadata': metadata,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'size': {'type': 'integer'},
+ 'updated_at': parameter_types.date_time_or_null,
+ # TODO(zhufl): user_id is added in 3.41, we should move it
+ # to the 3.41 schema file when microversion is supported
+ # in volume interfaces
+ # 'user_id': {'type': 'string', 'format': 'uuid'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at', 'metadata',
+ 'name', 'volume_id', 'id', 'size', 'updated_at']
+}
+
+common_snapshot_detail_schema = copy.deepcopy(common_snapshot_schema)
+common_snapshot_detail_schema['properties'].update(
+ {'os-extended-snapshot-attributes:progress': {'type': 'string'},
+ 'os-extended-snapshot-attributes:project_id': {
+ 'type': 'string', 'format': 'uuid'}})
+common_snapshot_detail_schema['required'].extend(
+ ['os-extended-snapshot-attributes:progress',
+ 'os-extended-snapshot-attributes:project_id'])
+
+list_snapshots_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshots': {
+ 'type': 'array',
+ 'items': common_snapshot_schema
+ },
+ 'snapshots_links': parameter_types.links,
+ # 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': ['snapshots'],
+ }
+}
+
+list_snapshots_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshots': {
+ 'type': 'array',
+ 'items': common_snapshot_detail_schema
+ },
+ 'snapshots_links': parameter_types.links,
+ # 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': ['snapshots'],
+ }
+}
+
+show_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_detail_schema
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot'],
+ }
+}
+
+create_snapshot = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_schema
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot'],
+ }
+}
+
+update_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_schema
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot'],
+ }
+}
+
+delete_snapshot = {'status_code': [202]}
+reset_snapshot_status = {'status_code': [202]}
+update_snapshot_status = {'status_code': [202]}
+
+create_snapshot_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': metadata
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata'],
+ }
+}
+
+show_snapshot_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': metadata
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata'],
+ }
+}
+
+update_snapshot_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': metadata
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata'],
+ }
+}
+
+show_snapshot_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': metadata
+ },
+ 'additionalProperties': False,
+ 'required': ['meta'],
+ }
+}
+
+update_snapshot_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': metadata
+ },
+ 'additionalProperties': False,
+ 'required': ['meta'],
+ }
+}
+
+delete_snapshot_metadata_item = {'status_code': [200]}
+force_delete_snapshot = {'status_code': [202]}
+unmanage_snapshot = {'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/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 23de064..812dc96 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -18,6 +18,8 @@
from tempest.lib.api_schema.response.compute.v2_1 import migrations as schema
from tempest.lib.api_schema.response.compute.v2_23 import migrations \
as schemav223
+from tempest.lib.api_schema.response.compute.v2_59 import migrations \
+ as schemav259
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -25,7 +27,8 @@
class MigrationsClient(base_compute_client.BaseComputeClient):
schema_versions_info = [
{'min': None, 'max': '2.22', 'schema': schema},
- {'min': '2.23', 'max': None, 'schema': schemav223}]
+ {'min': '2.23', 'max': '2.58', 'schema': schemav223},
+ {'min': '2.59', 'max': None, 'schema': schemav259}]
def list_migrations(self, **params):
"""List all migrations.
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index cbf7a8c..6723516 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -206,11 +206,17 @@
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:
body = json.loads(body)
+ else:
+ if isinstance(body, bytes):
+ body = body.decode('utf-8')
self.validate_response(schema, resp, body)
return rest_client.ResponseBody(resp, body)
@@ -605,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..b9c5776 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,80 @@
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):
+ """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.
+ """
+ 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
+ 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/encryption_types_client.py b/tempest/lib/services/volume/v3/encryption_types_client.py
index bd809d6..7cced57 100644
--- a/tempest/lib/services/volume/v3/encryption_types_client.py
+++ b/tempest/lib/services/volume/v3/encryption_types_client.py
@@ -15,6 +15,7 @@
from oslo_serialization import jsonutils as json
+from tempest.lib.api_schema.response.volume import encryption_types as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
@@ -43,7 +44,7 @@
url = "/types/%s/encryption" % volume_type_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_encryption_type, resp, body)
return rest_client.ResponseBody(resp, body)
def show_encryption_specs_item(self, volume_type_id, key):
@@ -51,7 +52,7 @@
url = "/types/%s/encryption/%s" % (volume_type_id, key)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_encryption_specs_item, resp, body)
return rest_client.ResponseBody(resp, body)
def create_encryption_type(self, volume_type_id, **kwargs):
@@ -65,14 +66,14 @@
post_body = json.dumps({'encryption': kwargs})
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.create_encryption_type, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_encryption_type(self, volume_type_id):
"""Delete the encryption type for the specified volume-type."""
resp, body = self.delete(
"/types/%s/encryption/provider" % volume_type_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_encryption_type, resp, body)
return rest_client.ResponseBody(resp, body)
def update_encryption_type(self, volume_type_id, **kwargs):
@@ -86,5 +87,5 @@
put_body = json.dumps({'encryption': kwargs})
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_encryption_type, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 99833ce..1dcd508 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_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_types as schema
from tempest.lib.common import rest_client
from tempest.lib.services.volume import base_client
@@ -38,13 +39,13 @@
post_body = json.dumps({'group_type': kwargs})
resp, body = self.post('group_types', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_type, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group_type(self, group_type_id):
"""Deletes the specified group_type."""
resp, body = self.delete("group_types/%s" % group_type_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group_type, resp, body)
return rest_client.ResponseBody(resp, body)
def list_group_types(self, **params):
@@ -60,7 +61,7 @@
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.list_group_types, resp, body)
return rest_client.ResponseBody(resp, body)
def show_default_group_type(self):
@@ -72,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):
@@ -84,7 +85,7 @@
url = "group_types/%s" % group_type_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group_type, resp, body)
return rest_client.ResponseBody(resp, body)
def update_group_type(self, group_type_id, **kwargs):
@@ -96,8 +97,8 @@
"""
post_body = json.dumps({'group_type': kwargs})
resp, body = self.put('group_types/%s' % group_type_id, post_body)
- self.expected_success(200, resp.status)
body = json.loads(body)
+ self.validate_response(schema.update_group_type, resp, body)
return rest_client.ResponseBody(resp, body)
def create_or_update_group_type_specs(self, group_type_id, group_specs):
@@ -111,7 +112,8 @@
post_body = json.dumps({'group_specs': group_specs})
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(
+ schema.create_or_update_group_type_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def list_group_type_specs(self, group_type_id):
@@ -119,7 +121,7 @@
url = 'group_types/%s/group_specs' % group_type_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.list_group_type_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group_type_specs_item(self, group_type_id, spec_id):
@@ -127,7 +129,7 @@
url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group_type_specs_item, resp, body)
return rest_client.ResponseBody(resp, body)
def update_group_type_specs_item(self, group_type_id, spec_id, spec):
@@ -141,12 +143,12 @@
put_body = json.dumps(spec)
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_group_type_specs_item, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group_type_specs_item(self, group_type_id, spec_id):
"""Deletes specified item of group specs for a given group type."""
resp, body = self.delete("group_types/%s/group_specs/%s" % (
group_type_id, spec_id))
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group_type_specs_item, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/limits_client.py b/tempest/lib/services/volume/v3/limits_client.py
index 9500254..a8d1377 100644
--- a/tempest/lib/services/volume/v3/limits_client.py
+++ b/tempest/lib/services/volume/v3/limits_client.py
@@ -15,6 +15,7 @@
from oslo_serialization import jsonutils as json
+from tempest.lib.api_schema.response.volume import limits as schema
from tempest.lib.common import rest_client
@@ -26,5 +27,5 @@
url = "limits"
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_limits, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/scheduler_stats_client.py b/tempest/lib/services/volume/v3/scheduler_stats_client.py
index 2ae8600..e18980d 100644
--- a/tempest/lib/services/volume/v3/scheduler_stats_client.py
+++ b/tempest/lib/services/volume/v3/scheduler_stats_client.py
@@ -15,6 +15,7 @@
from oslo_serialization import jsonutils as json
+from tempest.lib.api_schema.response.volume import scheduler_stats as schema
from tempest.lib.common import rest_client
@@ -28,9 +29,11 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-all-back-end-storage-pools
"""
url = 'scheduler-stats/get_pools'
+ schema_get_pools = schema.get_pools_no_detail
if detail:
url += '?detail=True'
+ schema_get_pools = schema.get_pools_with_detail
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema_get_pools, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index 264381d..8ca2044 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/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 snapshots as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
@@ -32,14 +33,16 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-snapshots-and-details
"""
url = 'snapshots'
+ list_schema = schema.list_snapshots_no_detail
if detail:
url += '/detail'
+ list_schema = schema.list_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_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def show_snapshot(self, snapshot_id):
@@ -52,7 +55,7 @@
url = "snapshots/%s" % snapshot_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def create_snapshot(self, **kwargs):
@@ -65,7 +68,7 @@
post_body = json.dumps({'snapshot': kwargs})
resp, body = self.post('snapshots', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def update_snapshot(self, snapshot_id, **kwargs):
@@ -78,7 +81,7 @@
put_body = json.dumps({'snapshot': kwargs})
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_snapshot(self, snapshot_id):
@@ -89,7 +92,7 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-snapshot
"""
resp, body = self.delete("snapshots/%s" % snapshot_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
@@ -108,7 +111,7 @@
"""Reset the specified snapshot's status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_snapshot_status, resp, body)
return rest_client.ResponseBody(resp, body)
def update_snapshot_status(self, snapshot_id, **kwargs):
@@ -121,7 +124,7 @@
post_body = json.dumps({'os-update_snapshot_status': kwargs})
url = 'snapshots/%s/action' % snapshot_id
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_snapshot_status, resp, body)
return rest_client.ResponseBody(resp, body)
def create_snapshot_metadata(self, snapshot_id, metadata):
@@ -135,7 +138,7 @@
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.post(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.create_snapshot_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata(self, snapshot_id):
@@ -148,7 +151,7 @@
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_snapshot_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata(self, snapshot_id, **kwargs):
@@ -162,7 +165,7 @@
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_snapshot_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata_item(self, snapshot_id, id):
@@ -170,7 +173,7 @@
url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_snapshot_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
@@ -184,21 +187,23 @@
url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
resp, body = self.put(url, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(
+ schema.update_snapshot_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
resp, body = self.delete(url)
- self.expected_success(200, resp.status)
+ self.validate_response(
+ schema.delete_snapshot_metadata_item, resp, body)
return rest_client.ResponseBody(resp, body)
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.force_delete_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
def unmanage_snapshot(self, snapshot_id):
@@ -206,5 +211,5 @@
post_body = json.dumps({'os-unmanage': {}})
url = 'snapshots/%s/action' % (snapshot_id)
resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unmanage_snapshot, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index efdfe8e..a80b77d 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
@@ -531,33 +532,32 @@
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
+ 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)
+ img_properties)
+ image = self._image_create('scenario-img',
+ img_container_format,
+ img_path,
+ disk_format=img_disk_format,
+ properties=img_properties)
LOG.debug("image:%s", image)
return image
@@ -868,15 +868,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
@@ -901,14 +901,14 @@
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 +927,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
@@ -1015,7 +1015,7 @@
kwargs = {
'floating_network_id': external_network_id,
'port_id': port_id,
- 'tenant_id': thing['tenant_id'],
+ 'tenant_id': thing.get('project_id') or thing['tenant_id'],
'fixed_ip_address': ip4,
}
if CONF.network.subnet_id:
@@ -1038,9 +1038,12 @@
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,8 +1055,6 @@
"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,
@@ -1121,18 +1122,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 +1141,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 +1153,23 @@
- 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 +1178,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:
@@ -1200,18 +1201,18 @@
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 +1278,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.
@@ -1288,8 +1289,8 @@
"""
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 +1300,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 +1311,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
@@ -1342,11 +1343,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
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_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/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index a33d4d4..a062d40 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -29,8 +29,10 @@
The following is the scenario outline:
* boot an instance and create a timestamp file in it
* snapshot the instance
+ * add version metadata to the snapshot image
* boot a second instance from the snapshot
* check the existence of the timestamp file in the second instance
+ * snapshot the instance again
"""
@@ -63,6 +65,11 @@
# snapshot the instance
snapshot_image = self.create_server_snapshot(server=server)
+ # add version metadata to the snapshot image
+ self.image_client.update_image(
+ snapshot_image['id'], [dict(add='/version',
+ value='8.0')])
+
# boot a second instance from the snapshot
server_from_snapshot = self.create_server(
image_id=snapshot_image['id'],
@@ -75,3 +82,6 @@
private_key=keypair['private_key'],
server=server_from_snapshot)
self.assertEqual(timestamp, timestamp2)
+
+ # snapshot the instance again
+ self.create_server_snapshot(server=server_from_snapshot)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 0782389..3b4bbda 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -252,8 +252,7 @@
@utils.services('compute', 'volume')
def test_boot_server_from_encrypted_volume_luks(self):
# Create an encrypted volume
- volume = self.create_encrypted_volume('nova.volume.encryptors.'
- 'luks.LuksEncryptor',
+ volume = self.create_encrypted_volume('luks',
volume_type='luks')
self.volumes_client.set_bootable_volume(volume['id'], bootable=True)
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/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 8366290..7bf7315 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -274,19 +274,22 @@
"name": "test"
},
"name": "test-volume-snapshot",
- "user_id": "40c2102f4a554b848d96b14f3eec39ed",
"volume_id": "173f7b48-c4c1-4e70-9acc-086b39073506",
"created_at": "2015-11-29T02:25:51.000000",
"size": 1,
"updated_at": "2015-11-20T05:36:40.000000",
- "os-extended-snapshot-attributes:progress": "100%",
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
"description": "volume snapshot"
},
{
"status": "available",
"name": "saved-snapshot",
+ "metadata": {},
"id": "1ad4c789-7e8w-4dwg-afc5",
+ "size": 1,
+ "volume_id": "af7c41be-1ff6-4233-a690-7ed61c34347f",
+ "created_at": "2015-11-20T05:39:40.000000",
+ "updated_at": "2015-11-20T05:39:40.000000",
"description": "snapshot in saved state"
}
]
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_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..5f8b990 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
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..130f90a
--- /dev/null
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -0,0 +1,138 @@
+# 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 tempfile
+from unittest import mock
+
+from tempest.lib.cmd import check_uuid
+from tempest.tests import base
+
+
+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/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
index ee4d4cb..fe671bd 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -35,14 +35,19 @@
"created_at": "2012-08-10T19:23:50Z",
"updated_at": "2012-08-12T11:11:33Z",
"self": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
- "file": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file",
+ "file": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927"
+ "dea/file",
"schema": "/v2/schemas/image",
"owner": None,
"min_ram": None,
"min_disk": None,
"disk_format": None,
"virtual_size": None,
- "container_format": None
+ "container_format": None,
+ "os_hash_algo": "sha512",
+ "os_hash_value": "ef7d1ed957ffafefb324d50ebc6685ed03d0e645d",
+ "os_hidden": False,
+ "protected": False,
}
FAKE_LIST_IMAGES = {
@@ -66,7 +71,10 @@
"size": 13167616,
"min_ram": 0,
"schema": "/v2/schemas/image",
- "virtual_size": None
+ "virtual_size": None,
+ "os_hash_algo": "sha512",
+ "os_hash_value": "ef7d1ed957ffafefb324d50ebc6685ed03d0e645d",
+ "os_hidden": False
},
{
"status": "active",
@@ -87,7 +95,10 @@
"size": 476704768,
"min_ram": 0,
"schema": "/v2/schemas/image",
- "virtual_size": None
+ "virtual_size": None,
+ "os_hash_algo": "sha512",
+ "os_hash_value": "ef7d1ed957ffafefb324d50ebc6685ed03d0e645d",
+ "os_hidden": False
}
],
"schema": "/v2/schemas/images",
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_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index 70a3ee5..7218224 100644
--- a/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -20,11 +20,11 @@
class TestEncryptionTypesClient(base.BaseServiceTest):
FAKE_CREATE_ENCRYPTION_TYPE = {
"encryption": {
- "volume_type_id": "cbc36478b0bd8e67e89",
+ "volume_type_id": "2d29462d-76cb-417c-8a9f-fb23140f1577",
"control_location": "front-end",
"encryption_id": "81e069c6-7394-4856-8df7-3b237ca61f74",
"key_size": 128,
- "provider": "LuksEncryptor",
+ "provider": "luks",
"cipher": "aes-xts-plain64"
}
}
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
index 8b853d7..33c7737 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -23,8 +23,8 @@
FAKE_CREATE_GROUP_TYPE = {
"group_type": {
"id": "6685584b-1eac-4da6-b5c3-555430cf68ff",
- "name": "grp-type-001",
- "description": "group type 001",
+ "name": "group-type-001",
+ "description": "Test group type 1",
"is_public": True,
"group_specs": {
"consistent_group_snapshot_enabled": "<is> False"
diff --git a/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
index 84c7589..7606a52 100644
--- a/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
@@ -25,12 +25,14 @@
"name": "pool1",
"capabilities": {
"updated": "2014-10-28T00:00:00-00:00",
- "total_capacity": 1024,
- "free_capacity": 100,
+ "total_capacity_gb": 1024,
+ "free_capacity_gb": 100,
"volume_backend_name": "pool1",
"reserved_percentage": 0,
"driver_version": "1.0.0",
+ "timestamp": "2014-10-28T00:00:00-00:00",
"storage_protocol": "iSCSI",
+ "vendor_name": "vendor",
"QoS_support": False
}
},
@@ -38,12 +40,14 @@
"name": "pool2",
"capabilities": {
"updated": "2014-10-28T00:00:00-00:00",
- "total_capacity": 512,
- "free_capacity": 200,
+ "total_capacity_gb": 512,
+ "free_capacity_gb": 200,
"volume_backend_name": "pool2",
"reserved_percentage": 0,
"driver_version": "1.0.2",
+ "timestamp": "2014-10-28T00:00:00-00:00",
"storage_protocol": "iSER",
+ "vendor_name": "vendor",
"QoS_support": True
}
}
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..d4313a2 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
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 a50905f..17fa9f1 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,8 +1,8 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-hacking>=3.0,<3.1.0;python_version>='3.5' # Apache-2.0
-mock>=2.0.0 # BSD
+hacking>=3.0.1,<3.1.0;python_version>='3.5' # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
+pycodestyle>=2.0.0,<2.6.0 # MIT
flake8-import-order==0.11 # LGPLv3
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 5ffef3e..530ce5e 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -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/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 961cd09..33675ed 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -98,8 +98,8 @@
if [[ -r doc/source/data/tempest-plugins-registry.footer ]]; then
cat doc/source/data/tempest-plugins-registry.footer
fi
-) > doc/source/plugin-registry.rst
+) > doc/source/plugins/plugin-registry.rst
if [[ -n ${1} ]]; then
- cp doc/source/plugin-registry.rst ${1}/doc/source/plugin-registry.rst
+ cp doc/source/plugins/plugin-registry.rst ${1}/doc/source/plugins/plugin-registry.rst
fi
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-integrated-gate-object-storage-blacklist.txt b/tools/tempest-integrated-gate-object-storage-blacklist.txt
index 064cf46..c164343 100644
--- a/tools/tempest-integrated-gate-object-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-object-storage-blacklist.txt
@@ -9,9 +9,10 @@
tempest.api.identity
# Skip network, compute, keystone only scenario tests
-tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_network_advanced_server_ops
-tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops
-tempest.scenario.test_network_v6.TestGettingAddress.test_security_groups_basic_ops
+tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps
+tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
+tempest.scenario.test_network_v6.TestGettingAddress
+tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
tempest.scenario.test_server_advanced_ops.TestServerAdvancedOps.test_server_sequence_suspend_resume
tempest.scenario.test_server_basic_ops.TestServerBasicOps.test_server_basic_ops
tempest.scenario.test_server_multinode.TestServerMultinode.test_schedule_to_all_nodes
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
index 3900f96..1ef6bb5 100644
--- a/tools/tempest-integrated-gate-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -8,6 +8,7 @@
tempest.api.identity
# Skip network only scenario tests.
-tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_network_advanced_server_ops
-tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops
-tempest.scenario.test_network_v6.TestGettingAddress.test_security_groups_basic_ops
+tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps
+tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
+tempest.scenario.test_network_v6.TestGettingAddress
+tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
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 e861c84..0477d6f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = pep8,py36,py37,bashate,pip-check-reqs
+envlist = pep8,py36,py38,bashate,pip-check-reqs
minversion = 3.1.1
skipsdist = True
ignore_basepython_conflict = True