Merge "Adding description for testcases - image part2"
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..87e277c 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
@@ -447,6 +450,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 +465,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 +534,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 +550,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 +566,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 +582,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 +598,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 +610,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 +646,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:
@@ -678,7 +680,7 @@
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,7 +714,7 @@
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
@@ -736,9 +738,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/CONTRIBUTING.rst b/CONTRIBUTING.rst
index a89ad94..2300763 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,17 +1,19 @@
-If you would like to contribute to the development of OpenStack, you must
-follow the steps in this page:
+The source repository for this project can be found at:
- https://docs.openstack.org/infra/manual/developers.html
+ https://opendev.org/openstack/tempest
-If you already have a good understanding of how the system works and your
-OpenStack accounts are set up, you can skip to the development workflow
-section of this documentation to learn how changes to OpenStack should be
-submitted for review via the Gerrit tool:
+Pull requests submitted through GitHub are not monitored.
- https://docs.openstack.org/infra/manual/developers.html#development-workflow
+To start contributing to OpenStack, follow the steps in the contribution guide
+to set up and use Gerrit:
-Pull requests submitted through GitHub will be ignored.
+ https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
-Bugs should be filed on Launchpad, not GitHub:
+Bugs should be filed on Launchpad:
https://bugs.launchpad.net/tempest
+
+For more specific information about contributing to this repository, see the
+Tempest contributor guide:
+
+ https://docs.openstack.org/tempest/latest/contributor/contributing.html
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..dd7c544 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']
diff --git a/doc/source/contributor/contributing.rst b/doc/source/contributor/contributing.rst
new file mode 100644
index 0000000..9c79a1f
--- /dev/null
+++ b/doc/source/contributor/contributing.rst
@@ -0,0 +1,59 @@
+============================
+So You Want to Contribute...
+============================
+
+For general information on contributing to OpenStack, please check out the
+`contributor guide <https://docs.openstack.org/contributors/>`_ to get started.
+It covers all the basics that are common to all OpenStack projects: the accounts
+you need, the basics of interacting with our Gerrit review system, how we
+communicate as a community, etc.
+
+Below will cover the more project specific information you need to get started
+with Tempest.
+
+Communication
+~~~~~~~~~~~~~
+* IRC channel ``#openstack-qa`` at FreeNode
+* Mailing list (prefix subjects with ``[qa]`` for faster responses)
+ http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
+
+Contacting the Core Team
+~~~~~~~~~~~~~~~~~~~~~~~~
+Please refer to the `Tempest Core Team
+<https://review.opendev.org/#/admin/groups/42,members>`_ contacts.
+
+New Feature Planning
+~~~~~~~~~~~~~~~~~~~~
+If you want to propose a new feature please read `Feature Proposal Process`_
+Tempest features are tracked on `Launchpad BP <https://blueprints.launchpad.net/tempest>`_.
+
+Task Tracking
+~~~~~~~~~~~~~
+We track our tasks in `Launchpad <https://bugs.launchpad.net/tempest>`_.
+
+If you're looking for some smaller, easier work item to pick up and get started
+on, search for the 'low-hanging-fruit' tag.
+
+Reporting a Bug
+~~~~~~~~~~~~~~~
+You found an issue and want to make sure we are aware of it? You can do so on
+`Launchpad <https://bugs.launchpad.net/tempest/+filebug>`__.
+More info about Launchpad usage can be found on `OpenStack docs page
+<https://docs.openstack.org/contributors/common/task-tracking.html#launchpad>`_
+
+Getting Your Patch Merged
+~~~~~~~~~~~~~~~~~~~~~~~~~
+All changes proposed to the Tempest require two ``Code-Review +2`` votes from
+Tempest core reviewers before one of the core reviewers can approve the patch by
+giving ``Workflow +1`` vote. More detailed guidelines for reviewers are available
+at :doc:`../REVIEWING`.
+
+Project Team Lead Duties
+~~~~~~~~~~~~~~~~~~~~~~~~
+All common PTL duties are enumerated in the `PTL guide
+<https://docs.openstack.org/project-team-guide/ptl.html>`_.
+
+The Release Process for QA is documented in `QA Release Process
+<https://wiki.openstack.org/wiki/QA/releases>`_.
+
+.. _Feature Proposal Process: https://wiki.openstack.org/wiki/QA#Feature_Proposal_.26_Design_discussions
diff --git a/doc/source/index.rst b/doc/source/index.rst
index a72e783..f878888 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -56,6 +56,16 @@
supported_version
+For Contributors
+================
+
+* If you are a new contributor to Tempest please refer: :doc:`contributor/contributing`
+
+.. toctree::
+ :hidden:
+
+ contributor/contributing
+
Developers Guide
================
@@ -77,8 +87,7 @@
.. toctree::
:maxdepth: 2
- plugin
- plugin-registry
+ plugins/index
Tempest & Plugins Compatible Version Policy
-------------------------------------------
@@ -88,6 +97,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
-------
@@ -96,14 +121,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 100%
rename from doc/source/plugin.rst
rename to doc/source/plugins/plugin.rst
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/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/tempest-ussuri-release-72b5770a3b97678f.yaml b/releasenotes/notes/tempest-ussuri-release-72b5770a3b97678f.yaml
new file mode 100644
index 0000000..37e56bb
--- /dev/null
+++ b/releasenotes/notes/tempest-ussuri-release-72b5770a3b97678f.yaml
@@ -0,0 +1,16 @@
+---
+prelude: >
+ This release is to tag the Tempest for OpenStack Ussuri release.
+ This release marks the start of Ussuri release support in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * Ussuri
+ * Train
+ * Stein
+
+ Current development of Tempest is for OpenStack Victoria development
+ cycle. Every Tempest commit is also tested against master during
+ the Victoria cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Ussuri (or future release)
+ cloud.
+ To be on safe side, use this tag to test the OpenStack Ussuri release.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 92df4c4..71df749 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']
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index bfd8b2d..d8702f9 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ v24.0.0
v23.0.0
v22.1.0
v22.0.0
diff --git a/releasenotes/source/v24.0.0.rst b/releasenotes/source/v24.0.0.rst
new file mode 100644
index 0000000..8131975
--- /dev/null
+++ b/releasenotes/source/v24.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v24.0.0 Release Notes
+=====================
+
+.. release-notes:: 24.0.0 Release Notes
+ :version: 24.0.0
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 8686f9a..1de3725 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -27,7 +27,8 @@
- name: Use stable branch upper-constraints till stable/rocky
set_fact:
- tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) }}"
+ # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+ tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky"]
- name: Set OS_TEST_TIMEOUT if requested
@@ -55,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/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_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 1f7eb7b..8879369 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from json import decoder as json_decoder
+
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
@@ -110,7 +112,11 @@
max_microversion = '2.32'
def verify_device_metadata(self, md_json):
- md_dict = json.loads(md_json)
+ try:
+ md_dict = json.loads(md_json)
+ except (json_decoder.JSONDecodeError, TypeError):
+ return False
+
for d in md_dict['devices']:
if d['type'] == 'nic':
if d['mac'] == self.port1['mac_address']:
@@ -310,7 +316,11 @@
raise cls.skipException('Metadata API must be enabled')
def verify_device_metadata(self, md_json):
- md_dict = json.loads(md_json)
+ try:
+ md_dict = json.loads(md_json)
+ except (json_decoder.JSONDecodeError, TypeError):
+ return False
+
found_devices = [d['tags'][0] for d in md_dict['devices']
if d.get('tags')]
try:
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 1ac9516..17cd443 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -25,6 +25,7 @@
class ServerRescueTestBase(base.BaseV2ComputeTest):
+ create_default_network = True
@classmethod
def skip_checks(cls):
@@ -105,11 +106,12 @@
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()
+ super(BaseServerStableDeviceRescueTest, self).skip_checks()
if not CONF.compute_feature_enabled.rescue:
msg = "Server rescue not available."
raise self.skipException(msg)
@@ -118,8 +120,15 @@
raise self.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']
+ hw_rescue_bus=None,
+ block_device_mapping_v2=None):
+ if block_device_mapping_v2:
+ server_id = self.create_test_server(
+ wait_until='ACTIVE',
+ block_device_mapping_v2=block_device_mapping_v2)['id']
+ else:
+ server_id = self.create_test_server(wait_until='ACTIVE')['id']
+
image_id = self.create_image_from_server(server_id,
wait_until='ACTIVE')['id']
if hw_rescue_bus:
@@ -141,6 +150,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(
@@ -175,3 +187,49 @@
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.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.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)
+
+ @decorators.idempotent_id('7fcc5d2c-130e-4750-95f5-7343f9d0a2f3')
+ def test_stable_device_rescue_bfv_snapshot_volume(self):
+ volume_id = self.create_volume()['id']
+ self.volumes_client.set_bootable_volume(volume_id, bootable=True)
+ snapshot_id = self.create_volume_snapshot(volume_id)['id']
+ block_device_mapping_v2 = [{
+ "boot_index": "0",
+ "source_type": "snapshot",
+ "volume_size": CONF.volume.volume_size,
+ "uuid": snapshot_id,
+ "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/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..c4a3e0e 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -88,8 +88,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 +109,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 +133,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 +234,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 +243,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 +271,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 +289,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 +304,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 +314,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 +335,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 +373,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_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_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/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/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 1535786..b230615 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -96,7 +96,7 @@
To see help on specific argument, please do: ``tempest account-generator
[OPTIONS] <accounts_file.yaml> -h``.
"""
-import argparse
+
import os
import traceback
@@ -248,21 +248,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 +257,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 +277,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/common/compute.py b/tempest/common/compute.py
index cd85ede..edb9d16 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -400,9 +400,24 @@
"""Upgrade the HTTP connection to a WebSocket and verify."""
# It is possible to pass the path as a query parameter in the request,
# so use it if present
+ # Given noVNC format
+ # https://x.com/vnc_auto.html?path=%3Ftoken%3Dxxx,
+ # url format is
+ # ParseResult(scheme='https', netloc='x.com',
+ # path='/vnc_auto.html', params='',
+ # query='path=%3Ftoken%3Dxxx', fragment='').
+ # qparams format is {'path': ['?token=xxx']}
qparams = urlparse.parse_qs(url.query)
- path = qparams['path'][0] if 'path' in qparams else '/websockify'
- reqdata = 'GET %s HTTP/1.1\r\n' % path
+ # according to references
+ # https://docs.python.org/3/library/urllib.parse.html
+ # https://tools.ietf.org/html/rfc3986#section-3.4
+ # qparams['path'][0] format is '?token=xxx' without / prefix
+ # remove / in /websockify to comply to references.
+ path = qparams['path'][0] if 'path' in qparams else 'websockify'
+ # Fix websocket request format by adding / prefix.
+ # Updated request format: GET /?token=xxx HTTP/1.1
+ # or GET /websockify HTTP/1.1
+ reqdata = 'GET /%s HTTP/1.1\r\n' % path
reqdata += 'Host: %s' % url.hostname
# Add port only if we have one specified
if url.port:
diff --git a/tempest/config.py b/tempest/config.py
index 1699c7d..204d977 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -833,7 +833,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 "
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..bcfa32e
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/group_types.py
@@ -0,0 +1,122 @@
+# 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']
+ }
+}
+
+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/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/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..e0bf5e2 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):
@@ -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/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/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/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/test-requirements.txt b/test-requirements.txt
index a50905f..17a7d2a 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,8 +1,9 @@
# 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
+hacking>=3.0.1,<3.1.0;python_version>='3.5' # Apache-2.0
mock>=2.0.0 # BSD
coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
+pycodestyle>=2.0.0,<2.6.0 # MIT
flake8-import-order==0.11 # LGPLv3
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-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 b484a41..2ff4aea 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -60,8 +60,8 @@
fi
}
-: ${UPPER_CONSTRAINTS_FILE:="https://releases.openstack.org/constraints/upper/master"}
-DEPS="-c${UPPER_CONSTRAINTS_FILE}"
+: ${TOX_CONSTRAINTS_FILE:="https://releases.openstack.org/constraints/upper/master"}
+DEPS="-c${TOX_CONSTRAINTS_FILE}"
# function to create virtualenv to perform sanity operation
function prepare_workspace {
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