Merge "Raise exception when error_deleting"
diff --git a/HACKING.rst b/HACKING.rst
index c0a857c..446d865 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -2,7 +2,7 @@
====================
- Step 1: Read the OpenStack Style Commandments
- http://docs.openstack.org/developer/hacking/
+ https://docs.openstack.org/hacking/latest/
- Step 2: Read on
Tempest Specific Commandments
@@ -22,7 +22,9 @@
- [T112] Check that tempest.lib should not import local tempest code
- [T113] Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
- [T114] Check that tempest.lib does not use tempest config
+- [T115] Check that admin tests should exist under admin path
- [N322] Method's default argument shouldn't be mutable
+- [T116] Unsupported 'message' Exception attribute in PY3
Test Data/Configuration
-----------------------
@@ -101,20 +103,20 @@
Service Tagging
---------------
Service tagging is used to specify which services are exercised by a particular
-test method. You specify the services with the tempest.test.services decorator.
-For example:
+test method. You specify the services with the ``tempest.test.services``
+decorator. For example:
@services('compute', 'image')
Valid service tag names are the same as the list of directories in tempest.api
that have tests.
-For scenario tests having a service tag is required. For the api tests service
-tags are only needed if the test method makes an api call (either directly or
+For scenario tests having a service tag is required. For the API tests service
+tags are only needed if the test method makes an API call (either directly or
indirectly through another service) that differs from the parent directory
-name. For example, any test that make an api call to a service other than nova
-in tempest.api.compute would require a service tag for those services, however
-they do not need to be tagged as compute.
+name. For example, any test that make an API call to a service other than Nova
+in ``tempest.api.compute`` would require a service tag for those services,
+however they do not need to be tagged as ``compute``.
Test fixtures and resources
---------------------------
@@ -197,7 +199,7 @@
Test skips because of Known Bugs
--------------------------------
If a test is broken because of a bug it is appropriate to skip the test until
-bug has been fixed. You should use the skip_because decorator so that
+bug has been fixed. You should use the ``skip_because`` decorator so that
Tempest's skip tracking tool can watch the bug status.
Example::
@@ -228,17 +230,17 @@
require admin privileges are outside of projects.
- Races between methods in the same class are not a problem because
- parallelization in tempest is at the test class level, but if there is a json
+ parallelization in Tempest is at the test class level, but if there is a json
and xml version of the same test class there could still be a race between
methods.
-- The rand_name() function from tempest.common.utils.data_utils should be used
- anywhere a resource is created with a name. Static naming should be avoided
- to prevent resource conflicts.
+- The rand_name() function from tempest.lib.common.utils.data_utils should be
+ used anywhere a resource is created with a name. Static naming should be
+ avoided to prevent resource conflicts.
- If the execution of a set of tests is required to be serialized then locking
- can be used to perform this. See AggregatesAdminTest in
- tempest.api.compute.admin for an example of using locking.
+ can be used to perform this. See usage of ``LockFixture`` for examples of
+ using locking.
Sample Configuration File
-------------------------
@@ -250,7 +252,7 @@
Unit Tests
----------
-Unit tests are a separate class of tests in tempest. They verify tempest
+Unit tests are a separate class of tests in Tempest. They verify Tempest
itself, and thus have a different set of guidelines around them:
1. They can not require anything running externally. All you should need to
@@ -320,8 +322,8 @@
Tempest.lib includes a ``check-uuid`` tool that will test for the existence
and uniqueness of idempotent_id metadata for every test. If you have
-tempest installed you run the tool against Tempest by calling from the
-tempest repo::
+Tempest installed you run the tool against Tempest by calling from the
+Tempest repo::
check-uuid
@@ -336,7 +338,7 @@
check-uuid --fix
-The ``check-uuid`` tool is used as part of the tempest gate job
+The ``check-uuid`` tool is used as part of the Tempest gate job
to ensure that all tests have an ``idempotent_id`` decorator.
Branchless Tempest Considerations
@@ -349,7 +351,7 @@
proposed commits to Tempest must work against both the master and all the
currently supported stable branches of the projects. As such there are a few
special considerations that have to be accounted for when pushing new changes
-to tempest.
+to Tempest.
1. New Tests for new features
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -369,14 +371,22 @@
When trying to land a bug fix which changes a tested API you'll have to use the
following procedure::
- - Propose change to the project, get a +2 on the change even with failing
- - Propose skip on Tempest which will only be approved after the
+ 1. Propose change to the project, get a +2 on the change even with failing
+ 2. Propose skip on Tempest which will only be approved after the
corresponding change in the project has a +2 on change
- - Land project change in master and all open stable branches (if required)
- - Land changed test in Tempest
+ 3. Land project change in master and all open stable branches (if required)
+ 4. Land changed test in Tempest
Otherwise the bug fix won't be able to land in the project.
+Handily, `Zuul’s cross-repository dependencies
+<https://docs.openstack.org/infra/zuul/gating.html#cross-repository-dependencies>`_.
+can be leveraged to do without step 2 and to have steps 3 and 4 happen
+"atomically". To do that, make the patch written in step 1 to depend (refer to
+Zuul's documentation above) on the patch written in step 4. The commit message
+for the Tempest change should have a link to the Gerrit review that justifies
+that change.
+
3. New Tests for existing features
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/README.rst b/README.rst
index ac93992..2e13fec 100644
--- a/README.rst
+++ b/README.rst
@@ -11,7 +11,7 @@
==============================================
The documentation for Tempest is officially hosted at:
-http://docs.openstack.org/developer/tempest/
+https://docs.openstack.org/tempest/latest/
This is a set of integration tests to be run against a live OpenStack
cluster. Tempest has batteries of tests for OpenStack API validation,
@@ -23,7 +23,7 @@
Tempest Design Principles that we strive to live by.
- Tempest should be able to run against any OpenStack cloud, be it a
- one node devstack install, a 20 node lxc cloud, or a 1000 node kvm
+ one node DevStack install, a 20 node LXC cloud, or a 1000 node KVM
cloud.
- Tempest should be explicit in testing features. It is easy to auto
discover features of a cloud incorrectly, and give people an
@@ -65,13 +65,13 @@
$ pip install tempest/
This can be done within a venv, but the assumption for this guide is that
- the Tempest cli entry point will be in your shell's PATH.
+ the Tempest CLI entry point will be in your shell's PATH.
-#. Installing Tempest may create a /etc/tempest dir, however if one isn't
- created you can create one or use ~/.tempest/etc or ~/.config/tempest in
- place of /etc/tempest. If none of these dirs are created tempest will create
- ~/.tempest/etc when it's needed. The contents of this dir will always
- automatically be copied to all etc/ dirs in local workspaces as an initial
+#. Installing Tempest may create a ``/etc/tempest dir``, however if one isn't
+ created you can create one or use ``~/.tempest/etc`` or ``~/.config/tempest`` in
+ place of ``/etc/tempest``. If none of these dirs are created Tempest will create
+ ``~/.tempest/etc`` when it's needed. The contents of this dir will always
+ automatically be copied to all ``etc/`` dirs in local workspaces as an initial
setup step. So if there is any common configuration you'd like to be shared
between local Tempest workspaces it's recommended that you pre-populate it
before running ``tempest init``.
@@ -90,12 +90,12 @@
is that you'll create a new working directory for each to maintain separate
configuration files and local artifact storage for each.
-#. Then cd into the newly created working dir and also modify the local
- config files located in the etc/ subdir created by the ``tempest init``
- command. Tempest is expecting a tempest.conf file in etc/ so if only a
+#. Then ``cd`` into the newly created working dir and also modify the local
+ config files located in the ``etc/`` subdir created by the ``tempest init``
+ command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a
sample exists you must rename or copy it to tempest.conf before making
any changes to it otherwise Tempest will not know how to load it. For
- details on configuring tempest refer to the :ref:`tempest-configuration`.
+ details on configuring Tempest refer to the :ref:`tempest-configuration`.
#. Once the configuration is done you're now ready to run Tempest. This can
be done using the :ref:`tempest_run` command. This can be done by either
@@ -117,15 +117,15 @@
will run the same set of tests as the default gate jobs.
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
-.. _ostestr: http://docs.openstack.org/developer/os-testr/
+.. _ostestr: https://docs.openstack.org/os-testr/latest/
Library
-------
Tempest exposes a library interface. This interface is a stable interface and
should be backwards compatible (including backwards compatibility with the
old tempest-lib package, with the exception of the import). If you plan to
-directly consume tempest in your project you should only import code from the
-tempest library interface, other pieces of tempest do not have the same
+directly consume Tempest in your project you should only import code from the
+Tempest library interface, other pieces of Tempest do not have the same
stable interface and there are no guarantees on the Python API unless otherwise
stated.
@@ -137,7 +137,7 @@
shows what changes have been released on each version.
Tempest's released versions are broken into 2 sets of information. Depending on
-how you intend to consume tempest you might need
+how you intend to consume Tempest you might need
The version is a set of 3 numbers:
@@ -146,12 +146,12 @@
While this is almost `semver`_ like, the way versioning is handled is slightly
different:
-X is used to represent the supported OpenStack releases for tempest tests
-in-tree, and to signify major feature changes to tempest. It's a monotonically
+X is used to represent the supported OpenStack releases for Tempest tests
+in-tree, and to signify major feature changes to Tempest. It's a monotonically
increasing integer where each version either indicates a new supported OpenStack
release, the drop of support for an OpenStack release (which will coincide with
the upstream stable branch going EOL), or a major feature lands (or is removed)
-from tempest.
+from Tempest.
Y.Z is used to represent library interface changes. This is treated the same
way as minor and patch versions from `semver`_ but only for the library
@@ -166,16 +166,16 @@
Detailed configuration of Tempest is beyond the scope of this
document see :ref:`tempest-configuration` for more details on configuring
-Tempest. The etc/tempest.conf.sample attempts to be a self-documenting version
-of the configuration.
+Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting
+version of the configuration.
You can generate a new sample tempest.conf file, run the following
command from the top level of the Tempest directory::
$ tox -e genconfig
-The most important pieces that are needed are the user ids, openstack
-endpoint, and basic flavors and images needed to run tests.
+The most important pieces that are needed are the user ids, OpenStack
+endpoints, and basic flavors and images needed to run tests.
Unit Tests
----------
@@ -190,13 +190,13 @@
is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the
Tempest suite.
-Alternatively, there are the py27 and py34 tox jobs which will run the unit
+Alternatively, there are the py27 and py35 tox jobs which will run the unit
tests with the corresponding version of python.
Python 2.6
----------
-Starting in the kilo release the OpenStack services dropped all support for
+Starting in the Kilo release the OpenStack services dropped all support for
python 2.6. This change has been mirrored in Tempest, starting after the
tempest-2 tag. This means that proposed changes to Tempest which only fix
python 2.6 compatibility will be rejected, and moving forward more features not
@@ -208,8 +208,8 @@
Python 3.x
----------
-Starting during the Pike cycle Tempest has a gating CI job that runs tempest
-with Python 3. Any tempest release after 15.0.0 should fully support running
+Starting during the Pike cycle Tempest has a gating CI job that runs Tempest
+with Python 3. Any Tempest release after 15.0.0 should fully support running
under Python 3 as well as Python 2.7.
Legacy run method
@@ -239,10 +239,10 @@
.. note::
- If you have a running devstack environment, Tempest will be
+ If you have a running DevStack environment, Tempest will be
automatically configured and placed in ``/opt/stack/tempest``. It
will have a configuration file already set up to work with your
- devstack installation.
+ DevStack installation.
Tempest is not tied to any single test runner, but `testr`_ is the most commonly
used tool. Also, the nosetests test runner is **not** recommended to run Tempest.
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 9b272bb..7d28320 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -21,6 +21,15 @@
to a Gerrit review.
+Execution time
+--------------
+While checking in the job logs that a new test is actually executed, also
+pay attention to the execution time of that test. Keep in mind that each test
+is going to be executed hundreds of time each day, because Tempest tests
+run in many OpenStack projects. It's worth considering how important/critical
+the feature under test is with how costly the new test is.
+
+
Unit Tests
----------
@@ -48,6 +57,17 @@
abstract the duplicated code into a function or method.
+Tests overlap
+-------------
+When a new test is being proposed, question whether this feature is not already
+tested with Tempest. Tempest has more than 1200 tests, spread amongst many
+directories, so it's easy to introduce test duplication. For example, testing
+volume attachment to a server could be a compute test or a volume test, depending
+on how you see it. So one must look carefully in the entire code base for possible
+overlap. As a rule of thumb, the older a feature is, the more likely it's
+already tested.
+
+
Being explicit
--------------
When tests are being added that depend on a configurable feature or extension,
@@ -60,8 +80,8 @@
Configuration Options
---------------------
-With the introduction of the tempest external test plugin interface we needed
-to provide a stable contract for tempest's configuration options. This means
+With the introduction of the Tempest external test plugin interface we needed
+to provide a stable contract for Tempest's configuration options. This means
we can no longer simply remove a configuration option when it's no longer used.
Patches proposed that remove options without a deprecation cycle should not
be approved. Similarly when changing default values with configuration we need
@@ -91,7 +111,7 @@
anything backwards incompatible or would require a user to take note or do
something extra.
-.. _reno: http://docs.openstack.org/developer/reno/
+.. _reno: https://docs.openstack.org/reno/latest/
Deprecated Code
---------------
diff --git a/doc/source/conf.py b/doc/source/conf.py
index cbfcc09..0cfdf34 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -22,10 +22,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys
import os
import subprocess
-import warnings
# Build the plugin registry
def build_plugin_registry(app):
@@ -34,7 +32,8 @@
subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir)
def setup(app):
- app.connect('builder-inited', build_plugin_registry)
+ if os.getenv('GENERATE_TEMPEST_PLUGIN_LIST', 'true').lower() == 'true':
+ app.connect('builder-inited', build_plugin_registry)
@@ -53,7 +52,7 @@
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
- 'oslosphinx',
+ 'openstackdocstheme',
'oslo_config.sphinxconfiggen',
]
@@ -62,6 +61,14 @@
todo_include_todos = True
+# openstackdocstheme options
+repository_name = 'openstack/tempest'
+bug_project = 'tempest'
+bug_tag = ''
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -117,7 +124,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'nature'
+html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -150,14 +157,6 @@
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
- "-n1"]
-try:
- html_last_updated_fmt = str(
- subprocess.Popen(git_cmd, stdout=subprocess.PIPE).communicate()[0])
-except Exception:
- warnings.warn('Cannot get last updated time from git repository. '
- 'Not setting "html_last_updated_fmt".')
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 2314222..8f2865a 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -28,7 +28,7 @@
- Run tests for admin APIs
- Generate test credentials on the fly (see `Dynamic Credentials`_)
-When keystone uses a policy that requires domain scoped tokens for admin
+When Keystone uses a policy that requires domain scoped tokens for admin
actions, the flag ``admin_domain_scope`` must be set to ``True``.
The admin user configured, if any, must have a role assigned to the domain to
be usable.
@@ -39,7 +39,7 @@
number of users available to run tests with.
You can specify the location of the file in the ``auth`` section in the
tempest.conf file. To see the specific format used in the file please refer to
-the accounts.yaml.sample file included in Tempest.
+the ``accounts.yaml.sample`` file included in Tempest.
Keystone Connection Info
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -47,19 +47,17 @@
to provide it with information about how it communicates with keystone.
This involves configuring the following options in the ``identity`` section:
- #. ``auth_version``
- #. ``uri``
- #. ``uri_v3``
+ - ``auth_version``
+ - ``uri``
+ - ``uri_v3``
The ``auth_version`` option is used to tell Tempest whether it should be using
-keystone's v2 or v3 api for communicating with keystone. (except for the
-identity api tests which will test a specific version) The two uri options are
+Keystone's v2 or v3 api for communicating with Keystone. The two uri options are
used to tell Tempest the url of the keystone endpoint. The ``uri`` option is
-used for keystone v2 request and ``uri_v3`` is used for keystone v3. You want to
+used for Keystone v2 request and ``uri_v3`` is used for Keystone v3. You want to
ensure that which ever version you set for ``auth_version`` has its uri option
defined.
-
Credential Provider Mechanisms
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -96,7 +94,7 @@
When the ``admin_domain_scope`` option is set to ``True``, provisioned admin
accounts will be assigned a role on domain configured in
``default_credentials_domain_name``. This will make the accounts provisioned
-usable in a cloud where domain scoped tokens are required by keystone for
+usable in a cloud where domain scoped tokens are required by Keystone for
admin operations. Note that the initial pre-provision admin accounts,
configured in tempest.conf, must have a role on the same domain as well, for
Dynamic Credentials to work.
@@ -141,10 +139,10 @@
tenants it's using are empty and may sporadically fail if there are unexpected
resources present.
-When the keystone in the target cloud requires domain scoped tokens to
+When the Keystone in the target cloud requires domain scoped tokens to
perform admin actions, all pre-provisioned admin users must have a role
assigned on the domain where test accounts a provisioned.
-The option ``admin_domain_scope`` is used to tell tempest that domain scoped
+The option ``admin_domain_scope`` is used to tell Tempest that domain scoped
tokens shall be used. ``default_credentials_domain_name`` is the domain where
test accounts are expected to be provisioned if no domain is specified.
@@ -168,7 +166,7 @@
#. ``flavor_ref_alt``
Both of these options are in the ``compute`` section of the config file and take
-in the flavor id (not the name) from nova. The ``flavor_ref`` option is what
+in the flavor id (not the name) from Nova. The ``flavor_ref`` option is what
will be used for booting almost all of the guests; ``flavor_ref_alt`` is only
used in tests where two different-sized servers are required (for example, a
resize test).
@@ -186,7 +184,7 @@
#. ``image_ref``
#. ``image_ref_alt``
-Both options are expecting an image id (not name) from nova. The ``image_ref``
+Both options are expecting an image id (not name) from Nova. The ``image_ref``
option is what will be used for booting the majority of servers in Tempest.
``image_ref_alt`` is used for tests that require two images such as rebuild. If
two images are not available you can set both options to the same image id and
@@ -225,7 +223,7 @@
Networking
----------
OpenStack has a myriad of different networking configurations possible and
-depending on which of the two network backends, nova-network or neutron, you are
+depending on which of the two network backends, nova-network or Neutron, you are
using things can vary drastically. Due to this complexity Tempest has to provide
a certain level of flexibility in its configuration to ensure it will work
against any cloud. This ends up causing a large number of permutations in
@@ -303,21 +301,21 @@
server with multiple networks. If this is not the case for your cloud then using
an accounts file is recommended because it provides the necessary flexibility to
describe your configuration. Dynamic credentials is not able to dynamically
-allocate things as necessary if neutron is not enabled.
+allocate things as necessary if Neutron is not enabled.
-With neutron and dynamic credentials enabled there should not be any additional
+With Neutron and dynamic credentials enabled there should not be any additional
configuration necessary to enable Tempest to create servers with working
networking, assuming you have properly configured the ``network`` section to
-work for your cloud. Tempest will dynamically create the neutron resources
+work for your cloud. Tempest will dynamically create the Neutron resources
necessary to enable using servers with that network. Also, just as with the
-accounts file, if you specify a fixed network name while using neutron and
+accounts file, if you specify a fixed network name while using Neutron and
dynamic credentials it will enable running tests which require a static network
and it will additionally be used as a fallback for server creation. However,
unlike accounts.yaml this should never be triggered.
However, there is an option ``create_isolated_networks`` to disable dynamic
credentials's automatic provisioning of network resources. If this option is set
-to False you will have to either rely on there only being a single/default
+to ``False`` you will have to either rely on there only being a single/default
network available for the server creation, or use ``fixed_network_name`` to
inform Tempest which network to use.
@@ -337,16 +335,16 @@
The ``run_validation`` is used to enable or disable ssh connectivity for
all tests (with the exception of scenario tests which do not have a flag for
-enabling or disabling ssh) To enable ssh connectivity this needs be set to ``true``.
+enabling or disabling ssh) To enable ssh connectivity this needs be set to ``True``.
-The ``connect_method`` option is used to tell tempest what kind of IP to use for
+The ``connect_method`` option is used to tell Tempest what kind of IP to use for
establishing a connection to the server. Two methods are available: ``fixed``
and ``floating``, the later being set by default. If this is set to floating
-tempest will create a floating ip for the server before attempted to connect
+Tempest will create a floating ip for the server before attempted to connect
to it. The IP for the floating ip is what is used for the connection.
For the ``auth_method`` option there is currently, only one valid option,
-``keypair``. With this set to ``keypair`` tempest will create an ssh keypair
+``keypair``. With this set to ``keypair`` Tempest will create an ssh keypair
and use that for authenticating against the created server.
Configuring Available Services
@@ -360,8 +358,8 @@
The ``service_available`` section of the config file is used to set which
services are available. It contains a boolean option for each service (except
-for keystone which is a hard requirement) set it to True if the service is
-available or False if it is not.
+for Keystone which is a hard requirement) set it to ``True`` if the service is
+available or ``False`` if it is not.
Service Catalog
^^^^^^^^^^^^^^^
@@ -383,17 +381,18 @@
service you can set the ``region`` option in that service's section.
It should also be noted that the default values for these options are set
-to what devstack uses (which is a de facto standard for service catalog
+to what DevStack uses (which is a de facto standard for service catalog
entries). So often nothing actually needs to be set on these options to enable
communication to a particular service. It is only if you are either not using
-the same ``catalog_type`` as devstack or you want Tempest to talk to a different
-endpoint type instead of publicURL for a service that these need to be changed.
+the same ``catalog_type`` as DevStack or you want Tempest to talk to a different
+endpoint type instead of ``publicURL`` for a service that these need to be
+changed.
.. note::
Tempest does not serve all kinds of fancy URLs in the service catalog. The
service catalog should be in a standard format (which is going to be
- standardized at the keystone level).
+ standardized at the Keystone level).
Tempest expects URLs in the Service catalog in the following format:
* ``http://example.com:1234/<version-info>``
@@ -421,7 +420,7 @@
configured as to which optional features are enabled. This is in order to
prevent bugs in the discovery mechanisms from masking failures.
-The service feature-enabled config sections are how Tempest addresses the
+The service ``feature-enabled`` config sections are how Tempest addresses the
optional feature question. Each service that has tests for optional features
contains one of these sections. The only options in it are boolean options
with the name of a feature which is used. If it is set to false any test which
@@ -432,7 +431,7 @@
API Extensions
^^^^^^^^^^^^^^
The service feature-enabled sections often contain an ``api-extensions`` option
-(or in the case of swift a ``discoverable_apis`` option). This is used to tell
+(or in the case of Swift a ``discoverable_apis`` option). This is used to tell
Tempest which api extensions (or configurable middleware) is used in your
deployment. It has two valid config states: either it contains a single value
``all`` (which is the default) which means that every api extension is assumed
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 1264ecc..f562850 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -2,18 +2,16 @@
Tempest Testing Project
=======================
---------
Overview
---------
+========
.. toctree::
:maxdepth: 2
overview
-------------
Field Guides
-------------
+============
Tempest contains tests of many different types, the field guides
attempt to explain these in a way that makes it easy to understand
where your test contributions should go.
@@ -26,11 +24,9 @@
field_guide/scenario
field_guide/unit_tests
-=========
-For users
-=========
+Users Guide
+===========
----------------------------
Tempest Configuration Guide
---------------------------
@@ -40,7 +36,6 @@
configuration
sampleconf
----------------------
Command Documentation
---------------------
@@ -53,11 +48,9 @@
workspace
run
-==============
-For developers
-==============
+Developers Guide
+================
------------
Development
-----------
@@ -70,7 +63,6 @@
test_removal
write_tests
--------
Plugins
-------
@@ -80,7 +72,6 @@
plugin
plugin-registry
--------
Library
-------
@@ -89,7 +80,6 @@
library
-==================
Indices and tables
==================
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 29248d1..a461a0f 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -68,3 +68,4 @@
library/api_microversion_testing
library/auth
library/clients
+ library/credential_providers
diff --git a/doc/source/library/credential_providers.rst b/doc/source/library/credential_providers.rst
new file mode 100644
index 0000000..f4eb37d
--- /dev/null
+++ b/doc/source/library/credential_providers.rst
@@ -0,0 +1,148 @@
+.. _cred_providers:
+
+Credential Providers
+====================
+
+These library interfaces are used to deal with allocating credentials on demand
+either dynamically by calling keystone to allocate new credentials, or from
+a list of preprovisioned credentials. These 2 modules are implementations of
+the same abstract credential providers class and can be used interchangeably.
+However, each implementation has some additional parameters that are used to
+influence the behavior of the modules. The API reference at the bottom of this
+doc shows the interface definitions for both modules, however that may be a bit
+opaque. You can see some examples of how to leverage this interface below.
+
+Initialization Example
+----------------------
+This example is from Tempest itself (from tempest/common/credentials_factory.py
+just modified slightly) and is how it initializes the credential provider based
+on config::
+
+ from tempest import config
+ from tempest.lib.common import dynamic_creds
+ from tempest.lib.common import preprov_creds
+
+ CONF = config.CONF
+
+ def get_credentials_provider(name, network_resources=None,
+ force_tenant_isolation=False,
+ identity_version=None):
+ # If a test requires a new account to work, it can have it via forcing
+ # dynamic credentials. A new account will be produced only for that test.
+ # In case admin credentials are not available for the account creation,
+ # the test should be skipped else it would fail.
+ identity_version = identity_version or CONF.identity.auth_version
+ if CONF.auth.use_dynamic_credentials or force_tenant_isolation:
+ admin_creds = get_configured_admin_credentials(
+ fill_in=True, identity_version=identity_version)
+ return dynamic_creds.DynamicCredentialProvider(
+ name=name,
+ network_resources=network_resources,
+ identity_version=identity_version,
+ admin_creds=admin_creds,
+ identity_admin_domain_scope=CONF.identity.admin_domain_scope,
+ identity_admin_role=CONF.identity.admin_role,
+ extra_roles=CONF.auth.tempest_roles,
+ neutron_available=CONF.service_available.neutron,
+ project_network_cidr=CONF.network.project_network_cidr,
+ project_network_mask_bits=CONF.network.project_network_mask_bits,
+ public_network_id=CONF.network.public_network_id,
+ create_networks=(CONF.auth.create_isolated_networks and not
+ CONF.network.shared_physical_network),
+ resource_prefix=CONF.resources_prefix,
+ credentials_domain=CONF.auth.default_credentials_domain_name,
+ admin_role=CONF.identity.admin_role,
+ identity_uri=CONF.identity.uri_v3,
+ identity_admin_endpoint_type=CONF.identity.v3_endpoint_type)
+ else:
+ if CONF.auth.test_accounts_file:
+ # Most params are not relevant for pre-created accounts
+ return preprov_creds.PreProvisionedCredentialProvider(
+ name=name, identity_version=identity_version,
+ accounts_lock_dir=lockutils.get_lock_path(CONF),
+ test_accounts_file=CONF.auth.test_accounts_file,
+ object_storage_operator_role=CONF.object_storage.operator_role,
+ object_storage_reseller_admin_role=reseller_admin_role,
+ credentials_domain=CONF.auth.default_credentials_domain_name,
+ admin_role=CONF.identity.admin_role,
+ identity_uri=CONF.identity.uri_v3,
+ identity_admin_endpoint_type=CONF.identity.v3_endpoint_type)
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
+
+This function just returns an initialized credential provider class based on the
+config file. The consumer of this function treats the output as the same
+regardless of whether it's a dynamic or preprovisioned provider object.
+
+Dealing with Credentials
+------------------------
+
+Once you have a credential provider object created the access patterns for
+allocating and removing credentials are the same across both the dynamic
+and preprovisioned credentials. These are defined in the abstract
+CredentialProvider class. At a high level the credentials provider enables
+you to get 3 basic types of credentials at once (per object): a primary, alt,
+and admin. You're also able to allocate a credential by role. These credentials
+are tracked by the provider object and delete must manually be called otherwise
+the created resources will not be deleted (or returned to the pool in the case
+of preprovisioned creds)
+
+Examples
+''''''''
+
+Continuing from the example above, to allocate credentials by the 3 basic types
+you can do the following::
+
+ provider = get_credentials_provider('my_tests')
+ primary_creds = provider.get_primary_creds()
+ alt_creds = provider.get_alt_creds()
+ admin_creds = provider.get_admin_creds()
+ # Make sure to delete the credentials when you're finished
+ provider.clear_creds()
+
+To create and interact with credentials by role you can do the following::
+
+ provider = get_credentials_provider('my_tests')
+ my_role_creds = provider.get_creds_by_role({'roles': ['my_role']})
+ # provider.clear_creds() will clear all creds including those allocated by
+ # role
+ provider.clear_creds()
+
+When multiple roles are specified a set of creds with all the roles assigned
+will be allocated::
+
+ provider = get_credentials_provider('my_tests')
+ my_role_creds = provider.get_creds_by_role({'roles': ['my_role',
+ 'my_other_role']})
+ # provider.clear_creds() will clear all creds including those allocated by
+ # role
+ provider.clear_creds()
+
+If you need multiple sets of credentials with the same roles you can also do
+this by leveraging the ``force_new`` kwarg::
+
+ provider = get_credentials_provider('my_tests')
+ my_role_creds = provider.get_creds_by_role({'roles': ['my_role']})
+ my_role_other_creds = provider.get_creds_by_role({'roles': ['my_role']},
+ force_new=True)
+ # provider.clear_creds() will clear all creds including those allocated by
+ # role
+ provider.clear_creds()
+
+API Reference
+=============
+
+------------------------------
+The dynamic credentials module
+------------------------------
+
+.. automodule:: tempest.lib.common.dynamic_creds
+ :members:
+
+--------------------------------------
+The pre-provisioned credentials module
+--------------------------------------
+
+.. automodule:: tempest.lib.common.preprov_creds
+ :members:
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index b516055..d80081d 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -111,7 +111,7 @@
This document explains how to implement Microversion tests using those
interfaces.
-.. _API Microversion testing Framework: http://docs.openstack.org/developer/tempest/library/api_microversion_testing.html
+.. _API Microversion testing Framework: https://docs.openstack.org/tempest/latest/library/api_microversion_testing.html
Step1: Add skip logic based on configured Microversion range
@@ -296,38 +296,46 @@
* `2.1`_
- .. _2.1: http://docs.openstack.org/developer/nova/api_microversion_history.html#id1
+ .. _2.1: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1
* `2.2`_
- .. _2.2: http://docs.openstack.org/developer/nova/api_microversion_history.html#id2
+ .. _2.2: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
* `2.10`_
- .. _2.10: http://docs.openstack.org/developer/nova/api_microversion_history.html#id9
+ .. _2.10: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
* `2.20`_
- .. _2.20: http://docs.openstack.org/developer/nova/api_microversion_history.html#id18
+ .. _2.20: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
* `2.25`_
- .. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka
+ .. _2.25: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
* `2.32`_
- .. _2.32: http://docs.openstack.org/developer/nova/api_microversion_history.html#id29
+ .. _2.32: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
* `2.37`_
- .. _2.37: http://docs.openstack.org/developer/nova/api_microversion_history.html#id34
+ .. _2.37: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
* `2.42`_
- .. _2.42: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-ocata
+ .. _2.42: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
+
+ * `2.47`_
+
+ .. _2.47: http://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+
+ * `2.48`_
+
+ .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
* Volume
* `3.3`_
- .. _3.3: https://docs.openstack.org/developer/cinder/devref/api_microversion_history.html#id4
+ .. _3.3: https://docs.openstack.org/cinder/ocata/devref/api_microversion_history.html#id4
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index b3af92f..77ef9ed 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -28,6 +28,7 @@
* tempest.lib.*
* tempest.config
* tempest.test_discover.plugins
+* tempest.common.credentials_factory
If there is an interface from tempest that you need to rely on in your plugin
which is not listed above, it likely needs to be migrated to tempest.lib. In
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index d06e4ba..07c3046 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -171,7 +171,7 @@
`tempest plugin mechanism`_
to maintain continuity after migrating the tests out of tempest.
-.. _tempest plugin mechanism: http://docs.openstack.org/developer/tempest/plugin.html
+.. _tempest plugin mechanism: https://docs.openstack.org/tempest/latest/plugin.html
API Compatibility
"""""""""""""""""
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 2363fa6..aec55e9 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -6,13 +6,13 @@
This guide serves as a starting point for developers working on writing new
Tempest tests. At a high level tests in Tempest are just tests that conform to
the standard python `unit test`_ framework. But there are several aspects of
-that are unique to tempest and it's role as an integration test suite running
+that are unique to Tempest and its role as an integration test suite running
against a real cloud.
.. _unit test: https://docs.python.org/3.6/library/unittest.html
-.. note:: This guide is for writing tests in the tempest repository. While many
- parts of this guide are also applicable to tempest plugins, not all
+.. note:: This guide is for writing tests in the Tempest repository. While many
+ parts of this guide are also applicable to Tempest plugins, not all
the APIs mentioned are considered stable or recommended for use in
plugins. Please refer to :ref:`tempest_plugin` for details about
writing plugins
@@ -24,8 +24,8 @@
The base unit of testing in Tempest is the `TestCase`_ (also called the test
class). Each TestCase contains test methods which are the individual tests that
will be executed by the test runner. But, the TestCase is the smallest self
-contained unit for tests from the tempest perspective. It's also the level at
-which tempest is parallel safe. In other words, multiple TestCases can be
+contained unit for tests from the Tempest perspective. It's also the level at
+which Tempest is parallel safe. In other words, multiple TestCases can be
executed in parallel, but individual test methods in the same TestCase can not.
Also, all test methods within a TestCase are assumed to be executed serially. As
such you can use the test case to store variables that are shared between
@@ -151,7 +151,7 @@
You can also specify credentials with specific roles assigned. This is useful
for cases where there are specific RBAC requirements hard coded into an API.
The canonical example of this are swift tests which often want to test swift's
-concepts of operator and reseller_admin. An actual example from tempest on how
+concepts of operator and reseller_admin. An actual example from Tempest on how
to do this is::
class PublicObjectTest(base.BaseObjectTest):
@@ -178,16 +178,16 @@
+-------------------+---------------------+
| Credentials Entry | Manager Variable |
+===================+=====================+
-| primary | cls.os |
+| primary | cls.os_primary |
+-------------------+---------------------+
-| admin | cls.os_adm |
+| admin | cls.os_admin |
+-------------------+---------------------+
| alt | cls.os_alt |
+-------------------+---------------------+
| [$label, $role] | cls.os_roles_$label |
+-------------------+---------------------+
-By default cls.os is available since it is allocated in the base tempest test
+By default cls.os_primary is available since it is allocated in the base Tempest test
class (located in tempest/test.py). If your TestCase inherits from a different
direct parent class (it'll still inherit from the BaseTestCase, just not
directly) be sure to check if that class overrides allocated credentials.
@@ -195,8 +195,8 @@
Dealing with Network Allocation
'''''''''''''''''''''''''''''''
-When neutron is enabled and a testing requires networking this isn't normally
-automatically setup when a tenant is created. Since tempest needs isolated
+When Neutron is enabled and a testing requires networking this isn't normally
+automatically setup when a tenant is created. Since Tempest needs isolated
tenants to function properly it also needs to handle network allocation. By
default the base test class will allocate a network, subnet, and router
automatically (this depends on the configured credential provider, for more
@@ -270,13 +270,13 @@
class TestExampleCase(test.BaseTestCase):
def test_example_create_server(self):
- self.os.servers_client.create_server(...)
+ self.os_primary.servers_client.create_server(...)
-is all you need to do. As described previously, in the above example the ``self.os``
-is created automatically because the base test class sets the ``credentials``
-attribute to allocate a primary credential set and initializes the client
-manager as ``self.os``. This same access pattern can be used for all of the
-clients in Tempest.
+is all you need to do. As described previously, in the above example the
+``self.os_primary`` is created automatically because the base test class sets the
+``credentials`` attribute to allocate a primary credential set and initializes
+the client manager as ``self.os_primary``. This same access pattern can be used
+for all of the clients in Tempest.
Credentials Objects
-------------------
@@ -293,7 +293,7 @@
class TestExampleCase(test.BaseTestCase):
def test_example_create_server(self):
- credentials = self.os.credentials
+ credentials = self.os_primary.credentials
The credentials object provides access to all of the credential information you
would need to make API requests. For example, building off the previous
@@ -304,7 +304,7 @@
class TestExampleCase(test.BaseTestCase):
def test_example_create_server(self):
- credentials = self.os.credentials
+ credentials = self.os_primary.credentials
username = credentials.username
user_id = credentials.user_id
password = credentials.password
diff --git a/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml b/releasenotes/notes/10/10.0-supported-openstack-releases-b88db468695348f6.yaml
similarity index 100%
rename from releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml
rename to releasenotes/notes/10/10.0-supported-openstack-releases-b88db468695348f6.yaml
diff --git a/releasenotes/notes/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml b/releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml
similarity index 75%
rename from releasenotes/notes/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml
rename to releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml
index 0ed3130..c1edd63 100644
--- a/releasenotes/notes/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml
+++ b/releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml
@@ -5,7 +5,7 @@
it lives directly in the tempest project. For more information refer to
the `library docs`_.
- .. _library docs: http://docs.openstack.org/developer/tempest/library.html#library
+ .. _library docs: https://docs.openstack.org/tempest/latest/library.html#current-library-apis
features:
- Tempest library interface
diff --git a/releasenotes/notes/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml b/releasenotes/notes/10/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml
similarity index 100%
rename from releasenotes/notes/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml
rename to releasenotes/notes/10/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml
diff --git a/releasenotes/notes/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml b/releasenotes/notes/11/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml
similarity index 100%
rename from releasenotes/notes/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml
rename to releasenotes/notes/11/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml
diff --git a/releasenotes/notes/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml b/releasenotes/notes/11/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml
similarity index 100%
rename from releasenotes/notes/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml
rename to releasenotes/notes/11/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml
diff --git a/releasenotes/notes/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml b/releasenotes/notes/11/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml
similarity index 100%
rename from releasenotes/notes/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml
rename to releasenotes/notes/11/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml
diff --git a/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml b/releasenotes/notes/12/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
similarity index 100%
rename from releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
rename to releasenotes/notes/12/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
diff --git a/releasenotes/notes/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml b/releasenotes/notes/12/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml
rename to releasenotes/notes/12/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml
diff --git a/releasenotes/notes/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml b/releasenotes/notes/12/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml
rename to releasenotes/notes/12/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml
diff --git a/releasenotes/notes/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml b/releasenotes/notes/12/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml
rename to releasenotes/notes/12/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml
diff --git a/releasenotes/notes/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml b/releasenotes/notes/12/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml
rename to releasenotes/notes/12/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml
diff --git a/releasenotes/notes/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml b/releasenotes/notes/12/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml
rename to releasenotes/notes/12/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml
diff --git a/releasenotes/notes/12.1.0-bug-1486834-7ebca15836ae27a9.yaml b/releasenotes/notes/12/12.1.0-bug-1486834-7ebca15836ae27a9.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-bug-1486834-7ebca15836ae27a9.yaml
rename to releasenotes/notes/12/12.1.0-bug-1486834-7ebca15836ae27a9.yaml
diff --git a/releasenotes/notes/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml b/releasenotes/notes/12/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml
rename to releasenotes/notes/12/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml
diff --git a/releasenotes/notes/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/12/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml
rename to releasenotes/notes/12/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml
diff --git a/releasenotes/notes/12.1.0-new-test-utils-module-adf34468c4d52719.yaml b/releasenotes/notes/12/12.1.0-new-test-utils-module-adf34468c4d52719.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-new-test-utils-module-adf34468c4d52719.yaml
rename to releasenotes/notes/12/12.1.0-new-test-utils-module-adf34468c4d52719.yaml
diff --git a/releasenotes/notes/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml b/releasenotes/notes/12/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml
rename to releasenotes/notes/12/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml
diff --git a/releasenotes/notes/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml b/releasenotes/notes/12/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml
rename to releasenotes/notes/12/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml
diff --git a/releasenotes/notes/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml b/releasenotes/notes/12/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
rename to releasenotes/notes/12/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
diff --git a/releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml b/releasenotes/notes/12/12.1.0-remove-trove-tests-666522e9113549f9.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml
rename to releasenotes/notes/12/12.1.0-remove-trove-tests-666522e9113549f9.yaml
diff --git a/releasenotes/notes/12.1.0-routers-client-as-library-25a363379da351f6.yaml b/releasenotes/notes/12/12.1.0-routers-client-as-library-25a363379da351f6.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-routers-client-as-library-25a363379da351f6.yaml
rename to releasenotes/notes/12/12.1.0-routers-client-as-library-25a363379da351f6.yaml
diff --git a/releasenotes/notes/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml b/releasenotes/notes/12/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml
rename to releasenotes/notes/12/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml
diff --git a/releasenotes/notes/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml b/releasenotes/notes/12/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml
similarity index 100%
rename from releasenotes/notes/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml
rename to releasenotes/notes/12/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml
diff --git a/releasenotes/notes/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml b/releasenotes/notes/12/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
rename to releasenotes/notes/12/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
diff --git a/releasenotes/notes/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml b/releasenotes/notes/12/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml
rename to releasenotes/notes/12/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml
diff --git a/releasenotes/notes/12.2.0-clients_module-16f3025f515bf9ec.yaml b/releasenotes/notes/12/12.2.0-clients_module-16f3025f515bf9ec.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-clients_module-16f3025f515bf9ec.yaml
rename to releasenotes/notes/12/12.2.0-clients_module-16f3025f515bf9ec.yaml
diff --git a/releasenotes/notes/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml b/releasenotes/notes/12/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml
rename to releasenotes/notes/12/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml
diff --git a/releasenotes/notes/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml b/releasenotes/notes/12/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml
rename to releasenotes/notes/12/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml
diff --git a/releasenotes/notes/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml b/releasenotes/notes/12/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml
rename to releasenotes/notes/12/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml
diff --git a/releasenotes/notes/12.2.0-service_client_config-8a1d7b4de769c633.yaml b/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-service_client_config-8a1d7b4de769c633.yaml
rename to releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
diff --git a/releasenotes/notes/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml b/releasenotes/notes/12/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml
similarity index 100%
rename from releasenotes/notes/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml
rename to releasenotes/notes/12/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml
diff --git a/releasenotes/notes/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml b/releasenotes/notes/13/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
rename to releasenotes/notes/13/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
diff --git a/releasenotes/notes/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml b/releasenotes/notes/13/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
rename to releasenotes/notes/13/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
diff --git a/releasenotes/notes/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml b/releasenotes/notes/13/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml
rename to releasenotes/notes/13/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml
diff --git a/releasenotes/notes/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml b/releasenotes/notes/13/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml
rename to releasenotes/notes/13/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml
diff --git a/releasenotes/notes/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml b/releasenotes/notes/13/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml
rename to releasenotes/notes/13/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml
diff --git a/releasenotes/notes/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml b/releasenotes/notes/13/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml
rename to releasenotes/notes/13/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml
diff --git a/releasenotes/notes/13.0.0-volume-clients-as-library-660811011be29d1a.yaml b/releasenotes/notes/13/13.0.0-volume-clients-as-library-660811011be29d1a.yaml
similarity index 100%
rename from releasenotes/notes/13.0.0-volume-clients-as-library-660811011be29d1a.yaml
rename to releasenotes/notes/13/13.0.0-volume-clients-as-library-660811011be29d1a.yaml
diff --git a/releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml b/releasenotes/notes/14/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
rename to releasenotes/notes/14/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
diff --git a/releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml b/releasenotes/notes/14/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
rename to releasenotes/notes/14/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
diff --git a/releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml b/releasenotes/notes/14/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
rename to releasenotes/notes/14/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
diff --git a/releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml b/releasenotes/notes/14/14.0.0-add-image-clients-af94564fb34ddca6.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml
rename to releasenotes/notes/14/14.0.0-add-image-clients-af94564fb34ddca6.yaml
diff --git a/releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/14/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
rename to releasenotes/notes/14/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
diff --git a/releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml b/releasenotes/notes/14/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
rename to releasenotes/notes/14/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
diff --git a/releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml b/releasenotes/notes/14/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
rename to releasenotes/notes/14/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
diff --git a/releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml b/releasenotes/notes/14/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
rename to releasenotes/notes/14/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
diff --git a/releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml b/releasenotes/notes/14/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
rename to releasenotes/notes/14/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
diff --git a/releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml b/releasenotes/notes/14/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
rename to releasenotes/notes/14/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
diff --git a/releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml b/releasenotes/notes/14/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
rename to releasenotes/notes/14/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
diff --git a/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml b/releasenotes/notes/14/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
rename to releasenotes/notes/14/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
diff --git a/releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml b/releasenotes/notes/14/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
rename to releasenotes/notes/14/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
diff --git a/releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml b/releasenotes/notes/14/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
rename to releasenotes/notes/14/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
diff --git a/releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml b/releasenotes/notes/14/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
rename to releasenotes/notes/14/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
diff --git a/releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml b/releasenotes/notes/14/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
similarity index 100%
rename from releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
rename to releasenotes/notes/14/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
diff --git a/releasenotes/notes/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/15/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
rename to releasenotes/notes/15/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
diff --git a/releasenotes/notes/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml b/releasenotes/notes/15/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml
rename to releasenotes/notes/15/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml
diff --git a/releasenotes/notes/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml b/releasenotes/notes/15/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml
rename to releasenotes/notes/15/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml
diff --git a/releasenotes/notes/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml b/releasenotes/notes/15/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml
rename to releasenotes/notes/15/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml
diff --git a/releasenotes/notes/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml b/releasenotes/notes/15/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml
rename to releasenotes/notes/15/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml
diff --git a/releasenotes/notes/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml b/releasenotes/notes/15/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml
rename to releasenotes/notes/15/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml
diff --git a/releasenotes/notes/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml b/releasenotes/notes/15/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml
rename to releasenotes/notes/15/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml
diff --git a/releasenotes/notes/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml b/releasenotes/notes/15/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml
rename to releasenotes/notes/15/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml
diff --git a/releasenotes/notes/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml b/releasenotes/notes/15/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml
rename to releasenotes/notes/15/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml
diff --git a/releasenotes/notes/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml b/releasenotes/notes/15/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml
rename to releasenotes/notes/15/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml
diff --git a/releasenotes/notes/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml b/releasenotes/notes/15/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml
rename to releasenotes/notes/15/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml
diff --git a/releasenotes/notes/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml b/releasenotes/notes/15/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml
similarity index 100%
rename from releasenotes/notes/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml
rename to releasenotes/notes/15/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml
diff --git a/releasenotes/notes/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml b/releasenotes/notes/16/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
rename to releasenotes/notes/16/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
diff --git a/releasenotes/notes/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml b/releasenotes/notes/16/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
rename to releasenotes/notes/16/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
diff --git a/releasenotes/notes/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml b/releasenotes/notes/16/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
rename to releasenotes/notes/16/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
diff --git a/releasenotes/notes/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml b/releasenotes/notes/16/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
rename to releasenotes/notes/16/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
diff --git a/releasenotes/notes/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml b/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
rename to releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
diff --git a/releasenotes/notes/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml b/releasenotes/notes/16/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml
rename to releasenotes/notes/16/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml
diff --git a/releasenotes/notes/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml b/releasenotes/notes/16/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
rename to releasenotes/notes/16/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
diff --git a/releasenotes/notes/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml b/releasenotes/notes/16/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
rename to releasenotes/notes/16/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
diff --git a/releasenotes/notes/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml b/releasenotes/notes/16/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml
rename to releasenotes/notes/16/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml
diff --git a/releasenotes/notes/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml b/releasenotes/notes/16/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
rename to releasenotes/notes/16/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
diff --git a/releasenotes/notes/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml b/releasenotes/notes/16/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
rename to releasenotes/notes/16/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
diff --git a/releasenotes/notes/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml b/releasenotes/notes/16/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
rename to releasenotes/notes/16/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
diff --git a/releasenotes/notes/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml b/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
rename to releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
diff --git a/releasenotes/notes/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml b/releasenotes/notes/16/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
rename to releasenotes/notes/16/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
diff --git a/releasenotes/notes/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml b/releasenotes/notes/16/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
rename to releasenotes/notes/16/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
diff --git a/releasenotes/notes/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
rename to releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml b/releasenotes/notes/16/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml b/releasenotes/notes/16/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml b/releasenotes/notes/16/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml b/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml b/releasenotes/notes/16/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
diff --git a/releasenotes/notes/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml b/releasenotes/notes/16/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
rename to releasenotes/notes/16/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
diff --git a/releasenotes/notes/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml b/releasenotes/notes/16/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
rename to releasenotes/notes/16/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
diff --git a/releasenotes/notes/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml b/releasenotes/notes/16/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml
rename to releasenotes/notes/16/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml
diff --git a/releasenotes/notes/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml b/releasenotes/notes/16/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
rename to releasenotes/notes/16/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
diff --git a/releasenotes/notes/16.0.0-mitaka-eol-88ff8355fff81b55.yaml b/releasenotes/notes/16/16.0.0-mitaka-eol-88ff8355fff81b55.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-mitaka-eol-88ff8355fff81b55.yaml
rename to releasenotes/notes/16/16.0.0-mitaka-eol-88ff8355fff81b55.yaml
diff --git a/releasenotes/notes/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml b/releasenotes/notes/16/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml
rename to releasenotes/notes/16/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml
diff --git a/releasenotes/notes/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml b/releasenotes/notes/16/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
rename to releasenotes/notes/16/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
diff --git a/releasenotes/notes/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
rename to releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
diff --git a/releasenotes/notes/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
rename to releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
diff --git a/releasenotes/notes/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
rename to releasenotes/notes/16/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
diff --git a/releasenotes/notes/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
rename to releasenotes/notes/16/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
diff --git a/releasenotes/notes/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml b/releasenotes/notes/16/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml
rename to releasenotes/notes/16/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml
diff --git a/releasenotes/notes/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml b/releasenotes/notes/16/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
rename to releasenotes/notes/16/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
diff --git a/releasenotes/notes/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml b/releasenotes/notes/16/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml
rename to releasenotes/notes/16/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml
diff --git a/releasenotes/notes/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml b/releasenotes/notes/16/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml
similarity index 100%
rename from releasenotes/notes/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml
rename to releasenotes/notes/16/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml
diff --git a/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
new file mode 100644
index 0000000..9115f03
--- /dev/null
+++ b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Add a new client to handle the OAUTH token feature from the identity API.
diff --git a/releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml b/releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml
new file mode 100644
index 0000000..dec4a27
--- /dev/null
+++ b/releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add create_group_from_source to groups_client in the volume service library.
diff --git a/releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml b/releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml
new file mode 100644
index 0000000..8221d78
--- /dev/null
+++ b/releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - A new config option in the network-feature-enabled section, floating_ips,
+ to specify whether floating ips are available in the cloud under test. By
+ default this is set to True.
diff --git a/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yaml b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yaml
new file mode 100644
index 0000000..1dc33aa
--- /dev/null
+++ b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Defines the identity v3 OS-EP-FILTER EndPoint Groups API client.
+ This client manages Create, Get, Update, Check, List, and Delete
+ of EndPoint Group.
+
diff --git a/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml b/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml
new file mode 100644
index 0000000..bc7bcc8
--- /dev/null
+++ b/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new config option 'manage_snapshot_ref' is added in the volume section,
+ to specify snapshot ref parameter for different storage backend drivers
+ when managing an existing snapshot. By default it is set to fit the LVM
+ driver.
diff --git a/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml b/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml
new file mode 100644
index 0000000..46f3b49
--- /dev/null
+++ b/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ The ``list_endpoints`` method of the v3 ``EndPointsClient`` class now has
+ an additional ``**params`` argument that enables passing additional
+ information in the query string of the HTTP request.
diff --git a/releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml b/releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml
new file mode 100644
index 0000000..cee2d76
--- /dev/null
+++ b/releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ The ``list_backups`` method of the v2 ``BackupsClient`` class now has
+ an additional ``**params`` argument that enables passing additional
+ information in the query string of the HTTP request.
diff --git a/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml b/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml
new file mode 100644
index 0000000..4abfe9e
--- /dev/null
+++ b/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ Add a missing return statement to the retype_volume API in the v2 volumes_client library.
+ This changes the response body from None to an empty dictionary.
+
diff --git a/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
new file mode 100644
index 0000000..8fdf4f0
--- /dev/null
+++ b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add --save-state option to allow saving state of cloud before tempest run.
diff --git a/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml b/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml
new file mode 100644
index 0000000..e0ac87c
--- /dev/null
+++ b/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add validation schema for Nova server diagnostics API
\ No newline at end of file
diff --git a/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml b/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml
new file mode 100644
index 0000000..140df60
--- /dev/null
+++ b/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add show snapshot metadata item API to v2 snapshots_client library.
+ This feature enables the possibility to show a snapshot's metadata for
+ a specific key.
diff --git a/releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yaml b/releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yaml
new file mode 100644
index 0000000..ac7c74e
--- /dev/null
+++ b/releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add show volume image metadata API to v2 volumes_client library.
+ This feature enables the possibility to show volume's image metadata.
diff --git a/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml b/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml
new file mode 100644
index 0000000..49a935c
--- /dev/null
+++ b/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add show volume metadata item API to v2 volumes_client library.
+ This feature enables the possibility to show a volume's metadata for
+ a specific key.
diff --git a/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml b/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml
new file mode 100644
index 0000000..361e387
--- /dev/null
+++ b/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Define v3 volumes_client for the volume service as a library interface,
+ allowing other projects to use this module as a stable library without
+ maintenance changes.
+ Add show volume summary API to v3 volumes_client library, min_microversion
+ of this API is 3.12.
+
+ * volumes_client(v3)
diff --git a/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml b/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml
new file mode 100644
index 0000000..7cd6887
--- /dev/null
+++ b/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Define v3 backups_client for the volume service as a library interface,
+ allowing other projects to use this module as a stable library without
+ maintenance changes.
+ Add update backup API to v3 backups_client library, min_microversion
+ of this API is 3.9.
+
+ * backups_client(v3)
diff --git a/releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml b/releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml
new file mode 100644
index 0000000..23c30af
--- /dev/null
+++ b/releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add update_group to groups_client in the volume service library.
diff --git a/releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml b/releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml
new file mode 100644
index 0000000..2ca6e5a
--- /dev/null
+++ b/releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add group_snapshots client for the volume service as library.
+ Add tempest tests for create group snapshot, delete group snapshot, show
+ group snapshot, and list group snapshots volume APIs.
diff --git a/releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml b/releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml
new file mode 100644
index 0000000..4fd3bee
--- /dev/null
+++ b/releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add list_group_type and show_group_type in the group_types client for
+ the volume service. Add tests for create/delete/show/list group types.
diff --git a/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml b/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml
new file mode 100644
index 0000000..898d366
--- /dev/null
+++ b/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add groups and group_types clients for the volume service as library.
+ Add tempest tests for create group, delete group, show group, and
+ list group volume APIs.
diff --git a/releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml b/releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml
new file mode 100644
index 0000000..6faa536
--- /dev/null
+++ b/releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ The credentials_factory.py module is now marked as stable for Tempest
+ plugins. It provides helpers that can be used by Tempest plugins to
+ obtain test credentials for their test cases in a format that honors the
+ Tempest configuration in use.
+ Credentials may be provisioned on the fly during the test run, or they
+ can be setup in advance and fed to test via a YAML file; they can be
+ setup for identity v2 or identity v3.
diff --git a/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml b/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml
new file mode 100644
index 0000000..c88522e
--- /dev/null
+++ b/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+ - |
+ Deprecate default value for configuration parameter v3_endpoint_type
+ of identity section in OpenStack Pike and modify the default value to
+ publicURL in OpenStack Q release.
diff --git a/releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml b/releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml
new file mode 100644
index 0000000..414adf1
--- /dev/null
+++ b/releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add the ``disable_log_reason`` and the ``update_forced_down`` API endpoints
+ to the compute ``services_client``.
+ Add '2.11' compute validation schema for compute services API.
diff --git a/releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml b/releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml
new file mode 100644
index 0000000..d94de3e
--- /dev/null
+++ b/releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add additional API endpoints to the identity v2 client token API:
+ - list_endpoints_for_token
+ - check_token_existence
diff --git a/releasenotes/notes/identity_client-635275d43abbb807.yaml b/releasenotes/notes/identity_client-635275d43abbb807.yaml
new file mode 100644
index 0000000..6f984b7
--- /dev/null
+++ b/releasenotes/notes/identity_client-635275d43abbb807.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Enhances the v3 identity client with the ``check_token_existence``
+ endpoint, allowing users to check the existence of tokens
diff --git a/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml b/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml
new file mode 100644
index 0000000..bfebcd9
--- /dev/null
+++ b/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml
@@ -0,0 +1,4 @@
+---
+prelude: >
+ This is an intermediate release during the Pike development cycle to
+ make new functionality available to plugins and other consumers.
diff --git a/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml b/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml
new file mode 100644
index 0000000..c20cbc6
--- /dev/null
+++ b/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ The tempest module tempest.common.dynamic creds which is used for
+ dynamically allocating credentials has been migrated into tempest lib.
diff --git a/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml b/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml
new file mode 100644
index 0000000..72b8e26
--- /dev/null
+++ b/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Define below object storage service clients as libraries.
+ Add new service clients to the library interface so the
+ other projects can use these modules as stable libraries
+ without any maintenance changes.
+
+ * bulk_middleware_client
+ * capabilities_client
diff --git a/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml b/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml
new file mode 100644
index 0000000..aa5f71a
--- /dev/null
+++ b/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - The tempest module tempest.common.preprov_creds which is used to provide
+ credentials from a list of preprovisioned resources has been migrated into
+ tempest lib at tempest.lib.common.preprov_creds.
+ - The InvalidTestResource exception class from tempest.exceptions has been
+ migrated into tempest.lib.exceptions
+ - The tempest module tempest.common.fixed_network which provided utilities for
+ finding fixed networks by and helpers for picking the network to use when
+ multiple tenant networks are available has been migrated into tempest lib
+ at tempest.lib.common.fixed_network.
diff --git a/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml b/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml
new file mode 100644
index 0000000..ec81dc5
--- /dev/null
+++ b/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml
@@ -0,0 +1,15 @@
+features:
+ - |
+ Move base_client from tempest.lib.services.volume.v3 to
+ tempest.lib.services.volume, so if we want to add new
+ interfaces based on a v2 client, we can make that v2
+ client inherit from volume.base_client.BaseClient to
+ get microversion support, and then to make the new v3
+ client inherit from the v2 client, thus to avoid the
+ multiple inheritance.
+deprecations:
+ - |
+ Deprecate class BaseClient from volume.v3.base_client
+ and move it to volume.base_client.
+ ``tempest.lib.services.volume.v3.base_client.BaseClient``
+ (new ``tempest.lib.services.volume.base_client.BaseClient``)
diff --git a/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml
new file mode 100644
index 0000000..9af57b1
--- /dev/null
+++ b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Define v2.0 ``tags_client`` for the network service as a library
+ interface, allowing other projects to use this module as a stable
+ library without maintenance changes.
+
+ * tags_client(v2.0)
diff --git a/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml b/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml
new file mode 100644
index 0000000..a540c7d
--- /dev/null
+++ b/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ Pause teardown
+ When pause_teardown flag in tempest.conf is set to True a pdb breakpoint
+ is added to tearDown and tearDownClass methods in test.py.
+ This allows to pause cleaning resources process, so that used resources
+ can be examined. Closer examination of used resources may lead to faster
+ debugging.
diff --git a/releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml b/releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml
new file mode 100644
index 0000000..b6391b6
--- /dev/null
+++ b/releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - |
+ When registering service clients from installed plugins, all registrations
+ are now processed, even if one or more fails. All exceptions encountered
+ during the registration process are recorded. If at least one exception
+ was encountered, the registration process fails and all interim errors are
+ reported.
+ - |
+ The __repr__ method is now implemented for the base `tempest.Exception`
+ class, its implementation is identical to __str__: it reports the error
+ message merged with input parameters.
diff --git a/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml b/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml
new file mode 100644
index 0000000..afb6006
--- /dev/null
+++ b/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ When receiving nullable list as a response body, tempest.lib
+ rest_client module raised an exception without valid json
+ deserialization. A new release fixes this bug.
diff --git a/releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml b/releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml
new file mode 100644
index 0000000..4d0f3ce
--- /dev/null
+++ b/releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - The Heat API tests have been removed from tempest, they were unmaintained.
+ The future direction of api test for heat is their in-tree Gabbi tests
diff --git a/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml b/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml
new file mode 100644
index 0000000..093228a
--- /dev/null
+++ b/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml
@@ -0,0 +1,5 @@
+---
+deprecations:
+ - |
+ Remove the support of python3.4, because in Ubuntu Xenial only
+ python3.5 is available (python3.4 is restricted to <= Mitaka).
diff --git a/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
new file mode 100644
index 0000000..6959ca7
--- /dev/null
+++ b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ The volume config option 'api_v3' default is changed to
+ ``True`` because the volume v3 API is CURRENT.
diff --git a/releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml b/releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml
new file mode 100644
index 0000000..dcaaceb
--- /dev/null
+++ b/releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add a new identity catalog client. At this point, the new client
+ contains a single functionality, "show_catalog", which returns a
+ catalog object.
diff --git a/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
new file mode 100644
index 0000000..ec21098
--- /dev/null
+++ b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added tempest workspace remove --name <workspace_name> --rmdir
+ feature to delete the workspace directory as well as entry.
diff --git a/releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yaml b/releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yaml
new file mode 100644
index 0000000..1c8fa77
--- /dev/null
+++ b/releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - verify_tempest_config command starts using extension_client of
+ cinder v2 API only, because cinder v3 API is current and v2 and
+ v1 are deprecated and v3 extension API is the same as v2. Then
+ we can reuse the v2 client for v3 API also.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 97e3a4d..3137541 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -37,10 +37,18 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'oslosphinx',
+ 'openstackdocstheme',
'reno.sphinxext',
]
+# openstackdocstheme options
+repository_name = 'openstack/tempest'
+bug_project = 'tempest'
+bug_tag = ''
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -111,7 +119,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -119,7 +127,6 @@
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index adec7a7..db01da0 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -1,11 +1,12 @@
===========================
- tempest Release Notes
+ Tempest Release Notes
===========================
.. toctree::
:maxdepth: 1
unreleased
+ v16.1.0
v16.0.0
v15.0.0
v14.0.0
diff --git a/releasenotes/source/v16.1.0.rst b/releasenotes/source/v16.1.0.rst
new file mode 100644
index 0000000..e24a70f
--- /dev/null
+++ b/releasenotes/source/v16.1.0.rst
@@ -0,0 +1,6 @@
+=====================
+v16.1.0 Release Notes
+=====================
+
+.. release-notes:: 16.1.0 Release Notes
+ :version: 16.1.0
diff --git a/requirements.txt b/requirements.txt
index 26e1f02..a74f5c2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,16 +2,16 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
-cliff>=2.6.0 # Apache-2.0
+cliff>=2.8.0 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
testtools>=1.4.0 # MIT
paramiko>=2.0 # LGPLv2.1+
netaddr!=0.7.16,>=0.7.13 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
oslo.concurrency>=3.8.0 # Apache-2.0
-oslo.config>=3.22.0 # Apache-2.0
+oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0
-oslo.serialization>=1.10.0 # Apache-2.0
+oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
six>=1.9.0 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
@@ -20,6 +20,6 @@
stevedore>=1.20.0 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
os-testr>=0.8.0 # Apache-2.0
-urllib3>=1.15.1 # MIT
+urllib3>=1.21.1 # MIT
debtcollector>=1.2.0 # Apache-2.0
unittest2 # BSD
diff --git a/setup.cfg b/setup.cfg
index b292970..04bb29f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
-home-page = http://docs.openstack.org/developer/tempest/
+home-page = https://docs.openstack.org/tempest/latest/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
@@ -16,7 +16,6 @@
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files]
diff --git a/tempest/README.rst b/tempest/README.rst
index 0feec41..663653e 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -9,12 +9,13 @@
OpenStack clouds.
As such Tempest tests come in many flavors, each with their own rules
-and guidelines. Below is the proposed Havana restructuring for Tempest
+and guidelines. Below is the overview of the Tempest respository structure
to make this clear.
| tempest/
| api/ - API tests
| scenario/ - complex scenario tests
+| tests/ - unit tests for Tempest internals
Each of these directories contains different types of tests. What
belongs in each directory, the rules and examples for good tests, are
@@ -24,8 +25,8 @@
----------------------
API tests are validation tests for the OpenStack API. They should not
-use the existing python clients for OpenStack, but should instead use
-the tempest implementations of clients. Having raw clients let us
+use the existing Python clients for OpenStack, but should instead use
+the Tempest implementations of clients. Having raw clients let us
pass invalid JSON to the APIs and see the results, something we could
not get with the native clients.
@@ -41,14 +42,14 @@
functionality. They are typically a series of steps where complicated
state requiring multiple services is set up exercised, and torn down.
-Scenario tests should not use the existing python clients for OpenStack,
-but should instead use the tempest implementations of clients.
+Scenario tests should not use the existing Python clients for OpenStack,
+but should instead use the Tempest implementations of clients.
:ref:`unit_tests_field_guide`
-----------------------------
Unit tests are the self checks for Tempest. They provide functional
-verification and regression checking for the internal components of tempest.
-They should be used to just verify that the individual pieces of tempest are
+verification and regression checking for the internal components of Tempest.
+They should be used to just verify that the individual pieces of Tempest are
working as expected.
diff --git a/tempest/api/README.rst b/tempest/api/README.rst
index 91e6ad6..a796922 100644
--- a/tempest/api/README.rst
+++ b/tempest/api/README.rst
@@ -13,7 +13,8 @@
It's also important to test not only the expected positive path on
APIs, but also to provide them with invalid data to ensure they fail
-in expected and documented ways. Over the course of the OpenStack
+in expected and documented ways. The latter type of tests is called
+``negative tests`` in Tempest source code. Over the course of the OpenStack
project Tempest has discovered many fundamental bugs by doing just
this.
@@ -22,7 +23,7 @@
spinning up a server, image, etc, then operating on it.
-Why are these tests in tempest?
+Why are these tests in Tempest?
-------------------------------
This is one of the core missions for the Tempest project, and where it
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 31976ec..0901374 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -86,8 +86,7 @@
body = self.client.create_agent(**self.params_agent)['agent']
self.addCleanup(self.client.delete_agent, body['agent_id'])
agents = self.client.list_agents()['agents']
- self.assertGreater(len(agents), 0,
- 'Cannot get any agents.(%s)' % agents)
+ self.assertNotEmpty(agents, 'Cannot get any agents.')
self.assertIn(body['agent_id'], map(lambda x: x['agent_id'], agents))
@decorators.idempotent_id('eabadde4-3cd7-4ec4-a4b5-5a936d2d4408')
@@ -105,8 +104,7 @@
agent_id_xen = agent_xen['agent_id']
agents = (self.client.list_agents(hypervisor=agent_xen['hypervisor'])
['agents'])
- self.assertGreater(len(agents), 0,
- 'Cannot get any agents.(%s)' % agents)
+ self.assertNotEmpty(agents, 'Cannot get any agents.')
self.assertIn(agent_id_xen, map(lambda x: x['agent_id'], agents))
self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'],
agents))
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 902ea9a..57d3983 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -125,7 +125,6 @@
name=aggregate_name, availability_zone=az_name)
self.assertEqual(az_name, aggregate['availability_zone'])
- self.assertIsNotNone(aggregate['id'])
aggregate_id = aggregate['id']
new_aggregate_name = aggregate_name + '_new'
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index c4db5e3..83fe215 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -111,10 +111,12 @@
LOG.info('(%s) Found more than one router for tenant.',
test_utils.find_test_caller())
- # Let's just blindly remove any networks, duplicate or otherwise, that
- # the test might have created even though Neutron will cleanup
- # duplicate resources automatically (so ignore 404s).
- networks = cls.networks_client.list_networks().get('networks', [])
+ # Remove any networks, duplicate or otherwise, that these tests
+ # created. All such networks will be in the current tenant. Neutron
+ # will cleanup duplicate resources automatically, so ignore 404s.
+ search_opts = {'tenant_id': cls.networks_client.tenant_id}
+ networks = cls.networks_client.list_networks(
+ **search_opts).get('networks', [])
for router in routers:
# Disassociate the subnets from the router. Because of the race
@@ -151,7 +153,7 @@
"""Tests that no networking is allocated for the server."""
# create the server with no networking
server, _ = compute.create_test_server(
- self.os, networks='none', wait_until='ACTIVE')
+ self.os_primary, networks='none', wait_until='ACTIVE')
self.addCleanup(self.delete_server, server['id'])
# get the server ips
addresses = self.servers_client.list_addresses(
@@ -176,7 +178,7 @@
# - Third request sees net1 and net2 for the tenant and fails with a
# NetworkAmbiguous 400 error.
_, servers = compute.create_test_server(
- self.os, networks='auto', wait_until='ACTIVE',
+ self.os_primary, networks='auto', wait_until='ACTIVE',
min_count=3)
server_nets = set()
for server in servers:
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index 50dec28..bbd39b6 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -29,10 +29,10 @@
def test_get_availability_zone_list(self):
# List of availability zone
availability_zone = self.client.list_availability_zones()
- self.assertGreater(len(availability_zone['availabilityZoneInfo']), 0)
+ self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
@decorators.idempotent_id('ef726c58-530f-44c2-968c-c7bed22d5b8c')
def test_get_availability_zone_list_detail(self):
# List of availability zones and available services
availability_zone = self.client.list_availability_zones(detail=True)
- self.assertGreater(len(availability_zone['availabilityZoneInfo']), 0)
+ self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index 3449aba..66bedd9 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -25,8 +25,6 @@
class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
- disk_config = 'AUTO'
-
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
diff --git a/tempest/api/compute/admin/test_delete_server.py b/tempest/api/compute/admin/test_delete_server.py
index 2569161..83444b9 100644
--- a/tempest/api/compute/admin/test_delete_server.py
+++ b/tempest/api/compute/admin/test_delete_server.py
@@ -29,7 +29,7 @@
def setup_clients(cls):
super(DeleteServersAdminTestJSON, cls).setup_clients()
cls.non_admin_client = cls.servers_client
- cls.admin_client = cls.os_adm.servers_client
+ cls.admin_client = cls.os_admin.servers_client
@decorators.idempotent_id('99774678-e072-49d1-9d2a-49a59bc56063')
def test_delete_server_while_in_error_state(self):
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 5a38acc..2c236ec 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -50,7 +50,7 @@
flavor_access = (self.admin_flavors_client.list_flavor_access(
flavor['id'])['flavor_access'])
- self.assertEqual(len(flavor_access), 0, str(flavor_access))
+ self.assertEmpty(flavor_access)
@decorators.idempotent_id('59e622f6-bdf6-45e3-8ba8-fedad905a6b4')
def test_flavor_access_add_remove(self):
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 4d7abb6..747cb42 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -83,8 +83,8 @@
# GET extra specs and verify the value of the key2
# is the same as before
- get_body = (self.admin_flavors_client.list_flavor_extra_specs(
- self.flavor['id'])['extra_specs'])
+ get_body = self.admin_flavors_client.list_flavor_extra_specs(
+ self.flavor['id'])['extra_specs']
self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
# UNSET extra specs that were set in this test
@@ -92,6 +92,9 @@
"key1")
self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'],
"key2")
+ get_body = self.admin_flavors_client.list_flavor_extra_specs(
+ self.flavor['id'])['extra_specs']
+ self.assertEmpty(get_body)
@decorators.idempotent_id('a99dad88-ae1c-4fba-aeb4-32f898218bd0')
def test_flavor_non_admin_get_all_keys(self):
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index 056b4bd..496f119 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -73,7 +73,7 @@
self.client.delete_floating_ips_bulk, self.ip_range)
self.assertEqual(self.ip_range, body['ip_range'])
ips_list = self.client.list_floating_ips_bulk()['floating_ip_info']
- self.assertNotEqual(0, len(ips_list))
+ self.assertNotEmpty(ips_list)
for ip in netaddr.IPNetwork(self.ip_range).iter_hosts():
self.assertIn(str(ip), map(lambda x: x['address'], ips_list))
body = (self.client.delete_floating_ips_bulk(self.ip_range)
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 8e2f6ed..00f3256 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -36,7 +36,7 @@
hosts = self.client.list_hosts()['hosts']
host = hosts[0]
hosts = self.client.list_hosts(zone=host['zone'])['hosts']
- self.assertGreaterEqual(len(hosts), 1)
+ self.assertNotEmpty(hosts)
self.assertIn(host, hosts)
@decorators.idempotent_id('9af3c171-fbf4-4150-a624-22109733c2a6')
@@ -44,30 +44,25 @@
# If send the request with a blank zone, the request will be successful
# and it will return all the hosts list
hosts = self.client.list_hosts(zone='')['hosts']
- self.assertNotEqual(0, len(hosts))
+ self.assertNotEmpty(hosts)
@decorators.idempotent_id('c6ddbadb-c94e-4500-b12f-8ffc43843ff8')
def test_list_hosts_with_nonexistent_zone(self):
# If send the request with a nonexistent zone, the request will be
# successful and no hosts will be returned
hosts = self.client.list_hosts(zone='xxx')['hosts']
- self.assertEqual(0, len(hosts))
+ self.assertEmpty(hosts)
@decorators.idempotent_id('38adbb12-aee2-4498-8aec-329c72423aa4')
def test_show_host_detail(self):
hosts = self.client.list_hosts()['hosts']
hosts = [host for host in hosts if host['service'] == 'compute']
- self.assertGreaterEqual(len(hosts), 1)
+ self.assertNotEmpty(hosts)
for host in hosts:
hostname = host['host_name']
resources = self.client.show_host(hostname)['host']
- self.assertGreaterEqual(len(resources), 1)
+ self.assertNotEmpty(resources)
host_resource = resources[0]['resource']
- self.assertIsNotNone(host_resource)
- self.assertIsNotNone(host_resource['cpu'])
- self.assertIsNotNone(host_resource['disk_gb'])
- self.assertIsNotNone(host_resource['memory_mb'])
- self.assertIsNotNone(host_resource['project'])
self.assertEqual(hostname, host_resource['host'])
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 4544267..404fd94 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -30,29 +30,26 @@
hypers = self.client.list_hypervisors()['hypervisors']
return hypers
- def assertHypervisors(self, hypers):
- self.assertGreater(len(hypers), 0, "No hypervisors found: %s" % hypers)
-
@decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
def test_get_hypervisor_list(self):
# List of hypervisor and available hypervisors hostname
hypers = self._list_hypervisors()
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('1e7fdac2-b672-4ad1-97a4-bad0e3030118')
def test_get_hypervisor_list_details(self):
# Display the details of the all hypervisor
hypers = self.client.list_hypervisors(detail=True)['hypervisors']
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
@decorators.idempotent_id('94ff9eae-a183-428e-9cdb-79fde71211cc')
def test_get_hypervisor_show_details(self):
# Display the details of the specified hypervisor
hypers = self._list_hypervisors()
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
details = self.client.show_hypervisor(hypers[0]['id'])['hypervisor']
- self.assertGreater(len(details), 0)
+ self.assertNotEmpty(details)
self.assertEqual(details['hypervisor_hostname'],
hypers[0]['hypervisor_hostname'])
@@ -60,19 +57,19 @@
def test_get_hypervisor_show_servers(self):
# Show instances about the specific hypervisors
hypers = self._list_hypervisors()
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
hostname = hypers[0]['hypervisor_hostname']
hypervisors = (self.client.list_servers_on_hypervisor(hostname)
['hypervisors'])
- self.assertGreater(len(hypervisors), 0)
+ self.assertNotEmpty(hypervisors)
@decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
def test_get_hypervisor_stats(self):
# Verify the stats of the all hypervisor
stats = (self.client.show_hypervisor_statistics()
['hypervisor_statistics'])
- self.assertGreater(len(stats), 0)
+ self.assertNotEmpty(stats)
@decorators.idempotent_id('91a50d7d-1c2b-4f24-b55a-a1fe20efca70')
def test_get_hypervisor_uptime(self):
@@ -116,7 +113,7 @@
@decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
def test_search_hypervisor(self):
hypers = self._list_hypervisors()
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
hypers = self.client.search_hypervisor(
hypers[0]['hypervisor_hostname'])['hypervisors']
- self.assertHypervisors(hypers)
+ self.assertNotEmpty(hypers, "No hypervisors found.")
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index af87287..431e823 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -47,7 +47,7 @@
@decorators.idempotent_id('51e663d0-6b89-4817-a465-20aca0667d03')
def test_show_hypervisor_with_non_admin_user(self):
hypers = self._list_hypervisors()
- self.assertGreater(len(hypers), 0)
+ self.assertNotEmpty(hypers)
self.assertRaises(
lib_exc.Forbidden,
@@ -58,7 +58,7 @@
@decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
def test_show_servers_with_non_admin_user(self):
hypers = self._list_hypervisors()
- self.assertGreater(len(hypers), 0)
+ self.assertNotEmpty(hypers)
self.assertRaises(
lib_exc.Forbidden,
@@ -96,7 +96,7 @@
@decorators.idempotent_id('6c3461f9-c04c-4e2a-bebb-71dc9cb47df2')
def test_get_hypervisor_uptime_with_non_admin_user(self):
hypers = self._list_hypervisors()
- self.assertGreater(len(hypers), 0)
+ self.assertNotEmpty(hypers)
self.assertRaises(
lib_exc.Forbidden,
@@ -131,7 +131,7 @@
@decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
def test_search_hypervisor_with_non_admin_user(self):
hypers = self._list_hypervisors()
- self.assertGreater(len(hypers), 0)
+ self.assertNotEmpty(hypers)
self.assertRaises(
lib_exc.Forbidden,
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 3859e64..256a267 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -29,13 +29,13 @@
LOG = logging.getLogger(__name__)
-class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
+class LiveMigrationTest(base.BaseV2ComputeAdminTest):
max_microversion = '2.24'
block_migration = None
@classmethod
def skip_checks(cls):
- super(LiveBlockMigrationTestJSON, cls).skip_checks()
+ super(LiveMigrationTest, cls).skip_checks()
if not CONF.compute_feature_enabled.live_migration:
skip_msg = ("%s skipped as live-migration is "
@@ -47,26 +47,9 @@
@classmethod
def setup_clients(cls):
- super(LiveBlockMigrationTestJSON, cls).setup_clients()
- cls.admin_hosts_client = cls.os_admin.hosts_client
+ super(LiveMigrationTest, cls).setup_clients()
cls.admin_migration_client = cls.os_admin.migrations_client
- @classmethod
- def _get_compute_hostnames(cls):
- body = cls.admin_hosts_client.list_hosts()['hosts']
- return [
- host_record['host_name']
- for host_record in body
- if host_record['service'] == 'compute'
- ]
-
- def _get_server_details(self, server_id):
- body = self.admin_servers_client.show_server(server_id)['server']
- return body
-
- def _get_host_for_server(self, server_id):
- return self._get_server_details(server_id)['OS-EXT-SRV-ATTR:host']
-
def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
kwargs = dict()
block_migration = getattr(self, 'block_migration', None)
@@ -79,11 +62,6 @@
server_id, host=dest_host, block_migration=block_migration,
**kwargs)
- def _get_host_other_than(self, host):
- for target_host in self._get_compute_hostnames():
- if host != target_host:
- return target_host
-
def _live_migrate(self, server_id, target_host, state,
volume_backed=False):
self._migrate_server_to(server_id, target_host, volume_backed)
@@ -97,7 +75,7 @@
if (live_migration['instance_uuid'] == server_id):
msg += "\n%s" % live_migration
msg += "]"
- self.assertEqual(target_host, self._get_host_for_server(server_id),
+ self.assertEqual(target_host, self.get_host_for_server(server_id),
msg)
def _test_live_migration(self, state='ACTIVE', volume_backed=False):
@@ -114,8 +92,8 @@
# Live migrate an instance to another host
server_id = self.create_test_server(wait_until="ACTIVE",
volume_backed=volume_backed)['id']
- source_host = self._get_host_for_server(server_id)
- destination_host = self._get_host_other_than(source_host)
+ source_host = self.get_host_for_server(server_id)
+ destination_host = self.get_host_other_than(server_id)
if state == 'PAUSED':
self.admin_servers_client.pause_server(server_id)
@@ -158,8 +136,7 @@
def test_iscsi_volume(self):
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
- actual_host = self._get_host_for_server(server_id)
- target_host = self._get_host_other_than(actual_host)
+ target_host = self.get_host_other_than(server_id)
volume = self.create_volume()
@@ -174,11 +151,11 @@
server = self.admin_servers_client.show_server(server_id)['server']
volume_id2 = server["os-extended-volumes:volumes_attached"][0]["id"]
- self.assertEqual(target_host, self._get_host_for_server(server_id))
+ self.assertEqual(target_host, self.get_host_for_server(server_id))
self.assertEqual(volume_id1, volume_id2)
-class LiveBlockMigrationRemoteConsolesV26TestJson(LiveBlockMigrationTestJSON):
+class LiveMigrationRemoteConsolesV26Test(LiveMigrationTest):
min_microversion = '2.6'
max_microversion = 'latest'
@@ -186,7 +163,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
'Serial console not supported.')
@testtools.skipUnless(
- test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ compute.is_scheduler_filter_enabled("DifferentHostFilter"),
'DifferentHostFilter is not available.')
def test_live_migration_serial_console(self):
"""Test the live-migration of an instance which has a serial console
@@ -201,8 +178,8 @@
hints = {'different_host': server01_id}
server02_id = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')['id']
- host01_id = self._get_host_for_server(server01_id)
- host02_id = self._get_host_for_server(server02_id)
+ host01_id = self.get_host_for_server(server01_id)
+ host02_id = self.get_host_for_server(server02_id)
self.assertNotEqual(host01_id, host02_id)
# At this step we have 2 instances on different hosts, both with
@@ -216,7 +193,7 @@
self._migrate_server_to(server01_id, host02_id)
waiters.wait_for_server_status(self.servers_client,
server01_id, 'ACTIVE')
- self.assertEqual(host02_id, self._get_host_for_server(server01_id))
+ self.assertEqual(host02_id, self.get_host_for_server(server01_id))
self._verify_console_interaction(server01_id)
# At this point, both instances have a valid serial console
# connection, which means the ports got updated.
@@ -252,7 +229,7 @@
self.assertIn(data, console_output)
-class LiveAutoBlockMigrationV225TestJSON(LiveBlockMigrationTestJSON):
+class LiveAutoBlockMigrationV225Test(LiveMigrationTest):
min_microversion = '2.25'
max_microversion = 'latest'
block_migration = 'auto'
diff --git a/tempest/api/compute/admin/test_live_block_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
similarity index 72%
rename from tempest/api/compute/admin/test_live_block_migration_negative.py
rename to tempest/api/compute/admin/test_live_migration_negative.py
index ab63154..deabbc2 100644
--- a/tempest/api/compute/admin/test_live_block_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -23,10 +23,10 @@
CONF = config.CONF
-class LiveBlockMigrationNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class LiveMigrationNegativeTest(base.BaseV2ComputeAdminTest):
@classmethod
def skip_checks(cls):
- super(LiveBlockMigrationNegativeTestJSON, cls).skip_checks()
+ super(LiveMigrationNegativeTest, cls).skip_checks()
if not CONF.compute_feature_enabled.live_migration:
raise cls.skipException("Live migration is not enabled")
@@ -47,3 +47,17 @@
server['id'], target_host)
waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('6e2f94f5-2ee8-4830-bef5-5bc95bb0795b')
+ def test_live_block_migration_suspended(self):
+ server = self.create_test_server(wait_until="ACTIVE")
+
+ self.admin_servers_client.suspend_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'], 'SUSPENDED')
+
+ destination_host = self.get_host_other_than(server['id'])
+
+ self.assertRaises(lib_exc.Conflict, self._migrate_server_to,
+ server['id'], destination_host)
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index 0ea0a78..acb0d90 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -62,4 +62,4 @@
self.assertIn(configured_network, [x['label'] for x in networks])
else:
network_labels = [x['label'] for x in networks]
- self.assertGreaterEqual(len(network_labels), 1)
+ self.assertNotEmpty(network_labels)
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 937540e..1aa9227 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -169,7 +169,6 @@
LOG.debug("get the current 'default' quota class values")
body = (self.adm_client.show_quota_class_set('default')
['quota_class_set'])
- self.assertIn('id', body)
self.assertEqual('default', body.pop('id'))
# restore the defaults when the test is done
self.addCleanup(self._restore_default_quotas, body.copy())
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
index 6c7cde2..f2f3b57 100644
--- a/tempest/api/compute/admin/test_security_group_default_rules.py
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -111,7 +111,7 @@
rule['id'])
rules = (self.adm_client.list_security_group_default_rules()
['security_group_default_rules'])
- self.assertNotEqual(0, len(rules))
+ self.assertNotEmpty(rules)
self.assertIn(rule, rules)
@decorators.idempotent_id('15cbb349-86b4-4f71-a048-04b7ef3f150b')
diff --git a/tempest/api/compute/admin/test_server_diagnostics.py b/tempest/api/compute/admin/test_server_diagnostics.py
new file mode 100644
index 0000000..005efdd
--- /dev/null
+++ b/tempest/api/compute/admin/test_server_diagnostics.py
@@ -0,0 +1,76 @@
+# Copyright 2017 Mirantis Inc.
+#
+# 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 time
+
+from tempest.api.compute import base
+from tempest.lib import decorators
+
+
+class ServerDiagnosticsTest(base.BaseV2ComputeAdminTest):
+ min_microversion = None
+ max_microversion = '2.47'
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerDiagnosticsTest, cls).setup_clients()
+ cls.client = cls.os_admin.servers_client
+
+ @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
+ def test_get_server_diagnostics(self):
+ server_id = self.create_test_server(wait_until='ACTIVE')['id']
+ diagnostics = self.client.show_server_diagnostics(server_id)
+
+ # NOTE(snikitin): Before microversion 2.48 response data from each
+ # hypervisor (libvirt, xen, vmware) was different. None of the fields
+ # were equal. As this test is common for libvirt, xen and vmware CI
+ # jobs we can't check any field in the response because all fields are
+ # different.
+ self.assertNotEmpty(diagnostics)
+
+
+class ServerDiagnosticsV248Test(base.BaseV2ComputeAdminTest):
+ min_microversion = '2.48'
+ max_microversion = 'latest'
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerDiagnosticsV248Test, cls).setup_clients()
+ cls.client = cls.os_admin.servers_client
+
+ @decorators.idempotent_id('64d0d48c-dff1-11e6-bf01-fe55135034f3')
+ def test_get_server_diagnostics(self):
+ server_id = self.create_test_server(wait_until='ACTIVE')['id']
+ # Response status and filed types will be checked by json schema
+ self.client.show_server_diagnostics(server_id)
+
+ # NOTE(snikitin): This is a special case for Xen hypervisor. In Xen
+ # case we're getting diagnostics stats from the RRDs which are updated
+ # every 5 seconds. It means that diagnostics information may be
+ # incomplete during first 5 seconds of VM life. In such cases methods
+ # which get diagnostics stats from Xen may raise exceptions or
+ # return `NaN` values. Such behavior must be handled correctly.
+ # Response must contain all diagnostics fields (may be with `None`
+ # values) and response status must be 200. Line above checks it by
+ # json schema.
+ time.sleep(10)
+ diagnostics = self.client.show_server_diagnostics(server_id)
+
+ # NOTE(snikitin): After 10 seconds diagnostics fields must contain
+ # not None values. But we will check only "memory_details.maximum"
+ # field because only this field meets all the following conditions:
+ # 1) This field may be unset because of Xen 5 seconds timeout.
+ # 2) This field is present in responses from all three supported
+ # hypervisors (libvirt, xen, vmware).
+ self.assertIsNotNone(diagnostics['memory_details']['maximum'])
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
new file mode 100644
index 0000000..d5b6674
--- /dev/null
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -0,0 +1,40 @@
+# Copyright 2017 Mirantis Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
+ min_microversion = None
+ max_microversion = '2.47'
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerDiagnosticsNegativeTest, cls).setup_clients()
+ cls.client = cls.servers_client
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
+ def test_get_server_diagnostics_by_non_admin(self):
+ # Non-admin user cannot view server diagnostics according to policy
+ server_id = self.create_test_server(wait_until='ACTIVE')['id']
+ self.assertRaises(lib_exc.Forbidden,
+ self.client.show_server_diagnostics, server_id)
+
+
+class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest):
+ min_microversion = '2.48'
+ max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 98bf4bf..3f06c4e 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -13,8 +13,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common import compute
-from tempest.common import fixed_network
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -65,7 +63,7 @@
params = {'status': 'invalid_status'}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
- self.assertEqual([], servers)
+ self.assertEmpty(servers)
@decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
def test_list_servers_by_admin(self):
@@ -126,26 +124,17 @@
@decorators.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f')
def test_list_servers_filter_by_exist_host(self):
# Filter the list of servers by existent host
- name = data_utils.rand_name(self.__class__.__name__ + '-server')
- network = self.get_tenant_network()
- network_kwargs = fixed_network.set_networks_kwarg(network)
- # We need to create the server as an admin, so we can't use
- # self.create_test_server() here as this method creates the server
- # in the "primary" (i.e non-admin) tenant.
- test_server, _ = compute.create_test_server(
- self.os_adm, wait_until="ACTIVE", name=name, **network_kwargs)
- self.addCleanup(self.client.delete_server, test_server['id'])
- server = self.client.show_server(test_server['id'])['server']
- self.assertEqual(server['status'], 'ACTIVE')
+ server = self.client.show_server(self.s1_id)['server']
hostname = server['OS-EXT-SRV-ATTR:host']
- params = {'host': hostname}
- body = self.client.list_servers(**params)
- servers = body['servers']
- nonexistent_params = {'host': 'nonexistent_host'}
+ params = {'host': hostname, 'all_tenants': '1'}
+ servers = self.client.list_servers(**params)['servers']
+ self.assertIn(server['id'], map(lambda x: x['id'], servers))
+
+ nonexistent_params = {'host': 'nonexistent_host',
+ 'all_tenants': '1'}
nonexistent_body = self.client.list_servers(**nonexistent_params)
nonexistent_servers = nonexistent_body['servers']
- self.assertIn(test_server['id'], map(lambda x: x['id'], servers))
- self.assertNotIn(test_server['id'],
+ self.assertNotIn(server['id'],
map(lambda x: x['id'], nonexistent_servers))
@decorators.idempotent_id('ee8ae470-db70-474d-b752-690b7892cab1')
@@ -164,17 +153,6 @@
server = self.client.show_server(self.s1_id)['server']
self.assertEqual(server['status'], 'ACTIVE')
- @decorators.skip_because(bug="1240043")
- @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
- def test_get_server_diagnostics_by_admin(self):
- # Retrieve server diagnostics by admin user
- diagnostic = self.client.show_server_diagnostics(self.s1_id)
- basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop',
- 'tx_packets', 'tx_errors', 'tx_drop',
- 'read_req', 'write_req', 'cpu', 'memory']
- for key in basic_attrs:
- self.assertIn(key, str(diagnostic.keys()))
-
@decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442')
def test_rebuild_server_in_error_state(self):
# The server in error state should be rebuilt using the provided
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index ca53696..3656770 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -32,7 +32,6 @@
def setup_clients(cls):
super(ServersAdminNegativeTestJSON, cls).setup_clients()
cls.client = cls.os_admin.servers_client
- cls.non_adm_client = cls.servers_client
cls.quotas_client = cls.os_admin.quotas_client
@classmethod
@@ -108,14 +107,6 @@
self.client.reset_state, '999', state='error')
@decorators.attr(type=['negative'])
- @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
- def test_get_server_diagnostics_by_non_admin(self):
- # Non-admin user can not view server diagnostics according to policy
- self.assertRaises(lib_exc.Forbidden,
- self.non_adm_client.show_server_diagnostics,
- self.s1_id)
-
- @decorators.attr(type=['negative'])
@decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221')
def test_migrate_non_existent_server(self):
# migrate a non existent server
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 858998a..2e7b07b 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -15,9 +15,9 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest import config
from tempest.lib import decorators
-from tempest import test
CONF = config.CONF
@@ -45,7 +45,7 @@
@decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
@testtools.skipUnless(
- test.is_scheduler_filter_enabled("SameHostFilter"),
+ compute.is_scheduler_filter_enabled("SameHostFilter"),
'SameHostFilter is not available.')
def test_create_servers_on_same_host(self):
hints = {'same_host': self.server01}
@@ -56,7 +56,7 @@
@decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
@testtools.skipUnless(
- test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ compute.is_scheduler_filter_enabled("DifferentHostFilter"),
'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts(self):
hints = {'different_host': self.server01}
@@ -67,7 +67,7 @@
@decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
@testtools.skipUnless(
- test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ compute.is_scheduler_filter_enabled("DifferentHostFilter"),
'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts_with_list_of_servers(self):
# This scheduler-hint supports list of servers also.
@@ -76,3 +76,38 @@
wait_until='ACTIVE')['id']
host02 = self._get_host(server02)
self.assertNotEqual(self.host01, host02)
+
+ @decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04')
+ @testtools.skipUnless(
+ compute.is_scheduler_filter_enabled("ServerGroupAntiAffinityFilter"),
+ 'ServerGroupAntiAffinityFilter is not available.')
+ def test_create_server_with_scheduler_hint_group_anti_affinity(self):
+ """Tests the ServerGroupAntiAffinityFilter
+
+ Creates two servers in an anti-affinity server group and
+ asserts the servers are in the group and on different hosts.
+ """
+ group_id = self.create_test_server_group(
+ policy=['anti-affinity'])['id']
+ hints = {'group': group_id}
+ reservation_id = self.create_test_server(
+ scheduler_hints=hints, wait_until='ACTIVE', min_count=2,
+ return_reservation_id=True)['reservation_id']
+
+ # Get the servers using the reservation_id.
+ servers = self.servers_client.list_servers(
+ detail=True, reservation_id=reservation_id)['servers']
+ self.assertEqual(2, len(servers))
+
+ # Assert the servers are in the group.
+ server_group = self.server_groups_client.show_server_group(
+ group_id)['server_group']
+ hosts = {}
+ for server in servers:
+ self.assertIn(server['id'], server_group['members'])
+ hosts[server['id']] = self._get_host(server['id'])
+
+ # Assert the servers are on different hosts.
+ hostnames = list(hosts.values())
+ self.assertNotEqual(hostnames[0], hostnames[1],
+ 'Servers are on the same host: %s' % hosts)
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index 1dfc13e..f3eb597 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -29,13 +29,13 @@
@decorators.idempotent_id('5be41ef4-53d1-41cc-8839-5c2a48a1b283')
def test_list_services(self):
services = self.client.list_services()['services']
- self.assertNotEqual(0, len(services))
+ self.assertNotEmpty(services)
@decorators.idempotent_id('f345b1ec-bc6e-4c38-a527-3ca2bc00bef5')
def test_get_service_by_service_binary_name(self):
binary_name = 'nova-compute'
services = self.client.list_services(binary=binary_name)['services']
- self.assertNotEqual(0, len(services))
+ self.assertNotEmpty(services)
for service in services:
self.assertEqual(binary_name, service['binary'])
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 38bb5ec..201670a 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -48,7 +48,7 @@
host_name = services[0]['host']
services = self.client.list_services(host=host_name,
binary='xxx')['services']
- self.assertEqual(0, len(services))
+ self.assertEmpty(services)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('64e7e7fb-69e8-4cb6-a71d-8d5eb0c98655')
@@ -57,4 +57,4 @@
binary_name = services[0]['binary']
services = self.client.list_services(host='xxx',
binary=binary_name)['services']
- self.assertEqual(0, len(services))
+ self.assertEmpty(services)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 141b9f3..746f83a 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -22,6 +22,7 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib.common import api_version_request
from tempest.lib.common import api_version_utils
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
@@ -96,8 +97,8 @@
cls.security_group_default_rules_client = (
cls.os_primary.security_group_default_rules_client)
cls.versions_client = cls.os_primary.compute_versions_client
-
- cls.volumes_client = cls.os_primary.volumes_v2_client
+ if CONF.service_available.cinder:
+ cls.volumes_client = cls.os_primary.volumes_client_latest
@classmethod
def resource_setup(cls):
@@ -202,9 +203,18 @@
"""
if 'name' not in kwargs:
kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server")
+
+ request_version = api_version_request.APIVersionRequest(
+ cls.request_microversion)
+ v2_37_version = api_version_request.APIVersionRequest('2.37')
+
+ # NOTE(snikitin): since microversion v2.37 'networks' field is required
+ if request_version >= v2_37_version and 'networks' not in kwargs:
+ kwargs['networks'] = 'none'
+
tenant_network = cls.get_tenant_network()
body, servers = compute.create_test_server(
- cls.os,
+ cls.os_primary,
validatable,
validation_resources=cls.validation_resources,
tenant_network=tenant_network,
@@ -350,7 +360,7 @@
@classmethod
def delete_volume(cls, volume_id):
"""Deletes the given volume and waits for it to be gone."""
- cls._delete_volume(cls.volumes_extensions_client, volume_id)
+ cls._delete_volume(cls.volumes_client, volume_id)
@classmethod
def get_server_ip(cls, server):
@@ -414,7 +424,7 @@
LOG.exception('Waiting for deletion of volume %s failed',
volume['id'])
- def attach_volume(self, server, volume, device=None):
+ def attach_volume(self, server, volume, device=None, check_reserved=False):
"""Attaches volume to server and waits for 'in-use' volume status.
The volume will be detached when the test tears down.
@@ -423,10 +433,15 @@
:param volume: The volume to attach.
:param device: Optional mountpoint for the attached volume. Note that
this is not guaranteed for all hypervisors and is not recommended.
+ :param check_reserved: Consider a status of reserved as valid for
+ completion. This is to handle new Cinder attach where we more
+ accurately use 'reserved' for things like attaching to a shelved
+ server.
"""
attach_kwargs = dict(volumeId=volume['id'])
if device:
attach_kwargs['device'] = device
+
attachment = self.servers_client.attach_volume(
server['id'], **attach_kwargs)['volumeAttachment']
# On teardown detach the volume and wait for it to be available. This
@@ -439,8 +454,11 @@
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.servers_client.detach_volume,
server['id'], volume['id'])
+ statuses = ['in-use']
+ if check_reserved:
+ statuses.append('reserved')
waiters.wait_for_volume_resource_status(self.volumes_client,
- volume['id'], 'in-use')
+ volume['id'], statuses)
return attachment
@@ -469,3 +487,21 @@
self.addCleanup(client.wait_for_resource_deletion, flavor['id'])
self.addCleanup(client.delete_flavor, flavor['id'])
return flavor
+
+ def get_host_for_server(self, server_id):
+ server_details = self.admin_servers_client.show_server(server_id)
+ return server_details['server']['OS-EXT-SRV-ATTR:host']
+
+ def get_host_other_than(self, server_id):
+ source_host = self.get_host_for_server(server_id)
+
+ list_hosts_resp = self.os_admin.hosts_client.list_hosts()['hosts']
+ hosts = [
+ host_record['host_name']
+ for host_record in list_hosts_resp
+ if host_record['service'] == 'compute'
+ ]
+
+ for target_host in hosts:
+ if source_host != target_host:
+ return target_host
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
index a39fec9..0e6c016 100644
--- a/tempest/api/compute/certificates/test_certificates.py
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -31,14 +31,9 @@
@decorators.idempotent_id('c070a441-b08e-447e-a733-905909535b1b')
def test_create_root_certificate(self):
# create certificates
- body = self.certificates_client.create_certificate()['certificate']
- self.assertIn('data', body)
- self.assertIn('private_key', body)
+ self.certificates_client.create_certificate()
@decorators.idempotent_id('3ac273d0-92d2-4632-bdfc-afbc21d4606c')
def test_get_root_certificate(self):
# get the root certificate
- body = (self.certificates_client.show_certificate('root')
- ['certificate'])
- self.assertIn('data', body)
- self.assertIn('private_key', body)
+ self.certificates_client.show_certificate('root')
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index bf4c958..d5bb45a 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -68,7 +68,7 @@
params = {'marker': flavor_id}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
'The list of flavors did not start after the marker.')
@decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107')
@@ -80,7 +80,7 @@
params = {'marker': flavor_id}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
'The list of flavors did not start after the marker.')
@decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79')
@@ -92,7 +92,7 @@
params = {self._min_disk: flavor['disk'] + 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292')
def test_list_flavors_detailed_filter_by_min_ram(self):
@@ -103,7 +103,7 @@
params = {self._min_ram: flavor['ram'] + 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4')
def test_list_flavors_filter_by_min_disk(self):
@@ -113,7 +113,7 @@
params = {self._min_disk: flavor['disk'] + 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa')
def test_list_flavors_filter_by_min_ram(self):
@@ -123,4 +123,4 @@
params = {self._min_ram: flavor['ram'] + 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 2769245..faa7b5d 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -30,6 +30,12 @@
floating_ip = None
@classmethod
+ def skip_checks(cls):
+ super(FloatingIPsTestJSON, cls).skip_checks()
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
+
+ @classmethod
def setup_clients(cls):
super(FloatingIPsTestJSON, cls).setup_clients()
cls.client = cls.floating_ips_client
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 96983b0..483bd95 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -28,6 +28,12 @@
class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest):
@classmethod
+ def skip_checks(cls):
+ super(FloatingIPsNegativeTestJSON, cls).skip_checks()
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
+
+ @classmethod
def setup_clients(cls):
super(FloatingIPsNegativeTestJSON, cls).setup_clients()
cls.client = cls.floating_ips_client
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 71f5f13..913b992 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -24,6 +24,12 @@
class FloatingIPDetailsTestJSON(base.BaseV2ComputeTest):
@classmethod
+ def skip_checks(cls):
+ super(FloatingIPDetailsTestJSON, cls).skip_checks()
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
+
+ @classmethod
def setup_clients(cls):
super(FloatingIPDetailsTestJSON, cls).setup_clients()
cls.client = cls.floating_ips_client
@@ -52,7 +58,7 @@
# Positive test:Should return the list of floating IPs
body = self.client.list_floating_ips()['floating_ips']
floating_ips = body
- self.assertNotEqual(0, len(floating_ips),
+ self.assertNotEmpty(floating_ips,
"Expected floating IPs. Got zero.")
for i in range(3):
self.assertIn(self.floating_ip[i], floating_ips)
@@ -84,5 +90,5 @@
def test_list_floating_ip_pools(self):
# Positive test:Should return the list of floating IP Pools
floating_ip_pools = self.pools_client.list_floating_ip_pools()
- self.assertNotEqual(0, len(floating_ip_pools['floating_ip_pools']),
+ self.assertNotEmpty(floating_ip_pools['floating_ip_pools'],
"Expected floating IP Pools. Got zero.")
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index 9d70bf7..b5bbb8c 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -26,6 +26,12 @@
class FloatingIPDetailsNegativeTestJSON(base.BaseV2ComputeTest):
@classmethod
+ def skip_checks(cls):
+ super(FloatingIPDetailsNegativeTestJSON, cls).skip_checks()
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
+
+ @classmethod
def setup_clients(cls):
super(FloatingIPDetailsNegativeTestJSON, cls).setup_clients()
cls.client = cls.floating_ips_client
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 83447b6..5987d39 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -80,11 +80,11 @@
@decorators.idempotent_id('3b7c6fe4-dfe7-477c-9243-b06359db51e6')
def test_create_image_specify_multibyte_character_image_name(self):
# prefix character is:
- # http://www.fileformat.info/info/unicode/char/1F4A9/index.htm
+ # http://unicode.org/cldr/utility/character.jsp?a=20A1
- # We use a string with 3 byte utf-8 character due to bug
- # #1370954 in glance which will 500 if mysql is used as the
- # backend and it attempts to store a 4 byte utf-8 character
+ # We use a string with 3 byte utf-8 character due to nova/glance which
+ # will return 400(Bad Request) if we attempt to send a name which has
+ # 4 byte utf-8 character.
utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
body = self.client.create_image(self.server_id, name=utf8_name)
image_id = data_utils.parse_image_id(body.response['location'])
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 6677aa2..acc8b3e 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -129,9 +129,9 @@
params = {'status': 'ACTIVE'}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('33163b73-79f5-4d07-a7ea-9213bcc468ff')
def test_list_images_filter_by_name(self):
@@ -140,9 +140,9 @@
params = {'name': self.image1['name']}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('9f238683-c763-45aa-b848-232ec3ce3105')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -152,14 +152,13 @@
params = {'server': self.server1['id']}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]),
- "Failed to find image %s in images. Got images %s" %
- (self.image1_id, images))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id],
+ "Failed to find image %s in images. "
+ "Got images %s" % (self.image1_id, images))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('05a377b8-28cf-4734-a1e6-2ab5c38bf606')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -173,12 +172,12 @@
params = {'server': link['href']}
images = self.client.list_images(**params)['images']
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('e3356918-4d3e-4756-81d5-abc4524ba29f')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -188,14 +187,13 @@
params = {'type': 'snapshot'}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.image_ref]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image_ref])
@decorators.idempotent_id('3a484ca9-67ba-451e-b494-7fcf28d32d62')
def test_list_images_limit_results(self):
@@ -212,8 +210,8 @@
# Filter by the image's created time
params = {'changes-since': self.image3['created']}
images = self.client.list_images(**params)['images']
- found = any([i for i in images if i['id'] == self.image3_id])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image3_id]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('9b0ea018-6185-4f71-948a-a123a107988e')
def test_list_images_with_detail_filter_by_status(self):
@@ -222,9 +220,9 @@
params = {'status': 'ACTIVE'}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('644ea267-9bd9-4f3b-af9f-dffa02396a17')
def test_list_images_with_detail_filter_by_name(self):
@@ -233,9 +231,9 @@
params = {'name': self.image1['name']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('ba2fa9a9-b672-47cc-b354-3b4c0600e2cb')
def test_list_images_with_detail_limit_results(self):
@@ -257,12 +255,12 @@
params = {'server': link['href']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('888c0cc0-7223-43c5-9db0-b125fd0a393b')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -273,14 +271,13 @@
images = self.client.list_images(detail=True, **params)['images']
self.client.show_image(self.image_ref)
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.image_ref]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image_ref])
@decorators.idempotent_id('7d439e18-ac2e-4827-b049-7e18004712c4')
def test_list_images_with_detail_filter_by_changes_since(self):
@@ -290,4 +287,4 @@
# Filter by the image's created time
params = {'changes-since': self.image1['created']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index 5d3cbf3..e2dbd72 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -44,12 +44,12 @@
def test_list_images(self):
# The list of all images should contain the image
images = self.client.list_images()['images']
- found = any([i for i in images if i['id'] == self.image_ref])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image_ref]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('9f94cb6b-7f10-48c5-b911-a0b84d7d4cd6')
def test_list_images_with_detail(self):
# Detailed list of all images should contain the expected images
images = self.client.list_images(detail=True)['images']
- found = any([i for i in images if i['id'] == self.image_ref])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image_ref]
+ self.assertNotEmpty(found)
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 0b7a967..3a54d51 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -51,13 +51,10 @@
# Keypair should be created, verified and deleted
k_name = data_utils.rand_name('keypair')
keypair = self.create_keypair(k_name)
- private_key = keypair['private_key']
key_name = keypair['name']
self.assertEqual(key_name, k_name,
"The created keypair name is not equal "
"to the requested name")
- self.assertIsNotNone(private_key,
- "Field private_key is empty or not found.")
@decorators.idempotent_id('a4233d5d-52d8-47cc-9a25-e1864527e3df')
def test_get_keypair_detail(self):
@@ -65,14 +62,9 @@
k_name = data_utils.rand_name('keypair')
self.create_keypair(k_name)
keypair_detail = self.client.show_keypair(k_name)['keypair']
- self.assertIn('name', keypair_detail)
- self.assertIn('public_key', keypair_detail)
self.assertEqual(keypair_detail['name'], k_name,
"The created keypair name is not equal "
"to requested name")
- public_key = keypair_detail['public_key']
- self.assertIsNotNone(public_key,
- "Field public_key is empty or not found.")
@decorators.idempotent_id('39c90c6a-304a-49dd-95ec-2366129def05')
def test_keypair_create_with_pub_key(self):
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 58352bd..0585fec 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -42,6 +42,6 @@
# check whether all expected elements exist
missing_elements =\
[ele for ele in expected_elements if ele not in absolute_limits]
- self.assertEqual(0, len(missing_elements),
+ self.assertEmpty(missing_elements,
"Failed to find element %s in absolute limits list"
% ', '.join(ele for ele in missing_elements))
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 3378ef8..124db0e 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -159,8 +159,8 @@
# Get rules of the created Security Group
rules = self.security_groups_client.show_security_group(
securitygroup_id)['security_group']['rules']
- self.assertTrue(any([i for i in rules if i['id'] == rule1_id]))
- self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
+ self.assertNotEmpty([i for i in rules if i['id'] == rule1_id])
+ self.assertNotEmpty([i for i in rules if i['id'] == rule2_id])
@decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf')
@test.services('network')
@@ -186,4 +186,4 @@
rules = (self.security_groups_client.show_security_group(sg1_id)
['security_group']['rules'])
# The group1 has no rules because group2 has deleted
- self.assertEqual(0, len(rules))
+ self.assertEmpty(rules)
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 930a58e..a101a19 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -69,7 +69,6 @@
# leading and trailing spaces
s_name = ' %s ' % data_utils.rand_name('securitygroup ')
securitygroup = self.create_security_group(name=s_name)
- self.assertIn('name', securitygroup)
securitygroup_name = securitygroup['name']
self.assertEqual(securitygroup_name, s_name,
"The created Security Group name is "
@@ -131,7 +130,6 @@
# Update security group name and description
# Create a security group
securitygroup = self.create_security_group()
- self.assertIn('id', securitygroup)
securitygroup_id = securitygroup['id']
# Update the name and description
s_new_name = data_utils.rand_name('sg-hth')
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 207778a..c4dff15 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -154,7 +154,6 @@
def test_update_security_group_with_invalid_sg_name(self):
# Update security_group with invalid sg_name should fail
securitygroup = self.create_security_group()
- self.assertIn('id', securitygroup)
securitygroup_id = securitygroup['id']
# Update Security Group with group name longer than 255 chars
s_new_name = 'securitygroup-'.ljust(260, '0')
@@ -170,7 +169,6 @@
def test_update_security_group_with_invalid_sg_des(self):
# Update security_group with invalid sg_des should fail
securitygroup = self.create_security_group()
- self.assertIn('id', securitygroup)
securitygroup_id = securitygroup['id']
# Update Security Group with group description longer than 255 chars
s_new_des = 'des-'.ljust(260, '0')
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index e50b29a..bfde847 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -15,6 +15,8 @@
import time
+import six
+
from tempest.api.compute import base
from tempest.common import compute
from tempest.common.utils import net_utils
@@ -49,7 +51,6 @@
cls.subnets_client = cls.os_primary.subnets_client
cls.ports_client = cls.os_primary.ports_client
- # TODO(mriedem): move this into a common waiters utility module
def wait_for_port_detach(self, port_id):
"""Waits for the port's device_id to be unset.
@@ -196,7 +197,7 @@
except lib_exc.BadRequest as e:
msg = ('Multiple possible networks found, use a Network ID to be '
'more specific.')
- if not CONF.compute.fixed_network_name and e.message == msg:
+ if not CONF.compute.fixed_network_name and six.text_type(e) == msg:
raise
else:
ifs.append(iface)
@@ -264,7 +265,8 @@
# create two servers
_, servers = compute.create_test_server(
- self.os, tenant_network=network, wait_until='ACTIVE', min_count=2)
+ self.os_primary, tenant_network=network,
+ wait_until='ACTIVE', min_count=2)
# add our cleanups for the servers since we bypassed the base class
for server in servers:
self.addCleanup(self.delete_server, server['id'])
diff --git a/tempest/api/compute/servers/test_availability_zone.py b/tempest/api/compute/servers/test_availability_zone.py
index 82e74ed..36828d6 100644
--- a/tempest/api/compute/servers/test_availability_zone.py
+++ b/tempest/api/compute/servers/test_availability_zone.py
@@ -29,4 +29,4 @@
def test_get_availability_zone_list_with_non_admin_user(self):
# List of availability zone with non-administrator user
availability_zone = self.client.list_availability_zones()
- self.assertGreater(len(availability_zone['availabilityZoneInfo']), 0)
+ self.assertNotEmpty(availability_zone['availabilityZoneInfo'])
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index db42c6c..b727ddd 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -17,6 +17,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common.utils.linux import remote_client
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -28,6 +29,7 @@
class ServersTestJSON(base.BaseV2ComputeTest):
disk_config = 'AUTO'
+ volume_backed = False
@classmethod
def setup_credentials(cls):
@@ -38,8 +40,6 @@
def setup_clients(cls):
super(ServersTestJSON, cls).setup_clients()
cls.client = cls.servers_client
- cls.networks_client = cls.os_primary.networks_client
- cls.subnets_client = cls.os_primary.subnets_client
@classmethod
def resource_setup(cls):
@@ -59,24 +59,11 @@
accessIPv4=cls.accessIPv4,
accessIPv6=cls.accessIPv6,
disk_config=disk_config,
- adminPass=cls.password)
+ adminPass=cls.password,
+ volume_backed=cls.volume_backed)
cls.server = (cls.client.show_server(server_initial['id'])
['server'])
- def _create_net_subnet_ret_net_from_cidr(self, cidr):
- name_net = data_utils.rand_name(self.__class__.__name__)
- net = self.networks_client.create_network(name=name_net)
- self.addCleanup(self.networks_client.delete_network,
- net['network']['id'])
-
- subnet = self.subnets_client.create_subnet(
- network_id=net['network']['id'],
- cidr=cidr,
- ip_version=4)
- self.addCleanup(self.subnets_client.delete_subnet,
- subnet['subnet']['id'])
- return net
-
@decorators.attr(type='smoke')
@decorators.idempotent_id('5de47127-9977-400a-936f-abcfbec1218f')
def test_verify_server_details(self):
@@ -87,7 +74,11 @@
self.assertEqual(self.server['accessIPv6'],
str(netaddr.IPAddress(self.accessIPv6)))
self.assertEqual(self.name, self.server['name'])
- self.assertEqual(self.image_ref, self.server['image']['id'])
+ if self.volume_backed:
+ # Image is an empty string as per documentation
+ self.assertEqual("", self.server['image'])
+ else:
+ self.assertEqual(self.image_ref, self.server['image']['id'])
self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
self.assertEqual(self.meta, self.server['metadata'])
@@ -97,16 +88,16 @@
# The created server should be in the list of all servers
body = self.client.list_servers()
servers = body['servers']
- found = any([i for i in servers if i['id'] == self.server['id']])
- self.assertTrue(found)
+ found = [i for i in servers if i['id'] == self.server['id']]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
def test_list_servers_with_detail(self):
# The created server should be in the detailed list of all servers
body = self.client.list_servers(detail=True)
servers = body['servers']
- found = any([i for i in servers if i['id'] == self.server['id']])
- self.assertTrue(found)
+ found = [i for i in servers if i['id'] == self.server['id']]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('cbc0f52f-05aa-492b-bdc1-84b575ca294b')
@testtools.skipUnless(CONF.validation.run_validation,
@@ -144,7 +135,7 @@
@decorators.idempotent_id('ed20d3fb-9d1f-4329-b160-543fbd5d9811')
@testtools.skipUnless(
- test.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
+ compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
'ServerGroupAffinityFilter is not available.')
def test_create_server_with_scheduler_hint_group(self):
# Create a server with the scheduler hint "group".
@@ -158,73 +149,6 @@
['server_group'])
self.assertIn(server['id'], server_group['members'])
- @decorators.idempotent_id('0578d144-ed74-43f8-8e57-ab10dbf9b3c2')
- @testtools.skipUnless(CONF.service_available.neutron,
- 'Neutron service must be available.')
- def test_verify_multiple_nics_order(self):
- # Verify that the networks order given at the server creation is
- # preserved within the server.
- net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
- net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24')
-
- networks = [{'uuid': net1['network']['id']},
- {'uuid': net2['network']['id']}]
-
- server_multi_nics = self.create_test_server(
- networks=networks, wait_until='ACTIVE')
-
- # Cleanup server; this is needed in the test case because with the LIFO
- # nature of the cleanups, if we don't delete the server first, the port
- # will still be part of the subnet and we'll get a 409 from Neutron
- # when trying to delete the subnet. The tear down in the base class
- # will try to delete the server and get a 404 but it's ignored so
- # we're OK.
- self.addCleanup(self.delete_server, server_multi_nics['id'])
-
- addresses = (self.client.list_addresses(server_multi_nics['id'])
- ['addresses'])
-
- # We can't predict the ip addresses assigned to the server on networks.
- # Sometimes the assigned addresses are ['19.80.0.2', '19.86.0.2'], at
- # other times ['19.80.0.3', '19.86.0.3']. So we check if the first
- # address is in first network, similarly second address is in second
- # network.
- addr = [addresses[net1['network']['name']][0]['addr'],
- addresses[net2['network']['name']][0]['addr']]
- networks = [netaddr.IPNetwork('19.80.0.0/24'),
- netaddr.IPNetwork('19.86.0.0/24')]
- for address, network in zip(addr, networks):
- self.assertIn(address, network)
-
- @decorators.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2')
- @testtools.skipUnless(CONF.service_available.neutron,
- 'Neutron service must be available.')
- def test_verify_duplicate_network_nics(self):
- # Verify that server creation does not fail when more than one nic
- # is created on the same network.
- net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
- net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24')
-
- networks = [{'uuid': net1['network']['id']},
- {'uuid': net2['network']['id']},
- {'uuid': net1['network']['id']}]
-
- server_multi_nics = self.create_test_server(
- networks=networks, wait_until='ACTIVE')
- self.addCleanup(self.delete_server, server_multi_nics['id'])
-
- addresses = (self.client.list_addresses(server_multi_nics['id'])
- ['addresses'])
-
- addr = [addresses[net1['network']['name']][0]['addr'],
- addresses[net2['network']['name']][0]['addr'],
- addresses[net1['network']['name']][1]['addr']]
- networks = [netaddr.IPNetwork('19.80.0.0/24'),
- netaddr.IPNetwork('19.86.0.0/24'),
- netaddr.IPNetwork('19.80.0.0/24')]
- for address, network in zip(addr, networks):
- self.assertIn(address, network)
-
class ServersTestManualDisk(ServersTestJSON):
disk_config = 'MANUAL'
@@ -235,3 +159,15 @@
if not CONF.compute_feature_enabled.disk_config:
msg = "DiskConfig extension not enabled."
raise cls.skipException(msg)
+
+
+class ServersTestBootFromVolume(ServersTestJSON):
+ """Run the `ServersTestJSON` tests with a volume backed VM"""
+ volume_backed = True
+
+ @classmethod
+ def skip_checks(cls):
+ super(ServersTestBootFromVolume, cls).skip_checks()
+ if not test.get_service_list()['volume']:
+ msg = "Volume service not enabled."
+ raise cls.skipException(msg)
diff --git a/tempest/api/compute/servers/test_create_server_multi_nic.py b/tempest/api/compute/servers/test_create_server_multi_nic.py
new file mode 100644
index 0000000..3447d85
--- /dev/null
+++ b/tempest/api/compute/servers/test_create_server_multi_nic.py
@@ -0,0 +1,120 @@
+# Copyright 2012 OpenStack Foundation
+# 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 netaddr
+import testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class ServersTestMultiNic(base.BaseV2ComputeTest):
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.prepare_instance_network()
+ super(ServersTestMultiNic, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServersTestMultiNic, cls).setup_clients()
+ cls.client = cls.servers_client
+ cls.networks_client = cls.os_primary.networks_client
+ cls.subnets_client = cls.os_primary.subnets_client
+
+ def _create_net_subnet_ret_net_from_cidr(self, cidr):
+ name_net = data_utils.rand_name(self.__class__.__name__)
+ net = self.networks_client.create_network(name=name_net)
+ self.addCleanup(self.networks_client.delete_network,
+ net['network']['id'])
+
+ subnet = self.subnets_client.create_subnet(
+ network_id=net['network']['id'],
+ cidr=cidr,
+ ip_version=4)
+ self.addCleanup(self.subnets_client.delete_subnet,
+ subnet['subnet']['id'])
+ return net
+
+ @decorators.idempotent_id('0578d144-ed74-43f8-8e57-ab10dbf9b3c2')
+ @testtools.skipUnless(CONF.service_available.neutron,
+ 'Neutron service must be available.')
+ def test_verify_multiple_nics_order(self):
+ # Verify that the networks order given at the server creation is
+ # preserved within the server.
+ net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
+ net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24')
+
+ networks = [{'uuid': net1['network']['id']},
+ {'uuid': net2['network']['id']}]
+
+ server_multi_nics = self.create_test_server(
+ networks=networks, wait_until='ACTIVE')
+
+ # Cleanup server; this is needed in the test case because with the LIFO
+ # nature of the cleanups, if we don't delete the server first, the port
+ # will still be part of the subnet and we'll get a 409 from Neutron
+ # when trying to delete the subnet. The tear down in the base class
+ # will try to delete the server and get a 404 but it's ignored so
+ # we're OK.
+ self.addCleanup(self.delete_server, server_multi_nics['id'])
+
+ addresses = (self.client.list_addresses(server_multi_nics['id'])
+ ['addresses'])
+
+ # We can't predict the ip addresses assigned to the server on networks.
+ # Sometimes the assigned addresses are ['19.80.0.2', '19.86.0.2'], at
+ # other times ['19.80.0.3', '19.86.0.3']. So we check if the first
+ # address is in first network, similarly second address is in second
+ # network.
+ addr = [addresses[net1['network']['name']][0]['addr'],
+ addresses[net2['network']['name']][0]['addr']]
+ networks = [netaddr.IPNetwork('19.80.0.0/24'),
+ netaddr.IPNetwork('19.86.0.0/24')]
+ for address, network in zip(addr, networks):
+ self.assertIn(address, network)
+
+ @decorators.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2')
+ @testtools.skipUnless(CONF.service_available.neutron,
+ 'Neutron service must be available.')
+ def test_verify_duplicate_network_nics(self):
+ # Verify that server creation does not fail when more than one nic
+ # is created on the same network.
+ net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
+ net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24')
+
+ networks = [{'uuid': net1['network']['id']},
+ {'uuid': net2['network']['id']},
+ {'uuid': net1['network']['id']}]
+
+ server_multi_nics = self.create_test_server(
+ networks=networks, wait_until='ACTIVE')
+ self.addCleanup(self.delete_server, server_multi_nics['id'])
+
+ addresses = (self.client.list_addresses(server_multi_nics['id'])
+ ['addresses'])
+
+ addr = [addresses[net1['network']['name']][0]['addr'],
+ addresses[net2['network']['name']][0]['addr'],
+ addresses[net1['network']['name']][1]['addr']]
+ networks = [netaddr.IPNetwork('19.80.0.0/24'),
+ netaddr.IPNetwork('19.86.0.0/24'),
+ netaddr.IPNetwork('19.80.0.0/24')]
+ for address, network in zip(addr, networks):
+ self.assertIn(address, network)
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 9ab508d..7ee1b02 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -84,10 +84,14 @@
if d['mac'] == self.net_2_200_mac:
self.assertEqual(d['tags'], ['net-2-200'])
- found_devices = [d['tags'][0] for d in md_dict['devices']]
- self.assertItemsEqual(found_devices, ['port-1', 'port-2', 'net-1',
- 'net-2-100', 'net-2-200',
- 'boot', 'other'])
+ # A hypervisor may present multiple paths to a tagged disk, so
+ # there may be duplicated tags in the metadata, use set() to
+ # remove duplicated tags.
+ found_devices = [d['tags'][0] for d in md_dict['devices']]
+ self.assertEqual(set(found_devices), set(['port-1', 'port-2',
+ 'net-1', 'net-2-100',
+ 'net-2-200', 'boot',
+ 'other']))
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@test.services('network', 'volume', 'image')
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 1ad153a..14aecfd 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -15,9 +15,9 @@
import testtools
from tempest.api.compute import base
-from tempest.common import fixed_network
from tempest.common import waiters
from tempest import config
+from tempest.lib.common import fixed_network
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -142,7 +142,7 @@
# Verify only the expected number of servers are returned
params = {'limit': 0}
servers = self.client.list_servers(**params)
- self.assertEqual(0, len(servers['servers']))
+ self.assertEmpty(servers['servers'])
@decorators.idempotent_id('37791bbd-90c0-4de0-831e-5f38cba9c6b3')
def test_list_servers_filter_by_exceed_limit(self):
@@ -262,7 +262,7 @@
# so as to ensure only one server is returned.
ip_list = {}
self.s1 = self.client.show_server(self.s1['id'])['server']
- # Get first ip address inspite of v4 or v6
+ # Get first ip address in spite of v4 or v6
ip_addr = self.s1['addresses'][self.fixed_network_name][0]['addr']
ip_list[ip_addr] = self.s1['id']
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 527f4bd..6c9b287 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -50,7 +50,7 @@
servers = body['servers']
actual = [srv for srv in servers
if srv['id'] == self.deleted_id]
- self.assertEqual([], actual)
+ self.assertEmpty(actual)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
@@ -58,7 +58,7 @@
# Listing servers for a non existing image returns empty list
body = self.client.list_servers(image='non_existing_image')
servers = body['servers']
- self.assertEqual([], servers)
+ self.assertEmpty(servers)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
@@ -66,7 +66,7 @@
# Listing servers by non existing flavor returns empty list
body = self.client.list_servers(flavor='non_existing_flavor')
servers = body['servers']
- self.assertEqual([], servers)
+ self.assertEmpty(servers)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c')
@@ -74,7 +74,7 @@
# Listing servers for a non existent server name returns empty list
body = self.client.list_servers(name='non_existing_server_name')
servers = body['servers']
- self.assertEqual([], servers)
+ self.assertEmpty(servers)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
@@ -82,13 +82,7 @@
# Return an empty list when invalid status is specified
body = self.client.list_servers(status='non_existing_status')
servers = body['servers']
- self.assertEqual([], servers)
-
- @decorators.idempotent_id('12c80a9f-2dec-480e-882b-98ba15757659')
- def test_list_servers_by_limits(self):
- # List servers by specifying limits
- body = self.client.list_servers(limit=1)
- self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
+ self.assertEmpty(servers)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
@@ -128,7 +122,7 @@
# Return an empty list when a date in the future is passed
changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
body = self.client.list_servers(**changes_since)
- self.assertEqual(0, len(body['servers']))
+ self.assertEmpty(body['servers'])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570')
@@ -138,4 +132,4 @@
servers = body['servers']
actual = [srv for srv in servers
if srv['id'] == self.deleted_id]
- self.assertEqual([], actual)
+ self.assertEmpty(actual)
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index 7cbb513..059454d 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -24,7 +24,7 @@
def test_multiple_create(self):
tenant_network = self.get_tenant_network()
body, servers = compute.create_test_server(
- self.os,
+ self.os_primary,
wait_until='ACTIVE',
min_count=2,
tenant_network=tenant_network)
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 90b0665..d9581e3 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -166,7 +166,7 @@
self._validate_novnc_html(body['url'])
# Do the WebSockify HTTP Request to novncproxy to do the RFB connection
self._websocket = compute.create_websocket(body['url'])
- # Validate that we succesfully connected and upgraded to Web Sockets
+ # Validate that we successfully connected and upgraded to Web Sockets
self._validate_websocket_upgrade()
# Validate the RFB Negotiation to determine if a valid VNC session
self._validate_rfb_negotiation()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 76b9c44..d1d29af 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -33,8 +33,6 @@
class ServerActionsTestJSON(base.BaseV2ComputeTest):
- run_ssh = CONF.validation.run_validation
-
def setUp(self):
# NOTE(afazekas): Normally we use the same server with all test cases,
# but if it has an issue, we build a new one
@@ -166,8 +164,7 @@
.format(image_ref, rebuilt_server['image']['id']))
self.assertEqual(image_ref, rebuilt_server['image']['id'], msg)
- @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
- def test_rebuild_server(self):
+ def _test_rebuild_server(self):
# Get the IPs the server has before rebuilding it
original_addresses = (self.client.show_server(self.server_id)['server']
['addresses'])
@@ -218,6 +215,10 @@
servers_client=self.client)
linux_client.validate_authentication()
+ @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
+ def test_rebuild_server(self):
+ self._test_rebuild_server()
+
@decorators.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
def test_rebuild_server_in_stop_state(self):
# The server in stop state should be rebuilt using the provided
@@ -260,7 +261,7 @@
self.attach_volume(server, volume)
# run general rebuild test
- self.test_rebuild_server()
+ self._test_rebuild_server()
# make sure the volume is attached to the instance after rebuild
vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
@@ -517,19 +518,33 @@
@decorators.idempotent_id('77eba8e0-036e-4635-944b-f7a8f3b78dc9')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
+ @test.services('image')
def test_shelve_unshelve_server(self):
+ if CONF.image_feature_enabled.api_v2:
+ glance_client = self.os_primary.image_client_v2
+ elif CONF.image_feature_enabled.api_v1:
+ glance_client = self.os_primary.image_client
+ else:
+ raise lib_exc.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
compute.shelve_server(self.client, self.server_id,
force_shelve_offload=True)
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
params = {'name': image_name}
- images = self.compute_images_client.list_images(**params)['images']
+ if CONF.image_feature_enabled.api_v2:
+ images = glance_client.list_images(params)['images']
+ elif CONF.image_feature_enabled.api_v1:
+ images = glance_client.list_images(
+ detail=True, **params)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
self.client.unshelve_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ glance_client.wait_for_resource_deletion(images[0]['id'])
@decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
def test_stop_start_server(self):
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index 92b1ff1..022ceba 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -48,9 +48,9 @@
# We do not know the exact network configuration, but an instance
# should at least have a single public or private address
- self.assertGreaterEqual(len(addresses), 1)
+ self.assertNotEmpty(addresses)
for network_addresses in addresses.values():
- self.assertGreaterEqual(len(network_addresses), 1)
+ self.assertNotEmpty(network_addresses)
for address in network_addresses:
self.assertTrue(address['addr'])
self.assertTrue(address['version'])
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 8760af6..b0ef3bc 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -42,81 +42,52 @@
def resource_setup(cls):
super(ServerRescueTestJSON, cls).resource_setup()
- # Floating IP creation
- body = cls.floating_ips_client.create_floating_ip(
- pool=CONF.network.floating_network_name)['floating_ip']
- cls.floating_ip_id = str(body['id']).strip()
- cls.floating_ip = str(body['ip']).strip()
-
- # Security group creation
- cls.sg_name = data_utils.rand_name('sg')
- sg_desc = data_utils.rand_name('sg-desc')
- cls.sg = cls.security_groups_client.create_security_group(
- name=cls.sg_name, description=sg_desc)['security_group']
- cls.sg_id = cls.sg['id']
-
- cls.password = data_utils.rand_password()
- # Server for positive tests
- server = cls.create_test_server(adminPass=cls.password,
+ password = data_utils.rand_password()
+ server = cls.create_test_server(adminPass=password,
wait_until='ACTIVE')
- cls.server_id = server['id']
-
- @classmethod
- def resource_cleanup(cls):
- # Deleting the floating IP which is created in this method
- cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
- cls.sg = cls.security_groups_client.delete_security_group(
- cls.sg_id)
- super(ServerRescueTestJSON, cls).resource_cleanup()
-
- def _unrescue(self, server_id):
- self.servers_client.unrescue_server(server_id)
- waiters.wait_for_server_status(self.servers_client, server_id,
- 'ACTIVE')
+ cls.servers_client.rescue_server(server['id'], adminPass=password)
+ waiters.wait_for_server_status(cls.servers_client, server['id'],
+ 'RESCUE')
+ cls.rescued_server_id = server['id']
@decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
def test_rescue_unrescue_instance(self):
- self.servers_client.rescue_server(
- self.server_id, adminPass=self.password)
- waiters.wait_for_server_status(self.servers_client, self.server_id,
+ password = data_utils.rand_password()
+ server = self.create_test_server(adminPass=password,
+ wait_until='ACTIVE')
+ self.servers_client.rescue_server(server['id'], adminPass=password)
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'RESCUE')
- self.servers_client.unrescue_server(self.server_id)
- waiters.wait_for_server_status(self.servers_client, self.server_id,
+ self.servers_client.unrescue_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
@decorators.idempotent_id('4842e0cf-e87d-4d9d-b61f-f4791da3cacc')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
+ @testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
+ "Floating ips are not available")
def test_rescued_vm_associate_dissociate_floating_ip(self):
- # Rescue the server
- self.servers_client.rescue_server(
- self.server_id, adminPass=self.password)
- waiters.wait_for_server_status(self.servers_client, self.server_id,
- 'RESCUE')
- self.addCleanup(self._unrescue, self.server_id)
-
# Association of floating IP to a rescued vm
- client = self.floating_ips_client
- client.associate_floating_ip_to_server(self.floating_ip,
- self.server_id)
+ floating_ip_body = self.floating_ips_client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
+ self.addCleanup(self.floating_ips_client.delete_floating_ip,
+ floating_ip_body['id'])
+
+ self.floating_ips_client.associate_floating_ip_to_server(
+ str(floating_ip_body['ip']).strip(), self.rescued_server_id)
# Disassociation of floating IP that was associated in this method
- client.disassociate_floating_ip_from_server(self.floating_ip,
- self.server_id)
+ self.floating_ips_client.disassociate_floating_ip_from_server(
+ str(floating_ip_body['ip']).strip(), self.rescued_server_id)
@decorators.idempotent_id('affca41f-7195-492d-8065-e09eee245404')
def test_rescued_vm_add_remove_security_group(self):
- # Rescue the server
- self.servers_client.rescue_server(
- self.server_id, adminPass=self.password)
- waiters.wait_for_server_status(self.servers_client, self.server_id,
- 'RESCUE')
- self.addCleanup(self._unrescue, self.server_id)
-
# Add Security group
- self.servers_client.add_security_group(self.server_id,
- name=self.sg_name)
+ sg = self.create_security_group()
+ self.servers_client.add_security_group(self.rescued_server_id,
+ name=sg['name'])
# Delete Security group
- self.servers_client.remove_security_group(self.server_id,
- name=self.sg_name)
+ self.servers_client.remove_security_group(self.rescued_server_id,
+ name=sg['name'])
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 11f236b..7fd1dd1 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -102,14 +102,10 @@
prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
self._update_server_name(server['id'], 'ACTIVE', prefix_name)
- @decorators.idempotent_id('6ac19cb1-27a3-40ec-b350-810bdc04c08e')
- def test_update_server_name_in_stop_state(self):
- # The server name should be changed to the provided value
- server = self.create_test_server(wait_until='ACTIVE')
+ # stop server and check server name update again
self.client.stop_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF')
# Update instance name with non-ASCII characters
- prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
updated_server = self._update_server_name(server['id'],
'SHUTOFF',
prefix_name)
@@ -138,3 +134,14 @@
waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
server = self.client.show_server(server['id'])['server']
self.assertEqual('2001:2001::3', server['accessIPv6'])
+
+
+class ServerShowV247Test(base.BaseV2ComputeTest):
+ min_microversion = '2.47'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
+ def test_show_server(self):
+ server = self.create_test_server()
+ # All fields will be checked by API schema
+ self.servers_client.show_server(server['id'])
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index a480a15..764767b 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -54,6 +54,11 @@
server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
+ server = cls.create_test_server()
+ cls.client.delete_server(server['id'])
+ waiters.wait_for_server_termination(cls.client, server['id'])
+ cls.deleted_server_id = server['id']
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('dbbfd247-c40c-449e-8f6c-d2aa7c7da7cf')
def test_server_name_blank(self):
@@ -170,25 +175,17 @@
@decorators.idempotent_id('98fa0458-1485-440f-873b-fe7f0d714930')
def test_rebuild_deleted_server(self):
# Rebuild a deleted server
- server = self.create_test_server()
- self.client.delete_server(server['id'])
- waiters.wait_for_server_termination(self.client, server['id'])
-
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
- server['id'], self.image_ref)
+ self.deleted_server_id, self.image_ref)
@decorators.related_bug('1660878', status_code=409)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
def test_reboot_deleted_server(self):
# Reboot a deleted server
- server = self.create_test_server()
- self.client.delete_server(server['id'])
- waiters.wait_for_server_termination(self.client, server['id'])
-
self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
- server['id'], type='SOFT')
+ self.deleted_server_id, type='SOFT')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d86141a7-906e-4731-b187-d64a2ea61422')
@@ -551,7 +548,7 @@
def setUp(self):
super(ServersNegativeTestMultiTenantJSON, self).setUp()
try:
- waiters.wait_for_server_status(self.client, self.server_id,
+ waiters.wait_for_server_status(self.servers_client, self.server_id,
'ACTIVE')
except Exception:
self.__class__.server_id = self.rebuild_server(self.server_id)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 610121b..a42b968 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -56,9 +56,8 @@
self.client.list_virtual_interfaces(self.server['id'])
else:
output = self.client.list_virtual_interfaces(self.server['id'])
- self.assertIsNotNone(output)
virt_ifaces = output
- self.assertNotEqual(0, len(virt_ifaces['virtual_interfaces']),
+ self.assertNotEmpty(virt_ifaces['virtual_interfaces'],
'Expected virtual interfaces, got 0 '
'interfaces.')
for virt_iface in virt_ifaces['virtual_interfaces']:
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 11517cc..502bc1b 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -52,6 +52,7 @@
validatable=True,
wait_until='ACTIVE',
adminPass=self.image_ssh_password)
+ self.addCleanup(self.delete_server, server['id'])
# Record addresses so that we can ssh later
server['addresses'] = self.servers_client.list_addresses(
server['id'])['addresses']
@@ -211,7 +212,8 @@
num_vol = self._count_volumes(server)
self._shelve_server(server)
attachment = self.attach_volume(server, volume,
- device=('/dev/%s' % self.device))
+ device=('/dev/%s' % self.device),
+ check_reserved=True)
# Unshelve the instance and check that attached volume exists
self._unshelve_server_and_check_volumes(server, num_vol + 1)
@@ -238,7 +240,8 @@
self._shelve_server(server)
# Attach and then detach the volume
- self.attach_volume(server, volume, device=('/dev/%s' % self.device))
+ self.attach_volume(server, volume, device=('/dev/%s' % self.device),
+ check_reserved=True)
self.servers_client.detach_volume(server['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
index 2f3a06e..0f436eb 100644
--- a/tempest/api/compute/volumes/test_volume_snapshots.py
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -27,6 +27,11 @@
class VolumesSnapshotsTestJSON(base.BaseV2ComputeTest):
+ # These tests will fail with a 404 starting from microversion 2.36. For
+ # more information, see:
+ # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(VolumesSnapshotsTestJSON, cls).skip_checks()
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index 43c837a..d83d49e 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -16,7 +16,6 @@
from testtools import matchers
from tempest.api.compute import base
-from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -27,6 +26,11 @@
class VolumesGetTestJSON(base.BaseV2ComputeTest):
+ # These tests will fail with a 404 starting from microversion 2.36. For
+ # more information, see:
+ # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(VolumesGetTestJSON, cls).skip_checks()
@@ -37,7 +41,7 @@
@classmethod
def setup_clients(cls):
super(VolumesGetTestJSON, cls).setup_clients()
- cls.client = cls.volumes_extensions_client
+ cls.volumes_client = cls.volumes_extensions_client
@decorators.idempotent_id('f10f25eb-9775-4d9d-9cbe-1cf54dae9d5f')
def test_volume_create_get_delete(self):
@@ -45,22 +49,15 @@
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
# Create volume
- volume = self.client.create_volume(size=CONF.volume.volume_size,
- display_name=v_name,
- metadata=metadata)['volume']
- self.assertIn('id', volume)
- self.addCleanup(self.delete_volume, volume['id'])
- self.assertIn('displayName', volume)
+ volume = self.create_volume(size=CONF.volume.volume_size,
+ display_name=v_name,
+ metadata=metadata)
self.assertEqual(volume['displayName'], v_name,
"The created volume name is not equal "
"to the requested name")
- self.assertIsNotNone(volume['id'],
- "Field volume id is empty or not found.")
- # Wait for Volume status to become ACTIVE
- waiters.wait_for_volume_resource_status(self.client, volume['id'],
- 'available')
# GET Volume
- fetched_volume = self.client.show_volume(volume['id'])['volume']
+ fetched_volume = self.volumes_client.show_volume(
+ volume['id'])['volume']
# Verification of details of fetched Volume
self.assertEqual(v_name,
fetched_volume['displayName'],
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index 0d8214f..b2aebe7 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -27,6 +27,11 @@
# If you are running a Devstack environment, ensure that the
# VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
+ # These tests will fail with a 404 starting from microversion 2.36. For
+ # more information, see:
+ # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(VolumesTestJSON, cls).skip_checks()
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 80db1be..87f7d8a 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -24,6 +24,11 @@
class VolumesNegativeTest(base.BaseV2ComputeTest):
+ # These tests will fail with a 404 starting from microversion 2.36. For
+ # more information, see:
+ # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(VolumesNegativeTest, cls).skip_checks()
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index db32f5a..59fc4d8 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -62,7 +62,7 @@
# Asserting LIST endpoints
missing_endpoints =\
[e for e in self.setup_endpoints if e not in fetched_endpoints]
- self.assertEqual(0, len(missing_endpoints),
+ self.assertEmpty(missing_endpoints,
"Failed to find endpoint %s in fetched list" %
', '.join(str(e) for e in missing_endpoints))
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 479663c..124bb5f 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -54,7 +54,7 @@
"""Return a list of all roles."""
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.roles]
- self.assertTrue(any(found))
+ self.assertNotEmpty(found)
self.assertEqual(len(found), len(self.roles))
@decorators.idempotent_id('c62d909d-6c21-48c0-ae40-0a0760e6db5e')
@@ -68,13 +68,13 @@
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role['name'] == role_name]
- self.assertTrue(any(found))
+ self.assertNotEmpty(found)
body = self.roles_client.delete_role(found[0]['id'])
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role['name'] == role_name]
- self.assertFalse(any(found))
+ self.assertEmpty(found)
@decorators.idempotent_id('db6870bd-a6ed-43be-a9b1-2f10a5c9994f')
def test_get_role_by_id(self):
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index 86d06e2..f3b7494 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -143,7 +143,7 @@
@decorators.idempotent_id('99b297f6-2b5d-47c7-97a9-8b6bb4f91042')
def test_assign_user_role_for_non_existent_role(self):
# Attempt to assign a non existent role to user should fail
- (user, tenant, role) = self._get_role_params()
+ (user, tenant, _) = self._get_role_params()
non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
self.roles_client.create_user_role_on_project,
@@ -153,7 +153,7 @@
@decorators.idempotent_id('b2285aaa-9e76-4704-93a9-7a8acd0a6c8f')
def test_assign_user_role_for_non_existent_tenant(self):
# Attempt to assign a role on a non existent tenant should fail
- (user, tenant, role) = self._get_role_params()
+ (user, _, role) = self._get_role_params()
non_existent_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound,
self.roles_client.create_user_role_on_project,
@@ -244,7 +244,7 @@
@decorators.idempotent_id('682adfb2-fd5f-4b0a-a9ca-322e9bebb907')
def test_list_user_roles_request_without_token(self):
# Request to list user's roles without a valid token should fail
- (user, tenant, role) = self._get_role_params()
+ (user, tenant, _) = self._get_role_params()
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
try:
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index 634cf21..e2ed5ef 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -41,7 +41,6 @@
self.assertIsNotNone(service_data['id'])
self.addCleanup(self._del_service, service_data['id'])
# Verifying response body of create service
- self.assertIn('id', service_data)
self.assertIn('name', service_data)
self.assertEqual(name, service_data['name'])
self.assertIn('type', service_data)
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index ad9b983..0f955bf 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -37,7 +37,7 @@
body = self.tenants_client.list_tenants()['tenants']
found = [tenant for tenant in body if tenant['id'] in tenant_ids]
- self.assertFalse(any(found), 'Tenants failed to delete')
+ self.assertEmpty(found, 'Tenants failed to delete')
@decorators.idempotent_id('d25e9f24-1310-4d29-b61b-d91299c21d6d')
def test_tenant_create_with_description(self):
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index b4c9389..6b30d23 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -14,14 +14,18 @@
# under the License.
from tempest.api.identity import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
class TokensTestJSON(base.BaseIdentityV2AdminTest):
@decorators.idempotent_id('453ad4d5-e486-4b2f-be72-cffc8149e586')
- def test_create_get_delete_token(self):
+ def test_create_check_get_delete_token(self):
# get a token by username and password
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
@@ -40,6 +44,7 @@
tenant['name'])
# Perform GET Token
token_id = body['token']['id']
+ self.client.check_token_existence(token_id)
token_details = self.client.show_token(token_id)['access']
self.assertEqual(token_id, token_details['token']['id'])
self.assertEqual(user['id'], token_details['user']['id'])
@@ -48,6 +53,9 @@
token_details['token']['tenant']['name'])
# then delete the token
self.client.delete_token(token_id)
+ self.assertRaises(lib_exc.NotFound,
+ self.client.check_token_existence,
+ token_id)
@decorators.idempotent_id('25ba82ee-8a32-4ceb-8f50-8b8c71e8765e')
def test_rescope_token(self):
@@ -101,3 +109,25 @@
# Use the unscoped token to get a token scoped to tenant2
body = self.token_client.auth_token(token_id,
tenant=tenant2_name)
+
+ @decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31')
+ def test_list_endpoints_for_token(self):
+ # get a token for the user
+ creds = self.os_primary.credentials
+ username = creds.username
+ password = creds.password
+ tenant_name = creds.tenant_name
+ token = self.token_client.auth(username,
+ password,
+ tenant_name)['token']
+ endpoints = self.client.list_endpoints_for_token(
+ token['id'])['endpoints']
+ self.assertIsInstance(endpoints, list)
+ # Store list of service names
+ service_names = [e['name'] for e in endpoints]
+ # Get the list of available services.
+ available_services = [s[0] for s in list(
+ CONF.service_available.items()) if s[1] is True]
+ # Verify that all available services are present.
+ for service in available_services:
+ self.assertIn(service, service_names)
diff --git a/tempest/api/identity/admin/v2/test_tokens_negative.py b/tempest/api/identity/admin/v2/test_tokens_negative.py
new file mode 100644
index 0000000..eb3e365
--- /dev/null
+++ b/tempest/api/identity/admin/v2/test_tokens_negative.py
@@ -0,0 +1,38 @@
+# Copyright 2017 AT&T Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class TokensAdminTestNegative(base.BaseIdentityV2AdminTest):
+
+ credentials = ['primary', 'admin', 'alt']
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('a0a0a600-4292-4364-99c5-922c834fdf05')
+ def test_check_token_existence_negative(self):
+ creds = self.os_primary.credentials
+ creds_alt = self.os_alt.credentials
+ username = creds.username
+ password = creds.password
+ tenant_name = creds.tenant_name
+ alt_tenant_name = creds_alt.tenant_name
+ body = self.token_client.auth(username, password, tenant_name)
+ self.assertRaises(lib_exc.Unauthorized,
+ self.client.check_token_existence,
+ body['token']['id'],
+ belongsTo=alt_tenant_name)
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 2711a2d..0d98af5 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -141,7 +141,7 @@
# verifying the user Id in the list
missing_users =\
[user for user in user_ids if user not in fetched_user_ids]
- self.assertEqual(0, len(missing_users),
+ self.assertEmpty(missing_users,
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
@@ -169,7 +169,7 @@
# verifying the user Id in the list
missing_users = [missing_user for missing_user in user_ids
if missing_user not in fetched_user_ids]
- self.assertEqual(0, len(missing_users),
+ self.assertEmpty(missing_users,
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 75e877a..15b2008 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -107,6 +107,6 @@
fetched_cred_ids.append(i['id'])
missing_creds = [c for c in created_cred_ids
if c not in fetched_cred_ids]
- self.assertEqual(0, len(missing_creds),
+ self.assertEmpty(missing_creds,
"Failed to find cred %s in fetched list" %
', '.join(m_cred for m_cred in missing_creds))
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index ac2faa9..302a0e5 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -79,7 +79,7 @@
admin_client = clients.Manager(credentials=creds)
# verify the user's token and see that it is scoped to the project
- token, auth_data = admin_client.auth_provider.get_auth()
+ token, _ = admin_client.auth_provider.get_auth()
result = admin_client.identity_v3_client.show_token(token)['token']
self.assertEqual(result['project']['domain']['id'], dom_id)
self.assertEqual(result['project']['id'], proj_id)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index cddba53..bf04ede 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -57,7 +57,7 @@
fetched_ids.append(d['id'])
missing_doms = [d for d in self.setup_domains
if d['id'] not in fetched_ids]
- self.assertEqual(0, len(missing_doms))
+ self.assertEmpty(missing_doms)
@decorators.idempotent_id('c6aee07b-4981-440c-bb0b-eb598f58ffe9')
def test_list_domains_filter_by_name(self):
@@ -93,7 +93,6 @@
name=d_name, description=d_desc)['domain']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self._delete_domain, domain['id'])
- self.assertIn('id', domain)
self.assertIn('description', domain)
self.assertIn('name', domain)
self.assertIn('enabled', domain)
@@ -147,7 +146,6 @@
d_name = data_utils.rand_name('domain')
domain = self.domains_client.create_domain(name=d_name)['domain']
self.addCleanup(self._delete_domain, domain['id'])
- self.assertIn('id', domain)
expected_data = {'name': d_name, 'enabled': True}
self.assertEqual('', domain['description'])
self.assertDictContainsSubset(expected_data, domain)
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 1a0b851..56f7d32 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -20,7 +20,6 @@
class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
- _interface = 'json'
@decorators.attr(type=['negative', 'gate'])
@decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
new file mode 100644
index 0000000..49dbba1
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -0,0 +1,157 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class EndPointGroupsTest(base.BaseIdentityV3AdminTest):
+
+ @classmethod
+ def setup_clients(cls):
+ super(EndPointGroupsTest, cls).setup_clients()
+ cls.client = cls.endpoint_groups_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(EndPointGroupsTest, cls).resource_setup()
+ cls.service_ids = list()
+ cls.endpoint_groups = list()
+
+ # Create endpoint group so as to use it for LIST test
+ service_id = cls._create_service()
+
+ name = data_utils.rand_name('service_group')
+ description = data_utils.rand_name('description')
+ filters = {'service_id': service_id}
+
+ endpoint_group = cls.client.create_endpoint_group(
+ name=name,
+ description=description,
+ filters=filters)['endpoint_group']
+
+ cls.endpoint_groups.append(endpoint_group)
+
+ @classmethod
+ def resource_cleanup(cls):
+ for e in cls.endpoint_groups:
+ cls.client.delete_endpoint_group(e['id'])
+ for s in cls.service_ids:
+ cls.services_client.delete_service(s)
+ super(EndPointGroupsTest, cls).resource_cleanup()
+
+ @classmethod
+ def _create_service(cls):
+ s_name = data_utils.rand_name('service')
+ s_type = data_utils.rand_name('type')
+ s_description = data_utils.rand_name('description')
+ service_data = (
+ cls.services_client.create_service(name=s_name,
+ type=s_type,
+ description=s_description))
+
+ service_id = service_data['service']['id']
+ cls.service_ids.append(service_id)
+ return service_id
+
+ @decorators.idempotent_id('7c69e7a1-f865-402d-a2ea-44493017315a')
+ def test_create_list_show_check_delete_endpoint_group(self):
+ service_id = self._create_service()
+ name = data_utils.rand_name('service_group')
+ description = data_utils.rand_name('description')
+ filters = {'service_id': service_id}
+
+ endpoint_group = self.client.create_endpoint_group(
+ name=name,
+ description=description,
+ filters=filters)['endpoint_group']
+
+ self.endpoint_groups.append(endpoint_group)
+
+ # Asserting created endpoint group response body
+ self.assertIn('id', endpoint_group)
+ self.assertEqual(name, endpoint_group['name'])
+ self.assertEqual(description, endpoint_group['description'])
+
+ # Checking if endpoint groups are present in the list of endpoints
+ # Note that there are two endpoint groups in the list, one created
+ # in the resource setup, one created in this test case.
+ fetched_endpoints = \
+ self.client.list_endpoint_groups()['endpoint_groups']
+
+ missing_endpoints = \
+ [e for e in self.endpoint_groups if e not in fetched_endpoints]
+
+ # Asserting LIST endpoints
+ self.assertEmpty(missing_endpoints,
+ "Failed to find endpoint %s in fetched list" %
+ ', '.join(str(e) for e in missing_endpoints))
+
+ # Show endpoint group
+ fetched_endpoint = self.client.show_endpoint_group(
+ endpoint_group['id'])['endpoint_group']
+
+ # Asserting if the attributes of endpoint group are the same
+ self.assertEqual(service_id,
+ fetched_endpoint['filters']['service_id'])
+ for attr in ('id', 'name', 'description'):
+ self.assertEqual(endpoint_group[attr], fetched_endpoint[attr])
+
+ # Check endpoint group
+ self.client.check_endpoint_group(endpoint_group['id'])
+
+ # Deleting the endpoint group created in this method
+ self.client.delete_endpoint_group(endpoint_group['id'])
+ self.endpoint_groups.remove(endpoint_group)
+
+ # Checking whether endpoint group is deleted successfully
+ fetched_endpoints = \
+ self.client.list_endpoint_groups()['endpoint_groups']
+ fetched_endpoint_ids = [e['id'] for e in fetched_endpoints]
+ self.assertNotIn(endpoint_group['id'], fetched_endpoint_ids)
+
+ @decorators.idempotent_id('51c8fc38-fa84-4e76-b5b6-6fc37770fb26')
+ def test_update_endpoint_group(self):
+ # Creating an endpoint group so as to check update endpoint group
+ # with new values
+ service1_id = self._create_service()
+ name = data_utils.rand_name('service_group')
+ description = data_utils.rand_name('description')
+ filters = {'service_id': service1_id}
+
+ endpoint_group = self.client.create_endpoint_group(
+ name=name,
+ description=description,
+ filters=filters)['endpoint_group']
+ self.endpoint_groups.append(endpoint_group)
+
+ # Creating new attr values to update endpoint group
+ service2_id = self._create_service()
+ name2 = data_utils.rand_name('service_group2')
+ description2 = data_utils.rand_name('description2')
+ filters = {'service_id': service2_id}
+
+ # Updating endpoint group with new attr values
+ updated_endpoint_group = self.client.update_endpoint_group(
+ endpoint_group['id'],
+ name=name2,
+ description=description2,
+ filters=filters)['endpoint_group']
+
+ self.assertEqual(name2, updated_endpoint_group['name'])
+ self.assertEqual(description2, updated_endpoint_group['description'])
+ self.assertEqual(service2_id,
+ updated_endpoint_group['filters']['service_id'])
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 09f92e2..5d48f68 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -29,58 +29,94 @@
def resource_setup(cls):
super(EndPointsTestJSON, cls).resource_setup()
cls.service_ids = list()
- s_name = data_utils.rand_name('service')
- s_type = data_utils.rand_name('type')
- s_description = data_utils.rand_name('description')
+
+ # Create endpoints so as to use for LIST and GET test cases
+ interfaces = ['public', 'internal']
+ cls.setup_endpoint_ids = list()
+ for i in range(2):
+ cls._create_service()
+ region = data_utils.rand_name('region')
+ url = data_utils.rand_url()
+ endpoint = cls.client.create_endpoint(
+ service_id=cls.service_ids[i], interface=interfaces[i],
+ url=url, region=region, enabled=True)['endpoint']
+ cls.setup_endpoint_ids.append(endpoint['id'])
+
+ @classmethod
+ def _create_service(cls, s_name=None, s_type=None, s_description=None):
+ if s_name is None:
+ s_name = data_utils.rand_name('service')
+ if s_type is None:
+ s_type = data_utils.rand_name('type')
+ if s_description is None:
+ s_description = data_utils.rand_name('description')
service_data = (
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description))
- cls.service_id = service_data['service']['id']
- cls.service_ids.append(cls.service_id)
- # Create endpoints so as to use for LIST and GET test cases
- cls.setup_endpoints = list()
- for _ in range(2):
- region = data_utils.rand_name('region')
- url = data_utils.rand_url()
- interface = 'public'
- endpoint = cls.client.create_endpoint(service_id=cls.service_id,
- interface=interface,
- url=url, region=region,
- enabled=True)['endpoint']
- cls.setup_endpoints.append(endpoint)
+ service = service_data['service']
+ cls.service_ids.append(service['id'])
+ return service
@classmethod
def resource_cleanup(cls):
- for e in cls.setup_endpoints:
- cls.client.delete_endpoint(e['id'])
+ for e in cls.setup_endpoint_ids:
+ cls.client.delete_endpoint(e)
for s in cls.service_ids:
cls.services_client.delete_service(s)
super(EndPointsTestJSON, cls).resource_cleanup()
@decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618')
def test_list_endpoints(self):
- # Get a list of endpoints
+ # Get the list of all the endpoints.
fetched_endpoints = self.client.list_endpoints()['endpoints']
- # Asserting LIST endpoints
+ fetched_endpoint_ids = [e['id'] for e in fetched_endpoints]
+ # Check that all the created endpoints are present in
+ # "fetched_endpoints".
missing_endpoints =\
- [e for e in self.setup_endpoints if e not in fetched_endpoints]
+ [e for e in self.setup_endpoint_ids
+ if e not in fetched_endpoint_ids]
self.assertEqual(0, len(missing_endpoints),
"Failed to find endpoint %s in fetched list" %
', '.join(str(e) for e in missing_endpoints))
+ # Check that filtering endpoints by service_id works.
+ fetched_endpoints_for_service = self.client.list_endpoints(
+ service_id=self.service_ids[0])['endpoints']
+ fetched_endpoints_for_alt_service = self.client.list_endpoints(
+ service_id=self.service_ids[1])['endpoints']
+
+ # Assert that both filters returned the correct result.
+ self.assertEqual(1, len(fetched_endpoints_for_service))
+ self.assertEqual(1, len(fetched_endpoints_for_alt_service))
+ self.assertEqual(set(self.setup_endpoint_ids),
+ set([fetched_endpoints_for_service[0]['id'],
+ fetched_endpoints_for_alt_service[0]['id']]))
+
+ # Check that filtering endpoints by interface works.
+ fetched_public_endpoints = self.client.list_endpoints(
+ interface='public')['endpoints']
+ fetched_internal_endpoints = self.client.list_endpoints(
+ interface='internal')['endpoints']
+
+ # Check that the expected endpoint_id is present per filter. [0] is
+ # public and [1] is internal.
+ self.assertIn(self.setup_endpoint_ids[0],
+ [e['id'] for e in fetched_public_endpoints])
+ self.assertIn(self.setup_endpoint_ids[1],
+ [e['id'] for e in fetched_internal_endpoints])
+
@decorators.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37')
def test_create_list_show_delete_endpoint(self):
region = data_utils.rand_name('region')
url = data_utils.rand_url()
interface = 'public'
- endpoint = self.client.create_endpoint(service_id=self.service_id,
+ endpoint = self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface,
url=url, region=region,
enabled=True)['endpoint']
- self.setup_endpoints.append(endpoint)
+ self.setup_endpoint_ids.append(endpoint['id'])
# Asserting Create Endpoint response body
- self.assertIn('id', endpoint)
self.assertEqual(region, endpoint['region'])
self.assertEqual(url, endpoint['url'])
@@ -93,7 +129,7 @@
fetched_endpoint = (
self.client.show_endpoint(endpoint['id'])['endpoint'])
# Asserting if the attributes of endpoint are the same
- self.assertEqual(self.service_id, fetched_endpoint['service_id'])
+ self.assertEqual(self.service_ids[0], fetched_endpoint['service_id'])
self.assertEqual(interface, fetched_endpoint['interface'])
self.assertEqual(url, fetched_endpoint['url'])
self.assertEqual(region, fetched_endpoint['region'])
@@ -101,7 +137,7 @@
# Deleting the endpoint created in this method
self.client.delete_endpoint(endpoint['id'])
- self.setup_endpoints.remove(endpoint)
+ self.setup_endpoint_ids.remove(endpoint['id'])
# Checking whether endpoint is deleted successfully
fetched_endpoints = self.client.list_endpoints()['endpoints']
@@ -117,7 +153,7 @@
url1 = data_utils.rand_url()
interface1 = 'public'
endpoint_for_update = (
- self.client.create_endpoint(service_id=self.service_id,
+ self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface1,
url=url1, region=region1,
enabled=True)['endpoint'])
@@ -126,11 +162,8 @@
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
- service2 = (
- self.services_client.create_service(name=s_name, type=s_type,
- description=s_description))
- service2 = service2['service']
- self.service_ids.append(service2['id'])
+ service2 = self._create_service(s_name=s_name, s_type=s_type,
+ s_description=s_description)
# Updating endpoint with new values
region2 = data_utils.rand_name('region')
url2 = data_utils.rand_url()
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 2694402..4bc987f 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -134,4 +134,4 @@
for g in body:
fetched_ids.append(g['id'])
missing_groups = [g for g in group_ids if g not in fetched_ids]
- self.assertEqual([], missing_groups)
+ self.assertEmpty(missing_groups)
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 49b6585..e61dbc8 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -16,17 +16,17 @@
from tempest import test
-class BaseInheritsV3Test(base.BaseIdentityV3AdminTest):
+class InheritsV3TestJSON(base.BaseIdentityV3AdminTest):
@classmethod
def skip_checks(cls):
- super(BaseInheritsV3Test, cls).skip_checks()
+ super(InheritsV3TestJSON, cls).skip_checks()
if not test.is_extension_enabled('OS-INHERIT', 'identity'):
raise cls.skipException("Inherits aren't enabled")
@classmethod
def resource_setup(cls):
- super(BaseInheritsV3Test, cls).resource_setup()
+ super(InheritsV3TestJSON, cls).resource_setup()
u_name = data_utils.rand_name('user-')
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
@@ -51,15 +51,12 @@
cls.projects_client.delete_project(cls.project['id'])
cls.domains_client.update_domain(cls.domain['id'], enabled=False)
cls.domains_client.delete_domain(cls.domain['id'])
- super(BaseInheritsV3Test, cls).resource_cleanup()
+ super(InheritsV3TestJSON, cls).resource_cleanup()
def _list_assertions(self, body, fetched_role_ids, role_id):
self.assertEqual(len(body), 1)
self.assertIn(role_id, fetched_role_ids)
-
-class InheritsV3TestJSON(BaseInheritsV3Test):
-
@decorators.idempotent_id('4e6f0366-97c8-423c-b2be-41eae6ac91c8')
def test_inherit_assign_list_check_revoke_roles_on_domains_user(self):
# Create role
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index bcbf6b6..47a3580 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -93,7 +93,7 @@
fetched_ids = [u['id'] for u in body]
missing_users = [u['id'] for u in self.users
if u['id'] not in fetched_ids]
- self.assertEqual(0, len(missing_users),
+ self.assertEmpty(missing_users,
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
diff --git a/tempest/api/identity/admin/v3/test_oauth_consumers.py b/tempest/api/identity/admin/v3/test_oauth_consumers.py
index f06fb8f..970ead3 100644
--- a/tempest/api/identity/admin/v3/test_oauth_consumers.py
+++ b/tempest/api/identity/admin/v3/test_oauth_consumers.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils import data_utils
+from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as exceptions
diff --git a/tempest/api/identity/admin/v3/test_policies.py b/tempest/api/identity/admin/v3/test_policies.py
index 730d469..2908fc4 100644
--- a/tempest/api/identity/admin/v3/test_policies.py
+++ b/tempest/api/identity/admin/v3/test_policies.py
@@ -41,7 +41,7 @@
for p in body:
fetched_ids.append(p['id'])
missing_pols = [p for p in policy_ids if p not in fetched_ids]
- self.assertEqual(0, len(missing_pols))
+ self.assertEmpty(missing_pols)
@decorators.attr(type='smoke')
@decorators.idempotent_id('e544703a-2f03-4cf2-9b0f-350782fdb0d3')
@@ -52,7 +52,6 @@
policy = self.policies_client.create_policy(blob=blob,
type=policy_type)['policy']
self.addCleanup(self._delete_policy, policy['id'])
- self.assertIn('id', policy)
self.assertIn('type', policy)
self.assertIn('blob', policy)
self.assertIsNotNone(policy['id'])
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index ac550a7..d00e408 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -98,7 +98,7 @@
missing_regions =\
[e for e in self.setup_regions if e not in fetched_regions]
# Asserting List Regions response
- self.assertEqual(0, len(missing_regions),
+ self.assertEmpty(missing_regions,
"Failed to find region %s in fetched list" %
', '.join(str(e) for e in missing_regions))
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index adb467c..ec904e6 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -33,7 +33,6 @@
role_name = data_utils.rand_name(name='role')
role = cls.roles_client.create_role(name=role_name)['role']
cls.roles.append(role)
- cls.fetched_role_ids = list()
u_name = data_utils.rand_name('user')
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
@@ -67,10 +66,6 @@
cls.roles_client.delete_role(role['id'])
super(RolesV3TestJSON, cls).resource_cleanup()
- def _list_assertions(self, body, fetched_role_ids, role_id):
- self.assertEqual(len(body), 1)
- self.assertIn(role_id, fetched_role_ids)
-
@decorators.attr(type='smoke')
@decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
def test_role_create_update_show_list(self):
@@ -104,11 +99,8 @@
roles = self.roles_client.list_user_roles_on_project(
self.project['id'], self.user_body['id'])['roles']
- for i in roles:
- self.fetched_role_ids.append(i['id'])
-
- self._list_assertions(roles, self.fetched_role_ids,
- self.role['id'])
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
self.roles_client.check_user_role_existence_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
@@ -124,11 +116,8 @@
roles = self.roles_client.list_user_roles_on_domain(
self.domain['id'], self.user_body['id'])['roles']
- for i in roles:
- self.fetched_role_ids.append(i['id'])
-
- self._list_assertions(roles, self.fetched_role_ids,
- self.role['id'])
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
self.roles_client.check_user_role_existence_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@@ -145,11 +134,9 @@
roles = self.roles_client.list_group_roles_on_project(
self.project['id'], self.group_body['id'])['roles']
- for i in roles:
- self.fetched_role_ids.append(i['id'])
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
- self._list_assertions(roles, self.fetched_role_ids,
- self.role['id'])
# Add user to group, and insure user has role on project
self.groups_client.add_group_user(self.group_body['id'],
self.user_body['id'])
@@ -179,11 +166,8 @@
roles = self.roles_client.list_group_roles_on_domain(
self.domain['id'], self.group_body['id'])['roles']
- for i in roles:
- self.fetched_role_ids.append(i['id'])
-
- self._list_assertions(roles, self.fetched_role_ids,
- self.role['id'])
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
self.roles_client.check_role_from_group_on_domain_existence(
self.domain['id'], self.group_body['id'], self.role['id'])
@@ -215,7 +199,7 @@
implies_role_id)
@decorators.idempotent_id('c90c316c-d706-4728-bcba-eb1912081b69')
- def test_implied_roles_create_delete(self):
+ def test_implied_roles_create_check_show_delete(self):
prior_role_id = self.roles[0]['id']
implies_role_id = self.roles[1]['id']
@@ -224,9 +208,19 @@
ignore_not_found=True)
# Check if the inference rule exists
- self.roles_client.show_role_inference_rule(
+ self.roles_client.check_role_inference_rule(
prior_role_id, implies_role_id)
+ # Show the inference rule and check its elements
+ resp_body = self.roles_client.show_role_inference_rule(
+ prior_role_id, implies_role_id)
+ self.assertIn('role_inference', resp_body)
+ role_inference = resp_body['role_inference']
+ for key1 in ['prior_role', 'implies']:
+ self.assertIn(key1, role_inference)
+ for key2 in ['id', 'links', 'name']:
+ self.assertIn(key2, role_inference[key1])
+
# Delete the inference rule
self.roles_client.delete_role_inference_rule(
prior_role_id, implies_role_id)
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index ad4e549..5afeb98 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -69,7 +69,6 @@
service = self.services_client.create_service(
type=serv_type, name=name)['service']
self.addCleanup(self.services_client.delete_service, service['id'])
- self.assertIn('id', service)
expected_data = {'name': name, 'type': serv_type}
self.assertDictContainsSubset(expected_data, service)
@@ -77,17 +76,26 @@
def test_list_services(self):
# Create, List, Verify and Delete Services
service_ids = list()
+ service_types = list()
for _ in range(3):
- name = data_utils.rand_name('service')
- serv_type = data_utils.rand_name('type')
+ name = data_utils.rand_name(self.__class__.__name__ + '-Service')
+ serv_type = data_utils.rand_name(self.__class__.__name__ + '-Type')
create_service = self.services_client.create_service(
type=serv_type, name=name)['service']
self.addCleanup(self.services_client.delete_service,
create_service['id'])
service_ids.append(create_service['id'])
+ service_types.append(serv_type)
# List and Verify Services
services = self.services_client.list_services()['services']
fetched_ids = [service['id'] for service in services]
found = [s for s in fetched_ids if s in service_ids]
self.assertEqual(len(found), len(service_ids))
+
+ # Check that filtering by service type works.
+ for serv_type in service_types:
+ fetched_services = self.services_client.list_services(
+ type=serv_type)['services']
+ self.assertEqual(1, len(fetched_services))
+ self.assertEqual(serv_type, fetched_services[0]['type'])
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 8c5e63b..5c3cd26 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -39,6 +39,7 @@
resp = self.token.auth(user_id=user['id'],
password=u_password).response
subject_token = resp['x-subject-token']
+ self.client.check_token_existence(subject_token)
# Perform GET Token
token_details = self.client.show_token(subject_token)['token']
self.assertEqual(resp['x-subject-token'], subject_token)
@@ -46,7 +47,7 @@
self.assertEqual(token_details['user']['name'], u_name)
# Perform Delete Token
self.client.delete_token(subject_token)
- self.assertRaises(lib_exc.NotFound, self.client.show_token,
+ self.assertRaises(lib_exc.NotFound, self.client.check_token_existence,
subject_token)
@decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112')
@@ -145,7 +146,7 @@
@decorators.idempotent_id('08ed85ce-2ba8-4864-b442-bcc61f16ae89')
def test_get_available_project_scopes(self):
- manager_project_id = self.manager.credentials.project_id
+ manager_project_id = self.os_primary.credentials.project_id
admin_user_id = self.os_admin.credentials.user_id
admin_role_id = self.get_role_by_name(CONF.identity.admin_role)['id']
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 3e6a2de..2530072 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -26,22 +26,27 @@
CONF = config.CONF
-class BaseTrustsV3Test(base.BaseIdentityV3AdminTest):
+class TrustsV3TestJSON(base.BaseIdentityV3AdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(TrustsV3TestJSON, cls).skip_checks()
+ if not CONF.identity_feature_enabled.trust:
+ raise cls.skipException("Trusts aren't enabled")
def setUp(self):
- super(BaseTrustsV3Test, self).setUp()
+ super(TrustsV3TestJSON, self).setUp()
# Use alt_username as the trustee
- if not CONF.identity_feature_enabled.trust:
- raise self.skipException("Trusts aren't enabled")
-
self.trust_id = None
+ self.create_trustor_and_roles()
+ self.addCleanup(self.cleanup_user_and_roles)
def tearDown(self):
if self.trust_id:
# Do the delete in tearDown not addCleanup - we want the test to
# fail in the event there is a bug which causes undeletable trusts
self.delete_trust()
- super(BaseTrustsV3Test, self).tearDown()
+ super(TrustsV3TestJSON, self).tearDown()
def create_trustor_and_roles(self):
# create a project that trusts will be granted on
@@ -190,14 +195,6 @@
self.trust_id)
self.trust_id = None
-
-class TrustsV3TestJSON(BaseTrustsV3Test):
-
- def setUp(self):
- super(TrustsV3TestJSON, self).setUp()
- self.create_trustor_and_roles()
- self.addCleanup(self.cleanup_user_and_roles)
-
@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
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 751962f..3813568 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -41,31 +41,26 @@
email=u_email, enabled=False)['user']
# Delete the User at the end of this method
self.addCleanup(self.users_client.delete_user, user['id'])
+
# Creating second project for updation
project = self.setup_test_project()
+
# Updating user details with new values
- u_name2 = data_utils.rand_name('user2')
- u_email2 = u_name2 + '@testmail.tm'
- u_description2 = u_name2 + ' description'
- update_user = self.users_client.update_user(
- user['id'], name=u_name2, description=u_description2,
- project_id=project['id'],
- email=u_email2, enabled=False)['user']
- self.assertEqual(u_name2, update_user['name'])
- self.assertEqual(u_description2, update_user['description'])
- self.assertEqual(project['id'],
- update_user['project_id'])
- self.assertEqual(u_email2, update_user['email'])
- self.assertEqual(False, update_user['enabled'])
- # GET by id after updation
+ update_kwargs = {'name': data_utils.rand_name('user2'),
+ 'description': data_utils.rand_name('desc2'),
+ 'project_id': project['id'],
+ 'email': 'user2@testmail.tm',
+ 'enabled': False}
+ updated_user = self.users_client.update_user(
+ user['id'], **update_kwargs)['user']
+ for field in update_kwargs:
+ self.assertEqual(update_kwargs[field], updated_user[field])
+
+ # GET by id after updating
new_user_get = self.users_client.show_user(user['id'])['user']
# Assert response body of GET after updation
- self.assertEqual(u_name2, new_user_get['name'])
- self.assertEqual(u_description2, new_user_get['description'])
- self.assertEqual(project['id'],
- new_user_get['project_id'])
- self.assertEqual(u_email2, new_user_get['email'])
- self.assertEqual(False, new_user_get['enabled'])
+ for field in update_kwargs:
+ self.assertEqual(update_kwargs[field], new_user_get[field])
@decorators.idempotent_id('2d223a0e-e457-4a70-9fb1-febe027a0ff9')
def test_update_user_password(self):
@@ -131,7 +126,7 @@
missing_projects =\
[p for p in assigned_project_ids
if p not in fetched_project_ids]
- self.assertEqual(0, len(missing_projects),
+ self.assertEmpty(missing_projects,
"Failed to find project %s in fetched list" %
', '.join(m_project for m_project
in missing_projects))
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 785485b..30d2a36 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -187,6 +187,7 @@
cls.non_admin_users_client = cls.os_primary.users_v3_client
cls.non_admin_token = cls.os_primary.token_v3_client
cls.non_admin_projects_client = cls.os_primary.projects_client
+ cls.non_admin_catalog_client = cls.os_primary.catalog_client
cls.non_admin_versions_client =\
cls.os_primary.identity_versions_v3_client
@@ -223,8 +224,10 @@
cls.projects_client = cls.os_admin.projects_client
cls.role_assignments = cls.os_admin.role_assignments_client
cls.oauth_consumers_client = cls.os_admin.oauth_consumers_client
+ cls.oauth_token_client = cls.os_admin.oauth_token_client
cls.domain_config_client = cls.os_admin.domain_config_client
cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
+ cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client
if CONF.identity.admin_domain_scope:
# NOTE(andreaf) When keystone policy requires it, the identity
diff --git a/tempest/api/identity/v3/test_catalog.py b/tempest/api/identity/v3/test_catalog.py
new file mode 100755
index 0000000..deec2dc
--- /dev/null
+++ b/tempest/api/identity/v3/test_catalog.py
@@ -0,0 +1,41 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest import config
+from tempest.lib import decorators
+
+
+CONF = config.CONF
+
+
+class IdentityCatalogTest(base.BaseIdentityV3Test):
+
+ @decorators.idempotent_id('56b57ced-22b8-4127-9b8a-565dfb0207e2')
+ def test_catalog_standardization(self):
+ # http://git.openstack.org/cgit/openstack/service-types-authority
+ # /tree/service-types.yaml
+ standard_service_values = [{'name': 'keystone', 'type': 'identity'},
+ {'name': 'nova', 'type': 'compute'},
+ {'name': 'glance', 'type': 'image'},
+ {'name': 'swift', 'type': 'object-store'}]
+ # next, we need to GET the catalog using the catalog client
+ catalog = self.non_admin_catalog_client.show_catalog()['catalog']
+ # get list of the service types present in the catalog
+ catalog_services = []
+ for service in catalog:
+ catalog_services.append(service['type'])
+ for service in standard_service_values:
+ # if service enabled, check if it has a standard typevalue
+ if service['name'] == 'keystone' or\
+ getattr(CONF.service_available, service['name']):
+ self.assertIn(service['type'], catalog_services)
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index c9d7a4d..4c72d82 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -15,12 +15,39 @@
from oslo_utils import timeutils
import six
+
from tempest.api.identity import base
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
class TokensV3Test(base.BaseIdentityV3Test):
+ @decorators.idempotent_id('a9512ac3-3909-48a4-b395-11f438e16260')
+ def test_validate_token(self):
+ creds = self.os_primary.credentials
+ user_id = creds.user_id
+ username = creds.username
+ password = creds.password
+ user_domain_id = creds.user_domain_id
+ # GET and validate token
+ subject_token, token_body = self.non_admin_token.get_token(
+ user_id=user_id,
+ username=username,
+ user_domain_id=user_domain_id,
+ password=password,
+ auth_data=True)
+ authenticated_token = self.non_admin_client.show_token(
+ subject_token)['token']
+ # sanity checking to make sure they are indeed the same token
+ self.assertEqual(authenticated_token, token_body)
+ # test to see if token has been properly authenticated
+ self.assertEqual(authenticated_token['user']['id'], user_id)
+ self.assertEqual(authenticated_token['user']['name'], username)
+ self.non_admin_client.delete_token(subject_token)
+ self.assertRaises(
+ lib_exc.NotFound, self.non_admin_client.show_token, subject_token)
+
@decorators.idempotent_id('6f8e4436-fc96-4282-8122-e41df57197a9')
def test_create_token(self):
@@ -54,15 +81,13 @@
self.assertEqual(subject_id, user_id)
else:
# Expect a user ID, but don't know what it will be.
- self.assertGreaterEqual(len(subject_id), 0,
- 'Expected user ID in token.')
+ self.assertIsNotNone(subject_id, 'Expected user ID in token.')
subject_name = resp['user']['name']
if username:
self.assertEqual(subject_name, username)
else:
# Expect a user name, but don't know what it will be.
- self.assertGreaterEqual(len(subject_name), 0,
- 'Expected user name in token.')
+ self.assertIsNotNone(subject_name, 'Expected user name in token.')
self.assertEqual(resp['methods'][0], 'password')
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 4902316..bf2e510 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -54,6 +54,6 @@
self.alt_tenant_id)
body = self.image_member_client.list_image_members(image_id)
members = body['members']
- self.assertEqual(0, len(members), str(members))
+ self.assertEmpty(members)
self.assertRaises(
lib_exc.NotFound, self.alt_img_cli.show_image, image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index b341ab7..76723f4 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -43,7 +43,8 @@
else:
msg = ("The container format and the disk format don't match. "
"Container format: %(container)s, Disk format: %(disk)s." %
- {'container': container_format, 'disk': disk_format})
+ {'container': container_format, 'disk':
+ CONF.image.disk_formats})
raise exceptions.InvalidConfiguration(msg)
else:
disk_format = CONF.image.disk_formats[0]
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 e2a3617..69bebfe 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -58,7 +58,7 @@
namespace['namespace'])
body = self.namespace_tags_client.list_namespace_tags(
namespace['namespace'])
- self.assertEqual([], body['tags'])
+ self.assertEmpty(body['tags'])
@decorators.idempotent_id('a2a3765e-1a2c-3f6d-a3a7-3cc3466ab875')
def test_create_update_delete_tag(self):
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 6389489..7304db9 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -49,7 +49,7 @@
@decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
def test_list_agents_non_admin(self):
body = self.agents_client.list_agents()
- self.assertEqual(len(body["agents"]), 0)
+ self.assertEmpty(body["agents"])
@decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f')
def test_show_agent(self):
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index c83dd7f..4d41e33 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -10,11 +10,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.network import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
+CONF = config.CONF
+
class ExternalNetworksTestJSON(base.BaseAdminNetworkTest):
@@ -91,6 +96,8 @@
self.assertFalse(show_net['router:external'])
@decorators.idempotent_id('82068503-2cf2-4ed4-b3be-ecb89432e4bb')
+ @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
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 11f520a..7ee819e 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -34,6 +34,8 @@
if not CONF.network.public_network_id:
msg = "The public_network_id option must be specified."
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/network/admin/test_metering_extensions.py b/tempest/api/network/admin/test_metering_extensions.py
index 18dccc3..21a7ab4 100644
--- a/tempest/api/network/admin/test_metering_extensions.py
+++ b/tempest/api/network/admin/test_metering_extensions.py
@@ -45,6 +45,28 @@
remote_ip_prefix, direction,
metering_label_id=cls.metering_label['id'])
+ @classmethod
+ def create_metering_label(cls, name, description):
+ """Wrapper utility that returns a test metering label."""
+ body = cls.admin_metering_labels_client.create_metering_label(
+ description=description,
+ name=name)
+ metering_label = body['metering_label']
+ cls.metering_labels.append(metering_label)
+ return metering_label
+
+ @classmethod
+ def create_metering_label_rule(cls, remote_ip_prefix, direction,
+ metering_label_id):
+ """Wrapper utility that returns a test metering label rule."""
+ client = cls.admin_metering_label_rules_client
+ body = client.create_metering_label_rule(
+ remote_ip_prefix=remote_ip_prefix, direction=direction,
+ metering_label_id=metering_label_id)
+ metering_label_rule = body['metering_label_rule']
+ cls.metering_label_rules.append(metering_label_rule)
+ return metering_label_rule
+
def _delete_metering_label(self, metering_label_id):
# Deletes a label and verifies if it is deleted or not
self.admin_metering_labels_client.delete_metering_label(
@@ -52,7 +74,7 @@
# Asserting that the label is not found in list after deletion
labels = self.admin_metering_labels_client.list_metering_labels(
id=metering_label_id)
- self.assertEqual(len(labels['metering_labels']), 0)
+ self.assertEmpty(labels['metering_labels'])
def _delete_metering_label_rule(self, metering_label_rule_id):
client = self.admin_metering_label_rules_client
@@ -67,7 +89,7 @@
# Verify label filtering
body = self.admin_metering_labels_client.list_metering_labels(id=33)
metering_labels = body['metering_labels']
- self.assertEqual(0, len(metering_labels))
+ self.assertEmpty(metering_labels)
@decorators.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
def test_create_delete_metering_label_with_filters(self):
@@ -104,7 +126,7 @@
# Verify rule filtering
body = client.list_metering_label_rules(id=33)
metering_label_rules = body['metering_label_rules']
- self.assertEqual(0, len(metering_label_rules))
+ self.assertEmpty(metering_label_rules)
@decorators.idempotent_id('f4d547cd-3aee-408f-bf36-454f8825e045')
def test_create_delete_metering_label_rule_with_filters(self):
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
new file mode 100644
index 0000000..f180cda
--- /dev/null
+++ b/tempest/api/network/admin/test_routers.py
@@ -0,0 +1,224 @@
+# Copyright 2013 OpenStack Foundation
+# 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 testtools
+
+from tempest.api.network import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+CONF = config.CONF
+
+
+class RoutersAdminTest(base.BaseAdminNetworkTest):
+ # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+ # as some router operations, such as enabling or disabling SNAT
+ # require admin credentials by default
+
+ def _cleanup_router(self, router):
+ self.delete_router(router)
+ self.routers.remove(router)
+
+ def _create_router(self, name=None, admin_state_up=False,
+ external_network_id=None, enable_snat=None):
+ # associate a cleanup with created routers to avoid quota limits
+ router = self.create_router(name, admin_state_up,
+ external_network_id, enable_snat)
+ self.addCleanup(self._cleanup_router, router)
+ return router
+
+ def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
+ interface = self.routers_client.add_router_interface(
+ router_id, subnet_id=subnet_id)
+ self.addCleanup(self._remove_router_interface_with_subnet_id,
+ router_id, subnet_id)
+ self.assertEqual(subnet_id, interface['subnet_id'])
+ return interface
+
+ def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+ body = self.routers_client.remove_router_interface(router_id,
+ subnet_id=subnet_id)
+ self.assertEqual(subnet_id, body['subnet_id'])
+
+ @classmethod
+ def skip_checks(cls):
+ super(RoutersAdminTest, cls).skip_checks()
+ if not test.is_extension_enabled('router', 'network'):
+ msg = "router extension not enabled."
+ raise cls.skipException(msg)
+
+ @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.
+ project = data_utils.rand_name('test_tenant_')
+ description = data_utils.rand_name('desc_')
+ project = self.identity_utils.create_project(name=project,
+ description=description)
+ project_id = project['id']
+ self.addCleanup(self.identity_utils.delete_project, project_id)
+
+ name = data_utils.rand_name('router-')
+ create_body = self.admin_routers_client.create_router(
+ name=name, tenant_id=project_id)
+ self.addCleanup(self.admin_routers_client.delete_router,
+ create_body['router']['id'])
+ self.assertEqual(project_id, create_body['router']['tenant_id'])
+
+ @decorators.idempotent_id('847257cc-6afd-4154-b8fb-af49f5670ce8')
+ @test.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
+ router = self._create_router(
+ external_network_id=CONF.network.public_network_id)
+ self._verify_router_gateway(
+ router['id'], {'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+
+ @decorators.idempotent_id('ea74068d-09e9-4fd7-8995-9b6a1ace920f')
+ @test.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_snat_explicit(self):
+ name = data_utils.rand_name('snat-router')
+ # Create a router enabling snat attributes
+ enable_snat_states = [False, True]
+ for enable_snat in enable_snat_states:
+ external_gateway_info = {
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': enable_snat}
+ create_body = self.admin_routers_client.create_router(
+ name=name, external_gateway_info=external_gateway_info)
+ self.addCleanup(self.admin_routers_client.delete_router,
+ create_body['router']['id'])
+ # Verify snat attributes after router creation
+ self._verify_router_gateway(create_body['router']['id'],
+ exp_ext_gw_info=external_gateway_info)
+
+ def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
+ show_body = self.admin_routers_client.show_router(router_id)
+ actual_ext_gw_info = show_body['router']['external_gateway_info']
+ if exp_ext_gw_info is None:
+ self.assertIsNone(actual_ext_gw_info)
+ return
+ # Verify only keys passed in exp_ext_gw_info
+ for k, v in exp_ext_gw_info.items():
+ self.assertEqual(v, actual_ext_gw_info[k])
+
+ def _verify_gateway_port(self, router_id):
+ list_body = self.admin_ports_client.list_ports(
+ network_id=CONF.network.public_network_id,
+ device_id=router_id)
+ self.assertEqual(len(list_body['ports']), 1)
+ gw_port = list_body['ports'][0]
+ fixed_ips = gw_port['fixed_ips']
+ self.assertNotEmpty(fixed_ips)
+ # Assert that all of the IPs from the router gateway port
+ # are allocated from a valid public subnet.
+ public_net_body = self.admin_networks_client.show_network(
+ CONF.network.public_network_id)
+ public_subnet_ids = public_net_body['network']['subnets']
+ for fixed_ip in fixed_ips:
+ subnet_id = fixed_ip['subnet_id']
+ self.assertIn(subnet_id, public_subnet_ids)
+
+ @decorators.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79')
+ @testtools.skipUnless(CONF.network.public_network_id,
+ 'The public_network_id option must be specified.')
+ def test_update_router_set_gateway(self):
+ router = self._create_router()
+ self.routers_client.update_router(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id})
+ # Verify operation - router
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('b386c111-3b21-466d-880c-5e72b01e1a33')
+ @test.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_update_router_set_gateway_with_snat_explicit(self):
+ router = self._create_router()
+ self.admin_routers_client.update_router(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('96536bc7-8262-4fb2-9967-5c46940fa279')
+ @test.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_update_router_set_gateway_without_snat(self):
+ router = self._create_router()
+ self.admin_routers_client.update_router(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('ad81b7ee-4f81-407b-a19c-17e623f763e8')
+ @testtools.skipUnless(CONF.network.public_network_id,
+ 'The public_network_id option must be specified.')
+ def test_update_router_unset_gateway(self):
+ router = self._create_router(
+ external_network_id=CONF.network.public_network_id)
+ self.routers_client.update_router(router['id'],
+ external_gateway_info={})
+ self._verify_router_gateway(router['id'])
+ # No gateway port expected
+ list_body = self.admin_ports_client.list_ports(
+ network_id=CONF.network.public_network_id,
+ device_id=router['id'])
+ self.assertFalse(list_body['ports'])
+
+ @decorators.idempotent_id('f2faf994-97f4-410b-a831-9bc977b64374')
+ @test.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_update_router_reset_gateway_without_snat(self):
+ router = self._create_router(
+ external_network_id=CONF.network.public_network_id)
+ self.admin_routers_client.update_router(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
+
+
+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 4ccad30..b6772b1 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -15,16 +15,17 @@
import testtools
-from tempest.api.network import base_routers as base
+from tempest.api.network import base
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest import test
-class RoutersTestDVR(base.BaseRouterTest):
+class RoutersTestDVR(base.BaseAdminNetworkTest):
@classmethod
- def resource_setup(cls):
+ def skip_checks(cls):
+ super(RoutersTestDVR, cls).skip_checks()
for ext in ['router', 'dvr']:
if not test.is_extension_enabled(ext, 'network'):
msg = "%s extension not enabled." % ext
@@ -35,6 +36,9 @@
# admin credentials to create router with distributed=True attribute
# and checking for BadRequest exception and that the resulting router
# has a distributed attribute.
+
+ @classmethod
+ def resource_setup(cls):
super(RoutersTestDVR, cls).resource_setup()
name = data_utils.rand_name('pretest-check')
router = cls.admin_routers_client.create_router(name=name)
diff --git a/tempest/api/network/admin/test_routers_negative.py b/tempest/api/network/admin/test_routers_negative.py
new file mode 100644
index 0000000..f350a15
--- /dev/null
+++ b/tempest/api/network/admin/test_routers_negative.py
@@ -0,0 +1,63 @@
+# Copyright 2013 OpenStack Foundation
+# 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 testtools
+
+from tempest.api.network import base
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+CONF = config.CONF
+
+
+class RoutersAdminNegativeTest(base.BaseAdminNetworkTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(RoutersAdminNegativeTest, cls).skip_checks()
+ if not test.is_extension_enabled('router', 'network'):
+ msg = "router extension not enabled."
+ raise cls.skipException(msg)
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('7101cc02-058a-11e7-93e1-fa163e4fa634')
+ @test.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_router_set_gateway_used_ip_returns_409(self):
+ # At first create a address from public_network_id
+ port = self.admin_ports_client.create_port(
+ network_id=CONF.network.public_network_id)['port']
+ self.addCleanup(self.admin_ports_client.delete_port,
+ port_id=port['id'])
+ # Add used ip and subnet_id in external_fixed_ips
+ 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 used ip
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_routers_client.create_router,
+ external_gateway_info=external_gateway_info)
+
+
+class RoutersAdminNegativeIpV6Test(RoutersAdminNegativeTest):
+ _ip_version = 6
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 359a444..6bec0d7 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -83,6 +83,7 @@
cls.os_primary.security_group_rules_client)
cls.network_versions_client = cls.os_primary.network_versions_client
cls.service_providers_client = cls.os_primary.service_providers_client
+ cls.tags_client = cls.os_primary.tags_client
@classmethod
def resource_setup(cls):
@@ -269,25 +270,3 @@
cls.admin_metering_labels_client = cls.os_admin.metering_labels_client
cls.admin_metering_label_rules_client = (
cls.os_admin.metering_label_rules_client)
-
- @classmethod
- def create_metering_label(cls, name, description):
- """Wrapper utility that returns a test metering label."""
- body = cls.admin_metering_labels_client.create_metering_label(
- description=description,
- name=name)
- metering_label = body['metering_label']
- cls.metering_labels.append(metering_label)
- return metering_label
-
- @classmethod
- def create_metering_label_rule(cls, remote_ip_prefix, direction,
- metering_label_id):
- """Wrapper utility that returns a test metering label rule."""
- client = cls.admin_metering_label_rules_client
- body = client.create_metering_label_rule(
- remote_ip_prefix=remote_ip_prefix, direction=direction,
- metering_label_id=metering_label_id)
- metering_label_rule = body['metering_label_rule']
- cls.metering_label_rules.append(metering_label_rule)
- return metering_label_rule
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
deleted file mode 100644
index 6d4e756..0000000
--- a/tempest/api/network/base_routers.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.network import base
-
-
-class BaseRouterTest(base.BaseAdminNetworkTest):
- # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
- # as some router operations, such as enabling or disabling SNAT
- # require admin credentials by default
-
- def _cleanup_router(self, router):
- self.delete_router(router)
- self.routers.remove(router)
-
- def _create_router(self, name=None, admin_state_up=False,
- external_network_id=None, enable_snat=None):
- # associate a cleanup with created routers to avoid quota limits
- router = self.create_router(name, admin_state_up,
- external_network_id, enable_snat)
- self.addCleanup(self._cleanup_router, router)
- return router
-
- def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
- interface = self.routers_client.add_router_interface(
- router_id, subnet_id=subnet_id)
- self.addCleanup(self._remove_router_interface_with_subnet_id,
- router_id, subnet_id)
- self.assertEqual(subnet_id, interface['subnet_id'])
- return interface
-
- def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
- body = self.routers_client.remove_router_interface(router_id,
- subnet_id=subnet_id)
- self.assertEqual(subnet_id, body['subnet_id'])
-
- def _remove_router_interface_with_port_id(self, router_id, port_id):
- body = self.routers_client.remove_router_interface(router_id,
- port_id=port_id)
- self.assertEqual(port_id, body['port_id'])
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index f0f92ac..c799b15 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -49,6 +49,8 @@
if not CONF.network.public_network_id:
msg = "The public_network_id option must be specified."
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/network/test_floating_ips_negative.py b/tempest/api/network/test_floating_ips_negative.py
index f5830ab..5ca17fe 100644
--- a/tempest/api/network/test_floating_ips_negative.py
+++ b/tempest/api/network/test_floating_ips_negative.py
@@ -40,6 +40,8 @@
if not CONF.network.public_network_id:
msg = "The public_network_id option must be specified."
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 97ad4d4..128544b 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -16,7 +16,7 @@
import netaddr
import testtools
-from tempest.api.network import base_routers as base
+from tempest.api.network import base
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -25,7 +25,32 @@
CONF = config.CONF
-class RoutersTest(base.BaseRouterTest):
+class RoutersTest(base.BaseNetworkTest):
+
+ def _cleanup_router(self, router):
+ self.delete_router(router)
+ self.routers.remove(router)
+
+ def _create_router(self, name=None, admin_state_up=False,
+ external_network_id=None, enable_snat=None):
+ # associate a cleanup with created routers to avoid quota limits
+ router = self.create_router(name, admin_state_up,
+ external_network_id, enable_snat)
+ self.addCleanup(self._cleanup_router, router)
+ return router
+
+ def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
+ interface = self.routers_client.add_router_interface(
+ router_id, subnet_id=subnet_id)
+ self.addCleanup(self._remove_router_interface_with_subnet_id,
+ router_id, subnet_id)
+ self.assertEqual(subnet_id, interface['subnet_id'])
+ return interface
+
+ def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+ body = self.routers_client.remove_router_interface(router_id,
+ subnet_id=subnet_id)
+ self.assertEqual(subnet_id, body['subnet_id'])
@classmethod
def skip_checks(cls):
@@ -73,55 +98,6 @@
router['id'])['router']
self.assertEqual(router_show['name'], updated_name)
- @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.
- project = data_utils.rand_name('test_tenant_')
- description = data_utils.rand_name('desc_')
- project = self.identity_utils.create_project(name=project,
- description=description)
- project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
-
- name = data_utils.rand_name('router-')
- create_body = self.admin_routers_client.create_router(
- name=name, tenant_id=project_id)
- self.addCleanup(self.admin_routers_client.delete_router,
- create_body['router']['id'])
- self.assertEqual(project_id, create_body['router']['tenant_id'])
-
- @decorators.idempotent_id('847257cc-6afd-4154-b8fb-af49f5670ce8')
- @test.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
- router = self._create_router(
- external_network_id=CONF.network.public_network_id)
- self._verify_router_gateway(
- router['id'], {'network_id': CONF.network.public_network_id,
- 'enable_snat': True})
-
- @decorators.idempotent_id('ea74068d-09e9-4fd7-8995-9b6a1ace920f')
- @test.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_snat_explicit(self):
- name = data_utils.rand_name('snat-router')
- # Create a router enabling snat attributes
- enable_snat_states = [False, True]
- for enable_snat in enable_snat_states:
- external_gateway_info = {
- 'network_id': CONF.network.public_network_id,
- 'enable_snat': enable_snat}
- create_body = self.admin_routers_client.create_router(
- name=name, external_gateway_info=external_gateway_info)
- self.addCleanup(self.admin_routers_client.delete_router,
- create_body['router']['id'])
- # Verify snat attributes after router creation
- self._verify_router_gateway(create_body['router']['id'],
- exp_ext_gw_info=external_gateway_info)
-
@decorators.attr(type='smoke')
@decorators.idempotent_id('b42e6e39-2e37-49cc-a6f4-8467e940900a')
def test_add_remove_router_interface_with_subnet_id(self):
@@ -153,8 +129,8 @@
interface = self.routers_client.add_router_interface(
router['id'],
port_id=port_body['port']['id'])
- self.addCleanup(self._remove_router_interface_with_port_id,
- router['id'], port_body['port']['id'])
+ self.addCleanup(self.routers_client.remove_router_interface,
+ router['id'], port_id=port_body['port']['id'])
self.assertIn('subnet_id', interface.keys())
self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
@@ -180,7 +156,7 @@
self.assertEqual(len(list_body['ports']), 1)
gw_port = list_body['ports'][0]
fixed_ips = gw_port['fixed_ips']
- self.assertGreaterEqual(len(fixed_ips), 1)
+ self.assertNotEmpty(fixed_ips)
# Assert that all of the IPs from the router gateway port
# are allocated from a valid public subnet.
public_net_body = self.admin_networks_client.show_network(
@@ -190,55 +166,6 @@
subnet_id = fixed_ip['subnet_id']
self.assertIn(subnet_id, public_subnet_ids)
- @decorators.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79')
- @testtools.skipUnless(CONF.network.public_network_id,
- 'The public_network_id option must be specified.')
- def test_update_router_set_gateway(self):
- router = self._create_router()
- self.routers_client.update_router(
- router['id'],
- external_gateway_info={
- 'network_id': CONF.network.public_network_id})
- # Verify operation - router
- self._verify_router_gateway(
- router['id'],
- {'network_id': CONF.network.public_network_id})
- self._verify_gateway_port(router['id'])
-
- @decorators.idempotent_id('b386c111-3b21-466d-880c-5e72b01e1a33')
- @test.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_update_router_set_gateway_with_snat_explicit(self):
- router = self._create_router()
- self.admin_routers_client.update_router(
- router['id'],
- external_gateway_info={
- 'network_id': CONF.network.public_network_id,
- 'enable_snat': True})
- self._verify_router_gateway(
- router['id'],
- {'network_id': CONF.network.public_network_id,
- 'enable_snat': True})
- self._verify_gateway_port(router['id'])
-
- @decorators.idempotent_id('96536bc7-8262-4fb2-9967-5c46940fa279')
- @test.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_update_router_set_gateway_without_snat(self):
- router = self._create_router()
- self.admin_routers_client.update_router(
- router['id'],
- external_gateway_info={
- 'network_id': CONF.network.public_network_id,
- 'enable_snat': False})
- self._verify_router_gateway(
- router['id'],
- {'network_id': CONF.network.public_network_id,
- 'enable_snat': False})
- self._verify_gateway_port(router['id'])
-
@decorators.idempotent_id('cbe42f84-04c2-11e7-8adb-fa163e4fa634')
@test.requires_ext(extension='ext-gw-mode', service='network')
@testtools.skipUnless(CONF.network.public_network_id,
@@ -270,39 +197,6 @@
'external_fixed_ips'][0]['ip_address'],
fixed_ip['ip_address'])
- @decorators.idempotent_id('ad81b7ee-4f81-407b-a19c-17e623f763e8')
- @testtools.skipUnless(CONF.network.public_network_id,
- 'The public_network_id option must be specified.')
- def test_update_router_unset_gateway(self):
- router = self._create_router(
- external_network_id=CONF.network.public_network_id)
- self.routers_client.update_router(router['id'],
- external_gateway_info={})
- self._verify_router_gateway(router['id'])
- # No gateway port expected
- list_body = self.admin_ports_client.list_ports(
- network_id=CONF.network.public_network_id,
- device_id=router['id'])
- self.assertFalse(list_body['ports'])
-
- @decorators.idempotent_id('f2faf994-97f4-410b-a831-9bc977b64374')
- @test.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_update_router_reset_gateway_without_snat(self):
- router = self._create_router(
- external_network_id=CONF.network.public_network_id)
- self.admin_routers_client.update_router(
- router['id'],
- external_gateway_info={
- 'network_id': CONF.network.public_network_id,
- 'enable_snat': False})
- self._verify_router_gateway(
- router['id'],
- {'network_id': CONF.network.public_network_id,
- 'enable_snat': False})
- self._verify_gateway_port(router['id'])
-
@decorators.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c')
@test.requires_ext(extension='extraroute', service='network')
def test_update_delete_extra_route(self):
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index fdd8dd8..db165ab 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -14,9 +14,8 @@
# under the License.
import netaddr
-import testtools
-from tempest.api.network import base_routers as base
+from tempest.api.network import base
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -26,7 +25,7 @@
CONF = config.CONF
-class RoutersNegativeTest(base.BaseRouterTest):
+class RoutersNegativeTest(base.BaseNetworkTest):
@classmethod
def skip_checks(cls):
@@ -75,37 +74,15 @@
network_name=data_utils.rand_name('router-network02-'))
subnet01 = self.create_subnet(network01)
subnet02 = self.create_subnet(network02)
- self._add_router_interface_with_subnet_id(self.router['id'],
- subnet01['id'])
+ interface = self.routers_client.add_router_interface(
+ self.router['id'], subnet_id=subnet01['id'])
+ self.addCleanup(self.routers_client.remove_router_interface,
+ self.router['id'], subnet_id=subnet01['id'])
+ self.assertEqual(subnet01['id'], interface['subnet_id'])
self.assertRaises(lib_exc.BadRequest,
- self._add_router_interface_with_subnet_id,
+ self.routers_client.add_router_interface,
self.router['id'],
- subnet02['id'])
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('7101cc02-058a-11e7-93e1-fa163e4fa634')
- @test.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_router_set_gateway_used_ip_returns_409(self):
- # At first create a address from public_network_id
- port = self.admin_ports_client.create_port(
- network_id=CONF.network.public_network_id)['port']
- self.addCleanup(self.admin_ports_client.delete_port,
- port_id=port['id'])
- # Add used ip and subnet_id in external_fixed_ips
- 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 used ip
- self.assertRaises(lib_exc.Conflict,
- self.admin_routers_client.create_router,
- external_gateway_info=external_gateway_info)
+ subnet_id=subnet02['id'])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20')
@@ -142,7 +119,7 @@
_ip_version = 6
-class DvrRoutersNegativeTest(base.BaseRouterTest):
+class DvrRoutersNegativeTest(base.BaseNetworkTest):
@classmethod
def skip_checks(cls):
@@ -151,13 +128,6 @@
msg = "DVR extension not enabled."
raise cls.skipException(msg)
- @classmethod
- def resource_setup(cls):
- super(DvrRoutersNegativeTest, cls).resource_setup()
- cls.router = cls.create_router()
- cls.network = cls.create_network()
- cls.subnet = cls.create_subnet(cls.network)
-
@decorators.attr(type=['negative'])
@decorators.idempotent_id('4990b055-8fc7-48ab-bba7-aa28beaad0b9')
def test_router_create_tenant_distributed_returns_forbidden(self):
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 0d5e230..a121864 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -83,7 +83,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('bfd128e5-3c92-44b6-9d66-7fe29d22c802')
def test_create_list_update_show_delete_security_group(self):
- group_create_body, name = self._create_security_group()
+ group_create_body, _ = self._create_security_group()
# List security groups and verify if created group is there in response
list_body = self.security_groups_client.list_security_groups()
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
new file mode 100644
index 0000000..567a462
--- /dev/null
+++ b/tempest/api/network/test_tags.py
@@ -0,0 +1,202 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+CONF = config.CONF
+
+
+class TagsTest(base.BaseNetworkTest):
+ """Tests the following operations in the tags API:
+
+ Update all tags.
+ Delete all tags.
+ Check tag existence.
+ Create a tag.
+ List tags.
+ Remove a tag.
+
+ v2.0 of the Neutron API is assumed. The tag extension allows users to set
+ tags on their networks. The extension supports networks only.
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(TagsTest, cls).skip_checks()
+ if not test.is_extension_enabled('tag', 'network'):
+ msg = "tag extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagsTest, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ @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.
+ 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',
+ self.network['id'])
+ self.tags_client.check_tag_existence('networks', self.network['id'],
+ tag_name)
+
+ # Validate that listing tags on a network resource works.
+ retrieved_tags = self.tags_client.list_tags(
+ 'networks', self.network['id'])['tags']
+ self.assertEqual([tag_name], retrieved_tags)
+
+ # Generate 3 new tag names.
+ replace_tags = [data_utils.rand_name(
+ self.__class__.__name__ + '-Tag') for _ in range(3)]
+
+ # Replace the current tag with the 3 new tags and validate that the
+ # network resource has the 3 new tags.
+ updated_tags = self.tags_client.update_all_tags(
+ 'networks', self.network['id'], replace_tags)['tags']
+ self.assertEqual(3, len(updated_tags))
+ self.assertEqual(set(replace_tags), set(updated_tags))
+
+ # Delete the first tag and check that it has been removed.
+ self.tags_client.delete_tag(
+ 'networks', self.network['id'], replace_tags[0])
+ self.assertRaises(lib_exc.NotFound,
+ self.tags_client.check_tag_existence, 'networks',
+ self.network['id'], replace_tags[0])
+ for i in range(1, 3):
+ self.tags_client.check_tag_existence(
+ 'networks', self.network['id'], replace_tags[i])
+
+ # Delete all the remaining tags and check that they have been removed.
+ self.tags_client.delete_all_tags('networks', self.network['id'])
+ for i in range(1, 3):
+ self.assertRaises(lib_exc.NotFound,
+ self.tags_client.check_tag_existence, 'networks',
+ self.network['id'], replace_tags[i])
+
+
+class TagsExtTest(base.BaseNetworkTest):
+ """Tests the following operations in the tags API:
+
+ Update all tags.
+ Delete all tags.
+ Check tag existence.
+ Create a tag.
+ List tags.
+ Remove a tag.
+
+ v2.0 of the Neutron API is assumed. The tag-ext extension allows users to
+ set tags on the following resources: subnets, ports, routers and
+ subnetpools.
+ """
+
+ # NOTE(felipemonteiro): The supported resource names are plural. Use
+ # the singular case for the corresponding class resource object.
+ SUPPORTED_RESOURCES = ['subnets', 'ports', 'routers', 'subnetpools']
+
+ @classmethod
+ def skip_checks(cls):
+ super(TagsExtTest, cls).skip_checks()
+ if not test.is_extension_enabled('tag-ext', 'network'):
+ msg = "tag-ext extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagsExtTest, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.port = cls.create_port(cls.network)
+ cls.router = cls.create_router()
+
+ subnetpool_name = data_utils.rand_name(cls.__name__ + '-Subnetpool')
+ prefix = CONF.network.default_network
+ cls.subnetpool = cls.subnetpools_client.create_subnetpool(
+ name=subnetpool_name, prefixes=prefix)['subnetpool']
+
+ @classmethod
+ def resource_cleanup(cls):
+ cls.subnetpools_client.delete_subnetpool(cls.subnetpool['id'])
+ super(TagsExtTest, cls).resource_cleanup()
+
+ def _create_tags_for_each_resource(self):
+ # Create a tag for each resource in `SUPPORTED_RESOURCES` and return
+ # the list of tag names.
+ tag_names = []
+
+ for resource in self.SUPPORTED_RESOURCES:
+ tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
+ tag_names.append(tag_name)
+ resource_object = getattr(self, resource[:-1])
+
+ self.tags_client.create_tag(resource, resource_object['id'],
+ tag_name)
+ self.addCleanup(self.tags_client.delete_all_tags, resource,
+ resource_object['id'])
+
+ return tag_names
+
+ @decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9')
+ def test_create_check_list_and_delete_tags(self):
+ tag_names = self._create_tags_for_each_resource()
+
+ for i, resource in enumerate(self.SUPPORTED_RESOURCES):
+ # Ensure that a tag was created for each resource.
+ resource_object = getattr(self, resource[:-1])
+ retrieved_tags = self.tags_client.list_tags(
+ resource, resource_object['id'])['tags']
+ self.assertEqual(1, len(retrieved_tags))
+ self.assertEqual(tag_names[i], retrieved_tags[0])
+
+ # Check that the expected tag exists for each resource.
+ self.tags_client.check_tag_existence(
+ resource, resource_object['id'], tag_names[i])
+
+ # Delete the tag and ensure it was deleted.
+ self.tags_client.delete_tag(
+ resource, resource_object['id'], tag_names[i])
+ retrieved_tags = self.tags_client.list_tags(
+ resource, resource_object['id'])['tags']
+ self.assertEmpty(retrieved_tags)
+
+ @decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791')
+ def test_update_and_delete_all_tags(self):
+ self._create_tags_for_each_resource()
+
+ for resource in self.SUPPORTED_RESOURCES:
+ # Generate 3 new tag names.
+ replace_tags = [data_utils.rand_name(
+ self.__class__.__name__ + '-Tag') for _ in range(3)]
+
+ # Replace the current tag with the 3 new tags and validate that the
+ # current resource has the 3 new tags.
+ resource_object = getattr(self, resource[:-1])
+ updated_tags = self.tags_client.update_all_tags(
+ resource, resource_object['id'], replace_tags)['tags']
+ self.assertEqual(3, len(updated_tags))
+ self.assertEqual(set(replace_tags), set(updated_tags))
+
+ # Delete all the tags and check that they have been removed.
+ self.tags_client.delete_all_tags(resource, resource_object['id'])
+ for i in range(3):
+ self.assertRaises(
+ lib_exc.NotFound, self.tags_client.check_tag_existence,
+ resource, resource_object['id'], replace_tags[i])
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 2bac8d3..11273e4 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -43,8 +43,7 @@
for cont in containers:
try:
params = {'limit': 9999, 'format': 'json'}
- resp, objlist = container_client.list_container_contents(
- cont, params)
+ _, objlist = container_client.list_container_contents(cont, params)
# delete every object in the container
for obj in objlist:
test_utils.call_and_ignore_notfound_exc(
@@ -99,7 +98,7 @@
cls.policies = None
if CONF.object_storage_feature_enabled.discoverability:
- _, body = cls.capabilities_client.list_capabilities()
+ body = cls.capabilities_client.list_capabilities()
if 'swift' in body and 'policies' in body['swift']:
cls.policies = body['swift']['policies']
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 0a72d75..7c538e8 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -58,9 +58,9 @@
# upload an archived file
with open(filepath) as fh:
mydata = fh.read()
- resp, body = self.bulk_client.upload_archive(
+ resp = self.bulk_client.upload_archive(
upload_path='', data=mydata, archive_file_format='tar')
- return resp, body
+ return resp
def _check_contents_deleted(self, container_name):
param = {'format': 'txt'}
@@ -73,21 +73,20 @@
def test_extract_archive(self):
# Test bulk operation of file upload with an archived file
filepath, container_name, object_name = self._create_archive()
- resp, _ = self._upload_archive(filepath)
-
+ 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)
- self.assertIn('content-type', resp)
- self.assertIn('x-trans-id', resp)
- self.assertIn('date', resp)
+ 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, custom_matchers.AreAllWellFormatted())
+ self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
param = {'format': 'json'}
resp, body = self.account_client.list_account_containers(param)
@@ -112,19 +111,19 @@
self._upload_archive(filepath)
data = '%s/%s\n%s' % (container_name, object_name, container_name)
- resp, body = self.bulk_client.delete_bulk_data(data=data)
+ 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)
- self.assertIn('content-type', resp)
- self.assertIn('x-trans-id', resp)
- self.assertIn('date', resp)
+ 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, custom_matchers.AreAllWellFormatted())
+ self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
# Check if uploaded contents are completely deleted
self._check_contents_deleted(container_name)
@@ -138,19 +137,19 @@
data = '%s/%s\n%s' % (container_name, object_name, container_name)
- resp, body = self.bulk_client.delete_bulk_data_with_post(data=data)
+ 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)
- self.assertIn('content-type', resp)
- self.assertIn('x-trans-id', resp)
- self.assertIn('date', resp)
+ 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, custom_matchers.AreAllWellFormatted())
+ self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
# Check if uploaded contents are completely deleted
self._check_contents_deleted(container_name)
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index 55a6c7a..60233b4 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -35,7 +35,7 @@
@classmethod
def resource_setup(cls):
super(AccountQuotasNegativeTest, cls).resource_setup()
- cls.container_name = cls.create_container()
+ cls.create_container()
# Retrieve a ResellerAdmin auth data and use it to set a quota
# on the client's account
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index e54b6e7..2fb676f 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -101,7 +101,7 @@
# Check only the format of common headers with custom matcher
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
- self.assertEqual(len(container_list), 0)
+ self.assertEmpty(container_list)
@decorators.idempotent_id('1c7efa35-e8a2-4b0b-b5ff-862c7fd83704')
def test_list_containers_with_format_json(self):
@@ -135,7 +135,7 @@
not CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_list_extensions(self):
- resp, extensions = self.capabilities_client.list_capabilities()
+ resp = self.capabilities_client.list_capabilities()
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
@@ -162,7 +162,7 @@
self.account_client.list_account_containers(params=params)
self.assertHeaders(resp, 'Account', 'GET')
- self.assertEqual(len(container_list), 0)
+ self.assertEmpty(container_list)
params = {'marker': self.containers[self.containers_count // 2]}
resp, container_list = \
@@ -182,7 +182,7 @@
resp, container_list = \
self.account_client.list_account_containers(params=params)
self.assertHeaders(resp, 'Account', 'GET')
- self.assertEqual(len(container_list), 0)
+ self.assertEmpty(container_list)
params = {'end_marker': self.containers[self.containers_count // 2]}
resp, container_list = \
@@ -297,7 +297,7 @@
create_update_metadata=metadata)
self.assertHeaders(resp, 'Account', 'POST')
- resp, body = self.account_client.list_account_metadata()
+ resp, _ = self.account_client.list_account_metadata()
self.assertIn('x-account-meta-test-account-meta1', resp)
self.assertEqual(resp['x-account-meta-test-account-meta1'],
metadata['test-account-meta1'])
@@ -352,7 +352,7 @@
self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata_1)
metadata_2 = {'test-account-meta2': 'Meta2'}
- resp, body = (
+ resp, _ = (
self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata_2,
delete_metadata=metadata_1))
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index d4e5ec5..4b66ebf 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -41,10 +41,10 @@
tenant_name = self.os_roles_operator_alt.credentials.tenant_name
username = self.os_roles_operator_alt.credentials.username
cont_headers = {'X-Container-Read': tenant_name + ':' + username}
- resp_meta, body = self.os_roles_operator.container_client.\
- update_container_metadata(
+ resp_meta, _ = (
+ self.os_roles_operator.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
@@ -68,10 +68,10 @@
tenant_name = self.os_roles_operator_alt.credentials.tenant_name
username = self.os_roles_operator_alt.credentials.username
cont_headers = {'X-Container-Write': tenant_name + ':' + username}
- resp_meta, body = self.os_roles_operator.container_client.\
- update_container_metadata(self.container_name,
- metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, _ = (
+ self.os_roles_operator.container_client.update_container_metadata(
+ self.container_name, metadata=cont_headers,
+ metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# set alternative authentication data; cannot simply use the
# other object client.
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 9c9d821..655626c 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -65,8 +65,8 @@
def test_delete_object_without_using_creds(self):
# create object
object_name = data_utils.rand_name(name='Object')
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, 'data')
+ self.object_client.create_object(self.container_name, object_name,
+ 'data')
# trying to delete object with empty headers
# X-Auth-Token is not provided
self.object_client.auth_provider.set_alt_auth_data(
@@ -134,7 +134,7 @@
# attempt to read object using non-authorized user
# update X-Container-Read metadata ACL
cont_headers = {'X-Container-Read': 'badtenant:baduser'}
- resp_meta, body = self.container_client.update_container_metadata(
+ resp_meta, _ = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -158,7 +158,7 @@
# attempt to write object using non-authorized user
# update X-Container-Write metadata ACL
cont_headers = {'X-Container-Write': 'badtenant:baduser'}
- resp_meta, body = self.container_client.update_container_metadata(
+ resp_meta, _ = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -183,7 +183,7 @@
cont_headers = {'X-Container-Read':
tenant_name + ':' + username,
'X-Container-Write': ''}
- resp_meta, body = self.container_client.update_container_metadata(
+ resp_meta, _ = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -208,7 +208,7 @@
cont_headers = {'X-Container-Read':
tenant_name + ':' + username,
'X-Container-Write': ''}
- resp_meta, body = self.container_client.update_container_metadata(
+ resp_meta, _ = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertHeaders(resp_meta, 'Container', 'POST')
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 2b5692d..76fe8d4 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -27,7 +27,7 @@
@decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
def test_create_container(self):
container_name = data_utils.rand_name(name='TestContainer')
- resp, body = self.container_client.create_container(container_name)
+ resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
self.assertHeaders(resp, 'Container', 'PUT')
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index a8d70c5..387b7b6 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -32,7 +32,7 @@
if CONF.object_storage_feature_enabled.discoverability:
# use /info to get default constraints
- _, body = cls.capabilities_client.list_capabilities()
+ body = cls.capabilities_client.list_capabilities()
cls.constraints = body['swift']
@decorators.attr(type=["negative"])
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 9e01c26..943011d 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -27,7 +27,7 @@
super(StaticWebTest, cls).resource_setup()
# This header should be posted on the container before every test
- cls.headers_public_read_acl = {'Read': '.r:*,.rlistings'}
+ headers_public_read_acl = {'Read': '.r:*,.rlistings'}
# Create test container and create one object in it
cls.container_name = cls.create_container()
@@ -36,7 +36,7 @@
cls.container_client.update_container_metadata(
cls.container_name,
- metadata=cls.headers_public_read_acl,
+ metadata=headers_public_read_acl,
metadata_prefix="X-Container-")
@classmethod
@@ -124,9 +124,8 @@
# test GET on http://account_url/container_name
# we should retrieve a listing of objects
- resp, body = self.account_client.request("GET",
- self.container_name,
- headers={})
+ _, body = self.account_client.request("GET", self.container_name,
+ headers={})
self.assertIn(self.object_name, body.decode())
css = '<link rel="stylesheet" type="text/css" href="listings.css" />'
self.assertIn(css, body.decode())
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 3c11a51..4cb1914 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -90,8 +90,7 @@
cont_client = [self.clients[c][0] for c in cont]
obj_client = [self.clients[c][1] for c in cont]
headers = make_headers(cont[1], cont_client[1])
- resp, body = \
- cont_client[0].put(str(cont[0]), body=None, headers=headers)
+ cont_client[0].put(str(cont[0]), body=None, headers=headers)
# create object in container
object_name = data_utils.rand_name(name='TestSyncObject')
data = object_name[::-1].encode() # Raw data, we need bytes
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 7768d23..ed1be90 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -54,8 +54,8 @@
# actually expire, so figure out how many secs in the future that is.
sleepy_time = int(resp['x-delete-at']) - int(time.time())
sleepy_time = sleepy_time if sleepy_time > 0 else 0
- resp, body = self.object_client.get_object(self.container_name,
- self.object_name)
+ resp, _ = self.object_client.get_object(self.container_name,
+ self.object_name)
self.assertHeaders(resp, 'Object', 'GET')
self.assertIn('x-delete-at', resp)
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 21ea6ae..556ca2f 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -48,7 +48,7 @@
data_segments = [data + str(i) for i in range(segments)]
# uploading segments
for i in range(segments):
- resp, _ = self.object_client.create_object_segments(
+ self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
return object_name, data_segments
@@ -184,7 +184,7 @@
# create object with transfer_encoding
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes(1024)
- status, _, resp_headers = self.object_client.put_object_with_chunk(
+ _, _, resp_headers = self.object_client.put_object_with_chunk(
container=self.container_name,
name=object_name,
contents=data_utils.chunkify(data, 512)
@@ -394,7 +394,7 @@
# update object metadata with x_object_manifest
# uploading segments
- object_name, data_segments = self._upload_segments()
+ object_name, _ = self._upload_segments()
# creating a manifest file
data_empty = ''
self.object_client.create_object(self.container_name,
@@ -414,7 +414,7 @@
self.container_name,
object_name)
self.assertIn('x-object-manifest', resp)
- self.assertNotEqual(len(resp['x-object-manifest']), 0)
+ self.assertNotEmpty(resp['x-object-manifest'])
@decorators.idempotent_id('0dbbe89c-6811-4d84-a2df-eca2bdd40c0e')
def test_update_object_metadata_with_x_object_metakey(self):
@@ -494,7 +494,7 @@
# get object metadata with x_object_manifest
# uploading segments
- object_name, data_segments = self._upload_segments()
+ object_name, _ = self._upload_segments()
# creating a manifest file
object_prefix = '%s/%s' % (self.container_name, object_name)
metadata = {'X-Object-Manifest': object_prefix}
@@ -520,11 +520,11 @@
self.assertTrue(resp['etag'].startswith('\"'))
self.assertTrue(resp['etag'].endswith('\"'))
self.assertTrue(resp['etag'].strip('\"').isalnum())
- self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
- self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEmpty(resp['content-type'])
self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*",
resp['x-trans-id']))
- self.assertNotEqual(len(resp['date']), 0)
+ self.assertNotEmpty(resp['date'])
self.assertEqual(resp['accept-ranges'], 'bytes')
self.assertEqual(resp['x-object-manifest'],
'%s/%s' % (self.container_name, object_name))
@@ -612,11 +612,11 @@
self.assertTrue(resp['etag'].startswith('\"'))
self.assertTrue(resp['etag'].endswith('\"'))
self.assertTrue(resp['etag'].strip('\"').isalnum())
- self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
- self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEmpty(resp['content-type'])
self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*",
resp['x-trans-id']))
- self.assertNotEqual(len(resp['date']), 0)
+ self.assertNotEmpty(resp['date'])
self.assertEqual(resp['accept-ranges'], 'bytes')
self.assertEqual(resp['x-object-manifest'],
'%s/%s' % (self.container_name, object_name))
@@ -955,7 +955,7 @@
local_data = "something different"
md5 = hashlib.md5(local_data.encode()).hexdigest()
headers = {'If-None-Match': md5}
- resp, body = self.object_client.get(url, headers=headers)
+ resp, _ = self.object_client.get(url, headers=headers)
self.assertHeaders(resp, 'Object', 'GET')
@@ -973,7 +973,7 @@
@classmethod
def setup_clients(cls):
super(PublicObjectTest, cls).setup_clients()
- cls.identity_client_alt = cls.os_alt.identity_client
+ cls.object_client_alt = cls.os_alt.object_client
def setUp(self):
super(PublicObjectTest, self).setUp()
@@ -1047,7 +1047,7 @@
self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
# get auth token of alternative user
- alt_auth_data = self.identity_client_alt.auth_provider.auth_data
+ alt_auth_data = self.object_client_alt.auth_provider.auth_data
self.object_client.auth_provider.set_alt_auth_data(
request_part='headers',
auth_data=alt_auth_data
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 085ad13..894e42d 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -127,7 +127,7 @@
# list static large object metadata using multipart manifest
object_name = self._create_large_object()
- resp, body = self.object_client.list_object_metadata(
+ resp, _ = self.object_client.list_object_metadata(
self.container_name,
object_name)
@@ -155,7 +155,7 @@
object_name = self._create_large_object()
params_del = {'multipart-manifest': 'delete'}
- resp, body = self.object_client.delete_object(
+ resp, _ = self.object_client.delete_object(
self.container_name,
object_name,
params=params_del)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 4b506f8..91bc677 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -128,7 +128,7 @@
url = self._get_temp_url(self.container_name,
self.object_name, "GET",
expires, key2)
- resp, body = self.object_client.get(url)
+ _, body = self.object_client.get(url)
self.assertEqual(body, self.content)
@decorators.idempotent_id('9b08dade-3571-4152-8a4f-a4f2a873a735')
@@ -168,7 +168,7 @@
expires, self.key)
# Testing a HEAD on this Temp URL
- resp, body = self.object_client.head(url)
+ resp, _ = self.object_client.head(url)
self.assertHeaders(resp, 'Object', 'HEAD')
@decorators.idempotent_id('9d9cfd90-708b-465d-802c-e4a8090b823d')
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index f4d63fd..c7d1fd5 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -46,7 +46,7 @@
@classmethod
def resource_cleanup(cls):
- resp, _ = cls.account_client.create_update_or_delete_account_metadata(
+ cls.account_client.create_update_or_delete_account_metadata(
delete_metadata=cls.metadata)
cls.delete_containers()
@@ -65,10 +65,10 @@
# create object
self.object_name = data_utils.rand_name(name='ObjectTemp')
- self.content = data_utils.arbitrary_string(size=len(self.object_name),
- base_text=self.object_name)
+ content = data_utils.arbitrary_string(size=len(self.object_name),
+ base_text=self.object_name)
self.object_client.create_object(self.container_name,
- self.object_name, self.content)
+ self.object_name, content)
def _get_expiry_date(self, expiration_time=1000):
return int(time.time() + expiration_time)
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
deleted file mode 100644
index d9d8017..0000000
--- a/tempest/api/orchestration/base.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# 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 os.path
-
-import yaml
-
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
-import tempest.test
-
-CONF = config.CONF
-
-
-class BaseOrchestrationTest(tempest.test.BaseTestCase):
- """Base test case class for all Orchestration API tests."""
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseOrchestrationTest, cls).skip_checks()
- if not CONF.service_available.heat:
- raise cls.skipException("Heat support is required")
-
- @classmethod
- def setup_credentials(cls):
- super(BaseOrchestrationTest, cls).setup_credentials()
- stack_owner_role = CONF.orchestration.stack_owner_role
- cls.os = cls.get_client_manager(roles=[stack_owner_role])
-
- @classmethod
- def setup_clients(cls):
- super(BaseOrchestrationTest, cls).setup_clients()
- cls.orchestration_client = cls.os_primary.orchestration_client
- cls.client = cls.orchestration_client
- cls.servers_client = cls.os_primary.servers_client
- cls.keypairs_client = cls.os_primary.keypairs_client
- cls.networks_client = cls.os_primary.networks_client
- cls.images_v2_client = cls.os_primary.image_client_v2
- cls.volumes_client = cls.os_primary.volumes_v2_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseOrchestrationTest, cls).resource_setup()
- cls.build_timeout = CONF.orchestration.build_timeout
- cls.build_interval = CONF.orchestration.build_interval
- cls.stacks = []
- cls.keypairs = []
- cls.images = []
-
- @classmethod
- def create_stack(cls, stack_name, template_data, parameters=None,
- environment=None, files=None):
- if parameters is None:
- parameters = {}
- body = cls.client.create_stack(
- stack_name,
- template=template_data,
- parameters=parameters,
- environment=environment,
- files=files)
- stack_id = body.response['location'].split('/')[-1]
- stack_identifier = '%s/%s' % (stack_name, stack_id)
- cls.stacks.append(stack_identifier)
- return stack_identifier
-
- @classmethod
- def _clear_stacks(cls):
- for stack_identifier in cls.stacks:
- test_utils.call_and_ignore_notfound_exc(
- cls.client.delete_stack, stack_identifier)
-
- for stack_identifier in cls.stacks:
- test_utils.call_and_ignore_notfound_exc(
- cls.client.wait_for_stack_status, stack_identifier,
- 'DELETE_COMPLETE')
-
- @classmethod
- def _create_keypair(cls, name_start='keypair-heat-'):
- kp_name = data_utils.rand_name(name_start)
- body = cls.keypairs_client.create_keypair(name=kp_name)['keypair']
- cls.keypairs.append(kp_name)
- return body
-
- @classmethod
- def _clear_keypairs(cls):
- for kp_name in cls.keypairs:
- try:
- cls.keypairs_client.delete_keypair(kp_name)
- except Exception:
- pass
-
- @classmethod
- def _create_image(cls, name_start='image-heat-', container_format='bare',
- disk_format='iso'):
- image_name = data_utils.rand_name(name_start)
- body = cls.images_v2_client.create_image(image_name,
- container_format,
- disk_format)
- image_id = body['id']
- cls.images.append(image_id)
- return body
-
- @classmethod
- def _clear_images(cls):
- for image_id in cls.images:
- test_utils.call_and_ignore_notfound_exc(
- cls.images_v2_client.delete_image, image_id)
-
- @classmethod
- def read_template(cls, name, ext='yaml'):
- loc = ["stacks", "templates", "%s.%s" % (name, ext)]
- fullpath = os.path.join(os.path.dirname(__file__), *loc)
-
- with open(fullpath, "r") as f:
- content = f.read()
- return content
-
- @classmethod
- def load_template(cls, name, ext='yaml'):
- loc = ["stacks", "templates", "%s.%s" % (name, ext)]
- fullpath = os.path.join(os.path.dirname(__file__), *loc)
-
- with open(fullpath, "r") as f:
- return yaml.safe_load(f)
-
- @classmethod
- def resource_cleanup(cls):
- cls._clear_stacks()
- cls._clear_keypairs()
- cls._clear_images()
- super(BaseOrchestrationTest, cls).resource_cleanup()
-
- @staticmethod
- def stack_output(stack, output_key):
- """Return a stack output value for a given key."""
- return next((o['output_value'] for o in stack['outputs']
- if o['output_key'] == output_key), None)
-
- def assert_fields_in_dict(self, obj, *fields):
- for field in fields:
- self.assertIn(field, obj)
-
- def list_resources(self, stack_identifier):
- """Get a dict mapping of resource names to types."""
- resources = self.client.list_resources(stack_identifier)['resources']
- self.assertIsInstance(resources, list)
- for res in resources:
- self.assert_fields_in_dict(res, 'logical_resource_id',
- 'resource_type', 'resource_status',
- 'updated_time')
-
- return dict((r['resource_name'], r['resource_type'])
- for r in resources)
-
- def get_stack_output(self, stack_identifier, output_key):
- body = self.client.show_stack(stack_identifier)['stack']
- return self.stack_output(body, output_key)
diff --git a/tempest/api/orchestration/stacks/__init__.py b/tempest/api/orchestration/stacks/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/orchestration/stacks/__init__.py
+++ /dev/null
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
deleted file mode 100644
index 61c271c..0000000
--- a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-heat_template_version: 2013-05-23
-
-parameters:
- volume_size:
- type: number
- default: 1
-
-resources:
- volume:
- type: OS::Cinder::Volume
- properties:
- size: { get_param: volume_size }
- description: a descriptive description
- name: volume_name
-
-outputs:
- status:
- description: status
- value: { get_attr: ['volume', 'status'] }
-
- size:
- description: size
- value: { get_attr: ['volume', 'size'] }
-
- display_description:
- description: display_description
- value: { get_attr: ['volume', 'display_description'] }
-
- display_name:
- value: { get_attr: ['volume', 'display_name'] }
-
- volume_id:
- value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
deleted file mode 100644
index 0bc6d69..0000000
--- a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-heat_template_version: 2013-05-23
-
-parameters:
- volume_size:
- type: number
- default: 1
-
-resources:
- volume:
- deletion_policy: 'Retain'
- type: OS::Cinder::Volume
- properties:
- size: { get_param: volume_size }
- description: a descriptive description
- name: volume_name
-
-outputs:
- status:
- description: status
- value: { get_attr: ['volume', 'status'] }
-
- size:
- description: size
- value: { get_attr: ['volume', 'size'] }
-
- display_description:
- description: display_description
- value: { get_attr: ['volume', 'display_description'] }
-
- display_name:
- value: { get_attr: ['volume', 'display_name'] }
-
- volume_id:
- value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
deleted file mode 100644
index ccb1b54..0000000
--- a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
+++ /dev/null
@@ -1,72 +0,0 @@
-heat_template_version: '2013-05-23'
-description: |
- Template which creates single EC2 instance
-parameters:
- KeyName:
- type: string
- InstanceType:
- type: string
- ImageId:
- type: string
- SubNetCidr:
- type: string
- ExternalNetworkId:
- type: string
- DNSServers:
- type: comma_delimited_list
- timeout:
- type: number
-resources:
- Network:
- type: OS::Neutron::Net
- properties:
- name: NewNetwork
- Subnet:
- type: OS::Neutron::Subnet
- properties:
- network_id: {Ref: Network}
- name: NewSubnet
- ip_version: 4
- cidr: { get_param: SubNetCidr }
- dns_nameservers: { get_param: DNSServers }
- Router:
- type: OS::Neutron::Router
- properties:
- name: NewRouter
- admin_state_up: true
- external_gateway_info:
- network: {get_param: ExternalNetworkId}
- RouterInterface:
- type: OS::Neutron::RouterInterface
- properties:
- router_id: {get_resource: Router}
- subnet_id: {get_resource: Subnet}
- Server:
- type: OS::Nova::Server
- metadata:
- Name: SmokeServerNeutron
- properties:
- image: {get_param: ImageId}
- flavor: {get_param: InstanceType}
- key_name: {get_param: KeyName}
- networks:
- - network: {get_resource: Network}
- user_data_format: RAW
- user_data:
- str_replace:
- template: |
- #!/bin/sh -v
-
- SIGNAL_DATA='{"Status": "SUCCESS", "Reason": "SmokeServerNeutron created", "Data": "Application has completed configuration.", "UniqueId": "00000"}'
- while ! curl --insecure --fail -X PUT -H 'Content-Type:' --data-binary "$SIGNAL_DATA" \
- 'wait_handle' ; do sleep 3; done
- params:
- wait_handle: {get_resource: WaitHandleNeutron}
- WaitHandleNeutron:
- type: AWS::CloudFormation::WaitConditionHandle
- WaitCondition:
- type: AWS::CloudFormation::WaitCondition
- depends_on: Server
- properties:
- Handle: {get_resource: WaitHandleNeutron}
- Timeout: {get_param: timeout}
diff --git a/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml b/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml
deleted file mode 100644
index 4f9df91..0000000
--- a/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-HeatTemplateFormatVersion: '2012-12-12'
-Description: |
- Template which creates some simple resources
-Parameters:
- trigger:
- Type: String
- Default: not_yet
- image:
- Type: String
- flavor:
- Type: String
-Resources:
- fluffy:
- Type: AWS::AutoScaling::LaunchConfiguration
- Metadata:
- kittens:
- - Tom
- - Stinky
- Properties:
- ImageId: {Ref: image}
- InstanceType: {Ref: flavor}
- UserData:
- Fn::Replace:
- - variable_a: {Ref: trigger}
- variable_b: bee
- - |
- A == variable_a
- B == variable_b
-Outputs:
- fluffy:
- Description: "fluffies irc nick"
- Value:
- Fn::Replace:
- - nick: {Ref: fluffy}
- - |
- #nick
diff --git a/tempest/api/orchestration/stacks/templates/nova_keypair.json b/tempest/api/orchestration/stacks/templates/nova_keypair.json
deleted file mode 100644
index 63d3817..0000000
--- a/tempest/api/orchestration/stacks/templates/nova_keypair.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "AWSTemplateFormatVersion" : "2010-09-09",
- "Description" : "Template which create two key pairs.",
- "Parameters" : {
- "KeyPairName1": {
- "Type": "String",
- "Default": "testkey1"
- },
- "KeyPairName2": {
- "Type": "String",
- "Default": "testkey2"
- }
- },
- "Resources" : {
- "KeyPairSavePrivate": {
- "Type": "OS::Nova::KeyPair",
- "Properties": {
- "name" : { "Ref" : "KeyPairName1" },
- "save_private_key": true
- }
- },
- "KeyPairDontSavePrivate": {
- "Type": "OS::Nova::KeyPair",
- "Properties": {
- "name" : { "Ref" : "KeyPairName2" },
- "save_private_key": false
- }
- }
- },
- "Outputs": {
- "KeyPair_PublicKey": {
- "Description": "Public Key of generated keypair.",
- "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "public_key"] }
- },
- "KeyPair_PrivateKey": {
- "Description": "Private Key of generated keypair.",
- "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "private_key"] }
- },
- "KeyPairDontSavePrivate_PublicKey": {
- "Description": "Public Key of generated keypair.",
- "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "public_key"] }
- },
- "KeyPairDontSavePrivate_PrivateKey": {
- "Description": "Private Key of generated keypair.",
- "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "private_key"] }
- }
- }
-}
diff --git a/tempest/api/orchestration/stacks/templates/nova_keypair.yaml b/tempest/api/orchestration/stacks/templates/nova_keypair.yaml
deleted file mode 100644
index 81ad99c..0000000
--- a/tempest/api/orchestration/stacks/templates/nova_keypair.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-heat_template_version: 2013-05-23
-
-description: >
- Template which creates two key pairs.
-
-parameters:
- KeyPairName1:
- type: string
- default: testkey
-
- KeyPairName2:
- type: string
- default: testkey2
-
-resources:
- KeyPairSavePrivate:
- type: OS::Nova::KeyPair
- properties:
- name: { get_param: KeyPairName1 }
- save_private_key: true
-
- KeyPairDontSavePrivate:
- type: OS::Nova::KeyPair
- properties:
- name: { get_param: KeyPairName2 }
- save_private_key: false
-
-outputs:
- KeyPair_PublicKey:
- description: Public Key of generated keypair
- value: { get_attr: [KeyPairSavePrivate, public_key] }
-
- KeyPair_PrivateKey:
- description: Private Key of generated keypair
- value: { get_attr: [KeyPairSavePrivate, private_key] }
-
- KeyPairDontSavePrivate_PublicKey:
- description: Public Key of generated keypair
- value: { get_attr: [KeyPairDontSavePrivate, public_key] }
-
- KeyPairDontSavePrivate_PrivateKey:
- description: Private Key of generated keypair
- value: { get_attr: [KeyPairDontSavePrivate, private_key] }
diff --git a/tempest/api/orchestration/stacks/templates/random_string.yaml b/tempest/api/orchestration/stacks/templates/random_string.yaml
deleted file mode 100644
index dfd2342..0000000
--- a/tempest/api/orchestration/stacks/templates/random_string.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-heat_template_version: 2013-05-23
-
-parameters:
- random_length:
- type: number
- default: 10
-
-resources:
- random:
- type: OS::Heat::RandomString
- properties:
- length: {get_param: random_length}
-
-outputs:
- random_length:
- value: {get_param: random_length}
- random_value:
- value: {get_attr: [random, value]}
diff --git a/tempest/api/orchestration/stacks/templates/swift_basic.yaml b/tempest/api/orchestration/stacks/templates/swift_basic.yaml
deleted file mode 100644
index 713f8bc..0000000
--- a/tempest/api/orchestration/stacks/templates/swift_basic.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-heat_template_version: 2013-05-23
-description: Template which creates a Swift container resource
-
-resources:
- SwiftContainerWebsite:
- deletion_policy: "Delete"
- type: OS::Swift::Container
- properties:
- X-Container-Read: ".r:*"
- X-Container-Meta:
- web-index: "index.html"
- web-error: "error.html"
-
- SwiftContainer:
- type: OS::Swift::Container
-
-outputs:
- WebsiteURL:
- description: "URL for website hosted on S3"
- value: { get_attr: [SwiftContainer, WebsiteURL] }
- DomainName:
- description: "Domain of Swift host"
- value: { get_attr: [SwiftContainer, DomainName] }
diff --git a/tempest/api/orchestration/stacks/test_environment.py b/tempest/api/orchestration/stacks/test_environment.py
deleted file mode 100644
index c8a9aa7..0000000
--- a/tempest/api/orchestration/stacks/test_environment.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-
-class StackEnvironmentTest(base.BaseOrchestrationTest):
-
- @decorators.idempotent_id('37d4346b-1abd-4442-b7b1-2a4e5749a1e3')
- def test_environment_parameter(self):
- """Test passing a stack parameter via the environment."""
- stack_name = data_utils.rand_name('heat')
- template = self.read_template('random_string')
- environment = {'parameters': {'random_length': 20}}
-
- stack_identifier = self.create_stack(stack_name, template,
- environment=environment)
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- random_len = self.get_stack_output(stack_identifier, 'random_length')
- self.assertEqual(20, random_len)
-
- random_value = self.get_stack_output(stack_identifier, 'random_value')
- self.assertEqual(20, len(random_value))
-
- @decorators.idempotent_id('73bce717-ad22-4853-bbef-6ed89b632701')
- def test_environment_provider_resource(self):
- """Test passing resource_registry defining a provider resource."""
- stack_name = data_utils.rand_name('heat')
- template = '''
-heat_template_version: 2013-05-23
-resources:
- random:
- type: My:Random::String
-outputs:
- random_value:
- value: {get_attr: [random, random_value]}
-'''
- environment = {'resource_registry':
- {'My:Random::String': 'my_random.yaml'}}
- files = {'my_random.yaml': self.read_template('random_string')}
-
- stack_identifier = self.create_stack(stack_name, template,
- environment=environment,
- files=files)
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- # random_string.yaml specifies a length of 10
- random_value = self.get_stack_output(stack_identifier, 'random_value')
- random_string_template = self.load_template('random_string')
- expected_length = random_string_template['parameters'][
- 'random_length']['default']
- self.assertEqual(expected_length, len(random_value))
-
- @decorators.idempotent_id('9d682e5a-f4bb-47d5-8472-9d3cacb855df')
- def test_files_provider_resource(self):
- """Test untyped defining of a provider resource via "files"."""
- # It's also possible to specify the filename directly in the template.
- # without adding the type alias to resource_registry
- stack_name = data_utils.rand_name('heat')
- template = '''
-heat_template_version: 2013-05-23
-resources:
- random:
- type: my_random.yaml
-outputs:
- random_value:
- value: {get_attr: [random, random_value]}
-'''
- files = {'my_random.yaml': self.read_template('random_string')}
-
- stack_identifier = self.create_stack(stack_name, template,
- files=files)
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- # random_string.yaml specifies a length of 10
- random_value = self.get_stack_output(stack_identifier, 'random_value')
- random_string_template = self.load_template('random_string')
- expected_length = random_string_template['parameters'][
- 'random_length']['default']
- self.assertEqual(expected_length, len(random_value))
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
deleted file mode 100644
index 9c9d4f9..0000000
--- a/tempest/api/orchestration/stacks/test_limits.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-CONF = config.CONF
-
-
-class TestServerStackLimits(base.BaseOrchestrationTest):
-
- @decorators.idempotent_id('ec9bed71-c460-45c9-ab98-295caa9fd76b')
- def test_exceed_max_template_size_fails(self):
- stack_name = data_utils.rand_name('heat')
- fill = 'A' * CONF.orchestration.max_template_size
- template = '''
-HeatTemplateFormatVersion: '2012-12-12'
-Description: '%s'
-Outputs:
- Foo: bar''' % fill
- ex = self.assertRaises(lib_exc.BadRequest, self.create_stack,
- stack_name, template)
- self.assertIn('exceeds maximum allowed size', str(ex))
-
- @decorators.idempotent_id('d1b83e73-7cad-4a22-9839-036548c5387c')
- def test_exceed_max_resources_per_stack(self):
- stack_name = data_utils.rand_name('heat')
- # Create a big template, one resource more than the limit
- template = 'heat_template_version: \'2013-05-23\'\nresources:\n'
- rsrc_snippet = ' random%s:\n type: \'OS::Heat::RandomString\'\n'
- num_resources = CONF.orchestration.max_resources_per_stack + 1
- for i in range(num_resources):
- template += rsrc_snippet % i
-
- ex = self.assertRaises(lib_exc.BadRequest, self.create_stack,
- stack_name, template)
- self.assertIn('Maximum resources per stack exceeded', str(ex))
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
deleted file mode 100644
index 36881bf..0000000
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# 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 netaddr
-from oslo_log import log as logging
-
-from tempest.api.orchestration import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions
-from tempest import test
-
-CONF = config.CONF
-
-LOG = logging.getLogger(__name__)
-
-
-class NeutronResourcesTestJSON(base.BaseOrchestrationTest):
-
- @classmethod
- def skip_checks(cls):
- super(NeutronResourcesTestJSON, cls).skip_checks()
- if not CONF.service_available.neutron:
- raise cls.skipException("Neutron support is required")
-
- @classmethod
- def setup_credentials(cls):
- cls.set_network_resources()
- super(NeutronResourcesTestJSON, cls).setup_credentials()
-
- @classmethod
- def setup_clients(cls):
- super(NeutronResourcesTestJSON, cls).setup_clients()
- cls.subnets_client = cls.os_primary.subnets_client
- cls.ports_client = cls.os_primary.ports_client
- cls.routers_client = cls.os_primary.routers_client
-
- @classmethod
- def resource_setup(cls):
- super(NeutronResourcesTestJSON, cls).resource_setup()
- cls.neutron_basic_template = cls.load_template('neutron_basic')
- cls.stack_name = data_utils.rand_name('heat')
- template = cls.read_template('neutron_basic')
- cls.keypair_name = (CONF.orchestration.keypair_name or
- cls._create_keypair()['name'])
- cls.external_network_id = CONF.network.public_network_id
-
- tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
- mask_bits = CONF.network.project_network_mask_bits
- cls.subnet_cidr = tenant_cidr.subnet(mask_bits).next()
-
- # create the stack
- cls.stack_identifier = cls.create_stack(
- cls.stack_name,
- template,
- parameters={
- 'KeyName': cls.keypair_name,
- 'InstanceType': CONF.orchestration.instance_type,
- 'ImageId': CONF.compute.image_ref,
- 'ExternalNetworkId': cls.external_network_id,
- 'timeout': CONF.orchestration.build_timeout,
- 'DNSServers': CONF.network.dns_servers,
- 'SubNetCidr': str(cls.subnet_cidr)
- })
- cls.stack_id = cls.stack_identifier.split('/')[1]
- try:
- cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
- resources = (cls.client.list_resources(cls.stack_identifier)
- ['resources'])
- except exceptions.TimeoutException:
- if CONF.compute_feature_enabled.console_output:
- # attempt to log the server console to help with debugging
- # the cause of the server not signalling the waitcondition
- # to heat.
- body = cls.client.show_resource(cls.stack_identifier,
- 'Server')
- server_id = body.get('physical_resource_id')
- if server_id:
- LOG.debug('Console output for %s', server_id)
- output = cls.servers_client.get_console_output(
- server_id)['output']
- LOG.debug(output)
- else:
- LOG.debug('Server resource is %s', body)
- raise # original exception
-
- cls.test_resources = {}
- for resource in resources:
- cls.test_resources[resource['logical_resource_id']] = resource
-
- @decorators.idempotent_id('f9e2664c-bc44-4eef-98b6-495e4f9d74b3')
- def test_created_resources(self):
- """Verifies created neutron resources."""
- resources = [('Network', self.neutron_basic_template['resources'][
- 'Network']['type']),
- ('Subnet', self.neutron_basic_template['resources'][
- 'Subnet']['type']),
- ('RouterInterface', self.neutron_basic_template[
- 'resources']['RouterInterface']['type']),
- ('Server', self.neutron_basic_template['resources'][
- 'Server']['type'])]
- for resource_name, resource_type in resources:
- resource = self.test_resources.get(resource_name, None)
- self.assertIsInstance(resource, dict)
- self.assertEqual(resource_name, resource['logical_resource_id'])
- self.assertEqual(resource_type, resource['resource_type'])
- self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
-
- @decorators.idempotent_id('c572b915-edb1-4e90-b196-c7199a6848c0')
- @test.services('network')
- def test_created_network(self):
- """Verifies created network."""
- network_id = self.test_resources.get('Network')['physical_resource_id']
- body = self.networks_client.show_network(network_id)
- network = body['network']
- self.assertIsInstance(network, dict)
- self.assertEqual(network_id, network['id'])
- self.assertEqual(self.neutron_basic_template['resources'][
- 'Network']['properties']['name'], network['name'])
-
- @decorators.idempotent_id('e8f84b96-f9d7-4684-ad5f-340203e9f2c2')
- @test.services('network')
- def test_created_subnet(self):
- """Verifies created subnet."""
- subnet_id = self.test_resources.get('Subnet')['physical_resource_id']
- body = self.subnets_client.show_subnet(subnet_id)
- subnet = body['subnet']
- network_id = self.test_resources.get('Network')['physical_resource_id']
- self.assertEqual(subnet_id, subnet['id'])
- self.assertEqual(network_id, subnet['network_id'])
- self.assertEqual(self.neutron_basic_template['resources'][
- 'Subnet']['properties']['name'], subnet['name'])
- self.assertEqual(sorted(CONF.network.dns_servers),
- sorted(subnet['dns_nameservers']))
- self.assertEqual(self.neutron_basic_template['resources'][
- 'Subnet']['properties']['ip_version'], subnet['ip_version'])
- self.assertEqual(str(self.subnet_cidr), subnet['cidr'])
-
- @decorators.idempotent_id('96af4c7f-5069-44bc-bdcf-c0390f8a67d1')
- @test.services('network')
- def test_created_router(self):
- """Verifies created router."""
- router_id = self.test_resources.get('Router')['physical_resource_id']
- body = self.routers_client.show_router(router_id)
- router = body['router']
- self.assertEqual(self.neutron_basic_template['resources'][
- 'Router']['properties']['name'], router['name'])
- self.assertEqual(self.external_network_id,
- router['external_gateway_info']['network_id'])
- self.assertEqual(True, router['admin_state_up'])
-
- @decorators.idempotent_id('89f605bd-153e-43ee-a0ed-9919b63423c5')
- @test.services('network')
- def test_created_router_interface(self):
- """Verifies created router interface."""
- router_id = self.test_resources.get('Router')['physical_resource_id']
- network_id = self.test_resources.get('Network')['physical_resource_id']
- subnet_id = self.test_resources.get('Subnet')['physical_resource_id']
- body = self.ports_client.list_ports()
- ports = body['ports']
- router_ports = filter(lambda port: port['device_id'] ==
- router_id, ports)
- created_network_ports = filter(lambda port: port['network_id'] ==
- network_id, router_ports)
- self.assertEqual(1, len(created_network_ports))
- router_interface = created_network_ports[0]
- fixed_ips = router_interface['fixed_ips']
- subnet_fixed_ips = filter(lambda port: port['subnet_id'] ==
- subnet_id, fixed_ips)
- self.assertEqual(1, len(subnet_fixed_ips))
- router_interface_ip = subnet_fixed_ips[0]['ip_address']
- self.assertEqual(str(self.subnet_cidr.iter_hosts().next()),
- router_interface_ip)
-
- @decorators.idempotent_id('75d85316-4ac2-4c0e-a1a9-edd2148fc10e')
- @test.services('compute', 'network')
- def test_created_server(self):
- """Verifies created sever."""
- server_id = self.test_resources.get('Server')['physical_resource_id']
- server = self.servers_client.show_server(server_id)['server']
- self.assertEqual(self.keypair_name, server['key_name'])
- self.assertEqual('ACTIVE', server['status'])
- network = server['addresses'][self.neutron_basic_template['resources'][
- 'Network']['properties']['name']][0]
- self.assertEqual(4, network['version'])
- self.assertIn(netaddr.IPAddress(network['addr']), self.subnet_cidr)
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
deleted file mode 100644
index 6b9c88b..0000000
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-CONF = config.CONF
-
-
-class StacksTestJSON(base.BaseOrchestrationTest):
-
- @classmethod
- def resource_setup(cls):
- super(StacksTestJSON, cls).resource_setup()
- cls.stack_name = data_utils.rand_name('heat')
- template = cls.read_template('non_empty_stack')
- image_id = (CONF.compute.image_ref or
- cls._create_image()['id'])
- flavor = CONF.orchestration.instance_type
- # create the stack
- cls.stack_identifier = cls.create_stack(
- cls.stack_name,
- template,
- parameters={
- 'trigger': 'start',
- 'image': image_id,
- 'flavor': flavor
- })
- cls.stack_id = cls.stack_identifier.split('/')[1]
- cls.resource_name = 'fluffy'
- cls.resource_type = 'AWS::AutoScaling::LaunchConfiguration'
- cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
-
- def _list_stacks(self, expected_num=None, **filter_kwargs):
- stacks = self.client.list_stacks(params=filter_kwargs)['stacks']
- self.assertIsInstance(stacks, list)
- if expected_num is not None:
- self.assertEqual(expected_num, len(stacks))
- return stacks
-
- @decorators.idempotent_id('065c652a-720d-4760-9132-06aedeb8e3ab')
- def test_stack_list(self):
- """Created stack should be in the list of existing stacks."""
- stacks = self._list_stacks()
- stacks_names = map(lambda stack: stack['stack_name'], stacks)
- self.assertIn(self.stack_name, stacks_names)
-
- @decorators.idempotent_id('992f96e3-41ee-4ff6-91c7-bcfb670c0919')
- def test_stack_show(self):
- """Getting details about created stack should be possible."""
- stack = self.client.show_stack(self.stack_name)['stack']
- self.assertIsInstance(stack, dict)
- self.assert_fields_in_dict(stack, 'stack_name', 'id', 'links',
- 'parameters', 'outputs', 'disable_rollback',
- 'stack_status_reason', 'stack_status',
- 'creation_time', 'updated_time',
- 'capabilities', 'notification_topics',
- 'timeout_mins', 'template_description')
- self.assert_fields_in_dict(stack['parameters'], 'AWS::StackId',
- 'trigger', 'AWS::Region', 'AWS::StackName')
- self.assertEqual(True, stack['disable_rollback'],
- 'disable_rollback should default to True')
- self.assertEqual(self.stack_name, stack['stack_name'])
- self.assertEqual(self.stack_id, stack['id'])
- self.assertEqual('fluffy', stack['outputs'][0]['output_key'])
-
- @decorators.idempotent_id('fe719f7a-305a-44d8-bbb5-c91e93d9da17')
- def test_suspend_resume_stack(self):
- """Suspend and resume a stack."""
- self.client.suspend_stack(self.stack_identifier)
- self.client.wait_for_stack_status(self.stack_identifier,
- 'SUSPEND_COMPLETE')
- self.client.resume_stack(self.stack_identifier)
- self.client.wait_for_stack_status(self.stack_identifier,
- 'RESUME_COMPLETE')
-
- @decorators.idempotent_id('c951d55e-7cce-4c1f-83a0-bad735437fa6')
- def test_list_resources(self):
- """Get list of created resources for the stack should be possible."""
- resources = self.list_resources(self.stack_identifier)
- self.assertEqual({self.resource_name: self.resource_type}, resources)
-
- @decorators.idempotent_id('2aba03b3-392f-4237-900b-1f5a5e9bd962')
- def test_show_resource(self):
- """Getting details about created resource should be possible."""
- resource = self.client.show_resource(self.stack_identifier,
- self.resource_name)['resource']
- self.assertIsInstance(resource, dict)
- self.assert_fields_in_dict(resource, 'resource_name', 'description',
- 'links', 'logical_resource_id',
- 'resource_status', 'updated_time',
- 'required_by', 'resource_status_reason',
- 'physical_resource_id', 'resource_type')
- self.assertEqual(self.resource_name, resource['logical_resource_id'])
- self.assertEqual(self.resource_type, resource['resource_type'])
-
- @decorators.idempotent_id('898070a9-eba5-4fae-b7d6-cf3ffa03090f')
- def test_resource_metadata(self):
- """Getting metadata for created resources should be possible."""
- metadata = self.client.show_resource_metadata(
- self.stack_identifier,
- self.resource_name)['metadata']
- self.assertIsInstance(metadata, dict)
- self.assertEqual(['Tom', 'Stinky'], metadata.get('kittens', None))
-
- @decorators.idempotent_id('46567533-0a7f-483b-8942-fa19e0f17839')
- def test_list_events(self):
- """Getting list of created events for the stack should be possible."""
- events = self.client.list_events(self.stack_identifier)['events']
- self.assertIsInstance(events, list)
-
- for event in events:
- self.assert_fields_in_dict(event, 'logical_resource_id', 'id',
- 'resource_status_reason',
- 'resource_status', 'event_time')
-
- resource_statuses = [event['resource_status'] for event in events]
- self.assertIn('CREATE_IN_PROGRESS', resource_statuses)
- self.assertIn('CREATE_COMPLETE', resource_statuses)
-
- @decorators.idempotent_id('92465723-1673-400a-909d-4773757a3f21')
- def test_show_event(self):
- """Getting details about an event should be possible."""
- events = self.client.list_resource_events(self.stack_identifier,
- self.resource_name)['events']
- self.assertNotEqual([], events)
- events.sort(key=lambda event: event['event_time'])
- event_id = events[0]['id']
- event = self.client.show_event(self.stack_identifier,
- self.resource_name, event_id)['event']
- self.assertIsInstance(event, dict)
- self.assert_fields_in_dict(event, 'resource_name', 'event_time',
- 'links', 'logical_resource_id',
- 'resource_status', 'resource_status_reason',
- 'physical_resource_id', 'id',
- 'resource_properties', 'resource_type')
- self.assertEqual(self.resource_name, event['resource_name'])
- self.assertEqual('state changed', event['resource_status_reason'])
- self.assertEqual(self.resource_name, event['logical_resource_id'])
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
deleted file mode 100644
index a51648f..0000000
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-
-class NovaKeyPairResourcesYAMLTest(base.BaseOrchestrationTest):
- _tpl_type = 'yaml'
- _resource = 'resources'
- _type = 'type'
-
- @classmethod
- def resource_setup(cls):
- super(NovaKeyPairResourcesYAMLTest, cls).resource_setup()
- cls.stack_name = data_utils.rand_name('heat')
- template = cls.read_template('nova_keypair', ext=cls._tpl_type)
-
- # create the stack, avoid any duplicated key.
- cls.stack_identifier = cls.create_stack(
- cls.stack_name,
- template,
- parameters={
- 'KeyPairName1': cls.stack_name + '_1',
- 'KeyPairName2': cls.stack_name + '_2'
- })
-
- cls.stack_id = cls.stack_identifier.split('/')[1]
- cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
- resources = (cls.client.list_resources(cls.stack_identifier)
- ['resources'])
- cls.test_resources = {}
- for resource in resources:
- cls.test_resources[resource['logical_resource_id']] = resource
-
- @decorators.idempotent_id('b476eac2-a302-4815-961f-18c410a2a537')
- def test_created_resources(self):
- """Verifies created keypair resource."""
-
- nova_keypair_template = self.load_template('nova_keypair',
- ext=self._tpl_type)
- resources = [('KeyPairSavePrivate',
- nova_keypair_template[self._resource][
- 'KeyPairSavePrivate'][self._type]),
- ('KeyPairDontSavePrivate',
- nova_keypair_template[self._resource][
- 'KeyPairDontSavePrivate'][self._type])]
-
- for resource_name, resource_type in resources:
- resource = self.test_resources.get(resource_name, None)
- self.assertIsInstance(resource, dict)
- self.assertEqual(resource_name, resource['logical_resource_id'])
- self.assertEqual(resource_type, resource['resource_type'])
- self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
-
- @decorators.idempotent_id('8d77dec7-91fd-45a6-943d-5abd45e338a4')
- def test_stack_keypairs_output(self):
- stack = self.client.show_stack(self.stack_name)['stack']
- self.assertIsInstance(stack, dict)
-
- output_map = {}
- for outputs in stack['outputs']:
- output_map[outputs['output_key']] = outputs['output_value']
- # Test that first key generated public and private keys
- self.assertIn('KeyPair_PublicKey', output_map)
- self.assertIn("Generated", output_map['KeyPair_PublicKey'])
- self.assertIn('KeyPair_PrivateKey', output_map)
- self.assertIn('-----BEGIN', output_map['KeyPair_PrivateKey'])
- # Test that second key generated public key, and private key is not
- # in the output due to save_private_key = false
- self.assertIn('KeyPairDontSavePrivate_PublicKey', output_map)
- self.assertIn('Generated',
- output_map['KeyPairDontSavePrivate_PublicKey'])
- self.assertIn(u'KeyPairDontSavePrivate_PrivateKey', output_map)
- private_key = output_map['KeyPairDontSavePrivate_PrivateKey']
- self.assertEqual(0, len(private_key))
-
-
-class NovaKeyPairResourcesAWSTest(NovaKeyPairResourcesYAMLTest):
- _tpl_type = 'json'
- _resource = 'Resources'
- _type = 'Type'
diff --git a/tempest/api/orchestration/stacks/test_resource_types.py b/tempest/api/orchestration/stacks/test_resource_types.py
deleted file mode 100644
index 6d0707e..0000000
--- a/tempest/api/orchestration/stacks/test_resource_types.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib import decorators
-
-
-class ResourceTypesTest(base.BaseOrchestrationTest):
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('7123d082-3577-4a30-8f00-f805327c4ffd')
- def test_resource_type_list(self):
- """Verify it is possible to list resource types."""
- resource_types = self.client.list_resource_types()['resource_types']
- self.assertIsInstance(resource_types, list)
- self.assertIn('OS::Nova::Server', resource_types)
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('0e85a483-828b-4a28-a0e3-f0a21809192b')
- def test_resource_type_show(self):
- """Verify it is possible to get schema about resource types."""
- resource_types = self.client.list_resource_types()['resource_types']
- self.assertNotEmpty(resource_types)
-
- for resource_type in resource_types:
- type_schema = self.client.show_resource_type(resource_type)
- self.assert_fields_in_dict(type_schema, 'properties',
- 'attributes', 'resource_type')
- self.assertEqual(resource_type, type_schema['resource_type'])
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('8401821d-65fe-4d43-9fa3-57d5ce3a35c7')
- def test_resource_type_template(self):
- """Verify it is possible to get template about resource types."""
- type_template = self.client.show_resource_type_template(
- 'OS::Nova::Server')
- self.assert_fields_in_dict(
- type_template,
- 'Outputs',
- 'Parameters', 'Resources')
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
deleted file mode 100644
index 8192999..0000000
--- a/tempest/api/orchestration/stacks/test_soft_conf.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-class TestSoftwareConfig(base.BaseOrchestrationTest):
-
- def setUp(self):
- super(TestSoftwareConfig, self).setUp()
- self.configs = []
- # Add 2 sets of software configuration
- self.configs.append(self._config_create('a'))
- self.configs.append(self._config_create('b'))
- # Create a deployment using config a's id
- self._deployment_create(self.configs[0]['id'])
-
- def _config_create(self, suffix):
- configuration = {'group': 'script',
- 'inputs': [],
- 'outputs': [],
- 'options': {}}
- configuration['name'] = 'heat_soft_config_%s' % suffix
- configuration['config'] = '#!/bin/bash echo init-%s' % suffix
- api_config = self.client.create_software_config(**configuration)
- configuration['id'] = api_config['software_config']['id']
- self.addCleanup(self._config_delete, configuration['id'])
- self._validate_config(configuration, api_config)
- return configuration
-
- def _validate_config(self, configuration, api_config):
- # Assert all expected keys are present with matching data
- for k in configuration:
- self.assertEqual(configuration[k],
- api_config['software_config'][k])
-
- def _deployment_create(self, config_id):
- self.server_id = data_utils.rand_name('dummy-server')
- self.action = 'ACTION_0'
- self.status = 'STATUS_0'
- self.input_values = {}
- self.output_values = []
- self.status_reason = 'REASON_0'
- self.signal_transport = 'NO_SIGNAL'
- self.deployment = self.client.create_software_deploy(
- self.server_id, config_id, self.action, self.status,
- self.input_values, self.output_values, self.status_reason,
- self.signal_transport)
- self.deployment_id = self.deployment['software_deployment']['id']
- self.addCleanup(self._deployment_delete, self.deployment_id)
-
- def _deployment_delete(self, deploy_id):
- self.client.delete_software_deploy(deploy_id)
- # Testing that it is really gone
- self.assertRaises(
- lib_exc.NotFound, self.client.show_software_deployment,
- self.deployment_id)
-
- def _config_delete(self, config_id):
- self.client.delete_software_config(config_id)
- # Testing that it is really gone
- self.assertRaises(
- lib_exc.NotFound, self.client.show_software_config, config_id)
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('136162ed-9445-4b9c-b7fc-306af8b5da99')
- def test_get_software_config(self):
- """Testing software config get."""
- for conf in self.configs:
- api_config = self.client.show_software_config(conf['id'])
- self._validate_config(conf, api_config)
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('1275c835-c967-4a2c-8d5d-ad533447ed91')
- def test_get_deployment_list(self):
- """Getting a list of all deployments"""
- deploy_list = self.client.list_software_deployments()
- deploy_ids = [deploy['id'] for deploy in
- deploy_list['software_deployments']]
- self.assertIn(self.deployment_id, deploy_ids)
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('fe7cd9f9-54b1-429c-a3b7-7df8451db913')
- def test_get_deployment_metadata(self):
- """Testing deployment metadata get"""
- metadata = self.client.show_software_deployment_metadata(
- self.server_id)
- conf_ids = [conf['id'] for conf in metadata['metadata']]
- self.assertIn(self.configs[0]['id'], conf_ids)
-
- def _validate_deployment(self, action, status, reason, config_id):
- deployment = self.client.show_software_deployment(self.deployment_id)
- self.assertEqual(action, deployment['software_deployment']['action'])
- self.assertEqual(status, deployment['software_deployment']['status'])
- self.assertEqual(reason,
- deployment['software_deployment']['status_reason'])
- self.assertEqual(config_id,
- deployment['software_deployment']['config_id'])
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('f29d21f3-ed75-47cf-8cdc-ef1bdeb4c674')
- def test_software_deployment_create_validate(self):
- """Testing software deployment was created as expected."""
- # Asserting that all fields were created
- self.assert_fields_in_dict(
- self.deployment['software_deployment'], 'action', 'config_id',
- 'id', 'input_values', 'output_values', 'server_id', 'status',
- 'status_reason')
- # Testing get for this deployment and verifying parameters
- self._validate_deployment(self.action, self.status,
- self.status_reason, self.configs[0]['id'])
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('2ac43ab3-34f2-415d-be2e-eabb4d14ee32')
- def test_software_deployment_update_no_metadata_change(self):
- """Testing software deployment update without metadata change."""
- metadata = self.client.show_software_deployment_metadata(
- self.server_id)
- # Updating values without changing the configuration ID
- new_action = 'ACTION_1'
- new_status = 'STATUS_1'
- new_reason = 'REASON_1'
- self.client.update_software_deploy(
- self.deployment_id, self.server_id, self.configs[0]['id'],
- new_action, new_status, self.input_values, self.output_values,
- new_reason, self.signal_transport)
- # Verifying get and that the deployment was updated as expected
- self._validate_deployment(new_action, new_status,
- new_reason, self.configs[0]['id'])
-
- # Metadata should not be changed at this point
- test_metadata = self.client.show_software_deployment_metadata(
- self.server_id)
- for key in metadata['metadata'][0]:
- self.assertEqual(
- metadata['metadata'][0][key],
- test_metadata['metadata'][0][key])
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('92c48944-d79d-4595-a840-8e1a581c1a72')
- def test_software_deployment_update_with_metadata_change(self):
- """Testing software deployment update with metadata change."""
- metadata = self.client.show_software_deployment_metadata(
- self.server_id)
- self.client.update_software_deploy(
- self.deployment_id, self.server_id, self.configs[1]['id'],
- self.action, self.status, self.input_values,
- self.output_values, self.status_reason, self.signal_transport)
- self._validate_deployment(self.action, self.status,
- self.status_reason, self.configs[1]['id'])
- # Metadata should now be changed
- new_metadata = self.client.show_software_deployment_metadata(
- self.server_id)
- # Its enough to test the ID in this case
- meta_id = metadata['metadata'][0]['id']
- test_id = new_metadata['metadata'][0]['id']
- self.assertNotEqual(meta_id, test_id)
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
deleted file mode 100644
index dc1bb4f..0000000
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-
-class StacksTestJSON(base.BaseOrchestrationTest):
- empty_template = "HeatTemplateFormatVersion: '2012-12-12'\n"
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('d35d628c-07f6-4674-85a1-74db9919e986')
- def test_stack_list_responds(self):
- stacks = self.client.list_stacks()['stacks']
- self.assertIsInstance(stacks, list)
-
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('10498bd5-a83e-4b62-a817-ce24afe938fe')
- def test_stack_crud_no_resources(self):
- stack_name = data_utils.rand_name('heat')
-
- # create the stack
- stack_identifier = self.create_stack(
- stack_name, self.empty_template)
- stack_id = stack_identifier.split('/')[1]
-
- # wait for create complete (with no resources it should be instant)
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- # check for stack in list
- stacks = self.client.list_stacks()['stacks']
- list_ids = list([stack['id'] for stack in stacks])
- self.assertIn(stack_id, list_ids)
-
- # fetch the stack
- stack = self.client.show_stack(stack_identifier)['stack']
- self.assertEqual('CREATE_COMPLETE', stack['stack_status'])
-
- # fetch the stack by name
- stack = self.client.show_stack(stack_name)['stack']
- self.assertEqual('CREATE_COMPLETE', stack['stack_status'])
-
- # fetch the stack by id
- stack = self.client.show_stack(stack_id)['stack']
- self.assertEqual('CREATE_COMPLETE', stack['stack_status'])
-
- # delete the stack
- self.client.delete_stack(stack_identifier)
- self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
diff --git a/tempest/api/orchestration/stacks/test_swift_resources.py b/tempest/api/orchestration/stacks/test_swift_resources.py
deleted file mode 100644
index d63cde8..0000000
--- a/tempest/api/orchestration/stacks/test_swift_resources.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class SwiftResourcesTestJSON(base.BaseOrchestrationTest):
- @classmethod
- def skip_checks(cls):
- super(SwiftResourcesTestJSON, cls).skip_checks()
- if not CONF.service_available.swift:
- raise cls.skipException("Swift support is required")
-
- @classmethod
- def setup_credentials(cls):
- super(SwiftResourcesTestJSON, cls).setup_credentials()
- stack_owner_role = CONF.orchestration.stack_owner_role
- operator_role = CONF.object_storage.operator_role
- cls.os = cls.get_client_manager(
- roles=[stack_owner_role, operator_role])
-
- @classmethod
- def setup_clients(cls):
- super(SwiftResourcesTestJSON, cls).setup_clients()
- cls.account_client = cls.os_primary.account_client
- cls.container_client = cls.os_primary.container_client
-
- @classmethod
- def resource_setup(cls):
- super(SwiftResourcesTestJSON, cls).resource_setup()
- cls.stack_name = data_utils.rand_name('heat')
- template = cls.read_template('swift_basic')
- # create the stack
- cls.stack_identifier = cls.create_stack(
- cls.stack_name,
- template)
- cls.stack_id = cls.stack_identifier.split('/')[1]
- cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
- cls.test_resources = {}
- resources = (cls.client.list_resources(cls.stack_identifier)
- ['resources'])
- for resource in resources:
- cls.test_resources[resource['logical_resource_id']] = resource
-
- @decorators.idempotent_id('1a6fe69e-4be4-4990-9a7a-84b6f18019cb')
- def test_created_resources(self):
- """Created stack should be in the list of existing stacks."""
- swift_basic_template = self.load_template('swift_basic')
- resources = [('SwiftContainer', swift_basic_template['resources'][
- 'SwiftContainer']['type']),
- ('SwiftContainerWebsite', swift_basic_template[
- 'resources']['SwiftContainerWebsite']['type'])]
- for resource_name, resource_type in resources:
- resource = self.test_resources.get(resource_name)
- self.assertIsInstance(resource, dict)
- self.assertEqual(resource_type, resource['resource_type'])
- self.assertEqual(resource_name, resource['logical_resource_id'])
- self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
-
- @decorators.idempotent_id('bd438b18-5494-4d5a-9ce6-d2a942ec5060')
- @test.services('object_storage')
- def test_created_containers(self):
- params = {'format': 'json'}
- _, container_list = \
- self.account_client.list_account_containers(params=params)
- created_containers = [cont for cont in container_list
- if cont['name'].startswith(self.stack_name)]
- self.assertEqual(2, len(created_containers))
-
- @decorators.idempotent_id('73d0c093-9922-44a0-8b1d-1fc092dee367')
- @test.services('object_storage')
- def test_acl(self):
- acl_headers = ('x-container-meta-web-index', 'x-container-read')
-
- swcont = self.test_resources.get(
- 'SwiftContainer')['physical_resource_id']
- swcont_website = self.test_resources.get(
- 'SwiftContainerWebsite')['physical_resource_id']
-
- headers, _ = self.container_client.list_container_metadata(swcont)
- for h in acl_headers:
- self.assertNotIn(h, headers)
- headers, _ = self.container_client.list_container_metadata(
- swcont_website)
- for h in acl_headers:
- self.assertIn(h, headers)
-
- @decorators.idempotent_id('fda06135-6777-4594-aefa-0f6107169698')
- @test.services('object_storage')
- def test_metadata(self):
- swift_basic_template = self.load_template('swift_basic')
- metadatas = swift_basic_template['resources']['SwiftContainerWebsite'][
- 'properties']['X-Container-Meta']
- swcont_website = self.test_resources.get(
- 'SwiftContainerWebsite')['physical_resource_id']
- headers, _ = self.container_client.list_container_metadata(
- swcont_website)
-
- for meta in metadatas:
- header_meta = "x-container-meta-%s" % meta
- self.assertIn(header_meta, headers)
- self.assertEqual(headers[header_meta], metadatas[meta])
diff --git a/tempest/api/orchestration/stacks/test_templates.py b/tempest/api/orchestration/stacks/test_templates.py
deleted file mode 100644
index 4ff951d..0000000
--- a/tempest/api/orchestration/stacks/test_templates.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-
-class TemplateYAMLTestJSON(base.BaseOrchestrationTest):
- template = """
-HeatTemplateFormatVersion: '2012-12-12'
-Description: |
- Template which creates only a new user
-Resources:
- CfnUser:
- Type: AWS::IAM::User
-"""
-
- @classmethod
- def resource_setup(cls):
- super(TemplateYAMLTestJSON, cls).resource_setup()
- cls.stack_name = data_utils.rand_name('heat')
- cls.stack_identifier = cls.create_stack(cls.stack_name, cls.template)
- cls.client.wait_for_stack_status(cls.stack_identifier,
- 'CREATE_COMPLETE')
- cls.stack_id = cls.stack_identifier.split('/')[1]
- cls.parameters = {}
-
- @decorators.idempotent_id('47430699-c368-495e-a1db-64c26fd967d7')
- def test_show_template(self):
- """Getting template used to create the stack."""
- self.client.show_template(self.stack_identifier)
-
- @decorators.idempotent_id('ed53debe-8727-46c5-ab58-eba6090ec4de')
- def test_validate_template(self):
- """Validating template passing it content."""
- self.client.validate_template(self.template,
- self.parameters)
-
-
-class TemplateAWSTestJSON(TemplateYAMLTestJSON):
- template = """
-{
- "AWSTemplateFormatVersion" : "2010-09-09",
- "Description" : "Template which creates only a new user",
- "Resources" : {
- "CfnUser" : {
- "Type" : "AWS::IAM::User"
- }
- }
-}
-"""
diff --git a/tempest/api/orchestration/stacks/test_templates_negative.py b/tempest/api/orchestration/stacks/test_templates_negative.py
deleted file mode 100644
index eb93d95..0000000
--- a/tempest/api/orchestration/stacks/test_templates_negative.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2014 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-class TemplateYAMLNegativeTestJSON(base.BaseOrchestrationTest):
- template = """
-HeatTemplateFormatVersion: '2012-12-12'
-Description: |
- Template which creates only a new user
-Resources:
- CfnUser:
- Type: AWS::IAM::User
-"""
-
- invalid_template_url = 'http:///template.yaml'
-
- @classmethod
- def resource_setup(cls):
- super(TemplateYAMLNegativeTestJSON, cls).resource_setup()
- cls.parameters = {}
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('5586cbca-ddc4-4152-9db8-fa1ce5fc1876')
- def test_validate_template_url(self):
- """Validating template passing url to it."""
- self.assertRaises(lib_exc.BadRequest,
- self.client.validate_template_url,
- template_url=self.invalid_template_url,
- parameters=self.parameters)
-
-
-class TemplateAWSNegativeTestJSON(TemplateYAMLNegativeTestJSON):
- template = """
-{
- "AWSTemplateFormatVersion" : "2010-09-09",
- "Description" : "Template which creates only a new user",
- "Resources" : {
- "CfnUser" : {
- "Type" : "AWS::IAM::User"
- }
- }
-}
-"""
-
- invalid_template_url = 'http:///template.template'
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
deleted file mode 100644
index 7ddf7af..0000000
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.orchestration import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class CinderResourcesTest(base.BaseOrchestrationTest):
-
- @classmethod
- def skip_checks(cls):
- super(CinderResourcesTest, cls).skip_checks()
- if not CONF.service_available.cinder:
- raise cls.skipException('Cinder support is required')
-
- def _cinder_verify(self, volume_id, template):
- self.assertIsNotNone(volume_id)
- volume = self.volumes_client.show_volume(volume_id)['volume']
- self.assertEqual('available', volume.get('status'))
- self.assertEqual(CONF.volume.volume_size, volume.get('size'))
-
- self.assertEqual(template['resources']['volume']['properties'][
- 'description'], volume.get('description'))
- self.assertEqual(template['resources']['volume']['properties'][
- 'name'], volume.get('name'))
-
- def _outputs_verify(self, stack_identifier, template):
- self.assertEqual('available',
- self.get_stack_output(stack_identifier, 'status'))
- self.assertEqual(str(CONF.volume.volume_size),
- self.get_stack_output(stack_identifier, 'size'))
- self.assertEqual(template['resources']['volume']['properties'][
- 'description'], self.get_stack_output(stack_identifier,
- 'display_description'))
- self.assertEqual(template['resources']['volume']['properties'][
- 'name'], self.get_stack_output(stack_identifier, 'display_name'))
-
- @decorators.idempotent_id('c3243329-7bdd-4730-b402-4d19d50c41d8')
- @test.services('volume')
- def test_cinder_volume_create_delete(self):
- """Create and delete a volume via OS::Cinder::Volume."""
- stack_name = data_utils.rand_name('heat')
- template = self.read_template('cinder_basic')
- stack_identifier = self.create_stack(
- stack_name,
- template,
- parameters={
- 'volume_size': CONF.volume.volume_size
- })
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- # Verify with cinder that the volume exists, with matching details
- volume_id = self.get_stack_output(stack_identifier, 'volume_id')
- cinder_basic_template = self.load_template('cinder_basic')
- self._cinder_verify(volume_id, cinder_basic_template)
-
- # Verify the stack outputs are as expected
- self._outputs_verify(stack_identifier, cinder_basic_template)
-
- # Delete the stack and ensure the volume is gone
- self.client.delete_stack(stack_identifier)
- self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
- self.assertRaises(lib_exc.NotFound,
- self.volumes_client.show_volume,
- volume_id)
-
- def _cleanup_volume(self, volume_id):
- """Cleanup the volume direct with cinder."""
- self.volumes_client.delete_volume(volume_id)
- self.volumes_client.wait_for_resource_deletion(volume_id)
-
- @decorators.idempotent_id('ea8b3a46-b932-4c18-907a-fe23f00b33f8')
- @test.services('volume')
- def test_cinder_volume_create_delete_retain(self):
- """Ensure the 'Retain' deletion policy is respected."""
- stack_name = data_utils.rand_name('heat')
- template = self.read_template('cinder_basic_delete_retain')
- stack_identifier = self.create_stack(
- stack_name,
- template,
- parameters={
- 'volume_size': CONF.volume.volume_size
- })
- self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-
- # Verify with cinder that the volume exists, with matching details
- volume_id = self.get_stack_output(stack_identifier, 'volume_id')
- self.addCleanup(self._cleanup_volume, volume_id)
- retain_template = self.load_template('cinder_basic_delete_retain')
- self._cinder_verify(volume_id, retain_template)
-
- # Verify the stack outputs are as expected
- self._outputs_verify(stack_identifier, retain_template)
-
- # Delete the stack and ensure the volume is *not* gone
- self.client.delete_stack(stack_identifier)
- self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
- self._cinder_verify(volume_id, retain_template)
-
- # Volume cleanup happens via addCleanup calling _cleanup_volume
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
new file mode 100644
index 0000000..0df5fbd
--- /dev/null
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class GroupTypesTest(base.BaseVolumeAdminTest):
+ _api_version = 3
+ min_microversion = '3.11'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
+ def test_group_type_create_list_show(self):
+ # Create/list/show group type.
+ name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
+ description = data_utils.rand_name("group-type-description")
+ group_specs = {"consistent_group_snapshot_enabled": "<is> False"}
+ params = {'name': name,
+ 'description': description,
+ 'group_specs': group_specs,
+ 'is_public': True}
+ body = self.create_group_type(**params)
+ self.assertIn('name', body)
+ err_msg = ("The created group_type %(var)s is not equal to the "
+ "requested %(var)s")
+ self.assertEqual(name, body['name'], err_msg % {"var": "name"})
+ self.assertEqual(description, body['description'],
+ err_msg % {"var": "description"})
+
+ group_list = (
+ self.admin_group_types_client.list_group_types()['group_types'])
+ self.assertIsInstance(group_list, list)
+ self.assertNotEmpty(group_list)
+
+ fetched_group_type = self.admin_group_types_client.show_group_type(
+ body['id'])['group_type']
+ for key in params.keys():
+ self.assertEqual(params[key], fetched_group_type[key],
+ '%s of the fetched group_type is different '
+ 'from the created group_type' % key)
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
new file mode 100644
index 0000000..baea37b
--- /dev/null
+++ b/tempest/api/volume/admin/test_groups.py
@@ -0,0 +1,322 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class GroupsTest(base.BaseVolumeAdminTest):
+ _api_version = 3
+ min_microversion = '3.14'
+ max_microversion = 'latest'
+
+ def _delete_group(self, grp_id, delete_volumes=True):
+ self.groups_client.delete_group(grp_id, delete_volumes)
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == grp_id:
+ self.volumes_client.wait_for_resource_deletion(vol['id'])
+ self.groups_client.wait_for_resource_deletion(grp_id)
+
+ def _delete_group_snapshot(self, group_snapshot_id, grp_id):
+ self.group_snapshots_client.delete_group_snapshot(
+ group_snapshot_id)
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for vol in vols:
+ for snap in snapshots:
+ if (vol['group_id'] == grp_id and
+ vol['id'] == snap['volume_id']):
+ self.snapshots_client.wait_for_resource_deletion(
+ snap['id'])
+ self.group_snapshots_client.wait_for_resource_deletion(
+ group_snapshot_id)
+
+ def _create_group(self, group_type, volume_type, grp_name=None):
+ if not grp_name:
+ grp_name = data_utils.rand_name('Group')
+ grp = self.groups_client.create_group(
+ group_type=group_type['id'],
+ volume_types=[volume_type['id']],
+ name=grp_name)['group']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self._delete_group, grp['id'])
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp['id'], 'available')
+ self.assertEqual(grp_name, grp['name'])
+ return grp
+
+ @decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
+ def test_group_create_show_list_delete(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ grp1_name = data_utils.rand_name('Group1')
+ grp1 = self._create_group(group_type, volume_type,
+ grp_name=grp1_name)
+ grp1_id = grp1['id']
+
+ grp2_name = data_utils.rand_name('Group2')
+ grp2 = self._create_group(group_type, volume_type,
+ grp_name=grp2_name)
+ grp2_id = grp2['id']
+
+ # Create volume
+ vol1_name = data_utils.rand_name("volume")
+ params = {'name': vol1_name,
+ 'volume_type': volume_type['id'],
+ 'group_id': grp1['id'],
+ 'size': CONF.volume.volume_size}
+ vol1 = self.volumes_client.create_volume(**params)['volume']
+ self.assertEqual(grp1['id'], vol1['group_id'])
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, vol1['id'], 'available')
+ vol1_id = vol1['id']
+
+ # Get a given group
+ grp1 = self.groups_client.show_group(grp1['id'])['group']
+ self.assertEqual(grp1_name, grp1['name'])
+ self.assertEqual(grp1_id, grp1['id'])
+
+ grp2 = self.groups_client.show_group(grp2['id'])['group']
+ self.assertEqual(grp2_name, grp2['name'])
+ self.assertEqual(grp2_id, grp2['id'])
+
+ # Get all groups with detail
+ grps = self.groups_client.list_groups(
+ detail=True)['groups']
+ filtered_grps = [g for g in grps if g['id'] in [grp1_id, grp2_id]]
+ self.assertEqual(2, len(filtered_grps))
+ for grp in filtered_grps:
+ self.assertEqual([volume_type['id']], grp['volume_types'])
+ self.assertEqual(group_type['id'], grp['group_type'])
+
+ vols = self.volumes_client.list_volumes(
+ detail=True)['volumes']
+ filtered_vols = [v for v in vols if v['id'] in [vol1_id]]
+ self.assertEqual(1, len(filtered_vols))
+ for vol in filtered_vols:
+ self.assertEqual(grp1_id, vol['group_id'])
+
+ # Delete group
+ # grp1 has a volume so delete_volumes flag is set to True by default
+ self._delete_group(grp1_id)
+ # grp2 is empty so delete_volumes flag can be set to False
+ self._delete_group(grp2_id, delete_volumes=False)
+ grps = self.groups_client.list_groups(
+ detail=True)['groups']
+ self.assertEmpty(grps)
+
+ @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
+ def test_group_snapshot_create_show_list_delete(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ grp = self._create_group(group_type, volume_type)
+
+ # Create volume
+ vol = self.create_volume(volume_type=volume_type['id'],
+ group_id=grp['id'])
+
+ # Create group snapshot
+ group_snapshot_name = data_utils.rand_name('group_snapshot')
+ group_snapshot = (
+ self.group_snapshots_client.create_group_snapshot(
+ group_id=grp['id'],
+ name=group_snapshot_name)['group_snapshot'])
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if vol['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client,
+ group_snapshot['id'], 'available')
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+ # Get a given group snapshot
+ group_snapshot = self.group_snapshots_client.show_group_snapshot(
+ group_snapshot['id'])['group_snapshot']
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+ # Get all group snapshots with detail
+ group_snapshots = (
+ self.group_snapshots_client.list_group_snapshots(
+ detail=True)['group_snapshots'])
+ self.assertIn((group_snapshot['name'], group_snapshot['id']),
+ [(m['name'], m['id']) for m in group_snapshots])
+
+ # Delete group snapshot
+ self._delete_group_snapshot(group_snapshot['id'], grp['id'])
+ group_snapshots = (
+ self.group_snapshots_client.list_group_snapshots(
+ detail=True)['group_snapshots'])
+ self.assertEmpty(group_snapshots)
+
+ @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
+ def test_create_group_from_group_snapshot(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create Group
+ grp = self._create_group(group_type, volume_type)
+
+ # Create volume
+ vol = self.create_volume(volume_type=volume_type['id'],
+ group_id=grp['id'])
+
+ # Create group_snapshot
+ group_snapshot_name = data_utils.rand_name('group_snapshot')
+ group_snapshot = (
+ self.group_snapshots_client.create_group_snapshot(
+ group_id=grp['id'],
+ name=group_snapshot_name)['group_snapshot'])
+ self.addCleanup(self._delete_group_snapshot,
+ group_snapshot['id'], grp['id'])
+ self.assertEqual(group_snapshot_name, group_snapshot['name'])
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if vol['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client,
+ group_snapshot['id'], 'available')
+
+ # Create Group from Group snapshot
+ grp_name2 = data_utils.rand_name('Group_from_snap')
+ grp2 = self.groups_client.create_group_from_source(
+ group_snapshot_id=group_snapshot['id'],
+ name=grp_name2)['group']
+ self.addCleanup(self._delete_group, grp2['id'])
+ self.assertEqual(grp_name2, grp2['name'])
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == grp2['id']:
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, vol['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp2['id'], 'available')
+
+ @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
+ def test_create_group_from_group(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create Group
+ grp = self._create_group(group_type, volume_type)
+
+ # Create volume
+ self.create_volume(volume_type=volume_type['id'], group_id=grp['id'])
+
+ # Create Group from Group
+ grp_name2 = data_utils.rand_name('Group_from_grp')
+ grp2 = self.groups_client.create_group_from_source(
+ source_group_id=grp['id'], name=grp_name2)['group']
+ self.addCleanup(self._delete_group, grp2['id'])
+ self.assertEqual(grp_name2, grp2['name'])
+ vols = self.volumes_client.list_volumes(
+ detail=True)['volumes']
+ for vol in vols:
+ if vol['group_id'] == grp2['id']:
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, vol['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp2['id'], 'available')
+
+ @decorators.idempotent_id('4a8a6fd2-8b3b-4641-8f54-6a6f99320006')
+ def test_group_update(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create Group
+ grp = self._create_group(group_type, volume_type)
+
+ # Create volumes
+ grp_vols = []
+ for _ in range(2):
+ vol = self.create_volume(volume_type=volume_type['id'],
+ group_id=grp['id'])
+ grp_vols.append(vol)
+ vol2 = grp_vols[1]
+
+ # Remove a volume from group and update name and description
+ new_grp_name = 'new_group'
+ new_desc = 'This is a new group'
+ grp_params = {'name': new_grp_name,
+ 'description': new_desc,
+ 'remove_volumes': vol2['id']}
+ self.groups_client.update_group(grp['id'], **grp_params)
+
+ # Wait for group status to become available
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp['id'], 'available')
+
+ # Get the updated Group
+ grp = self.groups_client.show_group(grp['id'])['group']
+ self.assertEqual(new_grp_name, grp['name'])
+ self.assertEqual(new_desc, grp['description'])
+
+ # Get volumes in the group
+ vols = self.volumes_client.list_volumes(
+ detail=True)['volumes']
+ grp_vols = []
+ for vol in vols:
+ if vol['group_id'] == grp['id']:
+ grp_vols.append(vol)
+ self.assertEqual(1, len(grp_vols))
+
+ # Add a volume to the group
+ grp_params = {'add_volumes': vol2['id']}
+ self.groups_client.update_group(grp['id'], **grp_params)
+
+ # Wait for group status to become available
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, grp['id'], 'available')
+
+ # Get volumes in the group
+ vols = self.volumes_client.list_volumes(
+ detail=True)['volumes']
+ grp_vols = []
+ for vol in vols:
+ if vol['group_id'] == grp['id']:
+ grp_vols.append(vol)
+ self.assertEqual(2, len(grp_vols))
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index a2d5fb1..9ff7160 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.volume import base
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions
CONF = config.CONF
@@ -31,9 +31,19 @@
managed by Cinder from a storage back end to Cinder
"""
+ @classmethod
+ def skip_checks(cls):
+ super(SnapshotManageAdminTest, cls).skip_checks()
+
+ if not CONF.volume_feature_enabled.manage_snapshot:
+ raise cls.skipException("Manage snapshot tests are disabled")
+
+ if len(CONF.volume.manage_snapshot_ref) != 2:
+ msg = ("Manage snapshot ref is not correctly configured, "
+ "it should be a list of two elements")
+ raise exceptions.InvalidConfiguration(msg)
+
@decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
- @testtools.skipUnless(CONF.volume_feature_enabled.manage_snapshot,
- "Manage snapshot tests are disabled")
def test_unmanage_manage_snapshot(self):
# Create a volume
volume = self.create_volume()
@@ -47,20 +57,30 @@
self.admin_snapshots_client.unmanage_snapshot(snapshot['id'])
self.admin_snapshots_client.wait_for_resource_deletion(snapshot['id'])
- # Fetch snapshot ids
- snapshot_list = [
- snap['id'] for snap in
- self.snapshots_client.list_snapshots()['snapshots']
- ]
-
- # Verify snapshot does not exist in snapshot list
- self.assertNotIn(snapshot['id'], snapshot_list)
+ # Verify the original snapshot does not exist in snapshot list
+ params = {'all_tenants': 1}
+ all_snapshots = self.admin_snapshots_client.list_snapshots(
+ detail=True, params=params)['snapshots']
+ self.assertNotIn(snapshot['id'], [v['id'] for v in all_snapshots])
# Manage the snapshot
- snapshot_ref = '_snapshot-%s' % snapshot['id']
+ name = data_utils.rand_name(self.__class__.__name__ +
+ '-Managed-Snapshot')
+ description = data_utils.rand_name(self.__class__.__name__ +
+ '-Managed-Snapshot-Description')
+ metadata = {"manage-snap-meta1": "value1",
+ "manage-snap-meta2": "value2",
+ "manage-snap-meta3": "value3"}
+ snapshot_ref = {
+ 'volume_id': volume['id'],
+ 'ref': {CONF.volume.manage_snapshot_ref[0]:
+ CONF.volume.manage_snapshot_ref[1] % snapshot['id']},
+ 'name': name,
+ 'description': description,
+ 'metadata': metadata
+ }
new_snapshot = self.admin_snapshot_manage_client.manage_snapshot(
- volume_id=volume['id'],
- ref={'source-name': snapshot_ref})['snapshot']
+ **snapshot_ref)['snapshot']
self.addCleanup(self.delete_snapshot, new_snapshot['id'],
self.admin_snapshots_client)
@@ -70,4 +90,9 @@
'available')
# Verify the managed snapshot has the expected parent volume
- self.assertEqual(new_snapshot['volume_id'], volume['id'])
+ # and the expected field values.
+ new_snapshot_info = self.admin_snapshots_client.show_snapshot(
+ new_snapshot['id'])['snapshot']
+ self.assertEqual(snapshot['size'], new_snapshot_info['size'])
+ for key in ['volume_id', 'name', 'description', 'metadata']:
+ self.assertEqual(snapshot_ref[key], new_snapshot_info[key])
diff --git a/tempest/api/volume/admin/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
index a039085..4b352e0 100644
--- a/tempest/api/volume/admin/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -18,6 +18,7 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions
CONF = config.CONF
@@ -32,8 +33,9 @@
raise cls.skipException("Manage volume tests are disabled")
if len(CONF.volume.manage_volume_ref) != 2:
- raise cls.skipException("Manage volume ref is not correctly "
- "configured")
+ msg = ("Manage volume ref is not correctly configured, "
+ "it should be a list of two elements")
+ raise exceptions.InvalidConfiguration(msg)
@decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e')
def test_unmanage_manage_volume(self):
diff --git a/tempest/api/volume/admin/test_volume_pools.py b/tempest/api/volume/admin/test_volume_pools.py
index 60a3bda..d389c26 100644
--- a/tempest/api/volume/admin/test_volume_pools.py
+++ b/tempest/api/volume/admin/test_volume_pools.py
@@ -22,7 +22,7 @@
class VolumePoolsAdminTestsJSON(base.BaseVolumeAdminTest):
def _assert_pools(self, with_detail=False):
- cinder_pools = self.admin_volume_client.show_pools(
+ cinder_pools = self.admin_scheduler_stats_client.list_pools(
detail=with_detail)['pools']
self.assertIn('name', cinder_pools[0])
if with_detail:
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
index 016d87a..f551575 100644
--- a/tempest/api/volume/admin/test_volume_quota_classes.py
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import random
+
from oslo_log import log as logging
from testtools import matchers
@@ -53,31 +55,41 @@
LOG.debug("Get the current default quota class values")
body = self.admin_quota_classes_client.show_quota_class_set(
'default')['quota_class_set']
- body.pop('id')
- # Restore the defaults when the test is done
- self.addCleanup(self._restore_default_quotas, body.copy())
+ # Note(jeremyZ) Only include specified quota keys to avoid the conflict
+ # that other tests may create/delete volume types or update volume
+ # type's default quotas in concurrency running.
+ update_kwargs = {key: body[key] for key in body if key in QUOTA_KEYS}
- # Increment some of the values for updating the default quota class.
- # For safety, only items with value >= 0 will be updated, and items
- # with value < 0 (-1 means unlimited) will be ignored.
- for quota, default in body.items():
+ # Restore the defaults when the test is done.
+ self.addCleanup(self._restore_default_quotas, update_kwargs.copy())
+
+ # Note(jeremyZ) Increment some of the values for updating the default
+ # quota class. For safety, only items with value >= 0 will be updated,
+ # and items with value < 0 (-1 means unlimited) will be ignored.
+ for quota, default in update_kwargs.items():
if default >= 0:
- body[quota] = default + 1
+ update_kwargs[quota] = default + 1
+
+ # Create a volume type for updating default quotas class.
+ volume_type_name = self.create_volume_type()['name']
+ for key in ['volumes', 'snapshots', 'gigabytes']:
+ update_kwargs['%s_%s' % (key, volume_type_name)] = \
+ random.randint(1, 10)
LOG.debug("Update limits for the default quota class set")
update_body = self.admin_quota_classes_client.update_quota_class_set(
- 'default', **body)['quota_class_set']
+ 'default', **update_kwargs)['quota_class_set']
self.assertThat(update_body.items(),
- matchers.ContainsAll(body.items()))
+ matchers.ContainsAll(update_kwargs.items()))
- # Verify current project's default quotas
+ # Verify current project's default quotas.
default_quotas = self.admin_quotas_client.show_default_quota_set(
- self.os_adm.credentials.tenant_id)['quota_set']
+ self.os_admin.credentials.tenant_id)['quota_set']
self.assertThat(default_quotas.items(),
- matchers.ContainsAll(body.items()))
+ matchers.ContainsAll(update_kwargs.items()))
- # Verify a new project's default quotas
+ # Verify a new project's default quotas.
project_name = data_utils.rand_name('quota_class_tenant')
description = data_utils.rand_name('desc_')
project_id = self.identity_utils.create_project(
@@ -86,4 +98,4 @@
default_quotas = self.admin_quotas_client.show_default_quota_set(
project_id)['quota_set']
self.assertThat(default_quotas.items(),
- matchers.ContainsAll(body.items()))
+ matchers.ContainsAll(update_kwargs.items()))
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index ae4b579..754104e 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -36,7 +36,7 @@
def setup_credentials(cls):
super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
- cls.alt_client = cls.os_alt.volumes_client
+ cls.alt_client = cls.os_alt.volumes_client_latest
@classmethod
def setup_clients(cls):
@@ -155,7 +155,7 @@
# Accepts a volume transfer
self.alt_transfer_client.accept_volume_transfer(
- transfer_id, auth_key=auth_key)['transfer']
+ transfer_id, auth_key=auth_key)
# Verify volume transferred is available
waiters.wait_for_volume_resource_status(
diff --git a/tempest/api/volume/admin/test_volume_retype_with_migration.py b/tempest/api/volume/admin/test_volume_retype_with_migration.py
index 94d5299..f0b3a4f 100644
--- a/tempest/api/volume/admin/test_volume_retype_with_migration.py
+++ b/tempest/api/volume/admin/test_volume_retype_with_migration.py
@@ -85,9 +85,7 @@
volume_source = self.admin_volume_client.show_volume(
self.src_vol['id'])['volume']
- # TODO(erlon): change this to volumes_client client after Bug
- # #1657806 is fixed
- self.admin_volume_client.retype_volume(
+ self.volumes_client.retype_volume(
self.src_vol['id'],
new_type=self.dst_vol_type['name'],
migration_policy='on-demand')
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 4aab9c1..293af81 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -41,13 +41,13 @@
def test_list_services(self):
services = (self.admin_volume_services_client.list_services()
['services'])
- self.assertNotEqual(0, len(services))
+ self.assertNotEmpty(services)
@decorators.idempotent_id('63a3e1ca-37ee-4983-826d-83276a370d25')
def test_get_service_by_service_binary_name(self):
services = (self.admin_volume_services_client.list_services(
binary=self.binary_name)['services'])
- self.assertNotEqual(0, len(services))
+ self.assertNotEmpty(services)
for service in services:
self.assertEqual(self.binary_name, service['binary'])
@@ -76,7 +76,7 @@
services = (self.admin_volume_services_client.list_services(
host=hostname, binary='cinder-volume')['services'])
- self.assertNotEqual(0, len(services),
+ self.assertNotEmpty(services,
'cinder-volume not found on host %s' % hostname)
self.assertEqual(hostname, _get_host(services[0]['host']))
self.assertEqual('cinder-volume', services[0]['binary'])
@@ -87,6 +87,6 @@
services = (self.admin_volume_services_client.list_services(
host=self.host_name, binary=self.binary_name))['services']
- self.assertNotEqual(0, len(services))
+ self.assertNotEmpty(services)
self.assertEqual(self.host_name, _get_host(services[0]['host']))
self.assertEqual(self.binary_name, services[0]['binary'])
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index 297ab6e..e93bcb5 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -30,7 +30,7 @@
@classmethod
def setup_clients(cls):
super(VolumeTypesAccessTest, cls).setup_clients()
- cls.alt_client = cls.os_alt.volumes_client
+ cls.alt_client = cls.os_alt.volumes_client_latest
@decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
def test_volume_type_access_add(self):
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ac717f8..af1024c 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -57,8 +57,6 @@
"to the requested name")
self.assertIsNotNone(volume['id'],
"Field volume id is empty or not found.")
- waiters.wait_for_volume_resource_status(self.volumes_client,
- volume['id'], 'available')
# Update volume with new volume_type
self.volumes_client.retype_volume(volume['id'],
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index acff7cd..b81a477 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -70,7 +70,7 @@
@test.services('compute')
def test_force_detach_volume(self):
# Create a server and a volume
- server_id = self.create_server(wait_until='ACTIVE')['id']
+ server_id = self.create_server()['id']
volume_id = self.create_volume()['id']
# Attach volume
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
index 64bc537..7bbe674 100644
--- a/tempest/api/volume/api_microversion_fixture.py
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -13,7 +13,7 @@
import fixtures
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class APIMicroversionFixture(fixtures.Fixture):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cc1e087..9142dc3 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -69,9 +69,14 @@
if CONF.service_available.glance:
cls.images_client = cls.os_primary.image_client_v2
+ if cls._api_version == 3:
+ cls.backups_client = cls.os_primary.backups_v3_client
+ cls.volumes_client = cls.os_primary.volumes_v3_client
+ else:
+ cls.backups_client = cls.os_primary.backups_v2_client
+ cls.volumes_client = cls.os_primary.volumes_v2_client
+
cls.snapshots_client = cls.os_primary.snapshots_v2_client
- cls.volumes_client = cls.os_primary.volumes_v2_client
- cls.backups_client = cls.os_primary.backups_v2_client
cls.volumes_extension_client =\
cls.os_primary.volumes_v2_extension_client
cls.availability_zone_client = (
@@ -79,6 +84,8 @@
cls.volume_limits_client = cls.os_primary.volume_v2_limits_client
cls.messages_client = cls.os_primary.volume_v3_messages_client
cls.versions_client = cls.os_primary.volume_v3_versions_client
+ cls.groups_client = cls.os_primary.groups_v3_client
+ cls.group_snapshots_client = cls.os_primary.group_snapshots_v3_client
def setUp(self):
super(BaseVolumeTest, self).setUp()
@@ -214,16 +221,17 @@
cls.snapshots_client.wait_for_resource_deletion,
snapshot)
- def create_server(self, **kwargs):
+ def create_server(self, wait_until='ACTIVE', **kwargs):
name = kwargs.pop(
'name',
data_utils.rand_name(self.__class__.__name__ + '-instance'))
tenant_network = self.get_tenant_network()
body, _ = compute.create_test_server(
- self.os,
+ self.os_primary,
tenant_network=tenant_network,
name=name,
+ wait_until=wait_until,
**kwargs)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -249,6 +257,8 @@
cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client
cls.admin_volume_client = cls.os_admin.volumes_v2_client
+ if cls._api_version == 3:
+ cls.admin_volume_client = cls.os_admin.volumes_v3_client
cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client
cls.admin_snapshot_manage_client = \
cls.os_admin.snapshot_manage_v2_client
@@ -265,6 +275,10 @@
cls.admin_scheduler_stats_client = \
cls.os_admin.volume_scheduler_stats_v2_client
cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
+ cls.admin_groups_client = cls.os_admin.groups_v3_client
+ cls.admin_group_snapshots_client = \
+ cls.os_admin.group_snapshots_v3_client
+ cls.admin_group_types_client = cls.os_admin.group_types_v3_client
@classmethod
def resource_setup(cls):
@@ -298,6 +312,16 @@
cls.volume_types.append(volume_type['id'])
return volume_type
+ def create_group_type(self, name=None, **kwargs):
+ """Create a test group-type"""
+ name = name or data_utils.rand_name(
+ self.__class__.__name__ + '-group-type')
+ group_type = self.admin_group_types_client.create_group_type(
+ name=name, **kwargs)['group_type']
+ self.addCleanup(self.admin_group_types_client.delete_group_type,
+ group_type['id'])
+ return group_type
+
@classmethod
def clear_qos_specs(cls):
for qos_id in cls.qos_specs:
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 666efdf..d0a87db 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -30,4 +30,4 @@
# List of availability zone
availability_zone = (self.client.list_availability_zones()
['availabilityZoneInfo'])
- self.assertGreater(len(availability_zone), 0)
+ self.assertNotEmpty(availability_zone)
diff --git a/tempest/api/volume/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
index 77baf18..129981b 100644
--- a/tempest/api/volume/test_image_metadata.py
+++ b/tempest/api/volume/test_image_metadata.py
@@ -40,7 +40,7 @@
@decorators.idempotent_id('03efff0b-5c75-4822-8f10-8789ac15b13e')
@test.services('image')
- def test_update_image_metadata(self):
+ def test_update_show_delete_image_metadata(self):
# Update image metadata
image_metadata = {'image_id': '5137a025-3c5f-43c1-bc64-5f41270040a5',
'image_name': 'image',
@@ -49,7 +49,7 @@
self.volumes_client.update_volume_image_metadata(self.volume['id'],
**image_metadata)
- # Fetch image metadata from the volume
+ # Fetch volume's image metadata by show_volume method
volume_image_metadata = self.volumes_client.show_volume(
self.volume['id'])['volume']['volume_image_metadata']
@@ -62,9 +62,9 @@
'ramdisk_id')
del image_metadata['ramdisk_id']
- # Fetch the new image metadata from the volume
- volume_image_metadata = self.volumes_client.show_volume(
- self.volume['id'])['volume']['volume_image_metadata']
+ # Fetch volume's image metadata by show_volume_image_metadata method
+ volume_image_metadata = self.volumes_client.show_volume_image_metadata(
+ self.volume['id'])['metadata']
# Verify image metadata was updated after item deletion
self.assertThat(volume_image_metadata.items(),
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index 164ed37..e6fe25d 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -55,6 +55,7 @@
# Create metadata
body = self.snapshots_client.create_snapshot_metadata(
self.snapshot['id'], metadata)['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
@@ -65,6 +66,7 @@
# Update metadata
body = self.snapshots_client.update_snapshot_metadata(
self.snapshot['id'], metadata=update)['metadata']
+ self.assertEqual(update, body)
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
self.assertEqual(update, body, 'Update snapshot metadata failed')
@@ -79,7 +81,7 @@
self.assertNotIn("key3", body)
@decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
- def test_update_snapshot_metadata_item(self):
+ def test_update_show_snapshot_metadata_item(self):
# Update metadata item for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
@@ -89,8 +91,8 @@
"key2": "value2",
"key3": "value3_update"}
# Create metadata for the snapshot
- body = self.snapshots_client.create_snapshot_metadata(
- self.snapshot['id'], metadata)['metadata']
+ self.snapshots_client.create_snapshot_metadata(
+ self.snapshot['id'], metadata)
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
@@ -98,6 +100,13 @@
# Update metadata item
body = self.snapshots_client.update_snapshot_metadata_item(
self.snapshot['id'], "key3", meta=update_item)['meta']
+ self.assertEqual(update_item, body)
+
+ # Get a specific metadata item of the snapshot
+ body = self.snapshots_client.show_snapshot_metadata_item(
+ self.snapshot['id'], "key3")['meta']
+ self.assertEqual({"key3": expect['key3']}, body)
+
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index 870b9f0..4018468 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -24,7 +24,7 @@
# NOTE(zhufl): This inherits from BaseVolumeAdminTest because
# it requires force_tenant_isolation=True, which need admin
# credentials to create non-admin users for the tests.
-class AbsoluteLimitsTests(base.BaseVolumeAdminTest):
+class AbsoluteLimitsTests(base.BaseVolumeAdminTest): # noqa
# avoid existing volumes of pre-defined tenant
force_tenant_isolation = True
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index b4d22fe..d203b2d 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -45,6 +45,7 @@
body = self.volumes_client.create_volume_metadata(self.volume['id'],
metadata)['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Get the metadata of the volume
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
@@ -54,6 +55,7 @@
# Update metadata
body = self.volumes_client.update_volume_metadata(
self.volume['id'], update)['metadata']
+ self.assertEqual(update, body)
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
self.assertEqual(update, body, 'Update metadata failed')
@@ -68,7 +70,7 @@
'Delete one item metadata of the volume failed')
@decorators.idempotent_id('862261c5-8df4-475a-8c21-946e50e36a20')
- def test_update_volume_metadata_item(self):
+ def test_update_show_volume_metadata_item(self):
# Update metadata item for the volume
metadata = {"key1": "value1",
"key2": "value2",
@@ -85,6 +87,13 @@
# Update metadata item
body = self.volumes_client.update_volume_metadata_item(
self.volume['id'], "key3", update_item)['meta']
+ self.assertEqual(update_item, body)
+
+ # Get a specific metadata item of the volume
+ body = self.volumes_client.show_volume_metadata_item(
+ self.volume['id'], "key3")['meta']
+ self.assertEqual({"key3": expect['key3']}, body)
+
# Get the metadata of the volume
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 5fd1904..4108da5 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from testtools import matchers
-
from tempest.api.volume import base
from tempest.common import waiters
from tempest.lib import decorators
@@ -56,13 +54,21 @@
# List volume transfers, the result should be greater than
# or equal to 1
body = self.client.list_volume_transfers()['transfers']
- self.assertThat(len(body), matchers.GreaterThan(0))
+ self.assertNotEmpty(body)
# Accept a volume transfer by alt_tenant
body = self.alt_client.accept_volume_transfer(
transfer_id, auth_key=auth_key)['transfer']
+ for key in ['id', 'name', 'links', 'volume_id']:
+ self.assertIn(key, body)
waiters.wait_for_volume_resource_status(self.alt_volumes_client,
volume['id'], 'available')
+ accepted_volume = self.alt_volumes_client.show_volume(
+ volume['id'])['volume']
+ self.assertEqual(self.os_alt.credentials.user_id,
+ accepted_volume['user_id'])
+ self.assertEqual(self.os_alt.credentials.project_id,
+ accepted_volume['os-vol-tenant-attr:tenant_id'])
@decorators.idempotent_id('ab526943-b725-4c07-b875-8e8ef87a2c30')
def test_create_list_delete_volume_transfer(self):
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 0e7f1e9..c4d10c3 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -38,7 +38,7 @@
@test.services('compute')
def test_attach_detach_volume_to_instance(self):
# Create a server
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
# Volume is attached and detached successfully from an instance
self.volumes_client.attach_volume(self.volume['id'],
instance_uuid=server['id'],
@@ -69,7 +69,7 @@
@test.services('compute')
def test_get_volume_attachment(self):
# Create a server
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
# Verify that a volume's attachment information is retrieved
self.volumes_client.attach_volume(self.volume['id'],
instance_uuid=server['id'],
@@ -115,12 +115,12 @@
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
- body = self.volumes_client.reserve_volume(self.volume['id'])
+ self.volumes_client.reserve_volume(self.volume['id'])
# To get the volume info
body = self.volumes_client.show_volume(self.volume['id'])['volume']
self.assertIn('attaching', body['status'])
# Unmark volume as reserved.
- body = self.volumes_client.unreserve_volume(self.volume['id'])
+ self.volumes_client.unreserve_volume(self.volume['id'])
# To get the volume info
body = self.volumes_client.show_volume(self.volume['id'])['volume']
self.assertIn('available', body['status'])
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 5ad209c..1f91db6 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -108,7 +108,7 @@
volume = self.create_volume()
self.addCleanup(self.volumes_client.delete_volume,
volume['id'])
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
# Attach volume to instance
self.attach_volume(server['id'], volume['id'])
# Create backup using force flag
@@ -118,9 +118,8 @@
name=backup_name, force=True)
self.assertEqual(backup_name, backup['name'])
- @testtools.skipUnless(CONF.service_available.glance,
- "Glance is not available")
@decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
+ @test.services('image')
def test_bootable_volume_backup_and_restore(self):
# Create volume from image
img_uuid = CONF.compute.image_ref
@@ -141,3 +140,39 @@
restored_volume_id)['volume']
self.assertEqual('true', restored_volume_info['bootable'])
+
+
+class VolumesBackupsV39Test(base.BaseVolumeTest):
+
+ _api_version = 3
+ min_microversion = '3.9'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesBackupsV39Test, cls).skip_checks()
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException("Cinder backup feature disabled")
+
+ @decorators.idempotent_id('9b374cbc-be5f-4d37-8848-7efb8a873dcc')
+ def test_update_backup(self):
+ # Create volume and backup
+ volume = self.create_volume()
+ backup = self.create_backup(volume_id=volume['id'])
+
+ # Update backup and assert response body for update_backup method
+ update_kwargs = {
+ 'name': data_utils.rand_name(self.__class__.__name__ + '-Backup'),
+ 'description': data_utils.rand_name("volume-backup-description")
+ }
+ update_backup = self.backups_client.update_backup(
+ backup['id'], **update_kwargs)['backup']
+ self.assertEqual(backup['id'], update_backup['id'])
+ self.assertEqual(update_kwargs['name'], update_backup['name'])
+ self.assertIn('links', update_backup)
+
+ # Assert response body for show_backup method
+ retrieved_backup = self.backups_client.show_backup(
+ backup['id'])['backup']
+ for key in update_kwargs:
+ self.assertEqual(update_kwargs[key], retrieved_backup[key])
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
index a6bbb0a..927bfa5 100644
--- a/tempest/api/volume/test_volumes_clone.py
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.volume import base
from tempest import config
from tempest.lib import decorators
+from tempest import test
CONF = config.CONF
@@ -31,6 +30,18 @@
if not CONF.volume_feature_enabled.clone:
raise cls.skipException("Cinder volume clones are disabled")
+ def _verify_volume_clone(self, source_volume, cloned_volume,
+ bootable='false', extra_size=0):
+
+ cloned_vol_details = self.volumes_client.show_volume(
+ cloned_volume['id'])['volume']
+
+ self.assertEqual(source_volume['id'],
+ cloned_vol_details['source_volid'])
+ self.assertEqual(source_volume['size'] + extra_size,
+ cloned_vol_details['size'])
+ self.assertEqual(bootable, cloned_vol_details['bootable'])
+
@decorators.idempotent_id('9adae371-a257-43a5-9555-dc7c88e66e0e')
def test_create_from_volume(self):
# Creates a volume from another volume passing a size different from
@@ -42,14 +53,10 @@
dst_vol = self.create_volume(source_volid=src_vol['id'],
size=src_size + 1)
- volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
- # Should allow
- self.assertEqual(volume['source_volid'], src_vol['id'])
- self.assertEqual(volume['size'], src_size + 1)
+ self._verify_volume_clone(src_vol, dst_vol, extra_size=1)
- @testtools.skipUnless(CONF.service_available.glance,
- "Glance is not available")
@decorators.idempotent_id('cbbcd7c6-5a6c-481a-97ac-ca55ab715d16')
+ @test.services('image')
def test_create_from_bootable_volume(self):
# Create volume from image
img_uuid = CONF.compute.image_ref
@@ -57,10 +64,5 @@
# Create a volume from the bootable volume
cloned_vol = self.create_volume(source_volid=src_vol['id'])
- cloned_vol_details = self.volumes_client.show_volume(
- cloned_vol['id'])['volume']
- # Verify cloned volume creation as expected
- self.assertEqual('true', cloned_vol_details['bootable'])
- self.assertEqual(src_vol['id'], cloned_vol_details['source_volid'])
- self.assertEqual(src_vol['size'], cloned_vol_details['size'])
+ self._verify_volume_clone(src_vol, cloned_vol, bootable='true')
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 712254e..ec9a0dd 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -137,3 +137,17 @@
origin = self.create_volume()
self._volume_create_get_update_delete(source_volid=origin['id'],
size=CONF.volume.volume_size)
+
+
+class VolumesSummaryTest(base.BaseVolumeTest):
+
+ _api_version = 3
+ min_microversion = '3.12'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
+ def test_show_volume_summary(self):
+ volume_summary = \
+ self.volumes_client.show_volume_summary()['volume-summary']
+ for key in ['total_size', 'total_count']:
+ self.assertIn(key, volume_summary)
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 9787160..8593d3a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -343,7 +343,7 @@
# If cannot follow make sure it's because we have finished
else:
- self.assertEqual([], remaining or [],
+ self.assertEmpty(remaining or [],
'No more pages reported, but still '
'missing ids %s' % remaining)
break
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index fc3bcab..4e19e62 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -170,7 +170,7 @@
@decorators.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@test.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
self.assertRaises(lib_exc.NotFound,
self.volumes_client.attach_volume,
@@ -261,7 +261,7 @@
params = {'name': v_name}
fetched_volume = self.volumes_client.list_volumes(
params=params)['volumes']
- self.assertEqual(0, len(fetched_volume))
+ self.assertEmpty(fetched_volume)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359')
@@ -271,7 +271,7 @@
fetched_volume = \
self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
- self.assertEqual(0, len(fetched_volume))
+ self.assertEmpty(fetched_volume)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c')
@@ -279,7 +279,7 @@
params = {'status': 'null'}
fetched_volume = self.volumes_client.list_volumes(
params=params)['volumes']
- self.assertEqual(0, len(fetched_volume))
+ self.assertEmpty(fetched_volume)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ba94b27b-be3f-496c-a00e-0283b373fa75')
@@ -288,7 +288,7 @@
fetched_volume = \
self.volumes_client.list_volumes(detail=True,
params=params)['volumes']
- self.assertEqual(0, len(fetched_volume))
+ self.assertEmpty(fetched_volume)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f')
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index be3f1f2..e68ab7e 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
from testtools import matchers
from tempest.api.volume import base
@@ -35,31 +36,17 @@
super(VolumesSnapshotTestJSON, cls).resource_setup()
cls.volume_origin = cls.create_volume()
- @decorators.idempotent_id('b467b54c-07a4-446d-a1cf-651dedcc3ff1')
+ @decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
@test.services('compute')
- def test_snapshot_create_with_volume_in_use(self):
- # Create a snapshot when volume status is in-use
+ def test_snapshot_create_delete_with_volume_in_use(self):
# Create a test instance
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
self.attach_volume(server['id'], self.volume_origin['id'])
# Snapshot a volume which attached to an instance with force=False
self.assertRaises(lib_exc.BadRequest, self.create_snapshot,
self.volume_origin['id'], force=False)
- # Snapshot a volume even if it's attached to an instance
- snapshot = self.create_snapshot(self.volume_origin['id'],
- force=True)
- # Delete the snapshot
- self.delete_snapshot(snapshot['id'])
-
- @decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
- @test.services('compute')
- def test_snapshot_delete_with_volume_in_use(self):
- # Create a test instance
- server = self.create_server(wait_until='ACTIVE')
- self.attach_volume(server['id'], self.volume_origin['id'])
-
# Snapshot a volume attached to an instance
snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True)
snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
@@ -79,7 +66,7 @@
snapshot1 = self.create_snapshot(self.volume_origin['id'])
# Create a server and attach it
- server = self.create_server(wait_until='ACTIVE')
+ server = self.create_server()
self.attach_volume(server['id'], self.volume_origin['id'])
# Now that the volume is attached, create another snapshots
@@ -163,3 +150,16 @@
# Should allow
self.assertEqual(volume['snapshot_id'], src_snap['id'])
self.assertEqual(volume['size'], src_size + 1)
+
+ @decorators.idempotent_id('bbcfa285-af7f-479e-8c1a-8c34fc16543c')
+ @testtools.skipUnless(CONF.volume_feature_enabled.backup,
+ "Cinder backup is disabled")
+ def test_snapshot_backup(self):
+ # Create a snapshot
+ snapshot = self.create_snapshot(volume_id=self.volume_origin['id'])
+
+ backup = self.create_backup(volume_id=self.volume_origin['id'],
+ snapshot_id=snapshot['id'])
+ backup_info = self.backups_client.show_backup(backup['id'])['backup']
+ self.assertEqual(self.volume_origin['id'], backup_info['volume_id'])
+ self.assertEqual(snapshot['id'], backup_info['snapshot_id'])
diff --git a/tempest/clients.py b/tempest/clients.py
index 73a4b20..e617c3c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -13,17 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
-
from tempest import config
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
from tempest.lib.services import clients
from tempest.services import object_storage
-from tempest.services import orchestration
CONF = config.CONF
-LOG = logging.getLogger(__name__)
class Manager(clients.ServiceClients):
@@ -41,8 +37,7 @@
_, identity_uri = get_auth_provider_class(credentials)
super(Manager, self).__init__(
credentials=credentials, identity_uri=identity_uri, scope=scope,
- region=CONF.identity.region,
- client_parameters=self._prepare_configuration())
+ region=CONF.identity.region)
# TODO(andreaf) When clients are initialised without the right
# parameters available, the calls below will trigger a KeyError.
# We should catch that and raise a better error.
@@ -53,44 +48,6 @@
self._set_image_clients()
self._set_network_clients()
- self.orchestration_client = orchestration.OrchestrationClient(
- self.auth_provider,
- CONF.orchestration.catalog_type,
- CONF.orchestration.region or CONF.identity.region,
- endpoint_type=CONF.orchestration.endpoint_type,
- build_interval=CONF.orchestration.build_interval,
- build_timeout=CONF.orchestration.build_timeout,
- **self.default_params)
-
- def _prepare_configuration(self):
- """Map values from CONF into Manager parameters
-
- This uses `config.service_client_config` for all services to collect
- most configuration items needed to init the clients.
- """
- # NOTE(andreaf) Once all service clients in Tempest are migrated
- # to tempest.lib, their configuration will be picked up from the
- # registry, and this method will become redundant.
-
- configuration = {}
-
- # Setup the parameters for all Tempest services which are not in lib.
- # NOTE(andreaf) Since client.py is an internal module of Tempest,
- # it doesn't have to consider plugin configuration.
- for service in clients._tempest_internal_modules():
- try:
- # NOTE(andreaf) Use the unversioned service name to fetch
- # the configuration since configuration is not versioned.
- service_for_config = service.split('.')[0]
- if service_for_config not in configuration:
- configuration[service_for_config] = (
- config.service_client_config(service_for_config))
- except lib_exc.UnknownServiceClient:
- LOG.warning(
- 'Could not load configuration for service %s', service)
-
- return configuration
-
def _set_network_clients(self):
self.network_agents_client = self.network.AgentsClient()
self.network_extensions_client = self.network.ExtensionsClient()
@@ -109,6 +66,7 @@
self.security_groups_client = self.network.SecurityGroupsClient()
self.network_versions_client = self.network.NetworkVersionsClient()
self.service_providers_client = self.network.ServiceProvidersClient()
+ self.tags_client = self.network.TagsClient()
def _set_image_clients(self):
if CONF.service_available.glance:
@@ -229,10 +187,15 @@
**params_v3)
self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient(
**params_v3)
+ self.oauth_token_client = self.identity_v3.OAUTHTokenClient(
+ **params_v3)
self.domain_config_client = self.identity_v3.DomainConfigurationClient(
**params_v3)
self.endpoint_filter_client = \
self.identity_v3.EndPointsFilterClient(**params_v3)
+ self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient(
+ **params_v3)
+ self.catalog_client = self.identity_v3.CatalogClient(**params_v3)
# Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding
@@ -254,49 +217,74 @@
def _set_volume_clients(self):
- self.volume_qos_client = self.volume_v1.QosSpecsClient()
- self.volume_qos_v2_client = self.volume_v2.QosSpecsClient()
- self.volume_services_client = self.volume_v1.ServicesClient()
- self.volume_services_v2_client = self.volume_v2.ServicesClient()
- self.backups_client = self.volume_v1.BackupsClient()
- self.backups_v2_client = self.volume_v2.BackupsClient()
- self.encryption_types_client = self.volume_v1.EncryptionTypesClient()
- self.encryption_types_v2_client = \
- self.volume_v2.EncryptionTypesClient()
- self.snapshot_manage_v2_client = self.volume_v2.SnapshotManageClient()
- self.snapshots_client = self.volume_v1.SnapshotsClient()
- self.snapshots_v2_client = self.volume_v2.SnapshotsClient()
- self.volume_manage_v2_client = self.volume_v2.VolumeManageClient()
- self.volumes_client = self.volume_v1.VolumesClient()
- self.volumes_v2_client = self.volume_v2.VolumesClient()
- self.volume_v3_messages_client = self.volume_v3.MessagesClient()
- self.volume_v3_versions_client = self.volume_v3.VersionsClient()
- self.volume_types_client = self.volume_v1.TypesClient()
- self.volume_types_v2_client = self.volume_v2.TypesClient()
- self.volume_hosts_client = self.volume_v1.HostsClient()
- self.volume_hosts_v2_client = self.volume_v2.HostsClient()
- self.volume_quotas_client = self.volume_v1.QuotasClient()
- self.volume_quotas_v2_client = self.volume_v2.QuotasClient()
- self.volume_quota_classes_v2_client = \
- self.volume_v2.QuotaClassesClient()
- self.volumes_extension_client = self.volume_v1.ExtensionsClient()
- self.volumes_v2_extension_client = self.volume_v2.ExtensionsClient()
- self.volume_availability_zone_client = \
- self.volume_v1.AvailabilityZoneClient()
- self.volume_v2_availability_zone_client = \
- self.volume_v2.AvailabilityZoneClient()
- self.volume_limits_client = self.volume_v1.LimitsClient()
- self.volume_v2_limits_client = self.volume_v2.LimitsClient()
- self.volume_capabilities_v2_client = \
- self.volume_v2.CapabilitiesClient()
- self.volume_scheduler_stats_v2_client = \
- self.volume_v2.SchedulerStatsClient()
- self.volume_transfers_v2_client = \
- self.volume_v2.TransfersClient()
+ if CONF.volume_feature_enabled.api_v1:
+ self.backups_client = self.volume_v1.BackupsClient()
+ self.encryption_types_client = \
+ self.volume_v1.EncryptionTypesClient()
+ self.snapshots_client = self.volume_v1.SnapshotsClient()
+ self.volume_availability_zone_client = \
+ self.volume_v1.AvailabilityZoneClient()
+ self.volume_hosts_client = self.volume_v1.HostsClient()
+ self.volume_limits_client = self.volume_v1.LimitsClient()
+ self.volume_qos_client = self.volume_v1.QosSpecsClient()
+ self.volume_quotas_client = self.volume_v1.QuotasClient()
+ self.volume_services_client = self.volume_v1.ServicesClient()
+ self.volume_types_client = self.volume_v1.TypesClient()
+ self.volumes_client = self.volume_v1.VolumesClient()
+ self.volumes_extension_client = self.volume_v1.ExtensionsClient()
+
+ if CONF.volume_feature_enabled.api_v2:
+ self.backups_v2_client = self.volume_v2.BackupsClient()
+ self.encryption_types_v2_client = \
+ self.volume_v2.EncryptionTypesClient()
+ self.snapshot_manage_v2_client = \
+ self.volume_v2.SnapshotManageClient()
+ self.snapshots_v2_client = self.volume_v2.SnapshotsClient()
+ self.volume_capabilities_v2_client = \
+ self.volume_v2.CapabilitiesClient()
+ self.volume_manage_v2_client = self.volume_v2.VolumeManageClient()
+ self.volume_qos_v2_client = self.volume_v2.QosSpecsClient()
+ self.volume_services_v2_client = self.volume_v2.ServicesClient()
+ self.volume_types_v2_client = self.volume_v2.TypesClient()
+ self.volume_hosts_v2_client = self.volume_v2.HostsClient()
+ self.volume_quotas_v2_client = self.volume_v2.QuotasClient()
+ self.volume_quota_classes_v2_client = \
+ self.volume_v2.QuotaClassesClient()
+ self.volume_scheduler_stats_v2_client = \
+ self.volume_v2.SchedulerStatsClient()
+ self.volume_transfers_v2_client = \
+ self.volume_v2.TransfersClient()
+ self.volume_v2_availability_zone_client = \
+ self.volume_v2.AvailabilityZoneClient()
+ self.volume_v2_limits_client = self.volume_v2.LimitsClient()
+ self.volumes_v2_client = self.volume_v2.VolumesClient()
+ self.volumes_v2_extension_client = \
+ self.volume_v2.ExtensionsClient()
+
+ # Set default client for users that don't need explicit version
+ self.volumes_client_latest = self.volumes_v2_client
+ self.snapshots_client_latest = self.snapshots_v2_client
+
+ if CONF.volume_feature_enabled.api_v3:
+ self.backups_v3_client = self.volume_v3.BackupsClient()
+ self.group_types_v3_client = self.volume_v3.GroupTypesClient()
+ self.groups_v3_client = self.volume_v3.GroupsClient()
+ self.group_snapshots_v3_client = \
+ self.volume_v3.GroupSnapshotsClient()
+ self.snapshots_v3_client = self.volume_v3.SnapshotsClient()
+ self.volume_v3_messages_client = self.volume_v3.MessagesClient()
+ self.volume_v3_versions_client = self.volume_v3.VersionsClient()
+ self.volumes_v3_client = self.volume_v3.VolumesClient()
+
+ # Set default client for users that don't need explicit version
+ self.volumes_client_latest = self.volumes_v3_client
+ self.snapshots_client_latest = self.snapshots_v3_client
def _set_object_storage_clients(self):
- # Mandatory parameters (always defined)
- params = self.parameters['object-storage']
+ # NOTE(andreaf) Load configuration from config. Once object storage
+ # is in lib, configuration will be pulled directly from the registry
+ # and this will not be required anymore.
+ params = config.service_client_config('object-storage')
self.account_client = object_storage.AccountClient(self.auth_provider,
**params)
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 172d9e1..8636405 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -102,8 +102,8 @@
import yaml
from tempest.common import credentials_factory
-from tempest.common import dynamic_creds
from tempest import config
+from tempest.lib.common import dynamic_creds
LOG = None
@@ -141,18 +141,10 @@
admin_creds = credentials_factory.get_credentials(
fill_in=False, identity_version=identity_version, **admin_creds_dict)
return dynamic_creds.DynamicCredentialProvider(
- identity_version=identity_version,
name=opts.tag,
network_resources=network_resources,
- neutron_available=CONF.service_available.neutron,
- create_networks=CONF.auth.create_isolated_networks,
- identity_admin_role=CONF.identity.admin_role,
- identity_admin_domain_scope=CONF.identity.admin_domain_scope,
- project_network_cidr=CONF.network.project_network_cidr,
- project_network_mask_bits=CONF.network.project_network_mask_bits,
- public_network_id=CONF.network.public_network_id,
- admin_creds=admin_creds,
- **credentials_factory.get_dynamic_provider_params())
+ **credentials_factory.get_dynamic_provider_params(
+ identity_version, admin_creds=admin_creds))
def generate_resources(cred_provider, admin):
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index f1c0a3e..11cd4bb 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -213,7 +213,9 @@
class StackService(BaseService):
def __init__(self, manager, **kwargs):
super(StackService, self).__init__(kwargs)
- self.client = manager.orchestration_client
+ params = config.service_client_config('orchestration')
+ self.client = manager.orchestration.OrchestrationClient(
+ manager.auth_provider, **params)
def list(self):
client = self.client
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index b36bf5c..350dd0b 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -97,15 +97,19 @@
from cliff import command
from os_testr import regex_builder
from os_testr import subunit_trace
+from oslo_serialization import jsonutils as json
import six
from testrepository.commands import run_argv
+from tempest.cmd import cleanup_service
from tempest.cmd import init
from tempest.cmd import workspace
+from tempest.common import credentials_factory as credentials
from tempest import config
CONF = config.CONF
+SAVED_STATE_JSON = "saved_state.json"
class TempestRun(command.Command):
@@ -174,6 +178,11 @@
else:
print("No .testr.conf file was found for local execution")
sys.exit(2)
+ if parsed_args.state:
+ self._init_state()
+ else:
+ pass
+
if parsed_args.combine:
temp_stream = tempfile.NamedTemporaryFile()
return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
@@ -203,6 +212,25 @@
def get_description(self):
return 'Run tempest'
+ def _init_state(self):
+ print("Initializing saved state.")
+ data = {}
+ self.global_services = cleanup_service.get_global_cleanup_services()
+ self.admin_mgr = credentials.AdminManager()
+ admin_mgr = self.admin_mgr
+ kwargs = {'data': data,
+ 'is_dry_run': False,
+ 'saved_state_json': data,
+ 'is_preserve': False,
+ 'is_save_state': True}
+ for service in self.global_services:
+ svc = service(admin_mgr, **kwargs)
+ svc.run()
+
+ with open(SAVED_STATE_JSON, 'w+') as f:
+ f.write(json.dumps(data,
+ sort_keys=True, indent=2, separators=(',', ': ')))
+
def get_parser(self, prog_name):
parser = super(TempestRun, self).get_parser(prog_name)
parser = self._add_args(parser)
@@ -253,6 +281,10 @@
parallel.add_argument('--serial', '-t', dest='parallel',
action='store_false',
help='Run tests serially')
+ parser.add_argument('--save-state', dest='state',
+ action='store_true',
+ help="To save the state of the cloud before "
+ "running tempest.")
# output args
parser.add_argument("--subunit", action='store_true',
help='Enable subunit v2 output')
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 8ee3055..f9ebe20 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -102,8 +102,8 @@
response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
body_re = re.compile(r'.*Body: (?P<body>.*)')
- # Based on mitaka defaults:
- # http://docs.openstack.org/mitaka/config-reference/
+ # Based on newton defaults:
+ # http://docs.openstack.org/newton/config-reference/
# firewalls-default-ports.html
services = {
"8776": "Block Storage",
@@ -122,7 +122,8 @@
"873": "rsync",
"3260": "iSCSI",
"3306": "MySQL",
- "5672": "AMQP"}
+ "5672": "AMQP",
+ "8082": "murano"}
def __init__(self, services=None):
super(UrlParser, self).__init__()
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 0a1881c..a72493d 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -14,6 +14,51 @@
# License for the specific language governing permissions and limitations
# under the License.
+"""
+Verifies user's current tempest configuration.
+
+This command is used for updating or user's tempest configuration file based on
+api queries or replacing all option in a tempest configuration file for a full
+list of extensions.
+
+General Options
+===============
+
+-u, --update
+------------
+Update the config file with results from api queries. This assumes whatever is
+set in the config file is incorrect.
+
+-o FILE, --output=FILE
+----------------------
+Output file to write an updated config file to. This has to be a separate file
+from the original one. If one isn't specified with -u the values which should
+be changed will be printed to STDOUT.
+
+-r, --replace-ext
+-----------------
+If specified the all option will be replaced with a full list of extensions.
+
+Environment Variables
+=====================
+
+The command is workspace aware - it uses tempest config file tempest.conf
+located in ./etc/ directory.
+The path to the config file and it's name can be changed through environment
+variables.
+
+TEMPEST_CONFIG_DIR
+------------------
+Path to a directory where tempest configuration file is stored. If the variable
+is set, the default path (./etc/) is overridden.
+
+TEMPEST_CONFIG
+--------------
+Name of a tempest configuration file. If the variable is specified, the default
+name (tempest.conf) is overridden.
+
+"""
+
import argparse
import os
import re
@@ -30,6 +75,8 @@
from tempest.common import credentials_factory as credentials
from tempest import config
import tempest.lib.common.http
+from tempest.lib import exceptions as lib_exc
+from tempest.services import object_storage
CONF = config.CONF
@@ -39,8 +86,8 @@
def _get_config_file():
- default_config_dir = os.path.join(os.path.abspath(
- os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "etc")
+ config_dir = os.getcwd()
+ default_config_dir = os.path.join(config_dir, "etc")
default_config_file = "tempest.conf"
conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir)
@@ -69,7 +116,25 @@
def verify_glance_api_versions(os, update):
# Check glance api versions
- _, versions = os.image_client.get_versions()
+ # Since we want to verify that the configuration is correct, we cannot
+ # rely on a specific version of the API being available.
+ try:
+ _, versions = os.image_v1.ImagesClient().get_versions()
+ except lib_exc.NotFound:
+ # If not found, we use v2. The assumption is that either v1 or v2
+ # are available, since glance is marked as available in the catalog.
+ # If not, glance should be disabled in Tempest conf.
+ try:
+ versions = os.image_v2.VersionsClient().list_versions()['versions']
+ versions = [x['id'] for x in versions]
+ except lib_exc.NotFound:
+ msg = ('Glance is available in the catalog, but no known version, '
+ '(v1.x or v2.x) of Glance could be found, so Glance should '
+ 'be configured as not available')
+ LOG.warn(msg)
+ print_and_or_update('glance', 'service-available', False, update)
+ return
+
if CONF.image_feature_enabled.api_v1 != contains_version('v1.', versions):
print_and_or_update('api_v1', 'image-feature-enabled',
not CONF.image_feature_enabled.api_v1, update)
@@ -92,13 +157,19 @@
def _get_api_versions(os, service):
+ # Clients are used to obtain the base_url. Each client applies the
+ # appropriate filters to the catalog to extract a base_url which
+ # matches the configured region and endpoint_type.
+ # The base URL is used to obtain the list of versions available.
client_dict = {
- 'nova': os.servers_client,
- 'keystone': os.identity_client,
- 'cinder': os.volumes_client,
+ 'nova': os.compute.ServersClient(),
+ 'keystone': os.identity_v3.IdentityClient(
+ endpoint_type=CONF.identity.v3_endpoint_type),
+ 'cinder': os.volume_v3.VolumesClient(),
}
- if service != 'keystone':
- # Since keystone may be listening on a path, do not remove the path.
+ if service != 'keystone' and service != 'cinder':
+ # Since keystone and cinder may be listening on a path,
+ # do not remove the path.
client_dict[service].skip_path()
endpoint = _get_unversioned_endpoint(client_dict[service].base_url)
@@ -165,17 +236,16 @@
def get_extension_client(os, service):
+ params = config.service_client_config('object-storage')
extensions_client = {
- 'nova': os.extensions_client,
- 'cinder': os.volumes_extension_client,
- 'neutron': os.network_extensions_client,
- 'swift': os.capabilities_client,
+ 'nova': os.compute.ExtensionsClient(),
+ 'neutron': os.network.ExtensionsClient(),
+ 'swift': object_storage.CapabilitiesClient(os.auth_provider, **params),
+ # NOTE: Cinder v3 API is current and v2 and v1 are deprecated.
+ # V3 extension API is the same as v2, so we reuse the v2 client
+ # for v3 API also.
+ 'cinder': os.volume_v2.ExtensionsClient(),
}
- # NOTE (e0ne): Use Cinder API v2 by default because v1 is deprecated
- if CONF.volume_feature_enabled.api_v2:
- extensions_client['cinder'] = os.volumes_v2_extension_client
- else:
- extensions_client['cinder'] = os.volumes_extension_client
if service not in extensions_client:
print('No tempest extensions client for %s' % service)
@@ -201,7 +271,7 @@
if service != 'swift':
resp = extensions_client.list_extensions()
else:
- __, resp = extensions_client.list_capabilities()
+ resp = extensions_client.list_capabilities()
# For Nova, Cinder and Neutron we use the alias name rather than the
# 'name' field because the alias is considered to be the canonical
# name.
@@ -345,8 +415,8 @@
help="Output file to write an updated config file to. "
"This has to be a separate file from the "
"original config file. If one isn't specified "
- "with -u the new config file will be printed to "
- "STDOUT")
+ "with -u the values which should be changed "
+ "will be printed to STDOUT")
parser.add_argument('-r', '--replace-ext', action='store_true',
help="If specified the all option will be replaced "
"with a full list of extensions")
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 96d2300..8166b4f 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -40,6 +40,8 @@
------
Deletes the entry for a given tempest workspace --name
+--rmdir Deletes the given tempest workspace directory
+
General Options
===============
@@ -49,6 +51,7 @@
"""
import os
+import shutil
import sys
from cliff import command
@@ -102,11 +105,16 @@
sys.exit(1)
@lockutils.synchronized('workspaces', external=True)
- def remove_workspace(self, name):
+ def remove_workspace_entry(self, name):
self._populate()
self._name_exists(name)
- self.workspaces.pop(name)
+ workspace_path = self.workspaces.pop(name)
self._write_file()
+ return workspace_path
+
+ @lockutils.synchronized('workspaces', external=True)
+ def remove_workspace_directory(self, workspace_path):
+ shutil.rmtree(workspace_path)
@lockutils.synchronized('workspaces', external=True)
def list_workspaces(self):
@@ -226,12 +234,16 @@
parser = super(TempestWorkspaceRemove, self).get_parser(prog_name)
add_global_arguments(parser)
parser.add_argument('--name', required=True)
+ parser.add_argument('--rmdir', action='store_true',
+ help='Deletes the given workspace directory')
return parser
def take_action(self, parsed_args):
self.manager = WorkspaceManager(parsed_args.workspace_path)
- self.manager.remove_workspace(parsed_args.name)
+ workspace_path = self.manager.remove_workspace_entry(parsed_args.name)
+ if parsed_args.rmdir:
+ self.manager.remove_workspace_directory(workspace_path)
sys.exit(0)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 9110c4a..47196ec 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -25,9 +25,9 @@
from oslo_log import log as logging
from oslo_utils import excutils
-from tempest.common import fixed_network
from tempest.common import waiters
from tempest import config
+from tempest.lib.common import fixed_network
from tempest.lib.common import rest_client
from tempest.lib.common.utils import data_utils
@@ -41,6 +41,27 @@
LOG = logging.getLogger(__name__)
+def is_scheduler_filter_enabled(filter_name):
+ """Check the list of enabled compute scheduler filters from config.
+
+ This function checks whether the given compute scheduler filter is
+ available and configured in the config file. If the
+ scheduler_available_filters option is set to 'all' (Default value. which
+ means default filters are configured in nova) in tempest.conf then, this
+ function returns True with assumption that requested filter 'filter_name'
+ is one of available filter in nova ("nova.scheduler.filters.all_filters").
+ """
+
+ filters = CONF.compute_feature_enabled.scheduler_available_filters
+ if not filters:
+ return False
+ if 'all' in filters:
+ return True
+ if filter_name in filters:
+ return True
+ return False
+
+
def create_test_server(clients, validatable=False, validation_resources=None,
tenant_network=None, wait_until=None,
volume_backed=False, name=None, flavor=None,
@@ -204,6 +225,19 @@
except Exception:
LOG.exception('Deleting server %s failed',
server['id'])
+ for server in servers:
+ # NOTE(artom) If the servers were booted with volumes
+ # and with delete_on_termination=False we need to wait
+ # for the servers to go away before proceeding with
+ # cleanup, otherwise we'll attempt to delete the
+ # volumes while they're still attached to servers that
+ # are in the process of being deleted.
+ try:
+ waiters.wait_for_server_termination(
+ clients.servers_client, server['id'])
+ except Exception:
+ LOG.exception('Server %s failed to delete in time',
+ server['id'])
return body, servers
@@ -252,16 +286,34 @@
def __init__(self, client_socket, url):
"""Contructor for the WebSocket wrapper to the socket."""
self._socket = client_socket
+ # cached stream for early frames.
+ self.cached_stream = b''
# Upgrade the HTTP connection to a WebSocket
self._upgrade(url)
+ def _recv(self, recv_size):
+ """Wrapper to receive data from the cached stream or socket."""
+ if recv_size <= 0:
+ return None
+
+ data_from_cached = b''
+ data_from_socket = b''
+ if len(self.cached_stream) > 0:
+ read_from_cached = min(len(self.cached_stream), recv_size)
+ data_from_cached += self.cached_stream[:read_from_cached]
+ self.cached_stream = self.cached_stream[read_from_cached:]
+ recv_size -= read_from_cached
+ if recv_size > 0:
+ data_from_socket = self._socket.recv(recv_size)
+ return data_from_cached + data_from_socket
+
def receive_frame(self):
"""Wrapper for receiving data to parse the WebSocket frame format"""
# We need to loop until we either get some bytes back in the frame
# or no data was received (meaning the socket was closed). This is
# done to handle the case where we get back some empty frames
while True:
- header = self._socket.recv(2)
+ header = self._recv(2)
# If we didn't receive any data, just return None
if not header:
return None
@@ -270,7 +322,7 @@
# that only the 2nd byte contains the length, and since the
# server doesn't do masking, we can just read the data length
if ord_func(header[1]) & 127 > 0:
- return self._socket.recv(ord_func(header[1]) & 127)
+ return self._recv(ord_func(header[1]) & 127)
def send_frame(self, data):
"""Wrapper for sending data to add in the WebSocket frame format."""
@@ -318,6 +370,15 @@
self._socket.sendall(reqdata.encode('utf8'))
self.response = data = self._socket.recv(4096)
# Loop through & concatenate all of the data in the response body
- while data and self.response.find(b'\r\n\r\n') < 0:
+ end_loc = self.response.find(b'\r\n\r\n')
+ while data and end_loc < 0:
data = self._socket.recv(4096)
self.response += data
+ end_loc = self.response.find(b'\r\n\r\n')
+
+ if len(self.response) > end_loc + 4:
+ # In case some frames (e.g. the first RFP negotiation) have
+ # arrived, cache it for next reading.
+ self.cached_stream = self.response[end_loc + 4:]
+ # ensure response ends with '\r\n\r\n'.
+ self.response = self.response[:end_loc + 4]
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index e6b46ed..a340531 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -14,10 +14,10 @@
from oslo_concurrency import lockutils
from tempest import clients
-from tempest.common import dynamic_creds
-from tempest.common import preprov_creds
from tempest import config
from tempest.lib import auth
+from tempest.lib.common import dynamic_creds
+from tempest.lib.common import preprov_creds
from tempest.lib import exceptions
CONF = config.CONF
@@ -39,19 +39,69 @@
# Subset of the parameters of credential providers that depend on configuration
-def get_common_provider_params():
+def _get_common_provider_params(identity_version):
+ if identity_version == 'v3':
+ identity_uri = CONF.identity.uri_v3
+ elif identity_version == 'v2':
+ identity_uri = CONF.identity.uri
+ else:
+ raise exceptions.InvalidIdentityVersion(
+ identity_version=identity_version)
return {
+ 'identity_version': identity_version,
+ 'identity_uri': identity_uri,
'credentials_domain': CONF.auth.default_credentials_domain_name,
'admin_role': CONF.identity.admin_role
}
-def get_dynamic_provider_params():
- return get_common_provider_params()
+def get_dynamic_provider_params(identity_version, admin_creds=None):
+ """Dynamic provider parameters setup from config
+
+ This helper returns a dict of parameter that can be used to initialise
+ a `DynamicCredentialProvider` according to tempest configuration.
+ Parameters that are not configuration specific (name, network_resources)
+ are not returned.
+
+ :param identity_version: 'v2' or 'v3'
+ :param admin_creds: An object of type `auth.Credentials`. If None, it
+ is built from the configuration file as well.
+ :return: A dict with the parameters
+ """
+ _common_params = _get_common_provider_params(identity_version)
+ admin_creds = admin_creds or get_configured_admin_credentials(
+ fill_in=True, identity_version=identity_version)
+ if identity_version == 'v3':
+ endpoint_type = CONF.identity.v3_endpoint_type
+ elif identity_version == 'v2':
+ endpoint_type = CONF.identity.v2_admin_endpoint_type
+ return dict(_common_params, **dict([
+ ('admin_creds', admin_creds),
+ ('identity_admin_domain_scope', CONF.identity.admin_domain_scope),
+ ('identity_admin_role', CONF.identity.admin_role),
+ ('extra_roles', CONF.auth.tempest_roles),
+ ('neutron_available', CONF.service_available.neutron),
+ ('project_network_cidr', CONF.network.project_network_cidr),
+ ('project_network_mask_bits', CONF.network.project_network_mask_bits),
+ ('public_network_id', CONF.network.public_network_id),
+ ('create_networks', (CONF.auth.create_isolated_networks and not
+ CONF.network.shared_physical_network)),
+ ('resource_prefix', CONF.resources_prefix),
+ ('identity_admin_endpoint_type', endpoint_type)
+ ]))
-def get_preprov_provider_params():
- _common_params = get_common_provider_params()
+def get_preprov_provider_params(identity_version):
+ """Pre-provisioned provider parameters setup from config
+
+ This helper returns a dict of parameter that can be used to initialise
+ a `PreProvisionedCredentialProvider` according to tempest configuration.
+ Parameters that are not configuration specific (name) are not returned.
+
+ :param identity_version: 'v2' or 'v3'
+ :return: A dict with the parameters
+ """
+ _common_params = _get_common_provider_params(identity_version)
reseller_admin_role = CONF.object_storage.reseller_admin_role
return dict(_common_params, **dict([
('accounts_lock_dir', lockutils.get_lock_path(CONF)),
@@ -61,53 +111,55 @@
]))
-# Return the right implementation of CredentialProvider based on config
-# Dropping interface and password, as they are never used anyways
-# TODO(andreaf) Drop them from the CredentialsProvider interface completely
def get_credentials_provider(name, network_resources=None,
force_tenant_isolation=False,
identity_version=None):
+ """Return the right implementation of CredentialProvider based on config
+
+ This helper returns the right implementation of CredentialProvider based on
+ config and on the value of force_tenant_isolation.
+
+ :param name: When provided, it makes it possible to associate credential
+ artifacts back to the owner (test class).
+ :param network_resources: Dictionary of network resources to be allocated
+ for each test account. Only valid for the dynamic
+ credentials provider.
+ :param force_tenant_isolation: Always return a `DynamicCredentialProvider`,
+ regardless of the configuration.
+ :param identity_version: Use the specified identity API version, regardless
+ of the configuration. Valid values are 'v2', 'v3'.
+ """
# If a test requires a new account to work, it can have it via forcing
# dynamic credentials. A new account will be produced only for that test.
# In case admin credentials are not available for the account creation,
# the test should be skipped else it would fail.
identity_version = identity_version or CONF.identity.auth_version
if CONF.auth.use_dynamic_credentials or force_tenant_isolation:
- admin_creds = get_configured_admin_credentials(
- fill_in=True, identity_version=identity_version)
return dynamic_creds.DynamicCredentialProvider(
name=name,
network_resources=network_resources,
- identity_version=identity_version,
- admin_creds=admin_creds,
- identity_admin_domain_scope=CONF.identity.admin_domain_scope,
- identity_admin_role=CONF.identity.admin_role,
- extra_roles=CONF.auth.tempest_roles,
- neutron_available=CONF.service_available.neutron,
- project_network_cidr=CONF.network.project_network_cidr,
- project_network_mask_bits=CONF.network.project_network_mask_bits,
- public_network_id=CONF.network.public_network_id,
- create_networks=(CONF.auth.create_isolated_networks and not
- CONF.network.shared_physical_network),
- resource_prefix=CONF.resources_prefix,
- **get_dynamic_provider_params())
+ **get_dynamic_provider_params(identity_version))
else:
if CONF.auth.test_accounts_file:
# Most params are not relevant for pre-created accounts
return preprov_creds.PreProvisionedCredentialProvider(
- name=name, identity_version=identity_version,
- **get_preprov_provider_params())
+ name=name,
+ **get_preprov_provider_params(identity_version))
else:
raise exceptions.InvalidConfiguration(
'A valid credential provider is needed')
-# We want a helper function here to check and see if admin credentials
-# are available so we can do a single call from skip_checks if admin
-# creds area available.
-# This depends on identity_version as there may be admin credentials
-# available for v2 but not for v3.
def is_admin_available(identity_version):
+ """Helper to check for admin credentials
+
+ Helper function to check if a set of admin credentials is available so we
+ can do a single call from skip_checks.
+ This helper depends on identity_version as there may be admin credentials
+ available for v2 but not for v3.
+
+ :param identity_version: 'v2' or 'v3'
+ """
is_admin = True
# If dynamic credentials is enabled admin will be available
if CONF.auth.use_dynamic_credentials:
@@ -115,8 +167,8 @@
# Check whether test accounts file has the admin specified or not
elif CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
- identity_version=identity_version, name='check_admin',
- **get_preprov_provider_params())
+ name='check_admin',
+ **get_preprov_provider_params(identity_version))
if not check_accounts.admin_available():
is_admin = False
else:
@@ -128,20 +180,24 @@
return is_admin
-# We want a helper function here to check and see if alt credentials
-# are available so we can do a single call from skip_checks if alt
-# creds area available.
-# This depends on identity_version as there may be alt credentials
-# available for v2 but not for v3.
def is_alt_available(identity_version):
+ """Helper to check for alt credentials
+
+ Helper function to check if a second set of credentials is available (aka
+ alt credentials) so we can do a single call from skip_checks.
+ This helper depends on identity_version as there may be alt credentials
+ available for v2 but not for v3.
+
+ :param identity_version: 'v2' or 'v3'
+ """
# If dynamic credentials is enabled alt will be available
if CONF.auth.use_dynamic_credentials:
return True
# Check whether test accounts file has the admin specified or not
if CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
- identity_version=identity_version, name='check_alt',
- **get_preprov_provider_params())
+ name='check_alt',
+ **get_preprov_provider_params(identity_version))
else:
raise exceptions.InvalidConfiguration(
'A valid credential provider is needed')
@@ -171,9 +227,19 @@
}
-# Read credentials from configuration, builds a Credentials object
-# based on the specified or configured version
def get_configured_admin_credentials(fill_in=True, identity_version=None):
+ """Get admin credentials from the config file
+
+ Read credentials from configuration, builds a Credentials object based on
+ the specified or configured version
+
+ :param fill_in: If True, a request to the Token API is submitted, and the
+ credential object is filled in with all names and IDs from
+ the token API response.
+ :param identity_version: The identity version to talk to and the type of
+ credentials object to be created. 'v2' or 'v3'.
+ :returns: An object of a sub-type of `auth.Credentials`
+ """
identity_version = identity_version or CONF.identity.auth_version
if identity_version not in ('v2', 'v3'):
@@ -202,9 +268,20 @@
return credentials
-# Wrapper around auth.get_credentials to use the configured identity version
-# if none is specified
def get_credentials(fill_in=True, identity_version=None, **kwargs):
+ """Get credentials from dict based on config
+
+ Wrapper around auth.get_credentials to use the configured identity version
+ if none is specified.
+
+ :param fill_in: If True, a request to the Token API is submitted, and the
+ credential object is filled in with all names and IDs from
+ the token API response.
+ :param identity_version: The identity version to talk to and the type of
+ credentials object to be created. 'v2' or 'v3'.
+ :param kwargs: Attributes to be used to build the Credentials object.
+ :returns: An object of a sub-type of `auth.Credentials`
+ """
params = dict(DEFAULT_PARAMS, **kwargs)
identity_version = identity_version or CONF.identity.auth_version
# In case of "v3" add the domain from config if not specified
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 9319d2a..52ccfa9 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -64,10 +64,14 @@
# Show header line too
selected.append(l)
# lsblk lists disk type in a column right-aligned with TYPE
- elif pos > 0 and l[pos:pos + 4] == "disk":
+ elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk":
selected.append(l)
- return "\n".join(selected)
+ if selected:
+ return "\n".join(selected)
+ else:
+ msg = "'TYPE' column is required but the output doesn't have it: "
+ raise tempest.lib.exceptions.TempestException(msg + output)
def get_boot_time(self):
cmd = 'cut -f1 -d. /proc/uptime'
@@ -89,12 +93,12 @@
def get_nic_name_by_mac(self, address):
cmd = "ip -o link | awk '/%s/ {print $2}'" % address
nic = self.exec_command(cmd)
- return nic.strip().strip(":").lower()
+ return nic.strip().strip(":").split('@')[0].lower()
def get_nic_name_by_ip(self, address):
cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
nic = self.exec_command(cmd)
- return nic.strip().strip(":").lower()
+ return nic.strip().strip(":").split('@')[0].lower()
def get_dns_servers(self):
cmd = 'cat /etc/resolv.conf'
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index b15796f..84f1c9d 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -13,20 +13,14 @@
from oslo_log import log as logging
-from tempest import config
-
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
-CONF = config.CONF
LOG = logging.getLogger(__name__)
-def _create_neutron_sec_group_rules(os, sec_group):
+def _create_neutron_sec_group_rules(os, sec_group, ethertype='IPv4'):
sec_group_rules_client = os.security_group_rules_client
- ethertype = 'IPv4'
- if CONF.validation.ip_version_for_ssh == 6:
- ethertype = 'IPv6'
sec_group_rules_client.create_security_group_rule(
security_group_id=sec_group['id'],
@@ -42,7 +36,8 @@
direction='ingress')
-def create_ssh_security_group(os, add_rule=False):
+def create_ssh_security_group(os, add_rule=False, ethertype='IPv4',
+ use_neutron=True):
security_groups_client = os.compute_security_groups_client
security_group_rules_client = os.compute_security_group_rules_client
sg_name = data_utils.rand_name('securitygroup-')
@@ -50,8 +45,9 @@
security_group = security_groups_client.create_security_group(
name=sg_name, description=sg_description)['security_group']
if add_rule:
- if CONF.service_available.neutron:
- _create_neutron_sec_group_rules(os, security_group)
+ if use_neutron:
+ _create_neutron_sec_group_rules(os, security_group,
+ ethertype=ethertype)
else:
security_group_rules_client.create_security_group_rule(
parent_group_id=security_group['id'], ip_protocol='tcp',
@@ -64,7 +60,10 @@
return security_group
-def create_validation_resources(os, validation_resources=None):
+def create_validation_resources(os, validation_resources=None,
+ ethertype='IPv4', use_neutron=True,
+ floating_network_id=None,
+ floating_network_name=None):
# Create and Return the validation resources required to validate a VM
validation_data = {}
if validation_resources:
@@ -78,12 +77,26 @@
if validation_resources['security_group_rules']:
add_rule = True
validation_data['security_group'] = \
- create_ssh_security_group(os, add_rule)
+ create_ssh_security_group(
+ os, add_rule, use_neutron=use_neutron, ethertype=ethertype)
if validation_resources['floating_ip']:
- floating_client = os.compute_floating_ips_client
- validation_data.update(
- floating_client.create_floating_ip(
- pool=CONF.network.floating_network_name))
+ if use_neutron:
+ floatingip = os.floating_ips_client.create_floatingip(
+ floating_network_id=floating_network_id)
+ # validation_resources['floating_ip'] has historically looked
+ # like a compute API POST /os-floating-ips response, so we need
+ # to mangle it a bit for a Neutron response with different
+ # fields.
+ validation_data['floating_ip'] = floatingip['floatingip']
+ validation_data['floating_ip']['ip'] = (
+ floatingip['floatingip']['floating_ip_address'])
+ else:
+ # NOTE(mriedem): The os-floating-ips compute API was deprecated
+ # in the 2.36 microversion. Any tests for CRUD operations on
+ # floating IPs using the compute API should be capped at 2.35.
+ validation_data.update(
+ os.compute_floating_ips_client.create_floating_ip(
+ pool=floating_network_name))
return validation_data
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 9c83c99..f4c2866 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -179,24 +179,27 @@
raise lib_exc.TimeoutException(message)
-def wait_for_volume_resource_status(client, resource_id, status):
- """Waits for a volume resource to reach a given status.
+def wait_for_volume_resource_status(client, resource_id, statuses):
+ """Waits for a volume resource to reach any of the specified statuses.
This function is a common function for volume, snapshot and backup
resources. The function extracts the name of the desired resource from
the client class name of the resource.
"""
- resource_name = re.findall(r'(Volume|Snapshot|Backup)',
- client.__class__.__name__)[0].lower()
+ if not isinstance(statuses, list):
+ statuses = [statuses]
+ resource_name = re.findall(
+ r'(volume|group-snapshot|snapshot|backup|group)',
+ client.resource_type)[-1].replace('-', '_')
show_resource = getattr(client, 'show_' + resource_name)
resource_status = show_resource(resource_id)[resource_name]['status']
start = int(time.time())
- while resource_status != status:
+ while resource_status not in statuses:
time.sleep(client.build_interval)
resource_status = show_resource(resource_id)[
'{}'.format(resource_name)]['status']
- if resource_status == 'error' and resource_status != status:
+ if resource_status == 'error' and resource_status not in statuses:
raise exceptions.VolumeResourceBuildErrorException(
resource_name=resource_name, resource_id=resource_id)
if resource_name == 'volume' and resource_status == 'error_restoring':
@@ -205,7 +208,7 @@
if int(time.time()) - start >= client.build_timeout:
message = ('%s %s failed to reach %s status (current %s) '
'within the required time (%s s).' %
- (resource_name, resource_id, status, resource_status,
+ (resource_name, resource_id, statuses, resource_status,
client.build_timeout))
raise lib_exc.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index f5b2f0d..af9eefc 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -161,7 +161,9 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for OpenStack Identity "
- "(Keystone) API v3"),
+ "(Keystone) API v3. The default value adminURL is "
+ "deprecated and will be modified to publicURL in "
+ "the next release."),
cfg.StrOpt('admin_role',
default='admin',
help="Role required to administrate keystone."),
@@ -645,6 +647,9 @@
cfg.BoolOpt('port_security',
default=False,
help="Does the test environment support port security?"),
+ cfg.BoolOpt('floating_ips',
+ default=True,
+ help='Does the test environment support floating_ips')
]
validation_group = cfg.OptGroup(name='validation',
@@ -763,6 +768,12 @@
"It contains two elements, the first is ref type "
"(like 'source-name', 'source-id', etc), the second is "
"volume name template used in storage backend"),
+ cfg.ListOpt('manage_snapshot_ref',
+ default=['source-name', '_snapshot-%s'],
+ help="A reference to existing snapshot for snapshot manage. "
+ "It contains two elements, the first is ref type "
+ "(like 'source-name', 'source-id', etc), the second is "
+ "snapshot name template used in storage backend"),
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
@@ -821,7 +832,7 @@
default=True,
help="Is the v2 volume API enabled"),
cfg.BoolOpt('api_v3',
- default=False,
+ default=True,
help="Is the v3 volume API enabled")
]
@@ -899,38 +910,58 @@
OrchestrationGroup = [
cfg.StrOpt('catalog_type',
default='orchestration',
- help="Catalog type of the Orchestration service."),
+ help="Catalog type of the Orchestration service.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.StrOpt('region',
default='',
help="The orchestration region name to use. If empty, the "
"value of identity.region is used instead. If no such "
"region is found in the service catalog, the first found "
- "one is used."),
+ "one is used.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
- help="The endpoint type to use for the orchestration service."),
+ help="The endpoint type to use for the orchestration service.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.StrOpt('stack_owner_role', default='heat_stack_owner',
- help='Role required for users to be able to manage stacks'),
+ help='Role required for users to be able to manage stacks',
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.IntOpt('build_interval',
default=1,
- help="Time in seconds between build status checks."),
+ help="Time in seconds between build status checks.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.IntOpt('build_timeout',
default=1200,
- help="Timeout in seconds to wait for a stack to build."),
+ help="Timeout in seconds to wait for a stack to build.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.StrOpt('instance_type',
default='m1.micro',
help="Instance type for tests. Needs to be big enough for a "
- "full OS plus the test workload"),
+ "full OS plus the test workload",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.StrOpt('keypair_name',
- help="Name of existing keypair to launch servers with."),
+ help="Name of existing keypair to launch servers with.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.IntOpt('max_template_size',
default=524288,
- help="Value must match heat configuration of the same name."),
+ help="Value must match heat configuration of the same name.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
cfg.IntOpt('max_resources_per_stack',
default=1000,
- help="Value must match heat configuration of the same name."),
+ help="Value must match heat configuration of the same name.",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
]
@@ -996,7 +1027,9 @@
help="Whether or not nova is expected to be available"),
cfg.BoolOpt('heat',
default=False,
- help="Whether or not Heat is expected to be available"),
+ help="Whether or not Heat is expected to be available",
+ deprecated_for_removal=True,
+ deprecated_reason='Heat support will be removed from Tempest'),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1037,6 +1070,15 @@
"prefix to ideintify resources which are "
"created by Tempest and no projects set "
"this option on OpenStack dev community."),
+ cfg.BoolOpt('pause_teardown',
+ default=False,
+ help="""Whether to pause a test in global teardown.
+
+The best use case is investigating used resources of one test.
+A test can be run as follows:
+ $ ostestr --pdb TEST_ID
+or
+ $ python -m testtools.run TEST_ID"""),
]
_opts = [
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index a437761..b5b2d71 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -52,12 +52,5 @@
"the configured network")
-# NOTE(andreaf) This exception is added here to facilitate the migration
-# of get_network_from_name and preprov_creds to tempest.lib, and it should
-# be migrated along with them
-class InvalidTestResource(exceptions.TempestException):
- message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
-
-
class RFCViolation(exceptions.RestClientException):
message = "RFC Violation"
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 4123ae5..aae685c 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -33,6 +33,7 @@
METHOD_GET_RESOURCE = re.compile(r"^\s*def (list|show)\_.+")
METHOD_DELETE_RESOURCE = re.compile(r"^\s*def delete_.+")
CLASS = re.compile(r"^class .+")
+EX_ATTRIBUTE = re.compile(r'(\s+|\()(e|ex|exc|exception).message(\s+|\))')
def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
@@ -273,6 +274,38 @@
yield(0, msg)
+def dont_put_admin_tests_on_nonadmin_path(logical_line, physical_line,
+ filename):
+ """Check admin tests should exist under admin path
+
+ T115
+ """
+
+ if 'tempest/api/' not in filename:
+ return
+
+ if pep8.noqa(physical_line):
+ return
+
+ if not re.match('class .*Test.*\(.*Admin.*\):', logical_line):
+ return
+
+ if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename):
+ msg = 'T115: All admin tests should exist under admin path.'
+ yield(0, msg)
+
+
+def unsupported_exception_attribute_PY3(logical_line):
+ """Check Unsupported 'message' exception attribute in PY3
+
+ T116
+ """
+ result = EX_ATTRIBUTE.search(logical_line)
+ msg = ("[T116] Unsupported 'message' Exception attribute in PY3")
+ if result:
+ yield(0, msg)
+
+
def factory(register):
register(import_no_clients_in_api_and_scenario_tests)
register(scenario_tests_need_service_tags)
@@ -287,3 +320,5 @@
register(dont_import_local_tempest_into_lib)
register(dont_use_config_in_tempest_lib)
register(use_rand_uuid_instead_of_uuid4)
+ register(dont_put_admin_tests_on_nonadmin_path)
+ register(unsupported_exception_attribute_PY3)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 33a7757..2954de0 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -420,8 +420,13 @@
'properties': {
'event': {'type': 'string'},
'start_time': parameter_types.date_time,
- 'finish_time': parameter_types.date_time,
- 'result': {'type': 'string'},
+ # The finish_time, result and optionally traceback are all
+ # possibly None (null) until the event is actually finished.
+ # The traceback would only be set if there was an error, but
+ # when the event is complete both finish_time and result will
+ # be set.
+ 'finish_time': parameter_types.date_time_or_null,
+ 'result': {'type': ['string', 'null']},
'traceback': {'type': ['string', 'null']}
},
'additionalProperties': False,
@@ -582,3 +587,10 @@
'required': ['adminPass']
}
}
+
+show_server_diagnostics = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object'
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/services.py b/tempest/lib/api_schema/response/compute/v2_1/services.py
index 6949f86..3b58ece 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/services.py
@@ -65,3 +65,25 @@
'required': ['service']
}
}
+
+disable_log_reason = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'disabled_reason': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['disabled_reason', 'binary', 'host', 'status']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['service']
+ }
+}
diff --git a/tempest/api/orchestration/__init__.py b/tempest/lib/api_schema/response/compute/v2_11/__init__.py
similarity index 100%
copy from tempest/api/orchestration/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_11/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py
new file mode 100644
index 0000000..18b833b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_11/services.py
@@ -0,0 +1,46 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import services
+
+
+list_services = copy.deepcopy(services.list_services)
+list_services['response_body']['properties']['services']['items'][
+ 'properties']['forced_down'] = {'type': 'boolean'}
+list_services['response_body']['properties']['services']['items'][
+ 'required'].append('forced_down')
+
+update_forced_down = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'binary': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'forced_down': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['binary', 'host', 'forced_down']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['service']
+ }
+}
diff --git a/tempest/api/orchestration/__init__.py b/tempest/lib/api_schema/response/compute/v2_13/__init__.py
similarity index 100%
copy from tempest/api/orchestration/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_13/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_13/servers.py b/tempest/lib/api_schema/response/compute/v2_13/servers.py
new file mode 100644
index 0000000..a90f3e4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_13/servers.py
@@ -0,0 +1,34 @@
+# Copyright 2017 NTT 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 servers
+
+
+common_server_group = copy.deepcopy(servers.common_server_group)
+common_server_group['properties']['project_id'] = {'type': 'string'}
+common_server_group['properties']['user_id'] = {'type': 'string'}
+common_server_group['required'].append('project_id')
+common_server_group['required'].append('user_id')
+
+create_show_server_group = copy.deepcopy(servers.create_show_server_group)
+create_show_server_group['response_body']['properties'][
+ 'server_group'] = common_server_group
+
+delete_server_group = copy.deepcopy(servers.delete_server_group)
+
+list_server_groups = copy.deepcopy(servers.list_server_groups)
+list_server_groups['response_body']['properties']['server_groups'][
+ 'items'] = common_server_group
diff --git a/tempest/api/orchestration/__init__.py b/tempest/lib/api_schema/response/compute/v2_47/__init__.py
similarity index 100%
copy from tempest/api/orchestration/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_47/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
new file mode 100644
index 0000000..37a084f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -0,0 +1,39 @@
+# 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_26 import servers as servers226
+
+flavor = {
+ 'type': 'object',
+ 'properties': {
+ 'original_name': {'type': 'string'},
+ 'disk': {'type': 'integer'},
+ 'ephemeral': {'type': 'integer'},
+ 'ram': {'type': 'integer'},
+ 'swap': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ 'extra_specs': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['original_name', 'disk', 'ephemeral', 'ram', 'swap', 'vcpus']
+}
+
+get_server = copy.deepcopy(servers226.get_server)
+get_server['response_body']['properties']['server'][
+ 'properties'].update({'flavor': flavor})
diff --git a/tempest/api/orchestration/__init__.py b/tempest/lib/api_schema/response/compute/v2_48/__init__.py
similarity index 100%
copy from tempest/api/orchestration/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_48/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
new file mode 100644
index 0000000..5904758
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -0,0 +1,115 @@
+# Copyright 2017 Mirantis Inc.
+#
+# 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
+from tempest.lib.api_schema.response.compute.v2_47 import servers as servers247
+
+
+show_server_diagnostics = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'state': {
+ 'type': 'string', 'enum': [
+ 'pending', 'running', 'paused', 'shutdown', 'crashed',
+ 'suspended']
+ },
+ 'driver': {
+ 'type': 'string', 'enum': [
+ 'libvirt', 'xenapi', 'vmwareapi', 'ironic', 'hyperv']
+ },
+ 'hypervisor': {'type': ['string', 'null']},
+ 'hypervisor_os': {'type': ['string', 'null']},
+ 'uptime': {'type': ['integer', 'null']},
+ 'config_drive': {'type': 'boolean'},
+ 'num_cpus': {'type': 'integer'},
+ 'num_nics': {'type': 'integer'},
+ 'num_disks': {'type': 'integer'},
+ 'memory_details': {
+ 'type': 'object',
+ 'properties': {
+ 'maximum': {'type': ['integer', 'null']},
+ 'used': {'type': ['integer', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['maximum', 'used']
+ },
+ 'cpu_details': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'null']},
+ 'time': {'type': ['integer', 'null']},
+ 'utilisation': {'type': ['integer', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'time', 'utilisation']
+ }
+ },
+ 'nic_details': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'mac_address': {'oneOf': [parameter_types.mac_address,
+ {'type': 'null'}]},
+ 'rx_octets': {'type': ['integer', 'null']},
+ 'rx_errors': {'type': ['integer', 'null']},
+ 'rx_drop': {'type': ['integer', 'null']},
+ 'rx_packets': {'type': ['integer', 'null']},
+ 'rx_rate': {'type': ['integer', 'null']},
+ 'tx_octets': {'type': ['integer', 'null']},
+ 'tx_errors': {'type': ['integer', 'null']},
+ 'tx_drop': {'type': ['integer', 'null']},
+ 'tx_packets': {'type': ['integer', 'null']},
+ 'tx_rate': {'type': ['integer', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['mac_address', 'rx_octets', 'rx_errors',
+ 'rx_drop',
+ 'rx_packets', 'rx_rate', 'tx_octets',
+ 'tx_errors',
+ 'tx_drop', 'tx_packets', 'tx_rate']
+ }
+ },
+ 'disk_details': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'read_bytes': {'type': ['integer', 'null']},
+ 'read_requests': {'type': ['integer', 'null']},
+ 'write_bytes': {'type': ['integer', 'null']},
+ 'write_requests': {'type': ['integer', 'null']},
+ 'errors_count': {'type': ['integer', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['read_bytes', 'read_requests', 'write_bytes',
+ 'write_requests', 'errors_count']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'state', 'driver', 'hypervisor', 'hypervisor_os', 'uptime',
+ 'config_drive', 'num_cpus', 'num_nics', 'num_disks',
+ 'memory_details', 'cpu_details', 'nic_details', 'disk_details'],
+ }
+}
+
+get_server = copy.deepcopy(servers247.get_server)
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 1b450ab..42ed41b 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -22,8 +22,9 @@
@six.add_metaclass(abc.ABCMeta)
class CredentialProvider(object):
- def __init__(self, identity_version, name=None, network_resources=None,
- credentials_domain=None, admin_role=None):
+ def __init__(self, identity_version, name=None,
+ network_resources=None, credentials_domain=None,
+ admin_role=None, identity_uri=None):
"""A CredentialProvider supplies credentials to test classes.
:param identity_version: Identity version of the credentials provided
@@ -33,8 +34,11 @@
credentials
:param credentials_domain: Domain credentials belong to
:param admin_role: Name of the role of the admin account
+ :param identity_uri: Identity URI of the target cloud. This *must* be
+ specified for anything to work.
"""
self.identity_version = identity_version
+ self.identity_uri = identity_uri
self.name = name or "test_creds"
self.network_resources = network_resources
self.credentials_domain = credentials_domain or 'Default'
diff --git a/tempest/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
similarity index 88%
rename from tempest/common/dynamic_creds.py
rename to tempest/lib/common/dynamic_creds.py
index 88fe26c..90e67b4 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -12,15 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import ipaddress
+
import netaddr
from oslo_log import log as logging
import six
-from tempest import clients
from tempest.lib.common import cred_client
from tempest.lib.common import cred_provider
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services import clients
LOG = logging.getLogger(__name__)
@@ -33,7 +35,8 @@
identity_admin_role='admin', extra_roles=None,
neutron_available=False, create_networks=True,
project_network_cidr=None, project_network_mask_bits=None,
- public_network_id=None, resource_prefix=None):
+ public_network_id=None, resource_prefix=None,
+ identity_admin_endpoint_type='public', identity_uri=None):
"""Creates credentials dynamically for tests
A credential provider that, based on an initial set of
@@ -67,10 +70,14 @@
:param project_network_mask_bits: The network mask bits to use for
created project networks
:param public_network_id: The id for the public network to use
+ :param identity_admin_endpoint_type: The endpoint type for identity
+ admin clients. Defaults to public.
+ :param identity_uri: Identity URI of the target cloud
"""
super(DynamicCredentialProvider, self).__init__(
- identity_version=identity_version, admin_role=admin_role,
- name=name, credentials_domain=credentials_domain,
+ identity_version=identity_version, identity_uri=identity_uri,
+ admin_role=admin_role, name=name,
+ credentials_domain=credentials_domain,
network_resources=network_resources)
self.network_resources = network_resources
self._creds = {}
@@ -84,6 +91,7 @@
self.default_admin_creds = admin_creds
self.identity_admin_domain_scope = identity_admin_domain_scope
self.identity_admin_role = identity_admin_role or 'admin'
+ self.identity_admin_endpoint_type = identity_admin_endpoint_type
self.extra_roles = extra_roles or []
(self.identity_admin_client,
self.tenants_admin_client,
@@ -94,7 +102,8 @@
self.routers_admin_client,
self.subnets_admin_client,
self.ports_admin_client,
- self.security_groups_admin_client) = self._get_admin_clients()
+ self.security_groups_admin_client) = self._get_admin_clients(
+ identity_admin_endpoint_type)
# Domain where isolated credentials are provisioned (v3 only).
# Use that of the admin account is None is configured.
self.creds_domain_name = None
@@ -110,32 +119,43 @@
self.domains_admin_client,
self.creds_domain_name)
- def _get_admin_clients(self):
+ def _get_admin_clients(self, endpoint_type):
"""Returns a tuple with instances of the following admin clients
(in this order):
identity
network
"""
- os = clients.Manager(self.default_admin_creds)
+ os = clients.ServiceClients(self.default_admin_creds,
+ self.identity_uri)
+ params = {'endpoint_type': endpoint_type}
if self.identity_version == 'v2':
- return (os.identity_client, os.tenants_client, os.users_client,
- os.roles_client, None,
- os.networks_client, os.routers_client, os.subnets_client,
- os.ports_client, os.security_groups_client)
+ return (os.identity_v2.IdentityClient(**params),
+ os.identity_v2.TenantsClient(**params),
+ os.identity_v2.UsersClient(**params),
+ os.identity_v2.RolesClient(**params), None,
+ os.network.NetworksClient(),
+ os.network.RoutersClient(),
+ os.network.SubnetsClient(),
+ os.network.PortsClient(),
+ os.network.SecurityGroupsClient())
else:
# We use a dedicated client manager for identity client in case we
# need a different token scope for them.
scope = 'domain' if self.identity_admin_domain_scope else 'project'
- identity_os = clients.Manager(self.default_admin_creds,
- scope=scope)
- return (identity_os.identity_v3_client,
- identity_os.projects_client,
- identity_os.users_v3_client, identity_os.roles_v3_client,
- identity_os.domains_client,
- os.networks_client, os.routers_client,
- os.subnets_client, os.ports_client,
- os.security_groups_client)
+ identity_os = clients.ServiceClients(self.default_admin_creds,
+ self.identity_uri,
+ scope=scope)
+ return (identity_os.identity_v3.IdentityClient(**params),
+ identity_os.identity_v3.ProjectsClient(**params),
+ identity_os.identity_v3.UsersClient(**params),
+ identity_os.identity_v3.RolesClient(**params),
+ identity_os.identity_v3.DomainsClient(**params),
+ os.network.NetworksClient(),
+ os.network.RoutersClient(),
+ os.network.SubnetsClient(),
+ os.network.PortsClient(),
+ os.network.SecurityGroupsClient())
def _create_creds(self, admin=False, roles=None):
"""Create credentials with random name.
@@ -275,14 +295,16 @@
name=subnet_name,
tenant_id=tenant_id,
enable_dhcp=self.network_resources['dhcp'],
- ip_version=4)
+ ip_version=(ipaddress.ip_network(
+ six.text_type(subnet_cidr)).version))
else:
resp_body = self.subnets_admin_client.\
create_subnet(network_id=network_id,
cidr=str(subnet_cidr),
name=subnet_name,
tenant_id=tenant_id,
- ip_version=4)
+ ip_version=(ipaddress.ip_network(
+ six.text_type(subnet_cidr)).version))
break
except lib_exc.BadRequest as e:
if 'overlaps with another subnet' not in str(e):
diff --git a/tempest/common/fixed_network.py b/tempest/lib/common/fixed_network.py
similarity index 99%
rename from tempest/common/fixed_network.py
rename to tempest/lib/common/fixed_network.py
index 4032c90..e2054a4 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/lib/common/fixed_network.py
@@ -14,8 +14,8 @@
from oslo_log import log as logging
-from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
LOG = logging.getLogger(__name__)
diff --git a/tempest/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
similarity index 96%
rename from tempest/common/preprov_creds.py
rename to tempest/lib/common/preprov_creds.py
index 8053cac..cd3a10e 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -20,12 +20,11 @@
import six
import yaml
-from tempest import clients
-from tempest.common import fixed_network
-from tempest import exceptions
from tempest.lib import auth
from tempest.lib.common import cred_provider
+from tempest.lib.common import fixed_network
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services import clients
LOG = logging.getLogger(__name__)
@@ -51,7 +50,7 @@
def __init__(self, identity_version, test_accounts_file,
accounts_lock_dir, name=None, credentials_domain=None,
admin_role=None, object_storage_operator_role=None,
- object_storage_reseller_admin_role=None):
+ object_storage_reseller_admin_role=None, identity_uri=None):
"""Credentials provider using pre-provisioned accounts
This credentials provider loads the details of pre-provisioned
@@ -79,10 +78,12 @@
(if no domain is configured)
:param object_storage_operator_role: name of the role
:param object_storage_reseller_admin_role: name of the role
+ :param identity_uri: Identity URI of the target cloud
"""
super(PreProvisionedCredentialProvider, self).__init__(
identity_version=identity_version, name=name,
- admin_role=admin_role, credentials_domain=credentials_domain)
+ admin_role=admin_role, credentials_domain=credentials_domain,
+ identity_uri=identity_uri)
self.test_accounts_file = test_accounts_file
if test_accounts_file:
accounts = read_accounts_yaml(self.test_accounts_file)
@@ -341,13 +342,14 @@
auth_url=None, fill_in=False,
identity_version=self.identity_version, **creds_dict)
net_creds = cred_provider.TestResources(credential)
- net_clients = clients.Manager(credentials=credential)
- compute_network_client = net_clients.compute_networks_client
+ net_clients = clients.ServiceClients(credentials=credential,
+ identity_uri=self.identity_uri)
+ compute_network_client = net_clients.compute.NetworksClient()
net_name = self.hash_dict['networks'].get(hash, None)
try:
network = fixed_network.get_network_from_name(
net_name, compute_network_client)
- except exceptions.InvalidTestResource:
+ except lib_exc.InvalidTestResource:
network = {}
net_creds.set_resources(network=network)
return net_creds
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index d72b4dd..f58d737 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -371,7 +371,7 @@
on the endpoint in the catalog will return a list of supported API
versions.
- :return tuple with response headers and list of version numbers
+ :return: tuple with response headers and list of version numbers
:rtype: tuple
"""
resp, body = self.get('')
@@ -474,7 +474,7 @@
# Ensure there are not more than one top-level keys
# NOTE(freerunner): Ensure, that JSON is not nullable to
# to prevent StopIteration Exception
- if len(body.keys()) != 1:
+ if not hasattr(body, "keys") or len(body.keys()) != 1:
return body
# Just return the "wrapped" element
first_key, first_item = six.next(six.iteritems(body))
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 657c0c1..d4ec6ad 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -84,10 +84,6 @@
ssh.set_missing_host_key_policy(
paramiko.AutoAddPolicy())
_start_time = time.time()
- if self.proxy_client is not None:
- proxy_chan = self._get_proxy_channel()
- else:
- proxy_chan = None
if self.pkey is not None:
LOG.info("Creating ssh connection to '%s:%d' as '%s'"
" with public key authentication",
@@ -98,6 +94,10 @@
self.host, self.port, self.username, str(self.password))
attempts = 0
while True:
+ if self.proxy_client is not None:
+ proxy_chan = self._get_proxy_channel()
+ else:
+ proxy_chan = None
try:
ssh.connect(self.host, port=self.port, username=self.username,
password=self.password,
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 64d6be2..aef2ff3 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -28,29 +28,37 @@
def wrapper(self, *args, **kwargs):
try:
return function(self, *args, **kwargs)
- except tempest.lib.exceptions.SSHTimeout:
- try:
- original_exception = sys.exc_info()
- caller = test_utils.find_test_caller() or "not found"
- if self.server:
- msg = 'Caller: %s. Timeout trying to ssh to server %s'
- LOG.debug(msg, caller, self.server)
- if self.console_output_enabled and self.servers_client:
- try:
- msg = 'Console log for server %s: %s'
- console_log = (
- self.servers_client.get_console_output(
- self.server['id'])['output'])
- LOG.debug(msg, self.server['id'], console_log)
- except Exception:
- msg = 'Could not get console_log for server %s'
- LOG.debug(msg, self.server['id'])
- # re-raise the original ssh timeout exception
- six.reraise(*original_exception)
- finally:
- # Delete the traceback to avoid circular references
- _, _, trace = original_exception
- del trace
+ except Exception as e:
+ caller = test_utils.find_test_caller() or "not found"
+ if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
+ message = ('Initializing SSH connection to %(ip)s failed. '
+ 'Error: %(error)s' % {'ip': self.ip_address,
+ 'error': e})
+ message = '(%s) %s' % (caller, message)
+ LOG.error(message)
+ raise
+ else:
+ try:
+ original_exception = sys.exc_info()
+ if self.server:
+ msg = 'Caller: %s. Timeout trying to ssh to server %s'
+ LOG.debug(msg, caller, self.server)
+ if self.console_output_enabled and self.servers_client:
+ try:
+ msg = 'Console log for server %s: %s'
+ console_log = (
+ self.servers_client.get_console_output(
+ self.server['id'])['output'])
+ LOG.debug(msg, self.server['id'], console_log)
+ except Exception:
+ msg = 'Could not get console_log for server %s'
+ LOG.debug(msg, self.server['id'])
+ # re-raise the original ssh timeout exception
+ six.reraise(*original_exception)
+ finally:
+ # Delete the traceback to avoid circular references
+ _, _, trace = original_exception
+ del trace
return wrapper
@@ -78,6 +86,7 @@
"""
self.server = server
self.servers_client = servers_client
+ self.ip_address = ip_address
self.console_output_enabled = console_output_enabled
self.ssh_shell_prologue = ssh_shell_prologue
self.ping_count = ping_count
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 68ce57a..c538c72 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -44,6 +44,9 @@
def __str__(self):
return self._error_string
+ def __repr__(self):
+ return self._error_string
+
class RestClientException(TempestException,
testtools.TestCase.failureException):
@@ -269,3 +272,7 @@
class DeleteErrorException(TempestException):
message = ("Resource %(resource_id)s failed to delete "
"and is in ERROR status")
+
+
+class InvalidTestResource(TempestException):
+ message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index cd3bab0..4fa7a7a 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -17,10 +17,12 @@
import copy
import importlib
import inspect
+import sys
import warnings
from debtcollector import removals
from oslo_log import log as logging
+import testtools
from tempest.lib import auth
from tempest.lib.common.utils import misc
@@ -85,6 +87,7 @@
extra_service_versions = set([])
_tempest_modules = set(tempest_modules())
plugin_services = ClientsRegistry().get_service_clients()
+ name_conflicts = []
for plugin_name in plugin_services:
plug_service_versions = set([x['service_version'] for x in
plugin_services[plugin_name]])
@@ -96,8 +99,8 @@
'claimed by another one' % (plugin_name,
extra_service_versions &
plug_service_versions))
- raise exceptions.PluginRegistrationException(
- name=plugin_name, detailed_error=detailed_error)
+ name_conflicts.append(exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error))
# NOTE(andreaf) Once all tempest clients are stable, the following
# if will have to be removed.
if not plug_service_versions.isdisjoint(
@@ -107,9 +110,14 @@
'claimed by a Tempest one' % (plugin_name,
_tempest_internal_modules() &
plug_service_versions))
- raise exceptions.PluginRegistrationException(
- name=plugin_name, detailed_error=detailed_error)
+ name_conflicts.append(exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error))
extra_service_versions |= plug_service_versions
+ if name_conflicts:
+ LOG.error(
+ 'Failed to list available modules due to name conflicts: %s',
+ name_conflicts)
+ raise testtools.MultipleExceptions(*name_conflicts)
return _tempest_modules | extra_service_versions
@@ -161,7 +169,7 @@
:param kwargs: Parameters to be passed to all clients. Parameters
values can be overwritten when clients are initialised, but
parameters cannot be deleted.
- :raise ImportError if the specified module_path cannot be imported
+ :raise ImportError: if the specified module_path cannot be imported
Example::
@@ -375,6 +383,7 @@
# Register service clients from the registry (__tempest__ and plugins)
clients_registry = ClientsRegistry()
plugin_service_clients = clients_registry.get_service_clients()
+ registration_errors = []
for plugin in plugin_service_clients:
service_clients = plugin_service_clients[plugin]
# Each plugin returns a list of service client parameters
@@ -385,10 +394,12 @@
try:
self.register_service_client_module(**service_client)
except Exception:
+ registration_errors.append(sys.exc_info())
LOG.exception(
'Failed to register service client from plugin %s '
'with parameters %s', plugin, service_client)
- raise
+ if registration_errors:
+ raise testtools.MultipleExceptions(*registration_errors)
def register_service_client_module(self, name, service_version,
module_path, client_names, **kwargs):
diff --git a/tempest/lib/services/compute/baremetal_nodes_client.py b/tempest/lib/services/compute/baremetal_nodes_client.py
index 06dc369..e44c195 100644
--- a/tempest/lib/services/compute/baremetal_nodes_client.py
+++ b/tempest/lib/services/compute/baremetal_nodes_client.py
@@ -25,7 +25,12 @@
"""Tests Baremetal API"""
def list_baremetal_nodes(self, **params):
- """List all baremetal nodes."""
+ """List all baremetal nodes.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#list-bare-metal-nodes
+ """
url = 'os-baremetal-nodes'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -35,7 +40,11 @@
return rest_client.ResponseBody(resp, body)
def show_baremetal_node(self, baremetal_node_id):
- """Return the details of a single baremetal node."""
+ """Show the details of a single baremetal node.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/compute/#show-bare-metal-node-details
+ """
url = 'os-baremetal-nodes/%s' % baremetal_node_id
resp, body = self.get(url)
body = json.loads(body)
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
index 3a935b4..03cd645 100644
--- a/tempest/lib/services/compute/server_groups_client.py
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -17,12 +17,17 @@
from oslo_serialization import jsonutils as json
from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.api_schema.response.compute.v2_13 import servers as schemav213
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class ServerGroupsClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.12', 'schema': schema},
+ {'min': '2.13', 'max': None, 'schema': schemav213}]
+
def create_server_group(self, **kwargs):
"""Create the server group.
@@ -34,12 +39,14 @@
resp, body = self.post('os-server-groups', post_body)
body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.create_show_server_group, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_server_group(self, server_group_id):
"""Delete the given server-group."""
resp, body = self.delete("os-server-groups/%s" % server_group_id)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.delete_server_group, resp, body)
return rest_client.ResponseBody(resp, body)
@@ -47,6 +54,7 @@
"""List the server-groups."""
resp, body = self.get("os-server-groups")
body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_server_groups, resp, body)
return rest_client.ResponseBody(resp, body)
@@ -54,5 +62,6 @@
"""Get the details of given server_group."""
resp, body = self.get("os-server-groups/%s" % server_group_id)
body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.create_show_server_group, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index ff65b25..598d5a6 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -27,6 +27,8 @@
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
+from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
+from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client
@@ -43,7 +45,9 @@
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
- {'min': '2.26', 'max': None, 'schema': schemav226}]
+ {'min': '2.26', 'max': '2.46', 'schema': schemav226},
+ {'min': '2.47', 'max': '2.47', 'schema': schemav247},
+ {'min': '2.48', 'max': None, 'schema': schemav248}]
def __init__(self, auth_provider, service, region,
enable_instance_password=True, **kwargs):
@@ -656,7 +660,10 @@
def show_server_diagnostics(self, server_id):
"""Get the usage data for a server."""
resp, body = self.get("servers/%s/diagnostics" % server_id)
- return rest_client.ResponseBody(resp, json.loads(body))
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.show_server_diagnostics, resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_instance_actions(self, server_id):
"""List the provided server action."""
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index 77ac82f..b046c35 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -18,12 +18,18 @@
from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import services as schema
+from tempest.lib.api_schema.response.compute.v2_11 import services \
+ as schemav211
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class ServicesClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.10', 'schema': schema},
+ {'min': '2.11', 'max': None, 'schema': schemav211}]
+
def list_services(self, **params):
"""Lists all running Compute services for a tenant.
@@ -37,7 +43,8 @@
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(schema.list_services, resp, body)
+ _schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(_schema.list_services, resp, body)
return rest_client.ResponseBody(resp, body)
def enable_service(self, **kwargs):
@@ -65,3 +72,31 @@
body = json.loads(body)
self.validate_response(schema.enable_disable_service, resp, body)
return rest_client.ResponseBody(resp, body)
+
+ def disable_log_reason(self, **kwargs):
+ """Disables scheduling for a Compute service and logs reason.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/disable-log-reason', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.disable_log_reason, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_forced_down(self, **kwargs):
+ """Set or unset ``forced_down`` flag for the service.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#update-forced-down
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/force-down', post_body)
+ body = json.loads(body)
+ # NOTE: Use schemav211.update_forced_down directly because there is no
+ # update_forced_down schema for <2.11.
+ self.validate_response(schemav211.update_forced_down, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v2/identity_client.py b/tempest/lib/services/identity/v2/identity_client.py
index 6caff0e..c610d65 100644
--- a/tempest/lib/services/identity/v2/identity_client.py
+++ b/tempest/lib/services/identity/v2/identity_client.py
@@ -11,6 +11,7 @@
# under the License.
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -45,3 +46,24 @@
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+
+ def list_endpoints_for_token(self, token_id):
+ """List endpoints for a token """
+ resp, body = self.get("tokens/%s/endpoints" % token_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_token_existence(self, token_id, **params):
+ """Validates a token and confirms that it belongs to a tenant.
+
+ For a full list of available parameters, please refer to the
+ official API reference:
+ https://developer.openstack.org/api-ref/identity/v2-admin/#validate-token
+ """
+ url = "tokens/%s" % token_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.head(url)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index 6f498d9..a539d08 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations under
# the License.
+from tempest.lib.services.identity.v3.catalog_client import \
+ CatalogClient
from tempest.lib.services.identity.v3.credentials_client import \
CredentialsClient
from tempest.lib.services.identity.v3.domain_configuration_client \
@@ -19,6 +21,8 @@
from tempest.lib.services.identity.v3.domains_client import DomainsClient
from tempest.lib.services.identity.v3.endpoint_filter_client import \
EndPointsFilterClient
+from tempest.lib.services.identity.v3.endpoint_groups_client import \
+ EndPointGroupsClient
from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
from tempest.lib.services.identity.v3.groups_client import GroupsClient
from tempest.lib.services.identity.v3.identity_client import IdentityClient
@@ -26,6 +30,8 @@
InheritedRolesClient
from tempest.lib.services.identity.v3.oauth_consumers_client import \
OAUTHConsumerClient
+from tempest.lib.services.identity.v3.oauth_token_client import \
+ OAUTHTokenClient
from tempest.lib.services.identity.v3.policies_client import PoliciesClient
from tempest.lib.services.identity.v3.projects_client import ProjectsClient
from tempest.lib.services.identity.v3.regions_client import RegionsClient
@@ -38,9 +44,11 @@
from tempest.lib.services.identity.v3.users_client import UsersClient
from tempest.lib.services.identity.v3.versions_client import VersionsClient
-__all__ = ['CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
+__all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient',
+ 'DomainConfigurationClient', 'EndPointGroupsClient',
'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
- 'PoliciesClient', 'ProjectsClient', 'RegionsClient',
- 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
- 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
+ 'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient',
+ 'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
+ 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
+ 'VersionsClient']
diff --git a/tempest/services/object_storage/capabilities_client.py b/tempest/lib/services/identity/v3/catalog_client.py
similarity index 71%
copy from tempest/services/object_storage/capabilities_client.py
copy to tempest/lib/services/identity/v3/catalog_client.py
index 0fe437f..232b85a 100644
--- a/tempest/services/object_storage/capabilities_client.py
+++ b/tempest/lib/services/identity/v3/catalog_client.py
@@ -1,6 +1,3 @@
-# Copyright 2012 OpenStack Foundation
-# 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
@@ -13,19 +10,20 @@
# License for the specific language governing permissions and limitations
# under the License.
+"""
+https://developer.openstack.org/api-ref/identity/v3/index.html#get-service-catalog
+"""
+
from oslo_serialization import jsonutils as json
from tempest.lib.common import rest_client
-class CapabilitiesClient(rest_client.RestClient):
+class CatalogClient(rest_client.RestClient):
+ api_version = "v3"
- def list_capabilities(self):
- self.skip_path()
- try:
- resp, body = self.get('info')
- finally:
- self.reset_path()
- body = json.loads(body)
+ def show_catalog(self):
+ resp, body = self.get('auth/catalog')
self.expected_success(200, resp.status)
- return resp, body
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/endpoint_groups_client.py b/tempest/lib/services/identity/v3/endpoint_groups_client.py
new file mode 100644
index 0000000..ce99389
--- /dev/null
+++ b/tempest/lib/services/identity/v3/endpoint_groups_client.py
@@ -0,0 +1,78 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class EndPointGroupsClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_endpoint_group(self, **kwargs):
+ """Create endpoint group.
+
+ For a full list of available parameters, please refer to the
+ official API reference:
+ https://developer.openstack.org/api-ref/identity/v3-ext/#create-endpoint-group
+ """
+ post_body = json.dumps({'endpoint_group': kwargs})
+ resp, body = self.post('OS-EP-FILTER/endpoint_groups', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_endpoint_group(self, endpoint_group_id, **kwargs):
+ """Update endpoint group.
+
+ For a full list of available parameters, please refer to the
+ official API reference:
+ https://developer.openstack.org/api-ref/identity/v3-ext/#update-endpoint-group
+ """
+ post_body = json.dumps({'endpoint_group': kwargs})
+ resp, body = self.patch(
+ 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_endpoint_group(self, endpoint_group_id):
+ """Delete endpoint group."""
+ resp_header, resp_body = self.delete(
+ 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id)
+ self.expected_success(204, resp_header.status)
+ return rest_client.ResponseBody(resp_header, resp_body)
+
+ def show_endpoint_group(self, endpoint_group_id):
+ """Get endpoint group."""
+ resp_header, resp_body = self.get(
+ 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id)
+ self.expected_success(200, resp_header.status)
+ resp_body = json.loads(resp_body)
+ return rest_client.ResponseBody(resp_header, resp_body)
+
+ def check_endpoint_group(self, endpoint_group_id):
+ """Check endpoint group."""
+ resp_header, resp_body = self.head(
+ 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id)
+ self.expected_success(200, resp_header.status)
+ return rest_client.ResponseBody(resp_header, resp_body)
+
+ def list_endpoint_groups(self):
+ """Get endpoint groups."""
+ resp_header, resp_body = self.get('OS-EP-FILTER/endpoint_groups')
+ self.expected_success(200, resp_header.status)
+ resp_body = json.loads(resp_body)
+ return rest_client.ResponseBody(resp_header, resp_body)
diff --git a/tempest/lib/services/identity/v3/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
index 91592de..e24dca7 100644
--- a/tempest/lib/services/identity/v3/endpoints_client.py
+++ b/tempest/lib/services/identity/v3/endpoints_client.py
@@ -18,6 +18,7 @@
"""
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -25,9 +26,17 @@
class EndPointsClient(rest_client.RestClient):
api_version = "v3"
- def list_endpoints(self):
- """GET endpoints."""
- resp, body = self.get('endpoints')
+ def list_endpoints(self, **params):
+ """List endpoints.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/#list-endpoints
+ """
+ url = 'endpoints'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/identity_client.py b/tempest/lib/services/identity/v3/identity_client.py
index 755c14b..2512a3e 100644
--- a/tempest/lib/services/identity/v3/identity_client.py
+++ b/tempest/lib/services/identity/v3/identity_client.py
@@ -44,6 +44,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def check_token_existence(self, resp_token):
+ """Validates a token."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.head("auth/tokens", headers=headers)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def list_auth_projects(self):
"""Get available project scopes."""
resp, body = self.get("auth/projects")
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
new file mode 100644
index 0000000..b1d298b
--- /dev/null
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -0,0 +1,236 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import binascii
+import hashlib
+import hmac
+import random
+import time
+
+import six
+from six.moves.urllib import parse as urlparse
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class OAUTHTokenClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def _escape(self, s):
+ """Escape a unicode string in an OAuth-compatible fashion."""
+ safe = b'~'
+ s = s.encode('utf-8') if isinstance(s, six.text_type) else s
+ s = urlparse.quote(s, safe)
+ if isinstance(s, six.binary_type):
+ s = s.decode('utf-8')
+ return s
+
+ def _generate_params_with_signature(self, client_key, uri,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ verifier=None,
+ http_method='GET'):
+ """Generate OAUTH params along with signature."""
+ timestamp = six.text_type(int(time.time()))
+ nonce = six.text_type(random.getrandbits(64)) + timestamp
+ oauth_params = [
+ ('oauth_nonce', nonce),
+ ('oauth_timestamp', timestamp),
+ ('oauth_version', '1.0'),
+ ('oauth_signature_method', 'HMAC-SHA1'),
+ ('oauth_consumer_key', client_key),
+ ]
+ if resource_owner_key:
+ oauth_params.append(('oauth_token', resource_owner_key))
+ if callback_uri:
+ oauth_params.append(('oauth_callback', callback_uri))
+ if verifier:
+ oauth_params.append(('oauth_verifier', verifier))
+
+ # normalize_params
+ key_values = [(self._escape(k), self._escape(v))
+ for k, v in oauth_params]
+ key_values.sort()
+ parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values]
+ normalized_params = '&'.join(parameter_parts)
+
+ # normalize_uri
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
+ scheme = scheme.lower()
+ netloc = netloc.lower()
+ normalized_uri = urlparse.urlunparse((scheme, netloc, path,
+ params, '', ''))
+
+ # construct base string
+ base_string = self._escape(http_method.upper())
+ base_string += '&'
+ base_string += self._escape(normalized_uri)
+ base_string += '&'
+ base_string += self._escape(normalized_params)
+
+ # sign using hmac-sha1
+ key = self._escape(client_secret or '')
+ key += '&'
+ key += self._escape(resource_owner_secret or '')
+ key_utf8 = key.encode('utf-8')
+ text_utf8 = base_string.encode('utf-8')
+ signature = hmac.new(key_utf8, text_utf8, hashlib.sha1)
+ sig = binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
+
+ oauth_params.append(('oauth_signature', sig))
+ return oauth_params
+
+ def _generate_oauth_header(self, oauth_params):
+ authorization_header = {}
+ authorization_header_parameters_parts = []
+ for oauth_parameter_name, value in oauth_params:
+ escaped_name = self._escape(oauth_parameter_name)
+ escaped_value = self._escape(value)
+ part = '{0}="{1}"'.format(escaped_name, escaped_value)
+ authorization_header_parameters_parts.append(part)
+
+ authorization_header_parameters = ', '.join(
+ authorization_header_parameters_parts)
+ oauth_string = 'OAuth %s' % authorization_header_parameters
+ authorization_header['Authorization'] = oauth_string
+
+ return authorization_header
+
+ def create_request_token(self, consumer_key, consumer_secret, project_id):
+ """Create request token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#create-request-token
+ """
+ endpoint = 'OS-OAUTH1/request_token'
+ headers = {'Requested-Project-Id': project_id}
+ oauth_params = self._generate_params_with_signature(
+ consumer_key,
+ self.base_url + '/' + endpoint,
+ client_secret=consumer_secret,
+ callback_uri='oob',
+ http_method='POST')
+ oauth_header = self._generate_oauth_header(oauth_params)
+ headers.update(oauth_header)
+ resp, body = self.post(endpoint,
+ body=None,
+ headers=headers)
+ self.expected_success(201, resp.status)
+ if not isinstance(body, str):
+ body = body.decode('utf-8')
+ body = dict(item.split("=") for item in body.split("&"))
+ return rest_client.ResponseBody(resp, body)
+
+ def authorize_request_token(self, request_token_id, role_ids):
+ """Authorize request token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#authorize-request-token
+ """
+ roles = [{'id': role_id} for role_id in role_ids]
+ body = {'roles': roles}
+ post_body = json.dumps(body)
+ resp, body = self.put("OS-OAUTH1/authorize/%s" % request_token_id,
+ post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_access_token(self, consumer_key, consumer_secret, request_key,
+ request_secret, oauth_verifier):
+ """Create access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#create-access-token
+ """
+ endpoint = 'OS-OAUTH1/access_token'
+ oauth_params = self._generate_params_with_signature(
+ consumer_key,
+ self.base_url + '/' + endpoint,
+ client_secret=consumer_secret,
+ resource_owner_key=request_key,
+ resource_owner_secret=request_secret,
+ verifier=oauth_verifier,
+ http_method='POST')
+ headers = self._generate_oauth_header(oauth_params)
+ resp, body = self.post(endpoint, body=None, headers=headers)
+ self.expected_success(201, resp.status)
+ if not isinstance(body, str):
+ body = body.decode('utf-8')
+ body = dict(item.split("=") for item in body.split("&"))
+ return rest_client.ResponseBody(resp, body)
+
+ def get_access_token(self, user_id, access_token_id):
+ """Get access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#get-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s"
+ % (user_id, access_token_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_access_token(self, user_id, access_token_id):
+ """Revoke access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#revoke-access-token
+ """
+ resp, body = self.delete("users/%s/OS-OAUTH1/access_tokens/%s"
+ % (user_id, access_token_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_access_tokens(self, user_id):
+ """List access tokens.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#list-access-tokens
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens"
+ % (user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_access_token_roles(self, user_id, access_token_id):
+ """List roles for an access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#list-roles-for-an-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles"
+ % (user_id, access_token_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_access_token_role(self, user_id, access_token_id, role_id):
+ """Show role details for an access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#show-role-details-for-an-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles/%s"
+ % (user_id, access_token_id, role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/services_client.py b/tempest/lib/services/identity/v3/services_client.py
index 17b0f24..7bbe850 100644
--- a/tempest/lib/services/identity/v3/services_client.py
+++ b/tempest/lib/services/identity/v3/services_client.py
@@ -76,7 +76,7 @@
url = 'services'
if params:
url += '?%s' % urllib.urlencode(params)
- resp, body = self.get('services')
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index 19e5463..419e593 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -31,11 +31,12 @@
ServiceProvidersClient
from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
from tempest.lib.services.network.subnets_client import SubnetsClient
+from tempest.lib.services.network.tags_client import TagsClient
from tempest.lib.services.network.versions_client import NetworkVersionsClient
__all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
'MeteringLabelRulesClient', 'MeteringLabelsClient',
- 'NetworksClient', 'PortsClient', 'QuotasClient', 'RoutersClient',
- 'SecurityGroupRulesClient', 'SecurityGroupsClient',
- 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient',
- 'NetworkVersionsClient']
+ 'NetworksClient', 'NetworkVersionsClient', 'PortsClient',
+ 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient',
+ 'SecurityGroupsClient', 'ServiceProvidersClient',
+ 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient']
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index b6f9c91..fe8b244 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -54,7 +54,8 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def create_resource(self, uri, post_data, expect_empty_body=False):
+ def create_resource(self, uri, post_data, expect_empty_body=False,
+ expect_response_code=201):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.post(req_uri, req_post_data)
@@ -65,10 +66,11 @@
body = json.loads(body)
else:
body = None
- self.expected_success(201, resp.status)
+ self.expected_success(expect_response_code, resp.status)
return rest_client.ResponseBody(resp, body)
- def update_resource(self, uri, post_data, expect_empty_body=False):
+ def update_resource(self, uri, post_data, expect_empty_body=False,
+ expect_response_code=200):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.put(req_uri, req_post_data)
@@ -79,5 +81,5 @@
body = json.loads(body)
else:
body = None
- self.expected_success(200, resp.status)
+ self.expected_success(expect_response_code, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/tags_client.py b/tempest/lib/services/network/tags_client.py
new file mode 100644
index 0000000..5d49a79
--- /dev/null
+++ b/tempest/lib/services/network/tags_client.py
@@ -0,0 +1,84 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.network import base
+
+
+class TagsClient(base.BaseNetworkClient):
+
+ def create_tag(self, resource_type, resource_id, tag):
+ """Adds a tag on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#add-a-tag
+ """
+ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag)
+ return self.update_resource(
+ uri, json.dumps({}), expect_response_code=201,
+ expect_empty_body=True)
+
+ def check_tag_existence(self, resource_type, resource_id, tag):
+ """Confirm that a given tag is set on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#confirm-a-tag
+ """
+ # TODO(felipemonteiro): Use the "check_resource" method in
+ # ``BaseNetworkClient`` once it has been implemented.
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, _ = self.get(uri)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def update_all_tags(self, resource_type, resource_id, tags):
+ """Replace all tags on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#replace-all-tags
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ put_body = {"tags": tags}
+ return self.update_resource(uri, put_body)
+
+ def delete_tag(self, resource_type, resource_id, tag):
+ """Removes a tag on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#remove-a-tag
+ """
+ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag)
+ return self.delete_resource(uri)
+
+ def delete_all_tags(self, resource_type, resource_id):
+ """Removes all tags on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#remove-all-tags
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ return self.delete_resource(uri)
+
+ def list_tags(self, resource_type, resource_id):
+ """Retrieves the tags for a resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#obtain-tag-list
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ return self.list_resources(uri)
diff --git a/tempest/lib/services/network/versions_client.py b/tempest/lib/services/network/versions_client.py
index a9c3bbf..f87fe87 100644
--- a/tempest/lib/services/network/versions_client.py
+++ b/tempest/lib/services/network/versions_client.py
@@ -15,7 +15,6 @@
import time
from oslo_serialization import jsonutils as json
-from six.moves import urllib
from tempest.lib.services.network import base
@@ -25,9 +24,7 @@
def list_versions(self):
"""Do a GET / to fetch available API version information."""
- endpoint = self.base_url
- url = urllib.parse.urlparse(endpoint)
- version_url = '%s://%s/' % (url.scheme, url.netloc)
+ version_url = self._get_base_version_url()
# Note: we do a raw_request here because we want to use
# an unversioned URL, not "v2/$project_id/".
diff --git a/tempest/api/orchestration/__init__.py b/tempest/lib/services/object_storage/__init__.py
similarity index 100%
rename from tempest/api/orchestration/__init__.py
rename to tempest/lib/services/object_storage/__init__.py
diff --git a/tempest/services/object_storage/bulk_middleware_client.py b/tempest/lib/services/object_storage/bulk_middleware_client.py
similarity index 76%
rename from tempest/services/object_storage/bulk_middleware_client.py
rename to tempest/lib/services/object_storage/bulk_middleware_client.py
index c194ea9..c11a105 100644
--- a/tempest/services/object_storage/bulk_middleware_client.py
+++ b/tempest/lib/services/object_storage/bulk_middleware_client.py
@@ -24,39 +24,39 @@
To extract containers and objects on Swift cluster from
uploaded archived file. For More information please check:
- http://docs.openstack.org/developer/swift/middleware.html#module-swift.common.middleware.bulk
+ https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk
"""
url = '%s?extract-archive=%s' % (upload_path, archive_file_format)
if headers is None:
headers = {}
resp, body = self.put(url, data, headers)
self.expected_success(200, resp.status)
- return resp, body
+ return rest_client.ResponseBodyData(resp, body)
def delete_bulk_data(self, data=None, headers=None):
"""Delete multiple objects or containers from their account.
For More information please check:
- http://docs.openstack.org/developer/swift/middleware.html#module-swift.common.middleware.bulk
+ https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk
"""
url = '?bulk-delete'
if headers is None:
headers = {}
- resp, body = self.delete(url, headers=headers, body=data)
+ resp, body = self.delete(url, headers, data)
self.expected_success(200, resp.status)
- return resp, body
+ return rest_client.ResponseBodyData(resp, body)
def delete_bulk_data_with_post(self, data=None, headers=None):
"""Delete multiple objects or containers with POST request.
For More information please check:
- http://docs.openstack.org/developer/swift/middleware.html#module-swift.common.middleware.bulk
+ https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk
"""
url = '?bulk-delete'
if headers is None:
headers = {}
- resp, body = self.post(url, headers=headers, body=data)
+ resp, body = self.post(url, data, headers)
self.expected_success([200, 204], resp.status)
- return resp, body
+ return rest_client.ResponseBodyData(resp, body)
diff --git a/tempest/services/object_storage/capabilities_client.py b/tempest/lib/services/object_storage/capabilities_client.py
similarity index 94%
rename from tempest/services/object_storage/capabilities_client.py
rename to tempest/lib/services/object_storage/capabilities_client.py
index 0fe437f..d31bbc2 100644
--- a/tempest/services/object_storage/capabilities_client.py
+++ b/tempest/lib/services/object_storage/capabilities_client.py
@@ -28,4 +28,4 @@
self.reset_path()
body = json.loads(body)
self.expected_success(200, resp.status)
- return resp, body
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/base_client.py b/tempest/lib/services/volume/base_client.py
new file mode 100644
index 0000000..c7fb21a
--- /dev/null
+++ b/tempest/lib/services/volume/base_client.py
@@ -0,0 +1,45 @@
+# Copyright 2016 Andrew Kerr
+# 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.common import api_version_utils
+from tempest.lib.common import rest_client
+
+VOLUME_MICROVERSION = None
+
+
+class BaseClient(rest_client.RestClient):
+ """Base volume service clients class to support microversion."""
+ api_microversion_header_name = 'Openstack-Api-Version'
+
+ def get_headers(self, accept_type=None, send_type=None):
+ headers = super(BaseClient, self).get_headers(
+ accept_type=accept_type, send_type=send_type)
+ if VOLUME_MICROVERSION:
+ headers[self.api_microversion_header_name] = ('volume %s' %
+ VOLUME_MICROVERSION)
+ return headers
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+
+ resp, resp_body = super(BaseClient, self).request(
+ method, url, extra_headers, headers, body, chunked)
+ if (VOLUME_MICROVERSION and
+ VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+ api_version_utils.assert_version_header_matches_request(
+ self.api_microversion_header_name,
+ 'volume %s' % VOLUME_MICROVERSION,
+ resp)
+ return resp, resp_body
diff --git a/tempest/lib/services/volume/v1/backups_client.py b/tempest/lib/services/volume/v1/backups_client.py
index 8677913..77c40b3 100644
--- a/tempest/lib/services/volume/v1/backups_client.py
+++ b/tempest/lib/services/volume/v1/backups_client.py
@@ -102,3 +102,8 @@
except lib_exc.NotFound:
return True
return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'backup'
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 2b5e82d..adfa6a6 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -14,12 +14,14 @@
# under the License.
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
-class BackupsClient(rest_client.RestClient):
+class BackupsClient(base_client.BaseClient):
"""Volume V2 Backups client"""
api_version = "v2"
@@ -63,11 +65,19 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_backups(self, detail=False):
- """Information for all the tenant's backups."""
+ def list_backups(self, detail=False, **params):
+ """List all the tenant's backups.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-backups
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-backups-with-details
+ """
url = "backups"
if detail:
url += "/detail"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -102,3 +112,8 @@
except lib_exc.NotFound:
return True
return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'backup'
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index eeff537..20f3356 100755
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -50,9 +50,9 @@
def create_encryption_type(self, volume_type_id, **kwargs):
"""Create encryption type.
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
"""
url = "/types/%s/encryption" % volume_type_id
post_body = json.dumps({'encryption': kwargs})
@@ -71,9 +71,9 @@
def update_encryption_type(self, volume_type_id, **kwargs):
"""Update an encryption type for an existing volume type.
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2
"""
url = "/types/%s/encryption/provider" % volume_type_id
put_body = json.dumps({'encryption': kwargs})
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index 8fcf4d0..f44bda3 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -24,8 +24,12 @@
api_version = "v2"
def list_hosts(self, **params):
- """Lists all hosts."""
+ """Lists all hosts.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
+ """
url = 'os-hosts'
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 983ed89..4bc2842 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -124,7 +124,12 @@
return rest_client.ResponseBody(resp, body)
def create_snapshot_metadata(self, snapshot_id, metadata):
- """Create metadata for the snapshot."""
+ """Create metadata for the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.post(url, put_body)
@@ -159,6 +164,14 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ def show_snapshot_metadata_item(self, snapshot_id, id):
+ """Show metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
"""Update metadata item for the snapshot."""
# TODO(piyush): Current api-site doesn't contain this API description.
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index 5d30615..af4fd8c 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -41,7 +41,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-types
+ https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
"""
url = 'types'
if params:
@@ -57,7 +57,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details
+ https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
"""
url = "types/%s" % volume_type_id
resp, body = self.get(url)
@@ -70,7 +70,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type
+ https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
"""
post_body = json.dumps({'volume_type': kwargs})
resp, body = self.post('types', post_body)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index 3e3ba35..d13e449 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -21,10 +21,11 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
from tempest.lib.services.volume.v2 import transfers_client
-class VolumesClient(rest_client.RestClient):
+class VolumesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
@@ -72,6 +73,10 @@
"""List all the volumes created.
Params can be a string (must be urlencoded) or a dictionary.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
"""
url = 'volumes'
if detail:
@@ -155,7 +160,12 @@
return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
- """set a bootable flag for a volume - true or false."""
+ """Set a bootable flag for a volume - true or false.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status
+ """
post_body = json.dumps({'os-set_bootable': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
@@ -247,7 +257,12 @@
return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
+ """Create metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "volumes/%s/metadata" % volume_id
resp, body = self.post(url, put_body)
@@ -264,7 +279,12 @@
return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
+ """Update metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "volumes/%s/metadata" % volume_id
resp, body = self.put(url, put_body)
@@ -272,6 +292,14 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ def show_volume_metadata_item(self, volume_id, id):
+ """Show metadata item for the volume."""
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
put_body = json.dumps({'meta': meta_item})
@@ -289,10 +317,16 @@
return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
- """Updates volume with new volume type."""
+ """Updates volume with new volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume
+ """
post_body = json.dumps({'os-retype': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
def force_detach_volume(self, volume_id, **kwargs):
"""Force detach a volume.
@@ -329,7 +363,16 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- @removals.remove(message="use show_pools from tempest.lib.services."
+ def show_volume_image_metadata(self, volume_id):
+ """Show image metadata for the volume."""
+ post_body = json.dumps({'os-show_image_metadata': {}})
+ url = "volumes/%s/action" % volume_id
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ @removals.remove(message="use list_pools from tempest.lib.services."
"volume.v2.scheduler_stats_client")
def show_pools(self, detail=False):
# List all the volumes pools (hosts)
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index 72ab785..2d85553 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -12,8 +12,18 @@
# License for the specific language governing permissions and limitations under
# the License.
+from tempest.lib.services.volume.v3.backups_client import BackupsClient
from tempest.lib.services.volume.v3.base_client import BaseClient
+from tempest.lib.services.volume.v3.group_snapshots_client import \
+ GroupSnapshotsClient
+from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient
+from tempest.lib.services.volume.v3.groups_client import GroupsClient
from tempest.lib.services.volume.v3.messages_client import MessagesClient
+from tempest.lib.services.volume.v3.snapshots_client import SnapshotsClient
from tempest.lib.services.volume.v3.versions_client import VersionsClient
+from tempest.lib.services.volume.v3.volumes_client import VolumesClient
-__all__ = ['MessagesClient', 'BaseClient', 'VersionsClient']
+__all__ = ['BackupsClient', 'BaseClient', 'GroupsClient',
+ 'GroupSnapshotsClient', 'GroupTypesClient',
+ 'MessagesClient', 'SnapshotsClient', 'VersionsClient',
+ 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
new file mode 100644
index 0000000..e742e39
--- /dev/null
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -0,0 +1,37 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v2 import backups_client
+
+
+class BackupsClient(backups_client.BackupsClient):
+ """Volume V3 Backups client"""
+ api_version = "v3"
+
+ def update_backup(self, backup_id, **kwargs):
+ """Updates the specified volume backup.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#update-a-backup
+ """
+ put_body = json.dumps({'backup': kwargs})
+ resp, body = self.put('backups/%s' % backup_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/base_client.py b/tempest/lib/services/volume/v3/base_client.py
index 958212a..e78380b 100644
--- a/tempest/lib/services/volume/v3/base_client.py
+++ b/tempest/lib/services/volume/v3/base_client.py
@@ -13,34 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.common import api_version_utils
-from tempest.lib.common import rest_client
+from debtcollector import moves
-VOLUME_MICROVERSION = None
+from tempest.lib.services.volume import base_client
-class BaseClient(rest_client.RestClient):
- """Base class to handle Cinder v3 client microversion support."""
- api_version = 'v3'
- api_microversion_header_name = 'Openstack-Api-Version'
-
- def get_headers(self, accept_type=None, send_type=None):
- headers = super(BaseClient, self).get_headers(
- accept_type=accept_type, send_type=send_type)
- if VOLUME_MICROVERSION:
- headers[self.api_microversion_header_name] = ('volume %s' %
- VOLUME_MICROVERSION)
- return headers
-
- def request(self, method, url, extra_headers=False, headers=None,
- body=None, chunked=False):
-
- resp, resp_body = super(BaseClient, self).request(
- method, url, extra_headers, headers, body, chunked)
- if (VOLUME_MICROVERSION and
- VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
- api_version_utils.assert_version_header_matches_request(
- self.api_microversion_header_name,
- 'volume %s' % VOLUME_MICROVERSION,
- resp)
- return resp, resp_body
+BaseClient = moves.moved_class(base_client.BaseClient, 'BaseClient', __name__,
+ version="Pike", removal_version='?')
+BaseClient.api_version = 'v3'
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
new file mode 100644
index 0000000..e644f02
--- /dev/null
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
+
+
+class GroupSnapshotsClient(base_client.BaseClient):
+ """Client class to send CRUD Volume Group Snapshot API requests"""
+ api_version = 'v3'
+
+ def create_group_snapshot(self, **kwargs):
+ """Creates a group snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#create-group-snapshot
+ """
+ post_body = json.dumps({'group_snapshot': kwargs})
+ resp, body = self.post('group_snapshots', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_group_snapshot(self, group_snapshot_id):
+ """Deletes a group snapshot.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#delete-group-snapshot
+ """
+ resp, body = self.delete('group_snapshots/%s' % group_snapshot_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_group_snapshot(self, group_snapshot_id):
+ """Returns the details of a single group snapshot.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#show-group-snapshot-details
+ """
+ url = "group_snapshots/%s" % str(group_snapshot_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_snapshots(self, **params):
+ """Information for all the tenant's group snapshots.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#list-group-snapshots
+ https://developer.openstack.org/api-ref/block-storage/v3/#list-group-snapshots-with-details
+ """
+ url = "group_snapshots"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_group_snapshot(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'group-snapshot'
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
new file mode 100644
index 0000000..97bac48
--- /dev/null
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume import base_client
+
+
+class GroupTypesClient(base_client.BaseClient):
+ """Client class to send CRUD Volume V3 Group Types API requests"""
+ api_version = 'v3'
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'group-type'
+
+ def create_group_type(self, **kwargs):
+ """Create group_type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#create-group-type
+ """
+ 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)
+ 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)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_types(self, **params):
+ """List all the group_types created.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#list-group-types
+ """
+ url = 'group_types'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_group_type(self, group_type_id):
+ """Returns the details of a single group_type.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#show-group-type-details
+ """
+ url = "group_types/%s" % group_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
new file mode 100644
index 0000000..b463fdf
--- /dev/null
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
+
+
+class GroupsClient(base_client.BaseClient):
+ """Client class to send CRUD Volume Group API requests"""
+ api_version = 'v3'
+
+ def create_group(self, **kwargs):
+ """Creates a group.
+
+ group_type and volume_types are required parameters in kwargs.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#create-group
+ """
+ post_body = json.dumps({'group': kwargs})
+ resp, body = self.post('groups', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_group(self, group_id, delete_volumes=True):
+ """Deletes a group.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#delete-group
+ """
+ post_body = {'delete-volumes': delete_volumes}
+ post_body = json.dumps({'delete': post_body})
+ resp, body = self.post('groups/%s/action' % group_id,
+ post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_group(self, group_id):
+ """Returns the details of a single group.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#show-group-details
+ """
+ url = "groups/%s" % str(group_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_groups(self, detail=False, **params):
+ """Lists information for all the tenant's groups.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#list-groups
+ https://developer.openstack.org/api-ref/block-storage/v3/#list-groups-with-details
+ """
+ url = "groups"
+ if detail:
+ url += "/detail"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_group_from_source(self, **kwargs):
+ """Creates a group from source.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#create-group-from-source
+ """
+ post_body = json.dumps({'create-from-src': kwargs})
+ resp, body = self.post('groups/action', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_group(self, group_id, **kwargs):
+ """Updates the specified group.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#update-group
+ """
+ put_body = json.dumps({'group': kwargs})
+ resp, body = self.put('groups/%s' % group_id, put_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_group(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'group'
diff --git a/tempest/lib/services/volume/v3/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
index 8a01864..0127271 100644
--- a/tempest/lib/services/volume/v3/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -17,11 +17,12 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class MessagesClient(base_client.BaseClient):
"""Client class to send user messages API requests."""
+ api_version = 'v3'
def show_message(self, message_id):
"""Show details for a single message."""
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
new file mode 100644
index 0000000..88c094f
--- /dev/null
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v2 import snapshots_client
+
+
+class SnapshotsClient(snapshots_client.SnapshotsClient):
+ """Client class to send CRUD Volume Snapshot V3 API requests."""
+ api_version = "v3"
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index e2941c4..5702f95 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -18,10 +18,11 @@
from tempest.lib.api_schema.response.volume import versions as schema
from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class VersionsClient(base_client.BaseClient):
+ api_version = 'v3'
def list_versions(self):
"""List API versions
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
new file mode 100644
index 0000000..5f4b278
--- /dev/null
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -0,0 +1,40 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v2 import volumes_client
+
+
+class VolumesClient(volumes_client.VolumesClient):
+ """Client class to send CRUD Volume V3 API requests"""
+ api_version = "v3"
+
+ def show_volume_summary(self, **params):
+ """Get volumes summary.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#get-volumes-summary
+ """
+ url = 'volumes/summary'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index 38e0de9..c1dcccc 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -21,9 +21,9 @@
4. create a snapshot of the vm
-Why are these tests in tempest?
+Why are these tests in Tempest?
-------------------------------
-This is one of tempests core purposes, testing the integration between
+This is one of Tempest's core purposes, testing the integration between
projects.
@@ -43,7 +43,7 @@
specific in your interactions. A giant "this is my data center" smoke
test is hard to debug when it goes wrong.
-A flow of interactions between glance and nova, like in the
+A flow of interactions between Glance and Nova, like in the
introduction, is a good example. Especially if it involves a repeated
interaction when a resource is setup, modified, detached, and then
reused later again.
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index ef4506c..2843222 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -47,40 +47,42 @@
def setup_clients(cls):
super(ScenarioTest, cls).setup_clients()
# Clients (in alphabetical order)
- cls.flavors_client = cls.manager.flavors_client
+ cls.flavors_client = cls.os_primary.flavors_client
cls.compute_floating_ips_client = (
- cls.manager.compute_floating_ips_client)
+ cls.os_primary.compute_floating_ips_client)
if CONF.service_available.glance:
# Check if glance v1 is available to determine which client to use.
if CONF.image_feature_enabled.api_v1:
- cls.image_client = cls.manager.image_client
+ cls.image_client = cls.os_primary.image_client
elif CONF.image_feature_enabled.api_v2:
- cls.image_client = cls.manager.image_client_v2
+ cls.image_client = cls.os_primary.image_client_v2
else:
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
# Compute image client
- cls.compute_images_client = cls.manager.compute_images_client
- cls.keypairs_client = cls.manager.keypairs_client
+ cls.compute_images_client = cls.os_primary.compute_images_client
+ cls.keypairs_client = cls.os_primary.keypairs_client
# Nova security groups client
cls.compute_security_groups_client = (
- cls.manager.compute_security_groups_client)
+ cls.os_primary.compute_security_groups_client)
cls.compute_security_group_rules_client = (
- cls.manager.compute_security_group_rules_client)
- cls.servers_client = cls.manager.servers_client
- cls.interface_client = cls.manager.interfaces_client
+ cls.os_primary.compute_security_group_rules_client)
+ cls.servers_client = cls.os_primary.servers_client
+ cls.interface_client = cls.os_primary.interfaces_client
# Neutron network client
- cls.networks_client = cls.manager.networks_client
- cls.ports_client = cls.manager.ports_client
- cls.routers_client = cls.manager.routers_client
- cls.subnets_client = cls.manager.subnets_client
- cls.floating_ips_client = cls.manager.floating_ips_client
- cls.security_groups_client = cls.manager.security_groups_client
+ cls.networks_client = cls.os_primary.networks_client
+ cls.ports_client = cls.os_primary.ports_client
+ cls.routers_client = cls.os_primary.routers_client
+ cls.subnets_client = cls.os_primary.subnets_client
+ cls.floating_ips_client = cls.os_primary.floating_ips_client
+ cls.security_groups_client = cls.os_primary.security_groups_client
cls.security_group_rules_client = (
- cls.manager.security_group_rules_client)
- cls.volumes_client = cls.manager.volumes_v2_client
- cls.snapshots_client = cls.manager.snapshots_v2_client
+ cls.os_primary.security_group_rules_client)
+ # Use the latest available volume clients
+ if CONF.service_available.cinder:
+ cls.volumes_client = cls.os_primary.volumes_client_latest
+ cls.snapshots_client = cls.os_primary.snapshots_client_latest
# ## Test functions library
#
@@ -133,7 +135,7 @@
# Needed for the cross_tenant_traffic test:
if clients is None:
- clients = self.manager
+ clients = self.os_primary
if name is None:
name = data_utils.rand_name(self.__class__.__name__ + "-server")
@@ -195,7 +197,7 @@
tenant_network = self.get_tenant_network()
- body, servers = compute.create_test_server(
+ body, _ = compute.create_test_server(
clients,
tenant_network=tenant_network,
wait_until=wait_until,
@@ -238,9 +240,26 @@
volume = self.volumes_client.show_volume(volume['id'])['volume']
return volume
+ 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')
+ return snapshot
+
def create_volume_type(self, client=None, name=None, backend_name=None):
if not client:
- client = self.admin_volume_types_client
+ client = self.os_admin.volume_types_v2_client
if not name:
class_name = self.__class__.__name__
name = data_utils.rand_name(class_name + '-volume-type')
@@ -313,13 +332,15 @@
return secgroup
- def get_remote_client(self, ip_address, username=None, private_key=None):
+ def get_remote_client(self, ip_address, username=None, private_key=None,
+ server=None):
"""Get a SSH client to a remote server
@param ip_address the server floating or fixed IP address to use
for ssh validation
@param username name of the Linux account on the remote server
@param private_key the SSH private key to use
+ @param server: server dict, used for debugging purposes
@return a RemoteClient object
"""
@@ -334,22 +355,10 @@
else:
password = CONF.validation.image_ssh_password
private_key = None
- linux_client = remote_client.RemoteClient(ip_address, username,
- pkey=private_key,
- password=password)
- try:
- linux_client.validate_authentication()
- except Exception as e:
- message = ('Initializing SSH connection to %(ip)s failed. '
- 'Error: %(error)s' % {'ip': ip_address,
- 'error': e})
- caller = test_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- LOG.exception(message)
- self._log_console_output()
- raise
-
+ linux_client = remote_client.RemoteClient(
+ ip_address, username, pkey=private_key, password=password,
+ server=server, servers_client=self.servers_client)
+ linux_client.validate_authentication()
return linux_client
def _image_create(self, name, fmt, path,
@@ -741,7 +750,7 @@
:returns: True if subnet with cidr already exist in tenant
False else
"""
- cidr_in_use = self.admin_manager.subnets_client.list_subnets(
+ cidr_in_use = self.os_admin.subnets_client.list_subnets(
tenant_id=tenant_id, cidr=cidr)['subnets']
return len(cidr_in_use) != 0
@@ -790,7 +799,7 @@
return subnet
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
- ports = self.admin_manager.ports_client.list_ports(
+ ports = self.os_admin.ports_client.list_ports(
device_id=server['id'], fixed_ip=ip_addr)['ports']
# A port can have more than one IP address in some cases.
# If the network is dual-stack (IPv4 + IPv6), this port is associated
@@ -811,7 +820,7 @@
if inactive:
LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
- self.assertNotEqual(0, len(port_map),
+ self.assertNotEmpty(port_map,
"No IPv4 addresses found in: %s" % ports)
self.assertEqual(len(port_map), 1,
"Found multiple IPv4 addresses: %s. "
@@ -820,9 +829,9 @@
return port_map[0]
def _get_network_by_name(self, network_name):
- net = self.admin_manager.networks_client.list_networks(
+ net = self.os_admin.networks_client.list_networks(
name=network_name)['networks']
- self.assertNotEqual(len(net), 0,
+ self.assertNotEmpty(net,
"Unable to get network by name: %s" % network_name)
return net[0]
@@ -1029,7 +1038,7 @@
if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
]
msg = "No default security group for tenant %s." % (tenant_id)
- self.assertGreater(len(sgs), 0, msg)
+ self.assertNotEmpty(sgs, msg)
return sgs[0]
def _create_security_group_rule(self, secgroup=None,
@@ -1259,6 +1268,17 @@
type_id, provider=provider, key_size=key_size, cipher=cipher,
control_location=control_location)['encryption']
+ def create_encrypted_volume(self, encryption_provider, volume_type,
+ key_size=256, cipher='aes-xts-plain64',
+ control_location='front-end'):
+ volume_type = self.create_volume_type(name=volume_type)
+ self.create_encryption_type(type_id=volume_type['id'],
+ provider=encryption_provider,
+ key_size=key_size,
+ cipher=cipher,
+ control_location=control_location)
+ return self.create_volume(volume_type=volume_type['name'])
+
class ObjectStorageScenarioTest(ScenarioTest):
"""Provide harness to do Object Storage scenario tests.
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 8408a1e..25227be 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -36,8 +36,8 @@
def setup_clients(cls):
super(TestAggregatesBasicOps, cls).setup_clients()
# Use admin client by default
- cls.aggregates_client = cls.admin_manager.aggregates_client
- cls.hosts_client = cls.admin_manager.hosts_client
+ cls.aggregates_client = cls.os_admin.aggregates_client
+ cls.hosts_client = cls.os_admin.hosts_client
def _create_aggregate(self, **kwargs):
aggregate = (self.aggregates_client.create_aggregate(**kwargs)
@@ -52,7 +52,7 @@
def _get_host_name(self):
hosts = self.hosts_client.list_hosts()['hosts']
- self.assertGreaterEqual(len(hosts), 1)
+ self.assertNotEmpty(hosts)
computes = [x for x in hosts if x['service'] == 'compute']
return computes[0]['host_name']
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index d7b86f6..cbdf307 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -48,15 +48,6 @@
return self.create_server(image_id=image, key_name=keypair['name'])
- def create_encrypted_volume(self, encryption_provider, volume_type):
- volume_type = self.create_volume_type(name=volume_type)
- self.create_encryption_type(type_id=volume_type['id'],
- provider=encryption_provider,
- key_size=256,
- cipher='aes-xts-plain64',
- control_location='front-end')
- return self.create_volume(volume_type=volume_type['name'])
-
def attach_detach_volume(self, server, volume):
attached_volume = self.nova_volume_attach(server, volume)
self.nova_volume_detach(server, attached_volume)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 5fee801..26a834b 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -103,6 +103,8 @@
@decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
+ @testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
+ 'Floating ips are not available')
@test.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
image = self.glance_image_create()
@@ -139,14 +141,16 @@
# check that we can SSH to the server before reboot
self.linux_client = self.get_remote_client(
- floating_ip['ip'], private_key=keypair['private_key'])
+ floating_ip['ip'], private_key=keypair['private_key'],
+ server=server)
self.nova_reboot(server)
# check that we can SSH to the server after reboot
# (both connections are part of the scenario)
self.linux_client = self.get_remote_client(
- floating_ip['ip'], private_key=keypair['private_key'])
+ floating_ip['ip'], private_key=keypair['private_key'],
+ server=server)
self.check_disks()
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index c2fc1a7..c8add8b 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -48,6 +48,8 @@
msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def setup_credentials(cls):
@@ -60,7 +62,7 @@
if test.is_extension_enabled('security-group', 'network'):
security_group = self._create_security_group()
security_groups = [{'name': security_group['name']}]
- network, subnet, router = self.create_networks()
+ network, _, _ = self.create_networks()
server = self.create_server(
networks=[{'uuid': network['id']}],
key_name=keypair['name'],
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 756ca4d..48ddac6 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -90,6 +90,8 @@
if not test.is_extension_enabled(ext, 'network'):
msg = "%s extension not enabled." % ext
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def setup_credentials(cls):
@@ -126,21 +128,21 @@
via checking the result of list_[networks,routers,subnets]
"""
- seen_nets = self.admin_manager.networks_client.list_networks()
+ seen_nets = self.os_admin.networks_client.list_networks()
seen_names = [n['name'] for n in seen_nets['networks']]
seen_ids = [n['id'] for n in seen_nets['networks']]
self.assertIn(self.network['name'], seen_names)
self.assertIn(self.network['id'], seen_ids)
if self.subnet:
- seen_subnets = self.admin_manager.subnets_client.list_subnets()
+ seen_subnets = self.os_admin.subnets_client.list_subnets()
seen_net_ids = [n['network_id'] for n in seen_subnets['subnets']]
seen_subnet_ids = [n['id'] for n in seen_subnets['subnets']]
self.assertIn(self.network['id'], seen_net_ids)
self.assertIn(self.subnet['id'], seen_subnet_ids)
if self.router:
- seen_routers = self.admin_manager.routers_client.list_routers()
+ seen_routers = self.os_admin.routers_client.list_routers()
seen_router_ids = [n['id'] for n in seen_routers['routers']]
seen_router_names = [n['name'] for n in seen_routers['routers']]
self.assertIn(self.router['name'],
@@ -210,7 +212,7 @@
self.servers, mtu=mtu)
def _disassociate_floating_ips(self):
- floating_ip, server = self.floating_ip_tuple
+ floating_ip, _ = self.floating_ip_tuple
self._disassociate_floating_ip(floating_ip)
self.floating_ip_tuple = Floating_IP_tuple(
floating_ip, None)
@@ -238,10 +240,10 @@
ip_address = old_floating_ip['floating_ip_address']
private_key = self._get_server_key(server)
ssh_client = self.get_remote_client(
- ip_address, private_key=private_key)
+ ip_address, private_key=private_key, server=server)
old_nic_list = self._get_server_nics(ssh_client)
# get a port from a list of one item
- port_list = self.admin_manager.ports_client.list_ports(
+ port_list = self.os_admin.ports_client.list_ports(
device_id=server['id'])['ports']
self.assertEqual(1, len(port_list))
old_port = port_list[0]
@@ -257,7 +259,7 @@
def check_ports():
self.new_port_list = [
port for port in
- self.admin_manager.ports_client.list_ports(
+ self.os_admin.ports_client.list_ports(
device_id=server['id'])['ports']
if port['id'] != old_port['id']
]
@@ -285,7 +287,7 @@
"guest after %s sec"
% CONF.network.build_timeout)
- num, new_nic = self.diff_list[0]
+ _, new_nic = self.diff_list[0]
ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % (
new_port['fixed_ips'][0]['ip_address'],
CONF.network.project_network_mask_bits,
@@ -293,7 +295,7 @@
ssh_client.exec_command("sudo ip link set %s up" % new_nic)
def _get_server_nics(self, ssh_client):
- reg = re.compile(r'(?P<num>\d+): (?P<nic_name>\w+):')
+ reg = re.compile(r'(?P<num>\d+): (?P<nic_name>\w+)[@]?.*:')
ipatxt = ssh_client.exec_command("ip address")
return reg.findall(ipatxt)
@@ -309,7 +311,7 @@
# get all network ports in the new network
internal_ips = (
p['fixed_ips'][0]['ip_address'] for p in
- self.admin_manager.ports_client.list_ports(
+ self.os_admin.ports_client.list_ports(
tenant_id=server['tenant_id'],
network_id=network['id'])['ports']
if p['device_owner'].startswith('network')
@@ -330,7 +332,7 @@
# which is always IPv4, so we must only test connectivity to
# external IPv4 IPs if the external network is dualstack.
v4_subnets = [
- s for s in self.admin_manager.subnets_client.list_subnets(
+ s for s in self.os_admin.subnets_client.list_subnets(
network_id=CONF.network.public_network_id)['subnets']
if s['ip_version'] == 4
]
@@ -346,7 +348,8 @@
ip_address = floating_ip['floating_ip_address']
private_key = self._get_server_key(self.floating_ip_tuple.server)
ssh_source = self.get_remote_client(
- ip_address, private_key=private_key)
+ ip_address, private_key=private_key,
+ server=self.floating_ip_tuple.server)
for remote_ip in address_list:
self.check_remote_connectivity(ssh_source, remote_ip,
@@ -573,7 +576,7 @@
ip_address = floating_ip['floating_ip_address']
private_key = self._get_server_key(server)
ssh_client = self.get_remote_client(
- ip_address, private_key=private_key)
+ ip_address, private_key=private_key, server=server)
dns_servers = [initial_dns_server]
servers = ssh_client.get_dns_servers()
@@ -628,9 +631,9 @@
admin_state_up attribute of instance port to True
"""
self._setup_network_and_servers()
- floating_ip, server = self.floating_ip_tuple
+ _, server = self.floating_ip_tuple
server_id = server['id']
- port_id = self.admin_manager.ports_client.list_ports(
+ port_id = self.os_admin.ports_client.list_ports(
device_id=server_id)['ports'][0]['id']
server_pip = server['addresses'][self.network['name']][0]['addr']
@@ -639,7 +642,8 @@
private_key = self._get_server_key(server2)
ssh_client = self.get_remote_client(server2_fip['floating_ip_address'],
- private_key=private_key)
+ private_key=private_key,
+ server=server2)
self.check_public_network_connectivity(
should_connect=True, msg="before updating "
@@ -685,7 +689,7 @@
'Server should have been created from a '
'pre-existing port.')
# Assert the port is bound to the server.
- port_list = self.admin_manager.ports_client.list_ports(
+ port_list = self.os_admin.ports_client.list_ports(
device_id=server['id'], network_id=self.network['id'])['ports']
self.assertEqual(1, len(port_list),
'There should only be one port created for '
@@ -704,7 +708,7 @@
# Boot another server with the same port to make sure nothing was
# left around that could cause issues.
server = self._create_server(self.network, port['id'])
- port_list = self.admin_manager.ports_client.list_ports(
+ port_list = self.os_admin.ports_client.list_ports(
device_id=server['id'], network_id=self.network['id'])['ports']
self.assertEqual(1, len(port_list),
'There should only be one port created for '
@@ -729,23 +733,23 @@
# TODO(yfried): refactor this test to be used for other agents (dhcp)
# as well
- list_hosts = (self.admin_manager.routers_client.
+ list_hosts = (self.os_admin.routers_client.
list_l3_agents_hosting_router)
- schedule_router = (self.admin_manager.network_agents_client.
+ schedule_router = (self.os_admin.network_agents_client.
create_router_on_l3_agent)
- unschedule_router = (self.admin_manager.network_agents_client.
+ unschedule_router = (self.os_admin.network_agents_client.
delete_router_from_l3_agent)
agent_list_alive = set(
a["id"] for a in
- self.admin_manager.network_agents_client.list_agents(
+ self.os_admin.network_agents_client.list_agents(
agent_type="L3 agent")['agents'] if a["alive"] is True
)
self._setup_network_and_servers()
# NOTE(kevinbenton): we have to use the admin credentials to check
# for the distributed flag because self.router only has a project view.
- admin = self.admin_manager.routers_client.show_router(
+ admin = self.os_admin.routers_client.show_router(
self.router['id'])
if admin['router'].get('distributed', False):
msg = "Rescheduling test does not apply to distributed routers."
@@ -823,12 +827,13 @@
self._create_new_network()
self._hotplug_server()
fip, server = self.floating_ip_tuple
- new_ports = self.admin_manager.ports_client.list_ports(
+ new_ports = self.os_admin.ports_client.list_ports(
device_id=server["id"], network_id=self.new_net["id"])['ports']
spoof_port = new_ports[0]
private_key = self._get_server_key(server)
ssh_client = self.get_remote_client(fip['floating_ip_address'],
- private_key=private_key)
+ private_key=private_key,
+ server=server)
spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
peer = self._create_server(self.new_net)
peer_address = peer['addresses'][self.new_net['name']][0]['addr']
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index daf4d13..bf26c2e 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -51,6 +51,8 @@
if CONF.network.shared_physical_network:
msg = 'Deployment uses a shared physical network'
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def setup_credentials(cls):
@@ -129,7 +131,7 @@
ips = self.define_server_ips(srv=srv)
ssh = self.get_remote_client(
ip_address=fip['floating_ip_address'],
- username=username)
+ username=username, server=srv)
return ssh, ips, srv["id"]
def turn_nic6_on(self, ssh, sid, network_id):
@@ -144,7 +146,7 @@
"""
ports = [
p["mac_address"] for p in
- self.admin_manager.ports_client.list_ports(
+ self.os_admin.ports_client.list_ports(
device_id=sid, network_id=network_id)['ports']
]
@@ -169,7 +171,7 @@
# Turn on 2nd NIC for Cirros when dualnet
if dualnet:
- network, network_v6 = net_list
+ _, network_v6 = net_list
self.turn_nic6_on(sshv4_1, sid1, network_v6['id'])
self.turn_nic6_on(sshv4_2, sid2, network_v6['id'])
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 2116fe8..51716e8 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -15,6 +15,7 @@
from oslo_log import log
import testtools
+from tempest.common import compute
from tempest.common.utils import net_info
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -148,6 +149,8 @@
msg = ('Deployment uses a shared physical network, security '
'groups not supported')
raise cls.skipException(msg)
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
@classmethod
def setup_credentials(cls):
@@ -160,7 +163,7 @@
super(TestSecurityGroupsBasicOps, cls).resource_setup()
cls.multi_node = CONF.compute.min_compute_nodes > 1 and \
- test.is_scheduler_filter_enabled("DifferentHostFilter")
+ compute.is_scheduler_filter_enabled("DifferentHostFilter")
if cls.multi_node:
LOG.info("Working in Multi Node mode")
else:
@@ -168,7 +171,7 @@
cls.floating_ips = {}
cls.tenants = {}
- cls.primary_tenant = cls.TenantProperties(cls.os)
+ 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
@@ -221,7 +224,7 @@
# Checks that we see the newly created network/subnet/router via
# checking the result of list_[networks,routers,subnets]
# Check that (router, subnet) couple exist in port_list
- seen_nets = self.admin_manager.networks_client.list_networks()
+ seen_nets = self.os_admin.networks_client.list_networks()
seen_names = [n['name'] for n in seen_nets['networks']]
seen_ids = [n['id'] for n in seen_nets['networks']]
@@ -230,13 +233,13 @@
seen_subnets = [
(n['id'], n['cidr'], n['network_id']) for n in
- self.admin_manager.subnets_client.list_subnets()['subnets']
+ self.os_admin.subnets_client.list_subnets()['subnets']
]
mysubnet = (tenant.subnet['id'], tenant.subnet['cidr'],
tenant.network['id'])
self.assertIn(mysubnet, seen_subnets)
- seen_routers = self.admin_manager.routers_client.list_routers()
+ seen_routers = self.os_admin.routers_client.list_routers()
seen_router_ids = [n['id'] for n in seen_routers['routers']]
seen_router_names = [n['name'] for n in seen_routers['routers']]
@@ -246,7 +249,7 @@
myport = (tenant.router['id'], tenant.subnet['id'])
router_ports = [
(i['device_id'], f['subnet_id'])
- for i in self.admin_manager.ports_client.list_ports(
+ for i in self.os_admin.ports_client.list_ports(
device_id=tenant.router['id'])['ports']
if net_info.is_router_interface_port(i)
for f in i['fixed_ips']
@@ -279,7 +282,7 @@
# Verify servers are on different compute nodes
if self.multi_node:
- adm_get_server = self.admin_manager.servers_client.show_server
+ adm_get_server = self.os_admin.servers_client.show_server
new_host = adm_get_server(server["id"])["server"][
"OS-EXT-SRV-ATTR:host"]
host_list = [adm_get_server(s)["server"]["OS-EXT-SRV-ATTR:host"]
@@ -447,7 +450,7 @@
mac_addr = mac_addr.strip().lower()
# Get the fixed_ips and mac_address fields of all ports. Select
# only those two columns to reduce the size of the response.
- port_list = self.admin_manager.ports_client.list_ports(
+ port_list = self.os_admin.ports_client.list_ports(
fields=['fixed_ips', 'mac_address'])['ports']
port_detail_list = [
(port['fixed_ips'][0]['subnet_id'],
@@ -541,7 +544,7 @@
dest=self._get_server_ip(server),
should_succeed=False)
server_id = server['id']
- port_id = self.admin_manager.ports_client.list_ports(
+ port_id = self.os_admin.ports_client.list_ports(
device_id=server_id)['ports'][0]['id']
# update port with new security group and check connectivity
@@ -605,7 +608,7 @@
access_point_ssh = self._connect_to_access_point(new_tenant)
server_id = server['id']
- port_id = self.admin_manager.ports_client.list_ports(
+ port_id = self.os_admin.ports_client.list_ports(
device_id=server_id)['ports'][0]['id']
# Flip the port's port security and check connectivity
@@ -647,7 +650,7 @@
sec_groups = []
server = self._create_server(name, tenant, sec_groups)
server_id = server['id']
- ports = self.admin_manager.ports_client.list_ports(
+ ports = self.os_admin.ports_client.list_ports(
device_id=server_id)['ports']
self.assertEqual(1, len(ports))
for port in ports:
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 2be9e06..0c441ab 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -43,6 +43,12 @@
* Terminate the instance
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestServerBasicOps, cls).skip_checks()
+ if not CONF.network_feature_enabled.floating_ips:
+ raise cls.skipException("Floating ips are not available")
+
def setUp(self):
super(TestServerBasicOps, self).setUp()
self.run_ssh = CONF.validation.run_validation
@@ -56,7 +62,8 @@
self.ssh_client = self.get_remote_client(
ip_address=self.fip,
username=self.ssh_user,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=self.instance)
def verify_metadata(self):
if self.run_ssh and CONF.compute_feature_enabled.metadata_service:
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index d9bff09..552ab27 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions
@@ -35,15 +34,6 @@
raise cls.skipException(
"Less than 2 compute nodes, skipping multinode tests.")
- @classmethod
- def setup_clients(cls):
- super(TestServerMultinode, cls).setup_clients()
- # Use admin client by default
- cls.manager = cls.admin_manager
- # this is needed so that we can use the availability_zone:host
- # scheduler hint, which is admin_only by default
- cls.servers_client = cls.admin_manager.servers_client
-
@decorators.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30')
@decorators.attr(type='smoke')
@test.services('compute', 'network')
@@ -74,9 +64,13 @@
for host in hosts[:CONF.compute.min_compute_nodes]:
# by getting to active state here, this means this has
# landed on the host in question.
+ # in order to use the availability_zone:host scheduler hint,
+ # admin client is need here.
inst = self.create_server(
+ clients=self.os_admin,
availability_zone='%(zone)s:%(host_name)s' % host)
- server = self.servers_client.show_server(inst['id'])['server']
+ server = self.os_admin.servers_client.show_server(
+ inst['id'])['server']
# ensure server is located on the requested host
self.assertEqual(host['host_name'], server['OS-EXT-SRV-ATTR:host'])
servers.append(server)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index debd664..3632648 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,9 +16,7 @@
from oslo_log import log as logging
import testtools
-from tempest.common import waiters
from tempest import config
-from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -57,20 +55,6 @@
if not CONF.volume_feature_enabled.snapshot:
raise cls.skipException("Cinder volume snapshots are disabled")
- def _create_volume_snapshot(self, volume):
- snapshot_name = data_utils.rand_name('scenario-snapshot')
- snapshot = self.snapshots_client.create_snapshot(
- volume_id=volume['id'], display_name=snapshot_name)['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.volumes_client,
- volume['id'], 'available')
- waiters.wait_for_volume_resource_status(self.snapshots_client,
- snapshot['id'], 'available')
- self.assertEqual(snapshot_name, snapshot['name'])
- return snapshot
-
def _wait_for_volume_available_on_the_system(self, ip_address,
private_key):
ssh = self.get_remote_client(ip_address, private_key=private_key)
@@ -116,7 +100,7 @@
self.nova_volume_detach(server, volume)
# snapshot the volume
- volume_snapshot = self._create_volume_snapshot(volume)
+ volume_snapshot = self.create_volume_snapshot(volume['id'])
# snapshot the instance
snapshot_image = self.create_server_snapshot(server=server)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3dfbf18..b6f3b38 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class TestVolumeBootPattern(manager.ScenarioTest):
+class TestVolumeBootPattern(manager.EncryptionScenarioTest):
# Boot from volume scenario is quite slow, and needs extra
# breathing room to get through deletes in the time allotted.
@@ -69,21 +69,6 @@
return self.create_server(image_id='', **create_kwargs)
- def _create_snapshot_from_volume(self, vol_id):
- snap_name = data_utils.rand_name(
- self.__class__.__name__ + '-snapshot')
- snap = self.snapshots_client.create_snapshot(
- volume_id=vol_id,
- force=True,
- display_name=snap_name)['snapshot']
- self.addCleanup(
- self.snapshots_client.wait_for_resource_deletion, snap['id'])
- self.addCleanup(self.snapshots_client.delete_snapshot, snap['id'])
- waiters.wait_for_volume_resource_status(self.snapshots_client,
- snap['id'], 'available')
- self.assertEqual(snap_name, snap['name'])
- return snap
-
def _delete_server(self, server):
self.servers_client.delete_server(server['id'])
waiters.wait_for_server_termination(self.servers_client, server['id'])
@@ -147,7 +132,7 @@
# snapshot a volume
LOG.info("Creating snapshot from volume: %s", volume_origin['id'])
- snapshot = self._create_snapshot_from_volume(volume_origin['id'])
+ snapshot = self.create_volume_snapshot(volume_origin['id'], force=True)
# create a 3rd instance from snapshot
LOG.info("Creating third instance from snapshot: %s", snapshot['id'])
@@ -177,7 +162,7 @@
boot_volume = self._create_volume_from_image()
# Create a snapshot
- boot_snapshot = self._create_snapshot_from_volume(boot_volume['id'])
+ boot_snapshot = self.create_volume_snapshot(boot_volume['id'])
# Create a server from a volume snapshot
server = self._boot_instance_from_resource(
@@ -227,3 +212,25 @@
# delete instance
self._delete_server(instance)
+
+ @decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125')
+ @testtools.skipUnless(CONF.compute_feature_enabled.attach_encrypted_volume,
+ 'Encrypted volume attach is not supported')
+ @test.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_type='luks')
+
+ self.volumes_client.set_bootable_volume(volume['id'], bootable=True)
+
+ # Boot a server from the encrypted volume
+ server = self._boot_instance_from_resource(
+ source_id=volume['id'],
+ source_type='volume',
+ delete_on_termination=False)
+
+ server_info = self.servers_client.show_server(server['id'])['server']
+ created_volume = server_info['os-extended-volumes:volumes_attached']
+ self.assertEqual(volume['id'], created_volume[0]['id'])
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index 63dc23d..5667fbb 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -40,7 +40,7 @@
@classmethod
def setup_clients(cls):
super(TestVolumeMigrateRetypeAttached, cls).setup_clients()
- cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
+ cls.admin_volumes_client = cls.os_admin.volumes_v2_client
@classmethod
def skip_checks(cls):
@@ -82,7 +82,7 @@
def _volume_retype_with_migration(self, volume_id, new_volume_type):
migration_policy = 'on-demand'
- self.volumes_client.retype_volume(
+ self.admin_volumes_client.retype_volume(
volume_id, new_type=new_volume_type,
migration_policy=migration_policy)
waiters.wait_for_volume_retype(self.volumes_client,
diff --git a/tempest/services/object_storage/__init__.py b/tempest/services/object_storage/__init__.py
index 1738566..a2f0992 100644
--- a/tempest/services/object_storage/__init__.py
+++ b/tempest/services/object_storage/__init__.py
@@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.services.object_storage.account_client import AccountClient
-from tempest.services.object_storage.bulk_middleware_client import \
+from tempest.lib.services.object_storage.bulk_middleware_client import \
BulkMiddlewareClient
-from tempest.services.object_storage.capabilities_client import \
+from tempest.lib.services.object_storage.capabilities_client import \
CapabilitiesClient
+from tempest.services.object_storage.account_client import AccountClient
from tempest.services.object_storage.container_client import ContainerClient
from tempest.services.object_storage.object_client import ObjectClient
diff --git a/tempest/test.py b/tempest/test.py
index e8108f4..47cbb5e 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -26,10 +26,10 @@
from tempest import clients
from tempest.common import credentials_factory as credentials
-from tempest.common import fixed_network
import tempest.common.validation_resources as vresources
from tempest import config
from tempest.lib.common import cred_client
+from tempest.lib.common import fixed_network
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -44,11 +44,6 @@
version='Mitaka', removal_version='?')
-related_bug = debtcollector.moves.moved_function(
- decorators.related_bug, 'related_bug', __name__,
- version='Pike', removal_version='?')
-
-
attr = debtcollector.moves.moved_function(
decorators.attr, 'attr', __name__,
version='Pike', removal_version='?')
@@ -63,7 +58,14 @@
'compute': CONF.service_available.nova,
'image': CONF.service_available.glance,
'volume': CONF.service_available.cinder,
+ # NOTE(masayukig): We have two network services which are neutron and
+ # nova-network. And we have no way to know whether nova-network is
+ # available or not. After the pending removal of nova-network from
+ # nova, we can treat the network/neutron case in the same manner as
+ # the other services.
'network': True,
+ # NOTE(masayukig): Tempest tests always require the identity service.
+ # So we should set this True here.
'identity': True,
'object_storage': CONF.service_available.swift,
}
@@ -137,27 +139,6 @@
return False
-def is_scheduler_filter_enabled(filter_name):
- """Check the list of enabled compute scheduler filters from config.
-
- This function checks whether the given compute scheduler filter is
- available and configured in the config file. If the
- scheduler_available_filters option is set to 'all' (Default value. which
- means default filters are configured in nova) in tempest.conf then, this
- function returns True with assumption that requested filter 'filter_name'
- is one of available filter in nova ("nova.scheduler.filters.all_filters").
- """
-
- filters = CONF.compute_feature_enabled.scheduler_available_filters
- if not filters:
- return False
- if 'all' in filters:
- return True
- if filter_name in filters:
- return True
- return False
-
-
at_exit_set = set()
@@ -247,6 +228,9 @@
@classmethod
def tearDownClass(cls):
+ # insert pdb breakpoint when pause_teardown is enabled
+ if CONF.pause_teardown:
+ cls.insert_pdb_breakpoint()
at_exit_set.discard(cls)
# It should never be overridden by descendants
if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
@@ -283,6 +267,22 @@
finally:
del trace # to avoid circular refs
+ def tearDown(self):
+ super(BaseTestCase, self).tearDown()
+ # insert pdb breakpoint when pause_teardown is enabled
+ if CONF.pause_teardown:
+ BaseTestCase.insert_pdb_breakpoint()
+
+ @classmethod
+ def insert_pdb_breakpoint(cls):
+ """Add pdb breakpoint.
+
+ This can help in debugging process, cleaning of resources is
+ paused, so they can be examined.
+ """
+ import pdb
+ pdb.set_trace()
+
@classmethod
def skip_checks(cls):
"""Class level skip checks.
@@ -373,9 +373,18 @@
@classmethod
def resource_setup(cls):
"""Class level resource setup for test cases."""
- if hasattr(cls, "os"):
+ if (CONF.validation.ip_version_for_ssh not in (4, 6) and
+ CONF.service_available.neutron):
+ msg = "Invalid IP version %s in ip_version_for_ssh. Use 4 or 6"
+ raise lib_exc.InvalidConfiguration(
+ msg % CONF.validation.ip_version_for_ssh)
+ if hasattr(cls, "os_primary"):
cls.validation_resources = vresources.create_validation_resources(
- cls.os, cls.validation_resources)
+ cls.os_primary, cls.validation_resources,
+ use_neutron=CONF.service_available.neutron,
+ ethertype='IPv' + str(CONF.validation.ip_version_for_ssh),
+ floating_network_id=CONF.network.public_network_id,
+ floating_network_name=CONF.network.floating_network_name)
else:
LOG.warning("Client manager not found, validation resources not"
" created")
@@ -388,8 +397,8 @@
resources, in case a failure during `resource_setup` should happen.
"""
if cls.validation_resources:
- if hasattr(cls, "os"):
- vresources.clear_validation_resources(cls.os,
+ if hasattr(cls, "os_primary"):
+ vresources.clear_validation_resources(cls.os_primary,
cls.validation_resources)
cls.validation_resources = {}
else:
diff --git a/tempest/tests/README.rst b/tempest/tests/README.rst
index e54d4c0..0587e7b 100644
--- a/tempest/tests/README.rst
+++ b/tempest/tests/README.rst
@@ -7,16 +7,16 @@
---------------------
Unit tests are the self checks for Tempest. They provide functional
-verification and regression checking for the internal components of tempest.
-They should be used to just verify that the individual pieces of tempest are
+verification and regression checking for the internal components of Tempest.
+They should be used to just verify that the individual pieces of Tempest are
working as expected. They should not require an external service to be running
-and should be able to run solely from the tempest tree.
+and should be able to run solely from the Tempest tree.
-Why are these tests in tempest?
+Why are these tests in Tempest?
-------------------------------
These tests exist to make sure that the mechanisms that we use inside of
-tempest to are valid and remain functional. They are only here for self
-validation of tempest.
+Tempest are valid and remain functional. They are only here for self
+validation of Tempest.
Scope of these tests
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 6773b2f..f907bd0 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -20,7 +20,6 @@
from tempest import config
from tempest.tests import base
from tempest.tests import fake_config
-from tempest.tests.lib import fake_identity
class FakeOpts(object):
@@ -85,14 +84,10 @@
class TestAccountGeneratorV2(base.TestCase, MockHelpersMixin):
identity_version = 2
- identity_response = fake_identity._fake_v2_response
def setUp(self):
super(TestAccountGeneratorV2, self).setUp()
self.mock_config_and_opts(self.identity_version)
- self.useFixture(fixtures.MockPatch(
- 'tempest.lib.auth.AuthProvider.set_auth',
- return_value=self.identity_response))
def test_get_credential_provider(self):
cp = account_generator.get_credential_provider(self.opts)
@@ -115,7 +110,6 @@
class TestAccountGeneratorV3(TestAccountGeneratorV2):
identity_version = 3
- identity_response = fake_identity._fake_v3_response
def setUp(self):
super(TestAccountGeneratorV3, self).setUp()
@@ -145,16 +139,13 @@
class TestGenerateResourcesV2(base.TestCase, MockHelpersMixin):
identity_version = 2
- identity_response = fake_identity._fake_v2_response
cred_client = 'tempest.lib.common.cred_client.V2CredsClient'
- dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+ dynamic_creds = ('tempest.lib.common.dynamic_creds.'
+ 'DynamicCredentialProvider')
def setUp(self):
super(TestGenerateResourcesV2, self).setUp()
self.mock_config_and_opts(self.identity_version)
- self.useFixture(fixtures.MockPatch(
- 'tempest.lib.auth.AuthProvider.set_auth',
- return_value=self.identity_response))
self.cred_provider = account_generator.get_credential_provider(
self.opts)
self.mock_resource_creation()
@@ -244,7 +235,6 @@
class TestGenerateResourcesV3(TestGenerateResourcesV2):
identity_version = 3
- identity_response = fake_identity._fake_v3_response
cred_client = 'tempest.lib.common.cred_client.V3CredsClient'
def setUp(self):
@@ -255,17 +245,14 @@
class TestDumpAccountsV2(base.TestCase, MockHelpersMixin):
identity_version = 2
- identity_response = fake_identity._fake_v2_response
cred_client = 'tempest.lib.common.cred_client.V2CredsClient'
- dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+ dynamic_creds = ('tempest.lib.common.dynamic_creds.'
+ 'DynamicCredentialProvider')
domain_is_in = False
def setUp(self):
super(TestDumpAccountsV2, self).setUp()
self.mock_config_and_opts(self.identity_version)
- self.useFixture(fixtures.MockPatch(
- 'tempest.lib.auth.AuthProvider.set_auth',
- return_value=self.identity_response))
self.cred_provider = account_generator.get_credential_provider(
self.opts)
self.mock_resource_creation()
@@ -337,7 +324,6 @@
class TestDumpAccountsV3(TestDumpAccountsV2):
identity_version = 3
- identity_response = fake_identity._fake_v3_response
cred_client = 'tempest.lib.common.cred_client.V3CredsClient'
domain_is_in = True
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 98bf145..810f9e5 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -12,13 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
import mock
from oslo_serialization import jsonutils as json
-from oslotest import mockpatch
+from tempest import clients
from tempest.cmd import verify_tempest_config
+from tempest.common import credentials_factory
from tempest import config
+from tempest.lib.common import rest_client
from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
from tempest.tests import fake_config
@@ -73,12 +77,12 @@
fake_config.FakePrivate)
def test_get_keystone_api_versions(self):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': {'values': [{'id': 'v2.0'}, {'id': 'v3.0'}]}}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.http.ClosingHttp.request',
return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
@@ -87,12 +91,12 @@
self.assertIn('v3.0', versions)
def test_get_cinder_api_versions(self):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.http.ClosingHttp.request',
return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
@@ -101,12 +105,12 @@
self.assertIn('v2.0', versions)
def test_get_nova_versions(self):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.http.ClosingHttp.request',
return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
@@ -117,17 +121,17 @@
def test_get_versions_invalid_response(self):
# When the response doesn't contain a JSON response, an error is
# logged.
- mock_log_error = self.useFixture(mockpatch.PatchObject(
+ mock_log_error = self.useFixture(fixtures.MockPatchObject(
verify_tempest_config.LOG, 'error')).mock
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint'))
# Simulated response is not JSON.
sample_body = (
'<html><head>Sample Response</head><body>This is the sample page '
'for the web server. Why are you requesting it?</body></html>')
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.http.ClosingHttp.request',
return_value=(None, sample_body)))
@@ -157,7 +161,7 @@
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_keystone_api_versions_no_v3(self, mock_request):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': {'values': [{'id': 'v2.0'}]}}
@@ -173,7 +177,7 @@
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_keystone_api_versions_no_v2(self, mock_request):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': {'values': [{'id': 'v3.0'}]}}
@@ -189,7 +193,7 @@
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v3(self, mock_request):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v2.0'}]}
@@ -199,11 +203,13 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_not_called()
+ print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+ False, True)
+ self.assertEqual(1, print_mock.call_count)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v2(self, mock_request):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v3.0'}]}
@@ -215,13 +221,11 @@
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
False, True)
- print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
- True, True)
- self.assertEqual(2, print_mock.call_count)
+ self.assertEqual(1, print_mock.call_count)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v1(self, mock_request):
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
@@ -231,15 +235,18 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
- True, True)
- self.assertEqual(1, print_mock.call_count)
+ print_mock.assert_not_called()
def test_verify_glance_version_no_v2_with_v1_1(self):
- def fake_get_versions():
- return (None, ['v1.1'])
+ # This test verifies that wrong config api_v2 = True is detected
+ class FakeClient(object):
+ def get_versions(self):
+ return (None, ['v1.0'])
+
fake_os = mock.MagicMock()
- fake_os.image_client.get_versions = fake_get_versions
+ fake_module = mock.MagicMock()
+ fake_module.ImagesClient = FakeClient
+ fake_os.image_v1 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
@@ -247,10 +254,15 @@
False, True)
def test_verify_glance_version_no_v2_with_v1_0(self):
- def fake_get_versions():
- return (None, ['v1.0'])
+ # This test verifies that wrong config api_v2 = True is detected
+ class FakeClient(object):
+ def get_versions(self):
+ return (None, ['v1.0'])
+
fake_os = mock.MagicMock()
- fake_os.image_client.get_versions = fake_get_versions
+ fake_module = mock.MagicMock()
+ fake_module.ImagesClient = FakeClient
+ fake_os.image_v1 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
@@ -258,25 +270,60 @@
False, True)
def test_verify_glance_version_no_v1(self):
- def fake_get_versions():
- return (None, ['v2.0'])
+ # This test verifies that wrong config api_v1 = True is detected
+ class FakeClient(object):
+ def get_versions(self):
+ raise lib_exc.NotFound()
+
+ def list_versions(self):
+ return {'versions': [{'id': 'v2.0'}]}
+
fake_os = mock.MagicMock()
- fake_os.image_client.get_versions = fake_get_versions
+ fake_module = mock.MagicMock()
+ fake_module.ImagesClient = FakeClient
+ fake_module.VersionsClient = FakeClient
+ fake_os.image_v1 = fake_module
+ fake_os.image_v2 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
print_mock.assert_called_once_with('api_v1', 'image-feature-enabled',
False, True)
+ def test_verify_glance_version_no_version(self):
+ # This test verifies that wrong config api_v1 = True is detected
+ class FakeClient(object):
+ def get_versions(self):
+ raise lib_exc.NotFound()
+
+ def list_versions(self):
+ raise lib_exc.NotFound()
+
+ fake_os = mock.MagicMock()
+ fake_module = mock.MagicMock()
+ fake_module.ImagesClient = FakeClient
+ fake_module.VersionsClient = FakeClient
+ fake_os.image_v1 = fake_module
+ fake_os.image_v2 = fake_module
+ with mock.patch.object(verify_tempest_config,
+ 'print_and_or_update') as print_mock:
+ verify_tempest_config.verify_glance_api_versions(fake_os, True)
+ print_mock.assert_called_once_with('glance',
+ 'service-available',
+ False, True)
+
def test_verify_extensions_neutron(self):
def fake_list_extensions():
return {'extensions': [{'alias': 'fake1'},
{'alias': 'fake2'},
{'alias': 'not_fake'}]}
fake_os = mock.MagicMock()
- fake_os.network_extensions_client.list_extensions = (
- fake_list_extensions)
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['fake1', 'fake2', 'fake3'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -297,9 +344,12 @@
{'alias': 'fake2'},
{'alias': 'not_fake'}]}
fake_os = mock.MagicMock()
- fake_os.network_extensions_client.list_extensions = (
- fake_list_extensions)
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['all'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -315,15 +365,17 @@
{'alias': 'fake2'},
{'alias': 'not_fake'}]}
fake_os = mock.MagicMock()
- # NOTE (e0ne): mock both v1 and v2 APIs
- fake_os.volumes_extension_client.list_extensions = fake_list_extensions
- fake_os.volumes_v2_extension_client.list_extensions = (
- fake_list_extensions)
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['fake1', 'fake2', 'fake3'])))
results = verify_tempest_config.verify_extensions(fake_os,
'cinder', {})
+
self.assertIn('cinder', results)
self.assertIn('fake1', results['cinder'])
self.assertTrue(results['cinder']['fake1'])
@@ -340,11 +392,12 @@
{'alias': 'fake2'},
{'alias': 'not_fake'}]}
fake_os = mock.MagicMock()
- # NOTE (e0ne): mock both v1 and v2 APIs
- fake_os.volumes_extension_client.list_extensions = fake_list_extensions
- fake_os.volumes_v2_extension_client.list_extensions = (
- fake_list_extensions)
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['all'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -359,8 +412,12 @@
return ([{'alias': 'fake1'}, {'alias': 'fake2'},
{'alias': 'not_fake'}])
fake_os = mock.MagicMock()
- fake_os.extensions_client.list_extensions = fake_list_extensions
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['fake1', 'fake2', 'fake3'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -381,8 +438,12 @@
{'alias': 'fake2'},
{'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
- fake_os.extensions_client.list_extensions = fake_list_extensions
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_extensions = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['all'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -394,13 +455,17 @@
def test_verify_extensions_swift(self):
def fake_list_extensions():
- return (None, {'fake1': 'metadata',
- 'fake2': 'metadata',
- 'not_fake': 'metadata',
- 'swift': 'metadata'})
+ return {'fake1': 'metadata',
+ 'fake2': 'metadata',
+ 'not_fake': 'metadata',
+ 'swift': 'metadata'}
fake_os = mock.MagicMock()
- fake_os.capabilities_client.list_capabilities = fake_list_extensions
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_capabilities = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['fake1', 'fake2', 'fake3'])))
results = verify_tempest_config.verify_extensions(fake_os, 'swift', {})
@@ -416,13 +481,17 @@
def test_verify_extensions_swift_all(self):
def fake_list_extensions():
- return (None, {'fake1': 'metadata',
- 'fake2': 'metadata',
- 'not_fake': 'metadata',
- 'swift': 'metadata'})
+ return {'fake1': 'metadata',
+ 'fake2': 'metadata',
+ 'not_fake': 'metadata',
+ 'swift': 'metadata'}
fake_os = mock.MagicMock()
- fake_os.capabilities_client.list_capabilities = fake_list_extensions
- self.useFixture(mockpatch.PatchObject(
+ fake_client = mock.MagicMock()
+ fake_client.list_capabilities = fake_list_extensions
+ self.useFixture(fixtures.MockPatchObject(
+ verify_tempest_config, 'get_extension_client',
+ return_value=fake_client))
+ self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['all'])))
results = verify_tempest_config.verify_extensions(fake_os,
@@ -431,3 +500,13 @@
self.assertIn('extensions', results['swift'])
self.assertEqual(sorted(['not_fake', 'fake1', 'fake2']),
sorted(results['swift']['extensions']))
+
+ def test_get_extension_client(self):
+ creds = credentials_factory.get_credentials(
+ fill_in=False, username='fake_user', project_name='fake_project',
+ password='fake_password')
+ os = clients.Manager(creds)
+ for service in ['nova', 'neutron', 'swift', 'cinder']:
+ extensions_client = verify_tempest_config.get_extension_client(
+ os, service)
+ self.assertIsInstance(extensions_client, rest_client.RestClient)
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index dc6c0c8..a1c8c53 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -80,13 +80,20 @@
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
- def test_run_workspace_remove(self):
+ def test_run_workspace_remove_entry(self):
cmd = ['tempest', 'workspace', 'remove',
'--workspace-path', self.store_file,
'--name', self.name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+ def test_run_workspace_remove_directory(self):
+ cmd = ['tempest', 'workspace', 'remove',
+ '--workspace-path', self.store_file,
+ '--name', self.name, '--rmdir']
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
class TestTempestWorkspaceManager(TestTempestWorkspaceBase):
def setUp(self):
@@ -117,8 +124,13 @@
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
- def test_workspace_manager_remove(self):
- self.workspace_manager.remove_workspace(self.name)
+ def test_workspace_manager_remove_entry(self):
+ self.workspace_manager.remove_workspace_entry(self.name)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+ def test_workspace_manager_remove_directory(self):
+ path = self.workspace_manager.remove_workspace_entry(self.name)
+ self.workspace_manager.remove_workspace_directory(path)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
def test_path_expansion(self):
diff --git a/tempest/tests/common/test_admin_available.py b/tempest/tests/common/test_admin_available.py
index 01a9cd0..7b3b1b0 100644
--- a/tempest/tests/common/test_admin_available.py
+++ b/tempest/tests/common/test_admin_available.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
from oslo_config import cfg
-from oslotest import mockpatch
from tempest.common import credentials_factory as credentials
from tempest import config
@@ -52,16 +52,16 @@
'project_name': 'admin',
'password': 'p',
'types': ['admin']})
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=accounts))
cfg.CONF.set_default('test_accounts_file',
use_accounts_file, group='auth')
- self.useFixture(mockpatch.Patch('os.path.isfile',
- return_value=True))
+ self.useFixture(fixtures.MockPatch('os.path.isfile',
+ return_value=True))
else:
- self.useFixture(mockpatch.Patch('os.path.isfile',
- return_value=False))
+ self.useFixture(fixtures.MockPatch('os.path.isfile',
+ return_value=False))
if admin_creds:
username = 'u'
project = 't'
diff --git a/tempest/tests/common/test_alt_available.py b/tempest/tests/common/test_alt_available.py
index 27db95c..a425bb8 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
from oslo_config import cfg
-from oslotest import mockpatch
from tempest.common import credentials_factory as credentials
from tempest import config
@@ -39,16 +39,16 @@
accounts = [dict(username="u%s" % ii,
project_name="t%s" % ii,
password="p") for ii in creds]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=accounts))
cfg.CONF.set_default('test_accounts_file',
use_accounts_file, group='auth')
- self.useFixture(mockpatch.Patch('os.path.isfile',
- return_value=True))
+ self.useFixture(fixtures.MockPatch('os.path.isfile',
+ return_value=True))
else:
- self.useFixture(mockpatch.Patch('os.path.isfile',
- return_value=False))
+ self.useFixture(fixtures.MockPatch('os.path.isfile',
+ return_value=False))
expected = len(set(creds)) > 1 or dynamic_creds
observed = credentials.is_alt_available(
identity_version=self.identity_version)
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
new file mode 100644
index 0000000..c108be9
--- /dev/null
+++ b/tempest/tests/common/test_compute.py
@@ -0,0 +1,106 @@
+# Copyright 2017 Citrix Systems
+# 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 six.moves.urllib import parse as urlparse
+
+import mock
+
+from tempest.common import compute
+from tempest.tests import base
+
+
+class TestCompute(base.TestCase):
+ def setUp(self):
+ super(TestCompute, self).setUp()
+ self.client_sock = mock.Mock()
+ self.url = urlparse.urlparse("http://www.fake.com:80")
+
+ def test_rfp_frame_not_cached(self):
+ # rfp negotiation frame arrived separately after upgrade
+ # response, so it's not cached.
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_frame_header = b'\x82\x0c'
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n',
+ rfp_frame_header,
+ RFP_VERSION]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.assertEqual(webSocket.response, expect_response)
+ # no cache
+ self.assertEqual(webSocket.cached_stream, b'')
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+
+ self.client_sock.recv.reset_mock()
+ recv_version = webSocket.receive_frame()
+
+ self.assertEqual(recv_version, RFP_VERSION)
+ self.client_sock.recv.assert_has_calls([mock.call(2),
+ mock.call(12)])
+
+ def test_rfp_frame_fully_cached(self):
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n%s' % rfp_version_frame]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+ self.assertEqual(webSocket.response, expect_response)
+ self.assertEqual(webSocket.cached_stream, rfp_version_frame)
+
+ self.client_sock.recv.reset_mock()
+ recv_version = webSocket.receive_frame()
+
+ self.client_sock.recv.assert_not_called()
+ self.assertEqual(recv_version, RFP_VERSION)
+ # cached_stream should be empty in the end.
+ self.assertEqual(webSocket.cached_stream, b'')
+
+ def test_rfp_frame_partially_cached(self):
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+ frame_part1 = rfp_version_frame[:6]
+ frame_part2 = rfp_version_frame[6:]
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n%s' % frame_part1,
+ frame_part2]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+ self.assertEqual(webSocket.response, expect_response)
+ self.assertEqual(webSocket.cached_stream, frame_part1)
+
+ self.client_sock.recv.reset_mock()
+
+ recv_version = webSocket.receive_frame()
+
+ self.client_sock.recv.assert_called_once_with(len(frame_part2))
+ self.assertEqual(recv_version, RFP_VERSION)
+ # cached_stream should be empty in the end.
+ self.assertEqual(webSocket.cached_stream, b'')
diff --git a/tempest/tests/common/test_credentials_factory.py b/tempest/tests/common/test_credentials_factory.py
new file mode 100644
index 0000000..020818e
--- /dev/null
+++ b/tempest/tests/common/test_credentials_factory.py
@@ -0,0 +1,279 @@
+# Copyright 2017 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+from oslo_config import cfg
+import testtools
+
+from tempest.common import credentials_factory as cf
+from tempest import config
+from tempest.lib.common import dynamic_creds
+from tempest.lib.common import preprov_creds
+from tempest.lib import exceptions
+from tempest.tests import base
+from tempest.tests import fake_config
+from tempest.tests.lib import fake_credentials
+
+
+class TestCredentialsFactory(base.TestCase):
+
+ def setUp(self):
+ super(TestCredentialsFactory, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
+
+ def test_get_dynamic_provider_params_creds_v2(self):
+ expected_uri = 'EXPECTED_V2_URI'
+ cfg.CONF.set_default('uri', expected_uri, group='identity')
+ admin_creds = fake_credentials.FakeCredentials()
+ params = cf.get_dynamic_provider_params('v2', admin_creds=admin_creds)
+ expected_params = dict(identity_uri=expected_uri,
+ admin_creds=admin_creds)
+ for key in expected_params:
+ self.assertIn(key, params)
+ self.assertEqual(expected_params[key], params[key])
+
+ def test_get_dynamic_provider_params_creds_v3(self):
+ expected_uri = 'EXPECTED_V3_URI'
+ cfg.CONF.set_default('uri_v3', expected_uri, group='identity')
+ admin_creds = fake_credentials.FakeCredentials()
+ params = cf.get_dynamic_provider_params('v3', admin_creds=admin_creds)
+ expected_params = dict(identity_uri=expected_uri,
+ admin_creds=admin_creds)
+ for key in expected_params:
+ self.assertIn(key, params)
+ self.assertEqual(expected_params[key], params[key])
+
+ def test_get_dynamic_provider_params_creds_vx(self):
+ admin_creds = fake_credentials.FakeCredentials()
+ invalid_version = 'invalid_version_x'
+ with testtools.ExpectedException(
+ exc_type=exceptions.InvalidIdentityVersion,
+ value_re='Invalid version ' + invalid_version):
+ cf.get_dynamic_provider_params(invalid_version,
+ admin_creds=admin_creds)
+
+ def test_get_dynamic_provider_params_no_creds(self):
+ expected_identity_version = 'v3'
+ with mock.patch.object(
+ cf, 'get_configured_admin_credentials') as admin_creds_mock:
+ cf.get_dynamic_provider_params(expected_identity_version)
+ admin_creds_mock.assert_called_once_with(
+ fill_in=True, identity_version=expected_identity_version)
+
+ def test_get_preprov_provider_params_creds_v2(self):
+ expected_uri = 'EXPECTED_V2_URI'
+ cfg.CONF.set_default('uri', expected_uri, group='identity')
+ params = cf.get_preprov_provider_params('v2')
+ self.assertIn('identity_uri', params)
+ self.assertEqual(expected_uri, params['identity_uri'])
+
+ def test_get_preprov_provider_params_creds_v3(self):
+ expected_uri = 'EXPECTED_V3_URI'
+ cfg.CONF.set_default('uri_v3', expected_uri, group='identity')
+ params = cf.get_preprov_provider_params('v3')
+ self.assertIn('identity_uri', params)
+ self.assertEqual(expected_uri, params['identity_uri'])
+
+ def test_get_preprov_provider_params_creds_vx(self):
+ invalid_version = 'invalid_version_x'
+ with testtools.ExpectedException(
+ exc_type=exceptions.InvalidIdentityVersion,
+ value_re='Invalid version ' + invalid_version):
+ cf.get_dynamic_provider_params(invalid_version)
+
+ @mock.patch.object(dynamic_creds, 'DynamicCredentialProvider')
+ @mock.patch.object(cf, 'get_dynamic_provider_params')
+ def test_get_credentials_provider_dynamic(
+ self, mock_dynamic_provider_params,
+ mock_dynamic_credentials_provider_class):
+ cfg.CONF.set_default('use_dynamic_credentials', True, group='auth')
+ expected_params = {'foo': 'bar'}
+ mock_dynamic_provider_params.return_value = expected_params
+ expected_name = 'my_name'
+ expected_network_resources = {'network': 'resources'}
+ expected_identity_version = 'identity_version'
+ cf.get_credentials_provider(
+ expected_name,
+ network_resources=expected_network_resources,
+ force_tenant_isolation=False,
+ identity_version=expected_identity_version)
+ mock_dynamic_provider_params.assert_called_once_with(
+ expected_identity_version)
+ mock_dynamic_credentials_provider_class.assert_called_once_with(
+ name=expected_name, network_resources=expected_network_resources,
+ **expected_params)
+
+ @mock.patch.object(preprov_creds, 'PreProvisionedCredentialProvider')
+ @mock.patch.object(cf, 'get_preprov_provider_params')
+ def test_get_credentials_provider_preprov(
+ self, mock_preprov_provider_params,
+ mock_preprov_credentials_provider_class):
+ cfg.CONF.set_default('use_dynamic_credentials', False, group='auth')
+ cfg.CONF.set_default('test_accounts_file', '/some/file', group='auth')
+ expected_params = {'foo': 'bar'}
+ mock_preprov_provider_params.return_value = expected_params
+ expected_name = 'my_name'
+ expected_identity_version = 'identity_version'
+ cf.get_credentials_provider(
+ expected_name,
+ force_tenant_isolation=False,
+ identity_version=expected_identity_version)
+ mock_preprov_provider_params.assert_called_once_with(
+ expected_identity_version)
+ mock_preprov_credentials_provider_class.assert_called_once_with(
+ name=expected_name, **expected_params)
+
+ def test_get_credentials_provider_preprov_no_file(self):
+ cfg.CONF.set_default('use_dynamic_credentials', False, group='auth')
+ cfg.CONF.set_default('test_accounts_file', None, group='auth')
+ with testtools.ExpectedException(
+ exc_type=exceptions.InvalidConfiguration):
+ cf.get_credentials_provider(
+ 'some_name',
+ force_tenant_isolation=False,
+ identity_version='some_version')
+
+ @mock.patch.object(dynamic_creds, 'DynamicCredentialProvider')
+ @mock.patch.object(cf, 'get_dynamic_provider_params')
+ def test_get_credentials_provider_force_dynamic(
+ self, mock_dynamic_provider_params,
+ mock_dynamic_credentials_provider_class):
+ cfg.CONF.set_default('use_dynamic_credentials', False, group='auth')
+ expected_params = {'foo': 'bar'}
+ mock_dynamic_provider_params.return_value = expected_params
+ expected_name = 'my_name'
+ expected_network_resources = {'network': 'resources'}
+ expected_identity_version = 'identity_version'
+ cf.get_credentials_provider(
+ expected_name,
+ network_resources=expected_network_resources,
+ force_tenant_isolation=True,
+ identity_version=expected_identity_version)
+ mock_dynamic_provider_params.assert_called_once_with(
+ expected_identity_version)
+ mock_dynamic_credentials_provider_class.assert_called_once_with(
+ name=expected_name, network_resources=expected_network_resources,
+ **expected_params)
+
+ @mock.patch.object(cf, 'get_credentials')
+ def test_get_configured_admin_credentials(self, mock_get_credentials):
+ cfg.CONF.set_default('auth_version', 'v3', 'identity')
+ all_params = [('admin_username', 'username', 'my_name'),
+ ('admin_password', 'password', 'secret'),
+ ('admin_project_name', 'project_name', 'my_pname'),
+ ('admin_domain_name', 'domain_name', 'my_dname')]
+ expected_result = 'my_admin_credentials'
+ mock_get_credentials.return_value = expected_result
+ for config_item, _, value in all_params:
+ cfg.CONF.set_default(config_item, value, 'auth')
+ # Build the expected params
+ expected_params = dict(
+ [(field, value) for _, field, value in all_params])
+ expected_params.update(cf.DEFAULT_PARAMS)
+ admin_creds = cf.get_configured_admin_credentials()
+ mock_get_credentials.assert_called_once_with(
+ fill_in=True, identity_version='v3', **expected_params)
+ self.assertEqual(expected_result, admin_creds)
+
+ @mock.patch.object(cf, 'get_credentials')
+ def test_get_configured_admin_credentials_not_fill_valid(
+ self, mock_get_credentials):
+ cfg.CONF.set_default('auth_version', 'v2', 'identity')
+ all_params = [('admin_username', 'username', 'my_name'),
+ ('admin_password', 'password', 'secret'),
+ ('admin_project_name', 'project_name', 'my_pname'),
+ ('admin_domain_name', 'domain_name', 'my_dname')]
+ expected_result = mock.Mock()
+ expected_result.is_valid.return_value = True
+ mock_get_credentials.return_value = expected_result
+ for config_item, _, value in all_params:
+ cfg.CONF.set_default(config_item, value, 'auth')
+ # Build the expected params
+ expected_params = dict(
+ [(field, value) for _, field, value in all_params])
+ expected_params.update(cf.DEFAULT_PARAMS)
+ admin_creds = cf.get_configured_admin_credentials(
+ fill_in=False, identity_version='v3')
+ mock_get_credentials.assert_called_once_with(
+ fill_in=False, identity_version='v3', **expected_params)
+ self.assertEqual(expected_result, admin_creds)
+ expected_result.is_valid.assert_called_once()
+
+ @mock.patch.object(cf, 'get_credentials')
+ def test_get_configured_admin_credentials_not_fill_not_valid(
+ self, mock_get_credentials):
+ cfg.CONF.set_default('auth_version', 'v2', 'identity')
+ expected_result = mock.Mock()
+ expected_result.is_valid.return_value = False
+ mock_get_credentials.return_value = expected_result
+ with testtools.ExpectedException(exceptions.InvalidConfiguration,
+ value_re='.*\n.*identity version v2'):
+ cf.get_configured_admin_credentials(fill_in=False)
+
+ @mock.patch('tempest.lib.auth.get_credentials')
+ def test_get_credentials_v2(self, mock_auth_get_credentials):
+ expected_uri = 'V2_URI'
+ expected_result = 'my_creds'
+ mock_auth_get_credentials.return_value = expected_result
+ cfg.CONF.set_default('uri', expected_uri, 'identity')
+ params = {'foo': 'bar'}
+ expected_params = params.copy()
+ expected_params.update(cf.DEFAULT_PARAMS)
+ result = cf.get_credentials(identity_version='v2', **params)
+ self.assertEqual(expected_result, result)
+ mock_auth_get_credentials.assert_called_once_with(
+ expected_uri, fill_in=True, identity_version='v2',
+ **expected_params)
+
+ @mock.patch('tempest.lib.auth.get_credentials')
+ def test_get_credentials_v3_no_domain(self, mock_auth_get_credentials):
+ expected_uri = 'V3_URI'
+ expected_result = 'my_creds'
+ expected_domain = 'my_domain'
+ mock_auth_get_credentials.return_value = expected_result
+ cfg.CONF.set_default('uri_v3', expected_uri, 'identity')
+ cfg.CONF.set_default('default_credentials_domain_name',
+ expected_domain, 'auth')
+ params = {'foo': 'bar'}
+ expected_params = params.copy()
+ expected_params['domain_name'] = expected_domain
+ expected_params.update(cf.DEFAULT_PARAMS)
+ result = cf.get_credentials(fill_in=False, identity_version='v3',
+ **params)
+ self.assertEqual(expected_result, result)
+ mock_auth_get_credentials.assert_called_once_with(
+ expected_uri, fill_in=False, identity_version='v3',
+ **expected_params)
+
+ @mock.patch('tempest.lib.auth.get_credentials')
+ def test_get_credentials_v3_domain(self, mock_auth_get_credentials):
+ expected_uri = 'V3_URI'
+ expected_result = 'my_creds'
+ expected_domain = 'my_domain'
+ mock_auth_get_credentials.return_value = expected_result
+ cfg.CONF.set_default('uri_v3', expected_uri, 'identity')
+ cfg.CONF.set_default('default_credentials_domain_name',
+ expected_domain, 'auth')
+ params = {'foo': 'bar', 'user_domain_name': expected_domain}
+ expected_params = params.copy()
+ expected_params.update(cf.DEFAULT_PARAMS)
+ result = cf.get_credentials(fill_in=False, identity_version='v3',
+ **params)
+ self.assertEqual(expected_result, result)
+ mock_auth_get_credentials.assert_called_once_with(
+ expected_uri, fill_in=False, identity_version='v3',
+ **expected_params)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index c2f622c..bc197b5 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -59,6 +59,7 @@
# Tests that the wait method raises VolumeRestoreErrorException if
# the volume status is 'error_restoring'.
client = mock.Mock(spec=volumes_client.VolumesClient,
+ resource_type="volume",
build_interval=1)
volume1 = {'volume': {'status': 'restoring-backup'}}
volume2 = {'volume': {'status': 'error_restoring'}}
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index ecb8e64..739357b 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -16,7 +16,6 @@
import fixtures
from oslo_config import cfg
-from oslotest import mockpatch
from tempest.common.utils.linux import remote_client
from tempest import config
@@ -64,8 +63,8 @@
cfg.CONF.set_default('connect_timeout', 1, group='validation')
self.conn = remote_client.RemoteClient('127.0.0.1', 'user', 'pass')
- self.ssh_mock = self.useFixture(mockpatch.PatchObject(self.conn,
- 'ssh_client'))
+ self.ssh_mock = self.useFixture(fixtures.MockPatchObject(self.conn,
+ 'ssh_client'))
def test_write_to_console_regular_str(self):
self.conn.write_to_console('test')
@@ -111,7 +110,7 @@
booted_at = 10000
uptime_sec = 5000.02
self.ssh_mock.mock.exec_command.return_value = uptime_sec
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
time, 'time', return_value=booted_at + uptime_sec))
self.assertEqual(self.conn.get_boot_time(),
time.localtime(booted_at))
diff --git a/tempest/tests/lib/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py
index 6206379..c063556 100644
--- a/tempest/tests/lib/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
import testtools
from tempest.lib.common import api_version_utils
@@ -30,7 +31,7 @@
cfg_max_version)
except testtools.TestCase.skipException as e:
if not expected_skip:
- raise testtools.TestCase.failureException(e.message)
+ raise testtools.TestCase.failureException(six.text_type(e))
def test_version_min_in_range(self):
self._test_version('2.2', '2.10', '2.1', '2.7')
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
similarity index 96%
rename from tempest/tests/common/test_dynamic_creds.py
rename to tempest/tests/lib/common/test_dynamic_creds.py
index b4fbd50..6aa7a42 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -12,13 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
import mock
from oslo_config import cfg
-from oslotest import mockpatch
from tempest.common import credentials_factory as credentials
-from tempest.common import dynamic_creds
from tempest import config
+from tempest.lib.common import dynamic_creds
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.identity.v2 import identity_client as v2_iden_client
@@ -46,7 +46,8 @@
fixed_params = {'name': 'test class',
'identity_version': 'v2',
- 'admin_role': 'admin'}
+ 'admin_role': 'admin',
+ 'identity_uri': 'fake_uri'}
token_client = v2_token_client
iden_client = v2_iden_client
@@ -84,7 +85,7 @@
tenant_name='fake_tenant')
def _mock_user_create(self, id, name):
- user_fix = self.useFixture(mockpatch.PatchObject(
+ user_fix = self.useFixture(fixtures.MockPatchObject(
self.users_client.UsersClient,
'create_user',
return_value=(rest_client.ResponseBody
@@ -92,7 +93,7 @@
return user_fix
def _mock_tenant_create(self, id, name):
- tenant_fix = self.useFixture(mockpatch.PatchObject(
+ tenant_fix = self.useFixture(fixtures.MockPatchObject(
self.tenants_client.TenantsClient,
'create_tenant',
return_value=(rest_client.ResponseBody
@@ -100,7 +101,7 @@
return tenant_fix
def _mock_list_roles(self, id, name):
- roles_fix = self.useFixture(mockpatch.PatchObject(
+ roles_fix = self.useFixture(fixtures.MockPatchObject(
self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
@@ -111,7 +112,7 @@
return roles_fix
def _mock_list_2_roles(self):
- roles_fix = self.useFixture(mockpatch.PatchObject(
+ roles_fix = self.useFixture(fixtures.MockPatchObject(
self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
@@ -122,7 +123,7 @@
return roles_fix
def _mock_assign_user_role(self):
- tenant_fix = self.useFixture(mockpatch.PatchObject(
+ tenant_fix = self.useFixture(fixtures.MockPatchObject(
self.roles_client.RolesClient,
'create_user_role_on_project',
return_value=(rest_client.ResponseBody
@@ -130,7 +131,7 @@
return tenant_fix
def _mock_list_role(self):
- roles_fix = self.useFixture(mockpatch.PatchObject(
+ roles_fix = self.useFixture(fixtures.MockPatchObject(
self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
@@ -140,7 +141,7 @@
return roles_fix
def _mock_list_ec2_credentials(self, user_id, tenant_id):
- ec2_creds_fix = self.useFixture(mockpatch.PatchObject(
+ ec2_creds_fix = self.useFixture(fixtures.MockPatchObject(
self.users_client.UsersClient,
'list_user_ec2_credentials',
return_value=(rest_client.ResponseBody
@@ -153,21 +154,21 @@
return ec2_creds_fix
def _mock_network_create(self, iso_creds, id, name):
- net_fix = self.useFixture(mockpatch.PatchObject(
+ net_fix = self.useFixture(fixtures.MockPatchObject(
iso_creds.networks_admin_client,
'create_network',
return_value={'network': {'id': id, 'name': name}}))
return net_fix
def _mock_subnet_create(self, iso_creds, id, name):
- subnet_fix = self.useFixture(mockpatch.PatchObject(
+ subnet_fix = self.useFixture(fixtures.MockPatchObject(
iso_creds.subnets_admin_client,
'create_subnet',
return_value={'subnet': {'id': id, 'name': name}}))
return subnet_fix
def _mock_router_create(self, id, name):
- router_fix = self.useFixture(mockpatch.PatchObject(
+ router_fix = self.useFixture(fixtures.MockPatchObject(
routers_client.RoutersClient,
'create_router',
return_value={'router': {'id': id, 'name': name}}))
@@ -619,7 +620,8 @@
fixed_params = {'name': 'test class',
'identity_version': 'v3',
- 'admin_role': 'admin'}
+ 'admin_role': 'admin',
+ 'identity_uri': 'fake_uri'}
token_client = v3_token_client
iden_client = v3_iden_client
@@ -634,7 +636,7 @@
def setUp(self):
super(TestDynamicCredentialProviderV3, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.useFixture(mockpatch.PatchObject(
+ self.useFixture(fixtures.MockPatchObject(
domains_client.DomainsClient, 'list_domains',
return_value=dict(domains=[dict(id='default',
name='Default')])))
@@ -645,7 +647,7 @@
pass
def _mock_tenant_create(self, id, name):
- project_fix = self.useFixture(mockpatch.PatchObject(
+ project_fix = self.useFixture(fixtures.MockPatchObject(
self.tenants_client.ProjectsClient,
'create_project',
return_value=(rest_client.ResponseBody
@@ -657,7 +659,7 @@
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
creds.creds_client = mock.MagicMock()
creds.creds_client.create_user_role.side_effect = lib_exc.Conflict
- with mock.patch('tempest.common.dynamic_creds.LOG') as log_mock:
+ 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.")
diff --git a/tempest/tests/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
similarity index 87%
rename from tempest/tests/common/test_preprov_creds.py
rename to tempest/tests/lib/common/test_preprov_creds.py
index 2fd375d..5402e47 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -20,14 +20,14 @@
import six
import testtools
+import fixtures
from oslo_concurrency.fixture import lockutils as lockutils_fixtures
from oslo_config import cfg
-from oslotest import mockpatch
-from tempest.common import preprov_creds
from tempest import config
from tempest.lib import auth
from tempest.lib.common import cred_provider
+from tempest.lib.common import preprov_creds
from tempest.lib import exceptions as lib_exc
from tempest.tests import base
from tempest.tests import fake_config
@@ -38,6 +38,7 @@
fixed_params = {'name': 'test class',
'identity_version': 'v2',
+ 'identity_uri': 'fake_uri',
'test_accounts_file': 'fake_accounts_file',
'accounts_lock_dir': 'fake_locks_dir',
'admin_role': 'admin',
@@ -86,10 +87,14 @@
self.patch(self.token_client, side_effect=self.identity_response)
self.useFixture(lockutils_fixtures.ExternalLockFixture())
self.test_accounts = self._fake_accounts(cfg.CONF.identity.admin_role)
- self.accounts_mock = self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.accounts_mock = self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=self.test_accounts))
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=True))
+ # NOTE(andreaf) Ensure config is loaded so service clients are
+ # registered in the registry before tests
+ config.service_client_config()
def tearDown(self):
super(TestPreProvisionedCredentials, self).tearDown()
@@ -138,7 +143,8 @@
def test_create_hash_file_previous_file(self):
# Emulate the lock existing on the filesystem
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=True))
with mock.patch('six.moves.builtins.open', mock.mock_open(),
create=True):
test_account_class = (
@@ -150,7 +156,8 @@
def test_create_hash_file_no_previous_file(self):
# Emulate the lock not existing on the filesystem
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=False))
with mock.patch('six.moves.builtins.open', mock.mock_open(),
create=True):
test_account_class = (
@@ -163,10 +170,12 @@
@mock.patch('oslo_concurrency.lockutils.lock')
def test_get_free_hash_no_previous_accounts(self, lock_mock):
# Emulate no pre-existing lock
- self.useFixture(mockpatch.Patch('os.path.isdir', return_value=False))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isdir', return_value=False))
hash_list = self._get_hash_list(self.test_accounts)
- mkdir_mock = self.useFixture(mockpatch.Patch('os.mkdir'))
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ mkdir_mock = self.useFixture(fixtures.MockPatch('os.mkdir'))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=False))
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
with mock.patch('six.moves.builtins.open', mock.mock_open(),
@@ -182,9 +191,10 @@
def test_get_free_hash_no_free_accounts(self, lock_mock):
hash_list = self._get_hash_list(self.test_accounts)
# Emulate pre-existing lock dir
- self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
- # Emulate all lcoks in list are in use
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ self.useFixture(fixtures.MockPatch('os.path.isdir', return_value=True))
+ # Emulate all locks in list are in use
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=True))
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
with mock.patch('six.moves.builtins.open', mock.mock_open(),
@@ -195,7 +205,7 @@
@mock.patch('oslo_concurrency.lockutils.lock')
def test_get_free_hash_some_in_use_accounts(self, lock_mock):
# Emulate no pre-existing lock
- self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
+ self.useFixture(fixtures.MockPatch('os.path.isdir', return_value=True))
hash_list = self._get_hash_list(self.test_accounts)
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -219,13 +229,14 @@
def test_remove_hash_last_account(self, lock_mock):
hash_list = self._get_hash_list(self.test_accounts)
# Pretend the pseudo-lock is there
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ self.useFixture(
+ fixtures.MockPatch('os.path.isfile', return_value=True))
# Pretend the lock dir is empty
- self.useFixture(mockpatch.Patch('os.listdir', return_value=[]))
+ self.useFixture(fixtures.MockPatch('os.listdir', return_value=[]))
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
- remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
- rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
+ rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
test_account_class.remove_hash(hash_list[2])
hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
hash_list[2])
@@ -237,14 +248,15 @@
def test_remove_hash_not_last_account(self, lock_mock):
hash_list = self._get_hash_list(self.test_accounts)
# Pretend the pseudo-lock is there
- self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ self.useFixture(fixtures.MockPatch(
+ 'os.path.isfile', return_value=True))
# Pretend the lock dir is empty
- self.useFixture(mockpatch.Patch('os.listdir', return_value=[
+ self.useFixture(fixtures.MockPatch('os.listdir', return_value=[
hash_list[1], hash_list[4]]))
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
- remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
- rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
+ rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
test_account_class.remove_hash(hash_list[2])
hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
hash_list[2])
@@ -258,8 +270,8 @@
def test_is_not_multi_user(self):
self.test_accounts = [self.test_accounts[0]]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=self.test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -270,7 +282,7 @@
**self.fixed_params)
hashes = test_accounts_class.hash_dict['roles']['role4']
temp_hash = hashes[0]
- get_free_hash_mock = self.useFixture(mockpatch.PatchObject(
+ get_free_hash_mock = self.useFixture(fixtures.MockPatchObject(
test_accounts_class, '_get_free_hash', return_value=temp_hash))
# Test a single role returns all matching roles
test_accounts_class._get_creds(roles=['role4'])
@@ -287,7 +299,7 @@
hashes2 = test_accounts_class.hash_dict['roles']['role2']
hashes = list(set(hashes) & set(hashes2))
temp_hash = hashes[0]
- get_free_hash_mock = self.useFixture(mockpatch.PatchObject(
+ get_free_hash_mock = self.useFixture(fixtures.MockPatchObject(
test_accounts_class, '_get_free_hash', return_value=temp_hash))
# Test an intersection of multiple roles
test_accounts_class._get_creds(roles=['role2', 'role4'])
@@ -304,7 +316,7 @@
admin_hashes = test_accounts_class.hash_dict['roles'][
cfg.CONF.identity.admin_role]
temp_hash = hashes[0]
- get_free_hash_mock = self.useFixture(mockpatch.PatchObject(
+ get_free_hash_mock = self.useFixture(fixtures.MockPatchObject(
test_accounts_class, '_get_free_hash', return_value=temp_hash))
# Test an intersection of multiple roles
test_accounts_class._get_creds()
@@ -322,8 +334,8 @@
{'username': 'test_user14', 'tenant_name': 'test_tenant14',
'password': 'p', 'roles': ['role-7', 'role-11'],
'resources': {'network': 'network-2'}}]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -350,8 +362,8 @@
def test_get_primary_creds_none_available(self):
admin_accounts = [x for x in self.test_accounts if 'test_admin'
in x['username']]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=admin_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -368,8 +380,8 @@
def test_get_alt_creds_none_available(self):
admin_accounts = [x for x in self.test_accounts if 'test_admin'
in x['username']]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=admin_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -389,8 +401,8 @@
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_admin1', 'tenant_name': 'test_tenant11',
'password': 'p', 'types': ['admin']}]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -403,8 +415,8 @@
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_admin1', 'tenant_name': 'test_tenant11',
'password': 'p', 'roles': [cfg.CONF.identity.admin_role]}]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -414,8 +426,8 @@
def test_get_admin_creds_none_available(self):
non_admin_accounts = [x for x in self.test_accounts if 'test_admin'
not in x['username']]
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=non_admin_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
@@ -428,6 +440,7 @@
fixed_params = {'name': 'test class',
'identity_version': 'v3',
+ 'identity_uri': 'fake_uri',
'test_accounts_file': 'fake_accounts_file',
'accounts_lock_dir': 'fake_locks_dir_v3',
'admin_role': 'admin',
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
similarity index 96%
rename from tempest/tests/lib/test_rest_client.py
rename to tempest/tests/lib/common/test_rest_client.py
index 43bb6d0..4c0bb57 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -15,8 +15,8 @@
import copy
import json
+import fixtures
import jsonschema
-from oslotest import mockpatch
import six
from tempest.lib.common import http
@@ -38,16 +38,16 @@
self.rest_client = rest_client.RestClient(
self.fake_auth_provider, None, None)
self.patchobject(http.ClosingHttp, 'request', self.fake_http.request)
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- '_log_request'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ '_log_request'))
class TestRestClientHTTPMethods(BaseRestClientTestClass):
def setUp(self):
self.fake_http = fake_http.fake_httplib2()
super(TestRestClientHTTPMethods, self).setUp()
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- '_error_checker'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ '_error_checker'))
def test_post(self):
__, return_dict = self.rest_client.post(self.url, {}, {})
@@ -70,8 +70,8 @@
self.assertEqual('PUT', return_dict['method'])
def test_head(self):
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- 'response_checker'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ 'response_checker'))
__, return_dict = self.rest_client.head(self.url)
self.assertEqual('HEAD', return_dict['method'])
@@ -122,8 +122,8 @@
self._verify_headers(resp)
def test_head(self):
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- 'response_checker'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ 'response_checker'))
resp, __ = self.rest_client.head(self.url)
self._verify_headers(resp)
@@ -136,8 +136,8 @@
def setUp(self):
self.fake_http = fake_http.fake_httplib2()
super(TestRestClientUpdateHeaders, self).setUp()
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- '_error_checker'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ '_error_checker'))
self.headers = {'X-Configuration-Session': 'session_id'}
def test_post_update_headers(self):
@@ -201,8 +201,8 @@
)
def test_head_update_headers(self):
- self.useFixture(mockpatch.PatchObject(self.rest_client,
- 'response_checker'))
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ 'response_checker'))
__, return_dict = self.rest_client.head(self.url,
extra_headers=True,
@@ -276,6 +276,11 @@
body = self.rest_client._parse_resp(json.dumps(self.null_dict))
self.assertEqual(self.null_dict, body)
+ def test_parse_empty_list(self):
+ empty_list = []
+ body = self.rest_client._parse_resp(json.dumps(empty_list))
+ self.assertEqual(empty_list, body)
+
class TestRestClientErrorCheckerJSON(base.TestCase):
c_type = "application/json"
@@ -1145,8 +1150,8 @@
}
def test_current_json_schema_validator_version(self):
- with mockpatch.PatchObject(jsonschema.Draft4Validator,
- "check_schema") as chk_schema:
+ with fixtures.MockPatchObject(jsonschema.Draft4Validator,
+ "check_schema") as chk_schema:
body = {'foo': 'test'}
self._test_validate_pass(self.schema, body)
chk_schema.mock.assert_called_once_with(
diff --git a/tempest/tests/lib/services/base.py b/tempest/tests/lib/services/base.py
index a244aa2..924f9f2 100644
--- a/tempest/tests/lib/services/base.py
+++ b/tempest/tests/lib/services/base.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
from oslo_serialization import jsonutils as json
-from oslotest import mockpatch
from tempest.tests import base
from tempest.tests.lib import fake_http
@@ -31,12 +31,47 @@
def check_service_client_function(self, function, function2mock,
body, to_utf=False, status=200,
- headers=None, **kwargs):
+ headers=None, mock_args=None,
+ resp_as_string=False,
+ **kwargs):
+ """Mock a service client function for unit testing.
+
+ :param function: The service client function to call.
+ :param function2mock: The REST call to mock inside the service client
+ function.
+ :param body: Expected response body returned by the service client
+ function.
+ :param to_utf: Whether to use UTF-8 encoding for response.
+ :param status: Expected response status returned by the service client
+ function.
+ :param headers: Expected headers returned by the service client
+ function.
+ :param mock_args: List/dict/value of expected args/kwargs called by
+ function2mock. For example:
+ * If mock_args=['foo'] then ``assert_called_once_with('foo')``
+ is called.
+ * If mock_args={'foo': 'bar'} then
+ ``assert_called_once_with(foo='bar')`` is called.
+ * If mock_args='foo' then ``assert_called_once_with('foo')``
+ is called.
+ :param resp_as_string: Whether response body is retruned as string.
+ This is for service client methods which return ResponseBodyData
+ object.
+ :param kwargs: kwargs that are passed to function.
+ """
mocked_response = self.create_response(body, to_utf, status, headers)
- self.useFixture(mockpatch.Patch(
+ fixture = self.useFixture(fixtures.MockPatch(
function2mock, return_value=mocked_response))
if kwargs:
resp = function(**kwargs)
else:
resp = function()
+ if resp_as_string:
+ resp = resp.data
self.assertEqual(body, resp)
+ if isinstance(mock_args, list):
+ fixture.mock.assert_called_once_with(*mock_args)
+ elif isinstance(mock_args, dict):
+ fixture.mock.assert_called_once_with(**mock_args)
+ elif mock_args is not None:
+ fixture.mock.assert_called_once_with(mock_args)
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
index 445ee22..cbd17c6 100644
--- a/tempest/tests/lib/services/compute/test_flavors_client.py
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -14,8 +14,8 @@
import copy
+import fixtures
from oslo_serialization import jsonutils as json
-from oslotest import mockpatch
from tempest.lib.services.compute import flavors_client
from tempest.tests.lib import fake_auth_provider
@@ -118,7 +118,7 @@
if bytes_body:
body = body.encode('utf-8')
response = fake_http.fake_http_response({}, status=200), body
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.rest_client.RestClient.get',
return_value=response))
self.assertEqual(is_deleted,
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_client.py b/tempest/tests/lib/services/compute/test_floating_ips_client.py
index 92737f2..950f9ce 100644
--- a/tempest/tests/lib/services/compute/test_floating_ips_client.py
+++ b/tempest/tests/lib/services/compute/test_floating_ips_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslotest import mockpatch
+import fixtures
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import floating_ips_client
@@ -99,14 +99,14 @@
server_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
def test_is_resource_deleted_true(self):
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.services.compute.floating_ips_client.'
'FloatingIPsClient.show_floating_ip',
side_effect=lib_exc.NotFound()))
self.assertTrue(self.client.is_resource_deleted('fake-id'))
def test_is_resource_deleted_false(self):
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.services.compute.floating_ips_client.'
'FloatingIPsClient.show_floating_ip',
return_value={"floating_ip": TestFloatingIpsClient.floating_ip}))
diff --git a/tempest/tests/lib/services/compute/test_images_client.py b/tempest/tests/lib/services/compute/test_images_client.py
index a9a570d..c2c3b76 100644
--- a/tempest/tests/lib/services/compute/test_images_client.py
+++ b/tempest/tests/lib/services/compute/test_images_client.py
@@ -14,8 +14,7 @@
import copy
-from oslotest import mockpatch
-
+import fixtures
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import images_client
from tempest.tests.lib import fake_auth_provider
@@ -187,15 +186,15 @@
def _test_resource_deleted(self, bytes_body=False):
params = {"id": self.FAKE_IMAGE_ID}
expected_op = self.FAKE_IMAGE_DATA['show']
- self.useFixture(mockpatch.Patch('tempest.lib.services.compute'
+ self.useFixture(fixtures.MockPatch('tempest.lib.services.compute'
'.images_client.ImagesClient.show_image',
- side_effect=lib_exc.NotFound))
+ side_effect=lib_exc.NotFound))
self.assertEqual(True, self.client.is_resource_deleted(**params))
tempdata = copy.deepcopy(self.FAKE_IMAGE_DATA['show'])
tempdata['image']['id'] = None
- self.useFixture(mockpatch.Patch('tempest.lib.services.compute'
+ self.useFixture(fixtures.MockPatch('tempest.lib.services.compute'
'.images_client.ImagesClient.show_image',
- return_value=expected_op))
+ return_value=expected_op))
self.assertEqual(False, self.client.is_resource_deleted(**params))
def test_list_images_with_str_body(self):
diff --git a/tempest/tests/lib/services/compute/test_security_groups_client.py b/tempest/tests/lib/services/compute/test_security_groups_client.py
index d293a08..7bbf20e 100644
--- a/tempest/tests/lib/services/compute/test_security_groups_client.py
+++ b/tempest/tests/lib/services/compute/test_security_groups_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslotest import mockpatch
+import fixtures
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import security_groups_client
@@ -103,11 +103,11 @@
def test_is_resource_deleted_true(self):
mod = ('tempest.lib.services.compute.security_groups_client.'
'SecurityGroupsClient.show_security_group')
- self.useFixture(mockpatch.Patch(mod, side_effect=lib_exc.NotFound))
+ self.useFixture(fixtures.MockPatch(mod, side_effect=lib_exc.NotFound))
self.assertTrue(self.client.is_resource_deleted('fake-id'))
def test_is_resource_deleted_false(self):
mod = ('tempest.lib.services.compute.security_groups_client.'
'SecurityGroupsClient.show_security_group')
- self.useFixture(mockpatch.Patch(mod, return_value='success'))
+ self.useFixture(fixtures.MockPatch(mod, return_value='success'))
self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_server_groups_client.py b/tempest/tests/lib/services/compute/test_server_groups_client.py
index bf03b84..9055a36 100644
--- a/tempest/tests/lib/services/compute/test_server_groups_client.py
+++ b/tempest/tests/lib/services/compute/test_server_groups_client.py
@@ -12,10 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslotest import mockpatch
-from tempest.tests.lib import fake_auth_provider
+import fixtures
+from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import server_groups_client
+from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib import fake_http
from tempest.tests.lib.services import base
@@ -36,7 +37,7 @@
fake_auth, 'compute', 'regionOne')
def _test_create_server_group(self, bytes_body=False):
- expected = {"server_group": TestServerGroupsClient.server_group}
+ expected = {"server_group": self.server_group}
self.check_service_client_function(
self.client.create_server_group,
'tempest.lib.common.rest_client.RestClient.post', expected,
@@ -50,13 +51,13 @@
def test_delete_server_group(self):
response = fake_http.fake_http_response({}, status=204), ''
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
'tempest.lib.common.rest_client.RestClient.delete',
return_value=response))
self.client.delete_server_group('fake-group')
def _test_list_server_groups(self, bytes_body=False):
- expected = {"server_groups": [TestServerGroupsClient.server_group]}
+ expected = {"server_groups": [self.server_group]}
self.check_service_client_function(
self.client.list_server_groups,
'tempest.lib.common.rest_client.RestClient.get',
@@ -69,7 +70,7 @@
self._test_list_server_groups(bytes_body=True)
def _test_show_server_group(self, bytes_body=False):
- expected = {"server_group": TestServerGroupsClient.server_group}
+ expected = {"server_group": self.server_group}
self.check_service_client_function(
self.client.show_server_group,
'tempest.lib.common.rest_client.RestClient.get',
@@ -81,3 +82,20 @@
def test_show_server_group_byte_body(self):
self._test_show_server_group(bytes_body=True)
+
+
+class TestServerGroupsClientMinV213(TestServerGroupsClient):
+
+ server_group = {
+ "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
+ "name": "test",
+ "policies": ["anti-affinity"],
+ "members": [],
+ "metadata": {},
+ "project_id": "0beb4bffb7a445eb8eb05fee3ee7660a",
+ "user_id": "86031628064a4f99bb66ec03c507dcd8"}
+
+ def setUp(self):
+ super(TestServerGroupsClientMinV213, self).setUp()
+ self.patchobject(base_compute_client, 'COMPUTE_MICROVERSION',
+ new='2.13')
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index a857329..86f6ad5 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -235,7 +235,7 @@
server_id=self.server_id
)
- def test_delete_server(self, bytes_body=False):
+ def test_delete_server(self):
self.check_service_client_function(
self.client.delete_server,
'tempest.lib.common.rest_client.RestClient.delete',
@@ -288,6 +288,7 @@
self.client.list_addresses_by_network,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_ADDRESS['addresses'],
+ bytes_body,
server_id=self.server_id,
network_id=self.network_id
)
@@ -303,6 +304,7 @@
self.client.action,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
server_id=self.server_id,
action_name='fake-action-name',
schema={'status_code': 200}
@@ -319,6 +321,7 @@
self.client.create_backup,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
status=202,
server_id=self.server_id,
backup_type='fake-backup',
@@ -339,6 +342,7 @@
self.client.evacuate_server,
'tempest.lib.common.rest_client.RestClient.post',
self.FAKE_SERVER_PASSWORD,
+ bytes_body,
**kwargs)
def test_change_password_with_str_body(self):
@@ -352,6 +356,7 @@
self.client.change_password,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
status=202,
server_id=self.server_id,
adminPass='fake-admin-pass'
@@ -368,16 +373,11 @@
self.client.show_password,
'tempest.lib.common.rest_client.RestClient.get',
{'password': 'fake-password'},
+ bytes_body,
server_id=self.server_id
)
- def test_delete_password_with_str_body(self):
- self._test_delete_password()
-
- def test_delete_password_with_bytes_body(self):
- self._test_delete_password(True)
-
- def _test_delete_password(self, bytes_body=False):
+ def test_delete_password(self):
self.check_service_client_function(
self.client.delete_password,
'tempest.lib.common.rest_client.RestClient.delete',
@@ -386,13 +386,7 @@
server_id=self.server_id
)
- def test_reboot_server_with_str_body(self):
- self._test_reboot_server()
-
- def test_reboot_server_with_bytes_body(self):
- self._test_reboot_server(True)
-
- def _test_reboot_server(self, bytes_body=False):
+ def test_reboot_server(self):
self.check_service_client_function(
self.client.reboot_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -413,18 +407,13 @@
self.client.rebuild_server,
'tempest.lib.common.rest_client.RestClient.post',
self.FAKE_REBUILD_SERVER,
+ bytes_body,
status=202,
server_id=self.server_id,
image_ref='fake-image-ref'
)
- def test_resize_server_with_str_body(self):
- self._test_resize_server()
-
- def test_resize_server_with_bytes_body(self):
- self._test_resize_server(True)
-
- def _test_resize_server(self, bytes_body=False):
+ def test_resize_server(self):
self.check_service_client_function(
self.client.resize_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -434,13 +423,7 @@
flavor_ref='fake-flavor-ref'
)
- def test_confirm_resize_server_with_str_body(self):
- self._test_confirm_resize_server()
-
- def test_confirm_resize_server_with_bytes_body(self):
- self._test_confirm_resize_server(True)
-
- def _test_confirm_resize_server(self, bytes_body=False):
+ def test_confirm_resize_server(self):
self.check_service_client_function(
self.client.confirm_resize_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -449,13 +432,7 @@
server_id=self.server_id
)
- def test_revert_resize_server_with_str_body(self):
- self._test_revert_resize()
-
- def test_revert_resize_server_with_bytes_body(self):
- self._test_revert_resize(True)
-
- def _test_revert_resize(self, bytes_body=False):
+ def test_revert_resize(self):
self.check_service_client_function(
self.client.revert_resize_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -475,6 +452,7 @@
self.client.list_server_metadata,
'tempest.lib.common.rest_client.RestClient.get',
{'metadata': {'fake-key': 'fake-meta-data'}},
+ bytes_body,
server_id=self.server_id
)
@@ -489,6 +467,7 @@
self.client.set_server_metadata,
'tempest.lib.common.rest_client.RestClient.put',
{'metadata': {'fake-key': 'fake-meta-data'}},
+ bytes_body,
server_id=self.server_id,
meta='fake-meta'
)
@@ -504,6 +483,7 @@
self.client.update_server_metadata,
'tempest.lib.common.rest_client.RestClient.post',
{'metadata': {'fake-key': 'fake-meta-data'}},
+ bytes_body,
server_id=self.server_id,
meta='fake-meta'
)
@@ -519,6 +499,7 @@
self.client.show_server_metadata_item,
'tempest.lib.common.rest_client.RestClient.get',
{'meta': {'fake-key': 'fake-meta-data'}},
+ bytes_body,
server_id=self.server_id,
key='fake-key'
)
@@ -534,18 +515,13 @@
self.client.set_server_metadata_item,
'tempest.lib.common.rest_client.RestClient.put',
{'meta': {'fake-key': 'fake-meta-data'}},
+ bytes_body,
server_id=self.server_id,
key='fake-key',
meta='fake-meta'
)
- def test_delete_server_metadata_item_with_str_body(self):
- self._test_delete_server_metadata()
-
- def test_delete_server_metadata_item_with_bytes_body(self):
- self._test_delete_server_metadata(True)
-
- def _test_delete_server_metadata(self, bytes_body=False):
+ def test_delete_server_metadata(self):
self.check_service_client_function(
self.client.delete_server_metadata_item,
'tempest.lib.common.rest_client.RestClient.delete',
@@ -555,13 +531,7 @@
key='fake-key'
)
- def test_stop_server_with_str_body(self):
- self._test_stop_server()
-
- def test_stop_server_with_bytes_body(self):
- self._test_stop_server(True)
-
- def _test_stop_server(self, bytes_body=False):
+ def test_stop_server(self):
self.check_service_client_function(
self.client.stop_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -570,13 +540,7 @@
server_id=self.server_id
)
- def test_start_server_with_str_body(self):
- self._test_start_server()
-
- def test_start_server_with_bytes_body(self):
- self._test_start_server(True)
-
- def _test_start_server(self, bytes_body=False):
+ def test_start_server(self):
self.check_service_client_function(
self.client.start_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -596,6 +560,7 @@
self.client.attach_volume,
'tempest.lib.common.rest_client.RestClient.post',
{'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ bytes_body,
server_id=self.server_id
)
@@ -621,6 +586,7 @@
self.client.detach_volume,
'tempest.lib.common.rest_client.RestClient.delete',
{},
+ bytes_body,
status=202,
server_id=self.server_id,
volume_id=self.FAKE_COMMON_VOLUME['volumeId']
@@ -637,6 +603,7 @@
self.client.show_volume_attachment,
'tempest.lib.common.rest_client.RestClient.get',
{'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ bytes_body,
server_id=self.server_id,
volume_id=self.FAKE_COMMON_VOLUME['volumeId']
)
@@ -652,6 +619,7 @@
self.client.list_volume_attachments,
'tempest.lib.common.rest_client.RestClient.get',
{'volumeAttachments': [self.FAKE_COMMON_VOLUME]},
+ bytes_body,
server_id=self.server_id
)
@@ -666,18 +634,13 @@
self.client.add_security_group,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
status=202,
server_id=self.server_id,
name='fake-name'
)
- def test_remove_security_group_with_str_body(self):
- self._test_remove_security_group()
-
- def test_remove_security_group_with_bytes_body(self):
- self._test_remove_security_group(True)
-
- def _test_remove_security_group(self, bytes_body=False):
+ def test_remove_security_group(self):
self.check_service_client_function(
self.client.remove_security_group,
'tempest.lib.common.rest_client.RestClient.post',
@@ -698,6 +661,7 @@
self.client.live_migrate_server,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
status=202,
server_id=self.server_id
)
@@ -713,17 +677,12 @@
self.client.migrate_server,
'tempest.lib.common.rest_client.RestClient.post',
{},
+ bytes_body,
status=202,
server_id=self.server_id
)
- def test_lock_server_with_str_body(self):
- self._test_lock_server()
-
- def test_lock_server_with_bytes_body(self):
- self._test_lock_server(True)
-
- def _test_lock_server(self, bytes_body=False):
+ def test_lock_server(self):
self.check_service_client_function(
self.client.lock_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -732,13 +691,7 @@
server_id=self.server_id
)
- def test_unlock_server_with_str_body(self):
- self._test_unlock_server()
-
- def test_unlock_server_with_bytes_body(self):
- self._test_unlock_server(True)
-
- def _test_unlock_server(self, bytes_body=False):
+ def test_unlock_server(self):
self.check_service_client_function(
self.client.unlock_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -747,13 +700,7 @@
server_id=self.server_id
)
- def test_suspend_server_with_str_body(self):
- self._test_suspend_server()
-
- def test_suspend_server_with_bytes_body(self):
- self._test_suspend_server(True)
-
- def _test_suspend_server(self, bytes_body=False):
+ def test_suspend_server(self):
self.check_service_client_function(
self.client.suspend_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -762,13 +709,7 @@
server_id=self.server_id
)
- def test_resume_server_with_str_body(self):
- self._test_resume_server()
-
- def test_resume_server_with_bytes_body(self):
- self._test_resume_server(True)
-
- def _test_resume_server(self, bytes_body=False):
+ def test_resume_server(self):
self.check_service_client_function(
self.client.resume_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -777,13 +718,7 @@
server_id=self.server_id
)
- def test_pause_server_with_str_body(self):
- self._test_pause_server()
-
- def test_pause_server_with_bytes_body(self):
- self._test_pause_server(True)
-
- def _test_pause_server(self, bytes_body=False):
+ def test_pause_server(self):
self.check_service_client_function(
self.client.pause_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -792,13 +727,7 @@
server_id=self.server_id
)
- def test_unpause_server_with_str_body(self):
- self._test_unpause_server()
-
- def test_unpause_server_with_bytes_body(self):
- self._test_unpause_server(True)
-
- def _test_unpause_server(self, bytes_body=False):
+ def test_unpause_server(self):
self.check_service_client_function(
self.client.unpause_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -807,13 +736,7 @@
server_id=self.server_id
)
- def test_reset_state_with_str_body(self):
- self._test_reset_state()
-
- def test_reset_state_with_bytes_body(self):
- self._test_reset_state(True)
-
- def _test_reset_state(self, bytes_body=False):
+ def test_reset_state(self):
self.check_service_client_function(
self.client.reset_state,
'tempest.lib.common.rest_client.RestClient.post',
@@ -823,13 +746,7 @@
state='fake-state'
)
- def test_shelve_server_with_str_body(self):
- self._test_shelve_server()
-
- def test_shelve_server_with_bytes_body(self):
- self._test_shelve_server(True)
-
- def _test_shelve_server(self, bytes_body=False):
+ def test_shelve_server(self):
self.check_service_client_function(
self.client.shelve_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -838,13 +755,7 @@
server_id=self.server_id
)
- def test_unshelve_server_with_str_body(self):
- self._test_unshelve_server()
-
- def test_unshelve_server_with_bytes_body(self):
- self._test_unshelve_server(True)
-
- def _test_unshelve_server(self, bytes_body=False):
+ def test_unshelve_server(self):
self.check_service_client_function(
self.client.unshelve_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -853,13 +764,7 @@
server_id=self.server_id
)
- def test_shelve_offload_server_with_str_body(self):
- self._test_shelve_offload_server()
-
- def test_shelve_offload_server_with_bytes_body(self):
- self._test_shelve_offload_server(True)
-
- def _test_shelve_offload_server(self, bytes_body=False):
+ def test_shelve_offload_server(self):
self.check_service_client_function(
self.client.shelve_offload_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -879,6 +784,7 @@
self.client.get_console_output,
'tempest.lib.common.rest_client.RestClient.post',
{'output': 'fake-output'},
+ bytes_body,
server_id=self.server_id,
length='fake-length'
)
@@ -894,6 +800,7 @@
self.client.list_virtual_interfaces,
'tempest.lib.common.rest_client.RestClient.get',
{'virtual_interfaces': [self.FAKE_VIRTUAL_INTERFACES]},
+ bytes_body,
server_id=self.server_id
)
@@ -908,16 +815,11 @@
self.client.rescue_server,
'tempest.lib.common.rest_client.RestClient.post',
{'adminPass': 'fake-admin-pass'},
+ bytes_body,
server_id=self.server_id
)
- def test_unrescue_server_with_str_body(self):
- self._test_unrescue_server()
-
- def test_unrescue_server_with_bytes_body(self):
- self._test_unrescue_server(True)
-
- def _test_unrescue_server(self, bytes_body=False):
+ def test_unrescue_server(self):
self.check_service_client_function(
self.client.unrescue_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -937,6 +839,7 @@
self.client.show_server_diagnostics,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_SERVER_DIAGNOSTICS,
+ bytes_body,
status=200,
server_id=self.server_id
)
@@ -952,6 +855,7 @@
self.client.list_instance_actions,
'tempest.lib.common.rest_client.RestClient.get',
{'instanceActions': [self.FAKE_INSTANCE_ACTIONS]},
+ bytes_body,
server_id=self.server_id
)
@@ -966,17 +870,12 @@
self.client.show_instance_action,
'tempest.lib.common.rest_client.RestClient.get',
{'instanceAction': self.FAKE_INSTANCE_WITH_EVENTS},
+ bytes_body,
server_id=self.server_id,
request_id='fake-request-id'
)
- def test_force_delete_server_with_str_body(self):
- self._test_force_delete_server()
-
- def test_force_delete_server_with_bytes_body(self):
- self._test_force_delete_server(True)
-
- def _test_force_delete_server(self, bytes_body=False):
+ def test_force_delete_server(self):
self.check_service_client_function(
self.client.force_delete_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -985,13 +884,7 @@
server_id=self.server_id
)
- def test_restore_soft_deleted_server_with_str_body(self):
- self._test_restore_soft_deleted_server()
-
- def test_restore_soft_deleted_server_with_bytes_body(self):
- self._test_restore_soft_deleted_server(True)
-
- def _test_restore_soft_deleted_server(self, bytes_body=False):
+ def test_restore_soft_deleted_server(self):
self.check_service_client_function(
self.client.restore_soft_deleted_server,
'tempest.lib.common.rest_client.RestClient.post',
@@ -1000,13 +893,7 @@
server_id=self.server_id
)
- def test_reset_network_with_str_body(self):
- self._test_reset_network()
-
- def test_reset_network_with_bytes_body(self):
- self._test_reset_network(True)
-
- def _test_reset_network(self, bytes_body=False):
+ def test_reset_network(self):
self.check_service_client_function(
self.client.reset_network,
'tempest.lib.common.rest_client.RestClient.post',
@@ -1015,13 +902,7 @@
server_id=self.server_id
)
- def test_inject_network_info_with_str_body(self):
- self._test_inject_network_info()
-
- def test_inject_network_info_with_bytes_body(self):
- self._test_inject_network_info(True)
-
- def _test_inject_network_info(self, bytes_body=False):
+ def test_inject_network_info(self):
self.check_service_client_function(
self.client.inject_network_info,
'tempest.lib.common.rest_client.RestClient.post',
@@ -1041,6 +922,7 @@
self.client.get_vnc_console,
'tempest.lib.common.rest_client.RestClient.post',
{'console': self.FAKE_VNC_CONSOLE},
+ bytes_body,
server_id=self.server_id,
type='fake-console-type'
)
@@ -1056,7 +938,8 @@
self.client.list_security_groups_by_server,
'tempest.lib.common.rest_client.RestClient.get',
{'security_groups': self.FAKE_SECURITY_GROUPS},
- server_id=self.server_id,
+ bytes_body,
+ server_id=self.server_id
)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
@@ -1151,15 +1034,7 @@
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
- def test_delete_tag_str_body(self, _):
- self._test_delete_tag()
-
- @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
- new_callable=mock.PropertyMock(return_value='2.26'))
- def test_delete_tag_byte_body(self, _):
- self._test_delete_tag(bytes_body=True)
-
- def _test_delete_tag(self, bytes_body=False):
+ def test_delete_tag(self, _):
self.check_service_client_function(
self.client.delete_tag,
'tempest.lib.common.rest_client.RestClient.delete',
@@ -1167,7 +1042,7 @@
server_id=self.server_id,
tag=self.FAKE_TAGS[0],
status=204,
- to_utf=bytes_body)
+ )
class TestServersClientMinV26(base.BaseServiceTest):
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 41da39c..2dd981c 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -14,6 +14,9 @@
import copy
+import mock
+
+from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import services_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -44,11 +47,21 @@
}
}
+ FAKE_UPDATE_FORCED_DOWN = {
+ "service":
+ {
+ "forced_down": True,
+ "binary": "nova-conductor",
+ "host": "controller"
+ }
+ }
+
def setUp(self):
super(TestServicesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = services_client.ServicesClient(
fake_auth, 'compute', 'regionOne')
+ self.addCleanup(mock.patch.stopall)
def test_list_services_with_str_body(self):
self.check_service_client_function(
@@ -68,7 +81,7 @@
'tempest.lib.common.rest_client.RestClient.put',
self.FAKE_SERVICE,
bytes_body,
- host_name="nova-conductor", binary="controller")
+ host="nova-conductor", binary="controller")
def test_enable_service_with_str_body(self):
self._test_enable_service()
@@ -85,10 +98,49 @@
'tempest.lib.common.rest_client.RestClient.put',
fake_service,
bytes_body,
- host_name="nova-conductor", binary="controller")
+ host="nova-conductor", binary="controller")
def test_disable_service_with_str_body(self):
self._test_disable_service()
def test_disable_service_with_bytes_body(self):
self._test_disable_service(bytes_body=True)
+
+ def _test_log_reason_disabled_service(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_SERVICE)
+ resp_body['service']['disabled_reason'] = 'test reason'
+
+ self.check_service_client_function(
+ self.client.disable_log_reason,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ resp_body,
+ bytes_body,
+ host="nova-conductor",
+ binary="controller",
+ disabled_reason='test reason')
+
+ def test_log_reason_disabled_service_with_str_body(self):
+ self._test_log_reason_disabled_service()
+
+ def test_log_reason_disabled_service_with_bytes_body(self):
+ self._test_log_reason_disabled_service(bytes_body=True)
+
+ def _test_update_forced_down(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_forced_down,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_FORCED_DOWN,
+ bytes_body,
+ host="nova-conductor",
+ binary="controller",
+ forced_down=True)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.11'))
+ def test_update_forced_down_with_str_body(self, _):
+ self._test_update_forced_down()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.11'))
+ def test_update_forced_down_with_bytes_body(self, _):
+ self._test_update_forced_down(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_snapshots_client.py b/tempest/tests/lib/services/compute/test_snapshots_client.py
index 1629943..1e2902c 100644
--- a/tempest/tests/lib/services/compute/test_snapshots_client.py
+++ b/tempest/tests/lib/services/compute/test_snapshots_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslotest import mockpatch
+import fixtures
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import snapshots_client
@@ -91,13 +91,13 @@
def test_is_resource_deleted_true(self):
module = ('tempest.lib.services.compute.snapshots_client.'
'SnapshotsClient.show_snapshot')
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
module, side_effect=lib_exc.NotFound))
self.assertTrue(self.client.is_resource_deleted('fake-id'))
def test_is_resource_deleted_false(self):
module = ('tempest.lib.services.compute.snapshots_client.'
'SnapshotsClient.show_snapshot')
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
module, return_value={}))
self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_versions_client.py b/tempest/tests/lib/services/compute/test_versions_client.py
index 255a0a3..40d424f 100644
--- a/tempest/tests/lib/services/compute/test_versions_client.py
+++ b/tempest/tests/lib/services/compute/test_versions_client.py
@@ -14,7 +14,7 @@
import copy
-from oslotest import mockpatch
+import fixtures
from tempest.lib.services.compute import versions_client
from tempest.tests.lib import fake_auth_provider
@@ -73,7 +73,7 @@
200)
def _test_get_version_by_url(self, bytes_body=False):
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
"tempest.lib.common.rest_client.RestClient.token",
return_value="Dummy Token"))
params = {"version_url": self.versions_client._get_base_version_url()}
diff --git a/tempest/tests/lib/services/compute/test_volumes_client.py b/tempest/tests/lib/services/compute/test_volumes_client.py
index b81cdbb..4b4f02e 100644
--- a/tempest/tests/lib/services/compute/test_volumes_client.py
+++ b/tempest/tests/lib/services/compute/test_volumes_client.py
@@ -14,7 +14,7 @@
import copy
-from oslotest import mockpatch
+import fixtures
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import volumes_client
@@ -102,13 +102,13 @@
def test_is_resource_deleted_true(self):
module = ('tempest.lib.services.compute.volumes_client.'
'VolumesClient.show_volume')
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
module, side_effect=lib_exc.NotFound))
self.assertTrue(self.client.is_resource_deleted('fake-id'))
def test_is_resource_deleted_false(self):
module = ('tempest.lib.services.compute.volumes_client.'
'VolumesClient.show_volume')
- self.useFixture(mockpatch.Patch(
+ self.useFixture(fixtures.MockPatch(
module, return_value={}))
self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/identity/v2/test_identity_client.py b/tempest/tests/lib/services/identity/v2/test_identity_client.py
index 96d50d7..303d1f7 100644
--- a/tempest/tests/lib/services/identity/v2/test_identity_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_identity_client.py
@@ -26,6 +26,33 @@
}
}
+ FAKE_ENDPOINTS_FOR_TOKEN = {
+ "endpoints_links": [],
+ "endpoints": [
+ {
+ "name": "nova",
+ "adminURL": "https://nova.region-one.internal.com/" +
+ "v2/be1319401cfa4a0aa590b97cc7b64d8d",
+ "region": "RegionOne",
+ "internalURL": "https://nova.region-one.internal.com/" +
+ "v2/be1319401cfa4a0aa590b97cc7b64d8d",
+ "type": "compute",
+ "id": "11b41ee1b00841128b7333d4bf1a6140",
+ "publicURL": "https://nova.region-one.public.com/v2/" +
+ "be1319401cfa4a0aa590b97cc7b64d8d"
+ },
+ {
+ "name": "neutron",
+ "adminURL": "https://neutron.region-one.internal.com/",
+ "region": "RegionOne",
+ "internalURL": "https://neutron.region-one.internal.com/",
+ "type": "network",
+ "id": "cdbfa3c416d741a9b5c968f2dc628acb",
+ "publicURL": "https://neutron.region-one.public.com/"
+ }
+ ]
+ }
+
FAKE_API_INFO = {
"name": "API_info",
"type": "API",
@@ -148,6 +175,22 @@
bytes_body,
token_id="cbc36478b0bd8e67e89")
+ def _test_list_endpoints_for_token(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints_for_token,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ENDPOINTS_FOR_TOKEN,
+ bytes_body,
+ token_id="cbc36478b0bd8e67e89")
+
+ def _test_check_token_existence(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_token_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ bytes_body,
+ token_id="cbc36478b0bd8e67e89")
+
def test_show_api_description_with_str_body(self):
self._test_show_api_description()
@@ -166,6 +209,18 @@
def test_show_token_with_bytes_body(self):
self._test_show_token(bytes_body=True)
+ def test_list_endpoints_for_token_with_str_body(self):
+ self._test_list_endpoints_for_token()
+
+ def test_list_endpoints_for_token_with_bytes_body(self):
+ self._test_list_endpoints_for_token(bytes_body=True)
+
+ def test_check_token_existence_with_bytes_body(self):
+ self._test_check_token_existence(bytes_body=True)
+
+ def test_check_token_existence_with_str_body(self):
+ self._test_check_token_existence()
+
def test_delete_token(self):
self.check_service_client_function(
self.client.delete_token,
diff --git a/tempest/tests/lib/services/identity/v3/test_catalog_client.py b/tempest/tests/lib/services/identity/v3/test_catalog_client.py
new file mode 100644
index 0000000..0ac8fe4
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_catalog_client.py
@@ -0,0 +1,86 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import catalog_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestCatalogClient(base.BaseServiceTest):
+ FAKE_CATALOG_INFO = {
+ 'catalog': [
+ {
+ 'endpoints': [
+ {
+ 'id': '39dc322ce86c4111b4f06c2eeae0841b',
+ 'interface': 'public',
+ 'region': 'RegionOne',
+ 'url': 'http://localhost:5000'
+ },
+ ],
+ 'id': 'ac58672276f848a7b1727850b3ebe826',
+ 'type': 'compute',
+ 'name': 'nova'
+ },
+ {
+ 'endpoints': [
+ {
+ 'id': '39dc322ce86c4111b4f06c2eeae0841b',
+ 'interface': 'public',
+ 'region': 'RegionOne',
+ 'url': 'http://localhost:5000'
+ },
+ ],
+ 'id': 'b7c5ed2b486a46dbb4c221499d22991c',
+ 'type': 'image',
+ 'name': 'glance'
+ },
+ {
+ 'endpoints': [
+ {
+ 'id': '39dc322ce86c4111b4f06c2eeae0841b',
+ 'interface': 'public',
+ 'region': 'RegionOne',
+ 'url': 'http://localhost:5000'
+ },
+ ],
+ 'id': '4363ae44bdf34a3981fde3b823cb9aa2',
+ 'type': 'identity',
+ 'name': 'keystone'
+ }
+
+ ],
+ 'links': {
+ 'self': 'http://localhost/identity/v3/catalog',
+ 'previous': None,
+ 'next': None
+ }
+ }
+
+ def setUp(self):
+ super(TestCatalogClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = catalog_client.CatalogClient(fake_auth, 'identity',
+ 'RegionOne')
+
+ def test_show_catalog_with_bytes_body(self):
+ self._test_show_catalog(bytes_body=True)
+
+ def test_show_catalog_with_str_body(self):
+ self._test_show_catalog()
+
+ def _test_show_catalog(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_catalog,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CATALOG_INFO,
+ bytes_body)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py
new file mode 100644
index 0000000..c724f0a
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py
@@ -0,0 +1,162 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.identity.v3 import endpoint_groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndPointGroupsClient(base.BaseServiceTest):
+ FAKE_CREATE_ENDPOINT_GROUP = {
+ "endpoint_group": {
+ "id": 1,
+ "name": "FAKE_ENDPOINT_GROUP",
+ "description": "FAKE SERVICE ENDPOINT GROUP",
+ "filters": {
+ "service_id": 1
+ }
+ }
+ }
+
+ FAKE_ENDPOINT_GROUP_INFO = {
+ "endpoint_group": {
+ "id": 1,
+ "name": "FAKE_ENDPOINT_GROUP",
+ "description": "FAKE SERVICE ENDPOINT GROUP",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/" +
+ "endpoint_groups/1"
+ },
+ "filters": {
+ "service_id": 1
+ }
+ }
+ }
+
+ FAKE_LIST_ENDPOINT_GROUPS = {
+ "endpoint_groups": [
+ {
+ "id": 1,
+ "name": "SERVICE_GROUP1",
+ "description": "FAKE SERVICE ENDPOINT GROUP",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/" +
+ "endpoint_groups/1"
+ },
+ "filters": {
+ "service_id": 1
+ }
+ },
+ {
+ "id": 2,
+ "name": "SERVICE_GROUP2",
+ "description": "FAKE SERVICE ENDPOINT GROUP",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/" +
+ "endpoint_groups/2"
+ },
+ "filters": {
+ "service_id": 2
+ }
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestEndPointGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoint_groups_client.EndPointGroupsClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_create_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_ENDPOINT_GROUP,
+ bytes_body,
+ status=201,
+ name="FAKE_ENDPOINT_GROUP",
+ filters={'service_id': "1"})
+
+ def _test_show_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ENDPOINT_GROUP_INFO,
+ bytes_body,
+ endpoint_group_id="1")
+
+ def _test_check_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ bytes_body,
+ status=200,
+ endpoint_group_id="1")
+
+ def _test_update_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_ENDPOINT_GROUP_INFO,
+ bytes_body,
+ endpoint_group_id="1",
+ name="NewName")
+
+ def _test_list_endpoint_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoint_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINT_GROUPS,
+ bytes_body)
+
+ def test_create_endpoint_group_with_str_body(self):
+ self._test_create_endpoint_group()
+
+ def test_create_endpoint_group_with_bytes_body(self):
+ self._test_create_endpoint_group(bytes_body=True)
+
+ def test_show_endpoint_group_with_str_body(self):
+ self._test_show_endpoint_group()
+
+ def test_show_endpoint_group_with_bytes_body(self):
+ self._test_show_endpoint_group(bytes_body=True)
+
+ def test_check_endpoint_group_with_str_body(self):
+ self._test_check_endpoint_group()
+
+ def test_check_endpoint_group_with_bytes_body(self):
+ self._test_check_endpoint_group(bytes_body=True)
+
+ def test_list_endpoint_groups_with_str_body(self):
+ self._test_list_endpoint_groups()
+
+ def test_list_endpoint_groups_with_bytes_body(self):
+ self._test_list_endpoint_groups(bytes_body=True)
+
+ def test_update_endpoint_group_with_str_body(self):
+ self._test_update_endpoint_group()
+
+ def test_update_endpoint_group_with_bytes_body(self):
+ self._test_update_endpoint_group(bytes_body=True)
+
+ def test_delete_endpoint_group(self):
+ self.check_service_client_function(
+ self.client.delete_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ endpoint_group_id="1",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
index f8c553f..ca15dd1 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -53,6 +53,8 @@
]
}
+ FAKE_SERVICE_ID = "a4dc5060-f757-4662-b658-edd2aefbb41d"
+
def setUp(self):
super(TestEndpointsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -72,12 +74,15 @@
adminurl="https://compute.north.internal.com/v1",
internalurl="https://compute.north.internal.com/v1")
- def _test_list_endpoints(self, bytes_body=False):
+ def _test_list_endpoints(self, bytes_body=False, mock_args='endpoints',
+ **params):
self.check_service_client_function(
self.client.list_endpoints,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_LIST_ENDPOINTS,
- bytes_body)
+ bytes_body,
+ mock_args=[mock_args],
+ **params)
def test_create_endpoint_with_str_body(self):
self._test_create_endpoint()
@@ -91,6 +96,16 @@
def test_list_endpoints_with_bytes_body(self):
self._test_list_endpoints(bytes_body=True)
+ def test_list_endpoints_with_params(self):
+ # Run the test separately for each param, to avoid assertion error
+ # resulting from randomized params order.
+ mock_args = 'endpoints?service_id=%s' % self.FAKE_SERVICE_ID
+ self._test_list_endpoints(mock_args=mock_args,
+ service_id=self.FAKE_SERVICE_ID)
+
+ mock_args = 'endpoints?interface=public'
+ self._test_list_endpoints(mock_args=mock_args, interface='public')
+
def test_delete_endpoint(self):
self.check_service_client_function(
self.client.delete_endpoint,
diff --git a/tempest/tests/lib/services/identity/v3/test_identity_client.py b/tempest/tests/lib/services/identity/v3/test_identity_client.py
index e435fe2..6572947 100644
--- a/tempest/tests/lib/services/identity/v3/test_identity_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_identity_client.py
@@ -109,6 +109,14 @@
resp_token="cbc36478b0bd8e67e89",
status=204)
+ def test_check_token_existence(self):
+ self.check_service_client_function(
+ self.client.check_token_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ resp_token="cbc36478b0bd8e67e89",
+ status=200)
+
def test_list_auth_projects_with_str_body(self):
self._test_list_auth_projects()
diff --git a/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
new file mode 100644
index 0000000..420ea5f
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
@@ -0,0 +1,215 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from tempest.lib.services.identity.v3 import oauth_token_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services import base
+
+
+class TestOAUTHTokenClient(base.BaseServiceTest):
+ FAKE_CREATE_REQUEST_TOKEN = {
+ 'oauth_token': '29971f',
+ 'oauth_token_secret': '238eb8',
+ 'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+ }
+
+ FAKE_AUTHORIZE_REQUEST_TOKEN = {
+ 'token': {
+ 'oauth_verifier': '8171'
+ }
+ }
+
+ FAKE_CREATE_ACCESS_TOKEN = {
+ 'oauth_token': 'accd36',
+ 'oauth_token_secret': 'aa47da',
+ 'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+ }
+
+ FAKE_ACCESS_TOKEN_INFO = {
+ 'access_token': {
+ 'consumer_id': '7fea2d',
+ 'id': '6be26a',
+ 'expires_at': '2013-09-11T06:07:51.501805Z',
+ 'links': {
+ 'roles': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles',
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+ },
+ 'project_id': 'b9fca3',
+ 'authorizing_user_id': 'ce9e07'
+ }
+ }
+
+ FAKE_LIST_ACCESS_TOKENS = {
+ 'access_tokens': [
+ {
+ 'consumer_id': '7fea2d',
+ 'id': '6be26a',
+ 'expires_at': '2013-09-11T06:07:51.501805Z',
+ 'links': {
+ 'roles': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/' +
+ '6be26a/roles',
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+ },
+ 'project_id': 'b9fca3',
+ 'authorizing_user_id': 'ce9e07'
+ }
+ ],
+ 'links': {
+ 'next': None,
+ 'previous': None,
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens'
+ }
+ }
+
+ FAKE_LIST_ACCESS_TOKEN_ROLES = {
+ 'roles': [
+ {
+ 'id': '26b860',
+ 'domain_id': 'fake_domain',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/' +
+ 'roles/26b860'
+ },
+ 'name': 'fake_role'
+ }
+ ],
+ 'links': {
+ 'next': None,
+ 'previous': None,
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles'
+ }
+ }
+
+ FAKE_ACCESS_TOKEN_ROLE_INFO = {
+ 'role': {
+ 'id': '26b860',
+ 'domain_id': 'fake_domain',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/' +
+ 'roles/26b860'
+ },
+ 'name': 'fake_role'
+ }
+ }
+
+ def setUp(self):
+ super(TestOAUTHTokenClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = oauth_token_client.OAUTHTokenClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _mock_token_response(self, body):
+ temp_response = [key + '=' + value for key, value in body.items()]
+ return '&'.join(temp_response)
+
+ def _test_authorize_request_token(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.authorize_request_token,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_AUTHORIZE_REQUEST_TOKEN,
+ bytes_body,
+ request_token_id=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+ role_ids=['26b860'],
+ status=200)
+
+ def test_create_request_token(self):
+ mock_resp = self._mock_token_response(self.FAKE_CREATE_REQUEST_TOKEN)
+ resp = fake_http.fake_http_response(None, status=201), mock_resp
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.rest_client.RestClient.post',
+ return_value=resp))
+
+ resp = self.client.create_request_token(
+ consumer_key='12345',
+ consumer_secret='23456',
+ project_id='c8f58432c6f00162f04d3250f')
+ self.assertEqual(self.FAKE_CREATE_REQUEST_TOKEN, resp)
+
+ def test_authorize_token_request_with_str_body(self):
+ self._test_authorize_request_token()
+
+ def test_authorize_token_request_with_bytes_body(self):
+ self._test_authorize_request_token(bytes_body=True)
+
+ def test_create_access_token(self):
+ mock_resp = self._mock_token_response(self.FAKE_CREATE_ACCESS_TOKEN)
+ req_secret = self.FAKE_CREATE_REQUEST_TOKEN['oauth_token_secret']
+ resp = fake_http.fake_http_response(None, status=201), mock_resp
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.rest_client.RestClient.post',
+ return_value=resp))
+
+ resp = self.client.create_access_token(
+ consumer_key='12345',
+ consumer_secret='23456',
+ request_key=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+ request_secret=req_secret,
+ oauth_verifier='8171')
+ self.assertEqual(self.FAKE_CREATE_ACCESS_TOKEN, resp)
+
+ def test_get_access_token(self):
+ self.check_service_client_function(
+ self.client.get_access_token,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ACCESS_TOKEN_INFO,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=200)
+
+ def test_list_access_tokens(self):
+ self.check_service_client_function(
+ self.client.list_access_tokens,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ACCESS_TOKENS,
+ user_id='ce9e07',
+ status=200)
+
+ def test_revoke_access_token(self):
+ self.check_service_client_function(
+ self.client.revoke_access_token,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['consumer_id'],
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=204)
+
+ def test_list_access_token_roles(self):
+ self.check_service_client_function(
+ self.client.list_access_token_roles,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ACCESS_TOKEN_ROLES,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=200)
+
+ def test_get_access_token_role(self):
+ self.check_service_client_function(
+ self.client.get_access_token_role,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ACCESS_TOKEN_ROLE_INFO,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ role_id=self.FAKE_ACCESS_TOKEN_ROLE_INFO['role']['id'],
+ status=200)
diff --git a/tempest/tests/lib/services/identity/v3/test_services_client.py b/tempest/tests/lib/services/identity/v3/test_services_client.py
index f87fcce..b464644 100644
--- a/tempest/tests/lib/services/identity/v3/test_services_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_services_client.py
@@ -101,12 +101,15 @@
bytes_body,
service_id="686766")
- def _test_list_services(self, bytes_body=False):
+ def _test_list_services(self, bytes_body=False, mock_args='services',
+ **params):
self.check_service_client_function(
self.client.list_services,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_LIST_SERVICES,
- bytes_body)
+ bytes_body,
+ mock_args=[mock_args],
+ **params)
def _test_update_service(self, bytes_body=False):
self.check_service_client_function(
@@ -134,6 +137,10 @@
def test_list_services_with_bytes_body(self):
self._test_list_services(bytes_body=True)
+ def test_list_services_with_params(self):
+ self._test_list_services(
+ type='fake-type', mock_args='services?type=fake-type')
+
def test_update_service_with_str_body(self):
self._test_update_service()
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 9648985..ee4d4cb 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -12,6 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+
+from tempest.lib.common.utils import data_utils
from tempest.lib.services.image.v2 import images_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -42,6 +45,57 @@
"container_format": None
}
+ FAKE_LIST_IMAGES = {
+ "images": [
+ {
+ "status": "active",
+ "name": "cirros-0.3.2-x86_64-disk",
+ "tags": [],
+ "container_format": "bare",
+ "created_at": "2014-11-07T17:07:06Z",
+ "disk_format": "qcow2",
+ "updated_at": "2014-11-07T17:19:09Z",
+ "visibility": "public",
+ "self": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27",
+ "min_disk": 0,
+ "protected": False,
+ "id": "1bea47ed-f6a9-463b-b423-14b9cca9ad27",
+ "file": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27/file",
+ "checksum": "64d7c1cd2b6f60c92c14662941cb7913",
+ "owner": "5ef70662f8b34079a6eddb8da9d75fe8",
+ "size": 13167616,
+ "min_ram": 0,
+ "schema": "/v2/schemas/image",
+ "virtual_size": None
+ },
+ {
+ "status": "active",
+ "name": "F17-x86_64-cfntools",
+ "tags": [],
+ "container_format": "bare",
+ "created_at": "2014-10-30T08:23:39Z",
+ "disk_format": "qcow2",
+ "updated_at": "2014-11-03T16:40:10Z",
+ "visibility": "public",
+ "self": "/v2/images/781b3762-9469-4cec-b58d-3349e5de4e9c",
+ "min_disk": 0,
+ "protected": False,
+ "id": "781b3762-9469-4cec-b58d-3349e5de4e9c",
+ "file": "/v2/images/781b3762-9469-4cec-b58d-3349e5de4e9c/file",
+ "checksum": "afab0f79bac770d61d24b4d0560b5f70",
+ "owner": "5ef70662f8b34079a6eddb8da9d75fe8",
+ "size": 476704768,
+ "min_ram": 0,
+ "schema": "/v2/schemas/image",
+ "virtual_size": None
+ }
+ ],
+ "schema": "/v2/schemas/images",
+ "first": "/v2/images"
+ }
+
+ FAKE_TAG_NAME = "fake tag"
+
def setUp(self):
super(TestImagesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -74,6 +128,14 @@
bytes_body,
image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
+ def _test_list_images(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_images,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_IMAGES,
+ bytes_body,
+ mock_args=['images'])
+
def test_create_image_with_str_body(self):
self._test_create_image()
@@ -104,8 +166,56 @@
'tempest.lib.common.rest_client.RestClient.delete',
{}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
+ def test_store_image_file(self):
+ data = six.BytesIO(data_utils.random_bytes())
+
+ self.check_service_client_function(
+ self.client.store_image_file,
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ {},
+ image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"],
+ status=204,
+ data=data)
+
+ def test_show_image_file(self):
+ # NOTE: The response for this API returns raw binary data, but an error
+ # is thrown if random bytes are used for the resp body since
+ # ``create_response`` then calls ``json.dumps``.
+ self.check_service_client_function(
+ self.client.show_image_file,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {},
+ resp_as_string=True,
+ image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"],
+ headers={'Content-Type': 'application/octet-stream'},
+ status=200)
+
+ def test_add_image_tag(self):
+ self.check_service_client_function(
+ self.client.add_image_tag,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"],
+ status=204,
+ tag=self.FAKE_TAG_NAME)
+
+ def test_delete_image_tag(self):
+ self.check_service_client_function(
+ self.client.delete_image_tag,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"],
+ status=204,
+ tag=self.FAKE_TAG_NAME)
+
def test_show_image_with_str_body(self):
self._test_show_image()
def test_show_image_with_bytes_body(self):
self._test_show_image(bytes_body=True)
+
+ def test_list_images_with_str_body(self):
+ self._test_list_images()
+
+ def test_list_images_with_bytes_body(self):
+ self._test_list_images(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_base_network_client.py b/tempest/tests/lib/services/network/test_base_network_client.py
new file mode 100644
index 0000000..e121cec
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_base_network_client.py
@@ -0,0 +1,96 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from tempest.lib.services.network import base as base_network_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services import base
+
+
+class TestBaseNetworkClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestBaseNetworkClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_network_client.BaseNetworkClient(
+ fake_auth, 'compute', 'regionOne')
+
+ self.mock_expected_success = mock.patch.object(
+ self.client, 'expected_success').start()
+
+ def _assert_empty(self, resp):
+ self.assertEqual([], list(resp.keys()))
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.post')
+ def test_create_resource(self, mock_post):
+ response = fake_http.fake_http_response(headers=None, status=201)
+ mock_post.return_value = response, '{"baz": "qux"}'
+
+ post_data = {'foo': 'bar'}
+ resp = self.client.create_resource('/fake_url', post_data)
+
+ self.assertEqual({'status': '201'}, resp.response)
+ self.assertEqual("qux", resp["baz"])
+ mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 201, 201)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.post')
+ def test_create_resource_expect_different_values(self, mock_post):
+ response = fake_http.fake_http_response(headers=None, status=200)
+ mock_post.return_value = response, '{}'
+
+ post_data = {'foo': 'bar'}
+ resp = self.client.create_resource('/fake_url', post_data,
+ expect_response_code=200,
+ expect_empty_body=True)
+
+ self.assertEqual({'status': '200'}, resp.response)
+ self._assert_empty(resp)
+ mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 200, 200)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.put')
+ def test_update_resource(self, mock_put):
+ response = fake_http.fake_http_response(headers=None, status=200)
+ mock_put.return_value = response, '{"baz": "qux"}'
+
+ put_data = {'foo': 'bar'}
+ resp = self.client.update_resource('/fake_url', put_data)
+
+ self.assertEqual({'status': '200'}, resp.response)
+ self.assertEqual("qux", resp["baz"])
+ mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 200, 200)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.put')
+ def test_update_resource_expect_different_values(self, mock_put):
+ response = fake_http.fake_http_response(headers=None, status=201)
+ mock_put.return_value = response, '{}'
+
+ put_data = {'foo': 'bar'}
+ resp = self.client.update_resource('/fake_url', put_data,
+ expect_response_code=201,
+ expect_empty_body=True)
+
+ self.assertEqual({'status': '201'}, resp.response)
+ self._assert_empty(resp)
+ mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 201, 201)
diff --git a/tempest/tests/lib/services/network/test_extensions_client.py b/tempest/tests/lib/services/network/test_extensions_client.py
new file mode 100644
index 0000000..27eb485
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_extensions_client.py
@@ -0,0 +1,201 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import extensions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestExtensionsClient(base.BaseServiceTest):
+
+ FAKE_EXTENSIONS = {
+ "extensions": [
+ {
+ "updated": "2013-01-20T00:00:00-00:00",
+ "name": "Neutron Service Type Management",
+ "links": [],
+ "alias": "service-type",
+ "description": "API for retrieving service providers for"
+ " Neutron advanced services"
+ },
+ {
+ "updated": "2012-10-05T10:00:00-00:00",
+ "name": "security-group",
+ "links": [],
+ "alias": "security-group",
+ "description": "The security groups extension."
+ },
+ {
+ "updated": "2013-02-07T10:00:00-00:00",
+ "name": "L3 Agent Scheduler",
+ "links": [],
+ "alias": "l3_agent_scheduler",
+ "description": "Schedule routers among l3 agents"
+ },
+ {
+ "updated": "2013-02-07T10:00:00-00:00",
+ "name": "Loadbalancer Agent Scheduler",
+ "links": [],
+ "alias": "lbaas_agent_scheduler",
+ "description": "Schedule pools among lbaas agents"
+ },
+ {
+ "updated": "2013-03-28T10:00:00-00:00",
+ "name": "Neutron L3 Configurable external gateway mode",
+ "links": [],
+ "alias": "ext-gw-mode",
+ "description":
+ "Extension of the router abstraction for specifying whether"
+ " SNAT should occur on the external gateway"
+ },
+ {
+ "updated": "2014-02-03T10:00:00-00:00",
+ "name": "Port Binding",
+ "links": [],
+ "alias": "binding",
+ "description": "Expose port bindings of a virtual port to"
+ " external application"
+ },
+ {
+ "updated": "2012-09-07T10:00:00-00:00",
+ "name": "Provider Network",
+ "links": [],
+ "alias": "provider",
+ "description": "Expose mapping of virtual networks to"
+ " physical networks"
+ },
+ {
+ "updated": "2013-02-03T10:00:00-00:00",
+ "name": "agent",
+ "links": [],
+ "alias": "agent",
+ "description": "The agent management extension."
+ },
+ {
+ "updated": "2012-07-29T10:00:00-00:00",
+ "name": "Quota management support",
+ "links": [],
+ "alias": "quotas",
+ "description": "Expose functions for quotas management per"
+ " tenant"
+ },
+ {
+ "updated": "2013-02-07T10:00:00-00:00",
+ "name": "DHCP Agent Scheduler",
+ "links": [],
+ "alias": "dhcp_agent_scheduler",
+ "description": "Schedule networks among dhcp agents"
+ },
+ {
+ "updated": "2013-06-27T10:00:00-00:00",
+ "name": "Multi Provider Network",
+ "links": [],
+ "alias": "multi-provider",
+ "description": "Expose mapping of virtual networks to"
+ " multiple physical networks"
+ },
+ {
+ "updated": "2013-01-14T10:00:00-00:00",
+ "name": "Neutron external network",
+ "links": [],
+ "alias": "external-net",
+ "description": "Adds external network attribute to network"
+ " resource."
+ },
+ {
+ "updated": "2012-07-20T10:00:00-00:00",
+ "name": "Neutron L3 Router",
+ "links": [],
+ "alias": "router",
+ "description": "Router abstraction for basic L3 forwarding"
+ " between L2 Neutron networks and access to external"
+ " networks via a NAT gateway."
+ },
+ {
+ "updated": "2013-07-23T10:00:00-00:00",
+ "name": "Allowed Address Pairs",
+ "links": [],
+ "alias": "allowed-address-pairs",
+ "description": "Provides allowed address pairs"
+ },
+ {
+ "updated": "2013-03-17T12:00:00-00:00",
+ "name": "Neutron Extra DHCP opts",
+ "links": [],
+ "alias": "extra_dhcp_opt",
+ "description": "Extra options configuration for DHCP. For"
+ " example PXE boot options to DHCP clients can be specified"
+ " (e.g. tftp-server, server-ip-address, bootfile-name)"
+ },
+ {
+ "updated": "2012-10-07T10:00:00-00:00",
+ "name": "LoadBalancing service",
+ "links": [],
+ "alias": "lbaas",
+ "description": "Extension for LoadBalancing service"
+ },
+ {
+ "updated": "2013-02-01T10:00:00-00:00",
+ "name": "Neutron Extra Route",
+ "links": [],
+ "alias": "extraroute",
+ "description": "Extra routes configuration for L3 router"
+ },
+ {
+ "updated": "2016-01-24T10:00:00-00:00",
+ "name": "Neutron Port Data Plane Status",
+ "links": [],
+ "alias": "data-plane-status",
+ "description": "Status of the underlying data plane."
+ }
+ ]
+ }
+
+ FAKE_EXTENSION_ALIAS = "service-type"
+
+ def setUp(self):
+ super(TestExtensionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.extensions_client = extensions_client.ExtensionsClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_extensions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.extensions_client.list_extensions,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_EXTENSIONS,
+ bytes_body,
+ 200)
+
+ def _test_show_extension(self, bytes_body=False):
+ self.check_service_client_function(
+ self.extensions_client.show_extension,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"extension": self.FAKE_EXTENSIONS["extensions"][0]},
+ bytes_body,
+ 200,
+ ext_alias=self.FAKE_EXTENSION_ALIAS)
+
+ def test_list_extensions_with_str_body(self):
+ self._test_list_extensions()
+
+ def test_list_extensions_with_bytes_body(self):
+ self._test_list_extensions(bytes_body=True)
+
+ def test_show_extension_with_str_body(self):
+ self._test_show_extension()
+
+ def test_show_extension_with_bytes_body(self):
+ self._test_show_extension(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_floating_ips_client.py b/tempest/tests/lib/services/network/test_floating_ips_client.py
new file mode 100644
index 0000000..c5b1845
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_floating_ips_client.py
@@ -0,0 +1,145 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import floating_ips_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestFloatingIPsClient(base.BaseServiceTest):
+
+ FAKE_FLOATING_IPS = {
+ "floatingips": [
+ {
+ "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f",
+ "description": "for test",
+ "created_at": "2016-12-21T10:55:50Z",
+ "updated_at": "2016-12-21T10:55:53Z",
+ "revision_number": 1,
+ "project_id": "4969c491a3c74ee4af974e6d800c62de",
+ "tenant_id": "4969c491a3c74ee4af974e6d800c62de",
+ "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57",
+ "fixed_ip_address": "10.0.0.3",
+ "floating_ip_address": "172.24.4.228",
+ "port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+ "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
+ "status": "ACTIVE"
+ },
+ {
+ "router_id": None,
+ "description": "for test",
+ "created_at": "2016-12-21T11:55:50Z",
+ "updated_at": "2016-12-21T11:55:53Z",
+ "revision_number": 2,
+ "project_id": "4969c491a3c74ee4af974e6d800c62de",
+ "tenant_id": "4969c491a3c74ee4af974e6d800c62de",
+ "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57",
+ "fixed_ip_address": None,
+ "floating_ip_address": "172.24.4.227",
+ "port_id": None,
+ "id": "61cea855-49cb-4846-997d-801b70c71bdd",
+ "status": "DOWN"
+ }
+ ]
+ }
+
+ FAKE_FLOATING_IP_ID = "2f245a7b-796b-4f26-9cf9-9e82d248fda7"
+
+ def setUp(self):
+ super(TestFloatingIPsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.floating_ips_client = floating_ips_client.FloatingIPsClient(
+ fake_auth, "compute", "regionOne")
+
+ def _test_list_floatingips(self, bytes_body=False):
+ self.check_service_client_function(
+ self.floating_ips_client.list_floatingips,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_FLOATING_IPS,
+ bytes_body,
+ 200)
+
+ def _test_create_floatingip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.floating_ips_client.create_floatingip,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"floatingip": self.FAKE_FLOATING_IPS["floatingips"][1]},
+ bytes_body,
+ 201,
+ floating_network_id="172.24.4.228")
+
+ def _test_show_floatingip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.floating_ips_client.show_floatingip,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"floatingip": self.FAKE_FLOATING_IPS["floatingips"][0]},
+ bytes_body,
+ 200,
+ floatingip_id=self.FAKE_FLOATING_IP_ID)
+
+ def _test_update_floatingip(self, bytes_body=False):
+ update_kwargs = {
+ "port_id": "fc861431-0e6c-4842-a0ed-e2363f9bc3a8"
+ }
+
+ resp_body = {
+ "floatingip": copy.deepcopy(
+ self.FAKE_FLOATING_IPS["floatingips"][0]
+ )
+ }
+ resp_body["floatingip"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.floating_ips_client.update_floatingip,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ floatingip_id=self.FAKE_FLOATING_IP_ID,
+ **update_kwargs)
+
+ def test_list_floatingips_with_str_body(self):
+ self._test_list_floatingips()
+
+ def test_list_floatingips_with_bytes_body(self):
+ self._test_list_floatingips(bytes_body=True)
+
+ def test_create_floatingip_with_str_body(self):
+ self._test_create_floatingip()
+
+ def test_create_floatingip_with_bytes_body(self):
+ self._test_create_floatingip(bytes_body=True)
+
+ def test_show_floatingips_with_str_body(self):
+ self._test_show_floatingip()
+
+ def test_show_floatingips_with_bytes_body(self):
+ self._test_show_floatingip(bytes_body=True)
+
+ def test_update_floatingip_with_str_body(self):
+ self._test_update_floatingip()
+
+ def test_update_floatingip_with_bytes_body(self):
+ self._test_update_floatingip(bytes_body=True)
+
+ def test_delete_floatingip(self):
+ self.check_service_client_function(
+ self.floating_ips_client.delete_floatingip,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ floatingip_id=self.FAKE_FLOATING_IP_ID)
diff --git a/tempest/tests/lib/services/network/test_metering_label_rules_client.py b/tempest/tests/lib/services/network/test_metering_label_rules_client.py
new file mode 100644
index 0000000..047c34f
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_metering_label_rules_client.py
@@ -0,0 +1,110 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import metering_label_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestMeteringLabelRulesClient(base.BaseServiceTest):
+
+ FAKE_METERING_LABEL_RULES = {
+ "metering_label_rules": [
+ {
+ "remote_ip_prefix": "20.0.0.0/24",
+ "direction": "ingress",
+ "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812",
+ "id": "9536641a-7d14-4dc5-afaf-93a973ce0eb8",
+ "excluded": False
+ },
+ {
+ "remote_ip_prefix": "10.0.0.0/24",
+ "direction": "ingress",
+ "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812",
+ "id": "ffc6fd15-40de-4e7d-b617-34d3f7a93aec",
+ "excluded": False
+ }
+ ]
+ }
+
+ FAKE_METERING_LABEL_RULE = {
+ "remote_ip_prefix": "20.0.0.0/24",
+ "direction": "ingress",
+ "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812"
+ }
+
+ FAKE_METERING_LABEL_ID = "e131d186-b02d-4c0b-83d5-0c0725c4f812"
+ FAKE_METERING_LABEL_RULE_ID = "9536641a-7d14-4dc5-afaf-93a973ce0eb8"
+
+ def setUp(self):
+ super(TestMeteringLabelRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.metering_label_rules_client = \
+ metering_label_rules_client.MeteringLabelRulesClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_metering_label_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_label_rules_client.list_metering_label_rules,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_METERING_LABEL_RULES,
+ bytes_body,
+ 200)
+
+ def _test_create_metering_label_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_label_rules_client.create_metering_label_rule,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"metering_label_rule": self.FAKE_METERING_LABEL_RULES[
+ "metering_label_rules"][0]},
+ bytes_body,
+ 201,
+ **self.FAKE_METERING_LABEL_RULE)
+
+ def _test_show_metering_label_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_label_rules_client.show_metering_label_rule,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"metering_label_rule": self.FAKE_METERING_LABEL_RULES[
+ "metering_label_rules"][0]},
+ bytes_body,
+ 200,
+ metering_label_rule_id=self.FAKE_METERING_LABEL_RULE_ID)
+
+ def test_delete_metering_label_rule(self):
+ self.check_service_client_function(
+ self.metering_label_rules_client.delete_metering_label_rule,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ metering_label_rule_id=self.FAKE_METERING_LABEL_RULE_ID)
+
+ def test_list_metering_label_rules_with_str_body(self):
+ self._test_list_metering_label_rules()
+
+ def test_list_metering_label_rules_with_bytes_body(self):
+ self._test_list_metering_label_rules(bytes_body=True)
+
+ def test_create_metering_label_rule_with_str_body(self):
+ self._test_create_metering_label_rule()
+
+ def test_create_metering_label_rule_with_bytes_body(self):
+ self._test_create_metering_label_rule(bytes_body=True)
+
+ def test_show_metering_label_rule_with_str_body(self):
+ self._test_show_metering_label_rule()
+
+ def test_show_metering_label_rule_with_bytes_body(self):
+ self._test_show_metering_label_rule(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_metering_labels_client.py b/tempest/tests/lib/services/network/test_metering_labels_client.py
new file mode 100644
index 0000000..a048326
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_metering_labels_client.py
@@ -0,0 +1,107 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import metering_labels_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestMeteringLabelsClient(base.BaseServiceTest):
+
+ FAKE_METERING_LABELS = {
+ "metering_labels": [
+ {
+ "project_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+ "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+ "description": "label1 description",
+ "name": "label1",
+ "id": "a6700594-5b7a-4105-8bfe-723b346ce866",
+ "shared": False
+ },
+ {
+ "project_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+ "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+ "description": "label2 description",
+ "name": "label2",
+ "id": "e131d186-b02d-4c0b-83d5-0c0725c4f812",
+ "shared": False
+ }
+ ]
+ }
+
+ FAKE_METERING_LABEL_ID = "a6700594-5b7a-4105-8bfe-723b346ce866"
+
+ def setUp(self):
+ super(TestMeteringLabelsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.metering_labels_client = \
+ metering_labels_client.MeteringLabelsClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_metering_labels(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_labels_client.list_metering_labels,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_METERING_LABELS,
+ bytes_body,
+ 200)
+
+ def _test_create_metering_label(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_labels_client.create_metering_label,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"metering_label": self.FAKE_METERING_LABELS[
+ "metering_labels"][1]},
+ bytes_body,
+ 201,
+ name="label1",
+ description="label1 description",
+ shared=False)
+
+ def _test_show_metering_label(self, bytes_body=False):
+ self.check_service_client_function(
+ self.metering_labels_client.show_metering_label,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"metering_label": self.FAKE_METERING_LABELS[
+ "metering_labels"][0]},
+ bytes_body,
+ 200,
+ metering_label_id=self.FAKE_METERING_LABEL_ID)
+
+ def test_delete_metering_label(self):
+ self.check_service_client_function(
+ self.metering_labels_client.delete_metering_label,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ metering_label_id=self.FAKE_METERING_LABEL_ID)
+
+ def test_list_metering_labels_with_str_body(self):
+ self._test_list_metering_labels()
+
+ def test_list_metering_labels_with_bytes_body(self):
+ self._test_list_metering_labels(bytes_body=True)
+
+ def test_create_metering_label_with_str_body(self):
+ self._test_create_metering_label()
+
+ def test_create_metering_label_with_bytes_body(self):
+ self._test_create_metering_label(bytes_body=True)
+
+ def test_show_metering_label_with_str_body(self):
+ self._test_show_metering_label()
+
+ def test_show_metering_label_with_bytes_body(self):
+ self._test_show_metering_label(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_ports_client.py b/tempest/tests/lib/services/network/test_ports_client.py
new file mode 100644
index 0000000..20ef3f1
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_ports_client.py
@@ -0,0 +1,198 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import ports_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestPortsClient(base.BaseServiceTest):
+
+ FAKE_PORTS = {
+ "ports": [
+ {
+ "admin_state_up": True,
+ "allowed_address_pairs": [],
+ "data_plane_status": None,
+ "description": "",
+ "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+ "device_owner": "network:router_gateway",
+ "extra_dhcp_opts": [],
+ "fixed_ips": [
+ {
+ "ip_address": "172.24.4.2",
+ "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062"
+ }
+ ],
+ "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
+ "mac_address": "fa:16:3e:58:42:ed",
+ "name": "",
+ "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3",
+ "project_id": "",
+ "security_groups": [],
+ "status": "ACTIVE",
+ "tenant_id": ""
+ },
+ {
+ "admin_state_up": True,
+ "allowed_address_pairs": [],
+ "data_plane_status": None,
+ "description": "",
+ "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+ "device_owner": "network:router_interface",
+ "extra_dhcp_opts": [],
+ "fixed_ips": [
+ {
+ "ip_address": "10.0.0.1",
+ "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17"
+ }
+ ],
+ "id": "f71a6703-d6de-4be1-a91a-a570ede1d159",
+ "mac_address": "fa:16:3e:bb:3c:e4",
+ "name": "",
+ "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2",
+ "project_id": "d397de8a63f341818f198abb0966f6f3",
+ "security_groups": [],
+ "status": "ACTIVE",
+ "tenant_id": "d397de8a63f341818f198abb0966f6f3"
+ }
+ ]
+ }
+
+ FAKE_PORT_ID = "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b"
+
+ FAKE_PORT1 = {
+ "admin_state_up": True,
+ "name": "",
+ "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3"
+ }
+
+ FAKE_PORT2 = {
+ "admin_state_up": True,
+ "name": "",
+ "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2"
+ }
+
+ FAKE_PORTS_REQ = {
+ "ports": [
+ FAKE_PORT1,
+ FAKE_PORT2
+ ]
+ }
+
+ def setUp(self):
+ super(TestPortsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.ports_client = ports_client.PortsClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_ports(self, bytes_body=False):
+ self.check_service_client_function(
+ self.ports_client.list_ports,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_PORTS,
+ bytes_body,
+ 200)
+
+ def _test_create_port(self, bytes_body=False):
+ self.check_service_client_function(
+ self.ports_client.create_port,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"port": self.FAKE_PORTS["ports"][0]},
+ bytes_body,
+ 201,
+ **self.FAKE_PORT1)
+
+ def _test_create_bulk_ports(self, bytes_body=False):
+ self.check_service_client_function(
+ self.ports_client.create_bulk_ports,
+ "tempest.lib.common.rest_client.RestClient.post",
+ self.FAKE_PORTS,
+ bytes_body,
+ 201,
+ ports=self.FAKE_PORTS_REQ)
+
+ def _test_show_port(self, bytes_body=False):
+ self.check_service_client_function(
+ self.ports_client.show_port,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"port": self.FAKE_PORTS["ports"][0]},
+ bytes_body,
+ 200,
+ port_id=self.FAKE_PORT_ID)
+
+ def _test_update_port(self, bytes_body=False):
+ update_kwargs = {
+ "admin_state_up": True,
+ "device_id": "d90a13da-be41-461f-9f99-1dbcf438fdf2",
+ "device_owner": "compute:nova",
+ "name": "test-for-port-update"
+ }
+
+ resp_body = {
+ "port": copy.deepcopy(
+ self.FAKE_PORTS["ports"][0]
+ )
+ }
+ resp_body["port"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.ports_client.update_port,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ port_id=self.FAKE_PORT_ID,
+ **update_kwargs)
+
+ def test_delete_port(self):
+ self.check_service_client_function(
+ self.ports_client.delete_port,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ port_id=self.FAKE_PORT_ID)
+
+ def test_list_ports_with_str_body(self):
+ self._test_list_ports()
+
+ def test_list_ports_with_bytes_body(self):
+ self._test_list_ports(bytes_body=True)
+
+ def test_create_port_with_str_body(self):
+ self._test_create_port()
+
+ def test_create_port_with_bytes_body(self):
+ self._test_create_port(bytes_body=True)
+
+ def test_create_bulk_port_with_str_body(self):
+ self._test_create_bulk_ports()
+
+ def test_create_bulk_port_with_bytes_body(self):
+ self._test_create_bulk_ports(bytes_body=True)
+
+ def test_show_port_with_str_body(self):
+ self._test_show_port()
+
+ def test_show_port_with_bytes_body(self):
+ self._test_show_port(bytes_body=True)
+
+ def test_update_port_with_str_body(self):
+ self._test_update_port()
+
+ def test_update_port_with_bytes_body(self):
+ self._test_update_port(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
new file mode 100644
index 0000000..e76bc9c
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -0,0 +1,99 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import quotas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQuotasClient(base.BaseServiceTest):
+
+ FAKE_QUOTAS = {
+ "quotas": [
+ {
+ "floatingip": 50,
+ "network": 15,
+ "port": 50,
+ "project_id": "bab7d5c60cd041a0a36f7c4b6e1dd978",
+ "rbac_policy": -1,
+ "router": 10,
+ "security_group": 10,
+ "security_group_rule": 100,
+ "subnet": 10,
+ "subnetpool": -1,
+ "tenant_id": "bab7d5c60cd041a0a36f7c4b6e1dd978"
+ }
+ ]
+ }
+
+ FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
+
+ def setUp(self):
+ super(TestQuotasClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.quotas_client = quotas_client.QuotasClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_list_quotas(self, bytes_body=False):
+ self.check_service_client_function(
+ self.quotas_client.list_quotas,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_QUOTAS,
+ bytes_body,
+ 200)
+
+ def _test_show_quotas(self, bytes_body=False):
+ self.check_service_client_function(
+ self.quotas_client.show_quotas,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"quota": self.FAKE_QUOTAS["quotas"][0]},
+ bytes_body,
+ 200,
+ tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+ def _test_update_quotas(self, bytes_body=False):
+ self.check_service_client_function(
+ self.quotas_client.update_quotas,
+ "tempest.lib.common.rest_client.RestClient.put",
+ {"quota": self.FAKE_QUOTAS["quotas"][0]},
+ bytes_body,
+ 200,
+ tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+ def test_reset_quotas(self):
+ self.check_service_client_function(
+ self.quotas_client.reset_quotas,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+ def test_list_quotas_with_str_body(self):
+ self._test_list_quotas()
+
+ def test_list_quotas_with_bytes_body(self):
+ self._test_list_quotas(bytes_body=True)
+
+ def test_show_quotas_with_str_body(self):
+ self._test_show_quotas()
+
+ def test_show_quotas_with_bytes_body(self):
+ self._test_show_quotas(bytes_body=True)
+
+ def test_update_quotas_with_str_body(self):
+ self._test_update_quotas()
+
+ def test_update_quotas_with_bytes_body(self):
+ self._test_update_quotas(bytes_body=True)
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
new file mode 100644
index 0000000..b9c17a1
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_security_group_rules_client.py
@@ -0,0 +1,138 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+import mock
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.network import base as network_base
+from tempest.lib.services.network import security_group_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSecurityGroupsClient(base.BaseServiceTest):
+
+ FAKE_SEC_GROUP_RULE_ID = "3c0e45ff-adaf-4124-b083-bf390e5482ff"
+
+ FAKE_SECURITY_GROUP_RULES = {
+ "security_group_rules": [
+ {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+ "port_range_max": None,
+ "port_range_min": None,
+ "protocol": None,
+ "remote_group_id": None,
+ "remote_ip_prefix": None,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": ""
+ },
+ {
+ "direction": "egress",
+ "ethertype": "IPv4",
+ "id": "93aa42e5-80db-4581-9391-3a608bd0e448",
+ "port_range_max": None,
+ "port_range_min": None,
+ "protocol": None,
+ "remote_group_id": None,
+ "remote_ip_prefix": None,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": ""
+ }
+ ]
+ }
+
+ FAKE_SECURITY_GROUP_RULE = copy.copy(
+ FAKE_SECURITY_GROUP_RULES['security_group_rules'][0])
+
+ def setUp(self):
+ super(TestSecurityGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_group_rules_client.SecurityGroupRulesClient(
+ fake_auth, 'network', 'regionOne')
+
+ def _test_list_security_group_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_security_group_rules,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SECURITY_GROUP_RULES,
+ bytes_body,
+ mock_args='v2.0/security-group-rules')
+
+ def _test_create_security_group_rule(self, bytes_body=False):
+ kwargs = {'direction': 'egress',
+ 'security_group_id': '85cc3048-abc3-43cc-89b3-377341426ac5',
+ 'remote_ip_prefix': None}
+ payload = json.dumps({"security_group_rule": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(network_base.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.create_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SECURITY_GROUP_RULE,
+ bytes_body,
+ status=201,
+ mock_args=['v2.0/security-group-rules', payload],
+ **kwargs)
+
+ def _test_show_security_group_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SECURITY_GROUP_RULE,
+ bytes_body,
+ security_group_rule_id=self.FAKE_SEC_GROUP_RULE_ID,
+ mock_args='v2.0/security-group-rules/%s'
+ % self.FAKE_SEC_GROUP_RULE_ID)
+
+ def test_list_security_group_rules_with_str_body(self):
+ self._test_list_security_group_rules()
+
+ def test_list_security_group_rules_with_bytes_body(self):
+ self._test_list_security_group_rules(bytes_body=True)
+
+ def test_create_security_group_rule_with_str_body(self):
+ self._test_create_security_group_rule()
+
+ def test_create_security_group_rule_with_bytes_body(self):
+ self._test_create_security_group_rule(bytes_body=True)
+
+ def test_show_security_group_rule_with_str_body(self):
+ self._test_show_security_group_rule()
+
+ def test_show_security_group_rule_with_bytes_body(self):
+ self._test_show_security_group_rule(bytes_body=True)
+
+ def test_delete_security_group_rule(self):
+ self.check_service_client_function(
+ self.client.delete_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ security_group_rule_id=self.FAKE_SEC_GROUP_RULE_ID,
+ mock_args='v2.0/security-group-rules/%s'
+ % self.FAKE_SEC_GROUP_RULE_ID)
diff --git a/tempest/tests/lib/services/network/test_security_groups_client.py b/tempest/tests/lib/services/network/test_security_groups_client.py
new file mode 100644
index 0000000..f96805f
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_security_groups_client.py
@@ -0,0 +1,174 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+import mock
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.network import base as network_base
+from tempest.lib.services.network import security_groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSecurityGroupsClient(base.BaseServiceTest):
+
+ FAKE_SEC_GROUP_ID = "85cc3048-abc3-43cc-89b3-377341426ac5"
+
+ FAKE_SECURITY_GROUPS = {
+ "security_groups": [
+ {
+ "description": "default",
+ "id": FAKE_SEC_GROUP_ID,
+ "name": "fake-security-group-name",
+ "security_group_rules": [
+ {
+ "direction": "egress",
+ "ethertype": "IPv4",
+ "id": "38ce2d8e-e8f1-48bd-83c2-d33cb9f50c3d",
+ "port_range_max": None,
+ "port_range_min": None,
+ "protocol": None,
+ "remote_group_id": None,
+ "remote_ip_prefix": None,
+ "security_group_id": FAKE_SEC_GROUP_ID,
+ "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": ""
+ },
+ {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "565b9502-12de-4ffd-91e9-68885cff6ae1",
+ "port_range_max": None,
+ "port_range_min": None,
+ "protocol": None,
+ "remote_group_id": None,
+ "remote_ip_prefix": None,
+ "security_group_id": FAKE_SEC_GROUP_ID,
+ "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": ""
+ }
+ ],
+ "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ ]
+ }
+
+ FAKE_SECURITY_GROUP = {
+ "security_group": copy.deepcopy(
+ FAKE_SECURITY_GROUPS["security_groups"][0])
+ }
+
+ def setUp(self):
+ super(TestSecurityGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_groups_client.SecurityGroupsClient(
+ fake_auth, 'network', 'regionOne')
+
+ def _test_list_security_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_security_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SECURITY_GROUPS,
+ bytes_body,
+ mock_args='v2.0/security-groups')
+
+ def _test_create_security_group(self, bytes_body=False):
+ kwargs = {'name': 'fake-security-group-name'}
+ payload = json.dumps({"security_group": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(network_base.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.create_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SECURITY_GROUP,
+ bytes_body,
+ status=201,
+ mock_args=['v2.0/security-groups', payload],
+ **kwargs)
+
+ def _test_show_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SECURITY_GROUP,
+ bytes_body,
+ security_group_id=self.FAKE_SEC_GROUP_ID,
+ mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID)
+
+ def _test_update_security_group(self, bytes_body=False):
+ kwargs = {'name': 'updated-security-group-name'}
+ resp_body = copy.deepcopy(self.FAKE_SECURITY_GROUP)
+ resp_body["security_group"]["name"] = 'updated-security-group-name'
+
+ payload = json.dumps({'security_group': kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(network_base.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.update_security_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ resp_body,
+ bytes_body,
+ security_group_id=self.FAKE_SEC_GROUP_ID,
+ mock_args=['v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID,
+ payload],
+ **kwargs)
+
+ def test_list_security_groups_with_str_body(self):
+ self._test_list_security_groups()
+
+ def test_list_security_groups_with_bytes_body(self):
+ self._test_list_security_groups(bytes_body=True)
+
+ def test_create_security_group_with_str_body(self):
+ self._test_create_security_group()
+
+ def test_create_security_group_with_bytes_body(self):
+ self._test_create_security_group(bytes_body=True)
+
+ def test_show_security_group_with_str_body(self):
+ self._test_show_security_group()
+
+ def test_show_security_group_with_bytes_body(self):
+ self._test_show_security_group(bytes_body=True)
+
+ def test_update_security_group_with_str_body(self):
+ self._test_update_security_group()
+
+ def test_update_security_group_with_bytes_body(self):
+ self._test_update_security_group(bytes_body=True)
+
+ def test_delete_security_group(self):
+ self.check_service_client_function(
+ self.client.delete_security_group,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ security_group_id=self.FAKE_SEC_GROUP_ID,
+ mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID)
diff --git a/tempest/tests/lib/services/network/test_subnetpools_client.py b/tempest/tests/lib/services/network/test_subnetpools_client.py
new file mode 100644
index 0000000..3abb438
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_subnetpools_client.py
@@ -0,0 +1,163 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import subnetpools_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSubnetsClient(base.BaseServiceTest):
+
+ FAKE_SUBNETPOOLS = {
+ "subnetpools": [
+ {
+ "min_prefixlen": "64",
+ "address_scope_id": None,
+ "default_prefixlen": "64",
+ "id": "03f761e6-eee0-43fc-a921-8acf64c14988",
+ "max_prefixlen": "64",
+ "name": "my-subnet-pool-ipv6",
+ "default_quota": None,
+ "is_default": False,
+ "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+ "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+ "prefixes": [
+ "2001:db8:0:2::/64",
+ "2001:db8::/63"
+ ],
+ "ip_version": 6,
+ "shared": False,
+ "description": "",
+ "revision_number": 2
+ },
+ {
+ "min_prefixlen": "24",
+ "address_scope_id": None,
+ "default_prefixlen": "25",
+ "id": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
+ "max_prefixlen": "30",
+ "name": "my-subnet-pool-ipv4",
+ "default_quota": None,
+ "is_default": False,
+ "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+ "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+ "prefixes": [
+ "10.10.0.0/21",
+ "192.168.0.0/16"
+ ],
+ "ip_version": 4,
+ "shared": False,
+ "description": "",
+ "revision_number": 2
+ }
+ ]
+ }
+
+ FAKE_SUBNETPOOL_ID = "03f761e6-eee0-43fc-a921-8acf64c14988"
+
+ def setUp(self):
+ super(TestSubnetsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.subnetpools_client = subnetpools_client.SubnetpoolsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_subnetpools(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnetpools_client.list_subnetpools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SUBNETPOOLS,
+ bytes_body,
+ 200)
+
+ def _test_create_subnetpool(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnetpools_client.create_subnetpool,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'subnetpool': self.FAKE_SUBNETPOOLS['subnetpools'][1]},
+ bytes_body,
+ 201,
+ name="my-subnet-pool-ipv4",
+ prefixes=["192.168.0.0/16", "10.10.0.0/21"])
+
+ def _test_show_subnetpool(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnetpools_client.show_subnetpool,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'subnetpool': self.FAKE_SUBNETPOOLS['subnetpools'][0]},
+ bytes_body,
+ 200,
+ subnetpool_id=self.FAKE_SUBNETPOOL_ID)
+
+ def _test_update_subnetpool(self, bytes_body=False):
+ update_kwargs = {
+ "name": "my-new-subnetpool-name",
+ "prefixes": [
+ "2001:db8::/64",
+ "2001:db8:0:1::/64",
+ "2001:db8:0:2::/64"
+ ],
+ "min_prefixlen": 64,
+ "default_prefixlen": 64,
+ "max_prefixlen": 64
+ }
+
+ resp_body = {
+ 'subnetpool': copy.deepcopy(
+ self.FAKE_SUBNETPOOLS['subnetpools'][0])
+ }
+ resp_body['subnetpool'].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.subnetpools_client.update_subnetpool,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ resp_body,
+ bytes_body,
+ 200,
+ subnetpool_id=self.FAKE_SUBNETPOOL_ID,
+ **update_kwargs)
+
+ def test_list_subnetpools_with_str_body(self):
+ self._test_list_subnetpools()
+
+ def test_list_subnetpools_with_bytes_body(self):
+ self._test_list_subnetpools(bytes_body=True)
+
+ def test_create_subnetpool_with_str_body(self):
+ self._test_create_subnetpool()
+
+ def test_create_subnetpool_with_bytes_body(self):
+ self._test_create_subnetpool(bytes_body=True)
+
+ def test_show_subnetpool_with_str_body(self):
+ self._test_show_subnetpool()
+
+ def test_show_subnetpool_with_bytes_body(self):
+ self._test_show_subnetpool(bytes_body=True)
+
+ def test_update_subnet_with_str_body(self):
+ self._test_update_subnetpool()
+
+ def test_update_subnet_with_bytes_body(self):
+ self._test_update_subnetpool(bytes_body=True)
+
+ def test_delete_subnetpool(self):
+ self.check_service_client_function(
+ self.subnetpools_client.delete_subnetpool,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ subnetpool_id=self.FAKE_SUBNETPOOL_ID)
diff --git a/tempest/tests/lib/services/network/test_subnets_client.py b/tempest/tests/lib/services/network/test_subnets_client.py
new file mode 100644
index 0000000..0aadf54
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_subnets_client.py
@@ -0,0 +1,250 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import subnets_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSubnetsClient(base.BaseServiceTest):
+
+ FAKE_SUBNET = {
+ "subnet": {
+ "name": "",
+ "enable_dhcp": True,
+ "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "segment_id": None,
+ "project_id": "4fd44f30292945e481c7b8a0c8908869",
+ "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+ "dns_nameservers": [],
+ "allocation_pools": [
+ {
+ "start": "192.168.199.2",
+ "end": "192.168.199.254"
+ }
+ ],
+ "host_routes": [],
+ "ip_version": 4,
+ "gateway_ip": "192.168.199.1",
+ "cidr": "192.168.199.0/24",
+ "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126",
+ "created_at": "2016-10-10T14:35:47Z",
+ "description": "",
+ "ipv6_address_mode": None,
+ "ipv6_ra_mode": None,
+ "revision_number": 2,
+ "service_types": [],
+ "subnetpool_id": None,
+ "updated_at": "2016-10-10T14:35:47Z"
+ }
+ }
+
+ FAKE_UPDATED_SUBNET = {
+ "subnet": {
+ "name": "my_subnet",
+ "enable_dhcp": True,
+ "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+ "segment_id": None,
+ "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "dns_nameservers": [],
+ "allocation_pools": [
+ {
+ "start": "10.0.0.2",
+ "end": "10.0.0.254"
+ }
+ ],
+ "host_routes": [],
+ "ip_version": 4,
+ "gateway_ip": "10.0.0.1",
+ "cidr": "10.0.0.0/24",
+ "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+ "description": ""
+ }
+ }
+
+ FAKE_SUBNETS = {
+ "subnets": [
+ {
+ "name": "private-subnet",
+ "enable_dhcp": True,
+ "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+ "segment_id": None,
+ "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+ "dns_nameservers": [],
+ "allocation_pools": [
+ {
+ "start": "10.0.0.2",
+ "end": "10.0.0.254"
+ }
+ ],
+ "host_routes": [],
+ "ip_version": 4,
+ "gateway_ip": "10.0.0.1",
+ "cidr": "10.0.0.0/24",
+ "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+ "created_at": "2016-10-10T14:35:34Z",
+ "description": "",
+ "ipv6_address_mode": None,
+ "ipv6_ra_mode": None,
+ "revision_number": 2,
+ "service_types": [],
+ "subnetpool_id": None,
+ "updated_at": "2016-10-10T14:35:34Z"
+ },
+ {
+ "name": "my_subnet",
+ "enable_dhcp": True,
+ "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "segment_id": None,
+ "project_id": "4fd44f30292945e481c7b8a0c8908869",
+ "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+ "dns_nameservers": [],
+ "allocation_pools": [
+ {
+ "start": "192.0.0.2",
+ "end": "192.255.255.254"
+ }
+ ],
+ "host_routes": [],
+ "ip_version": 4,
+ "gateway_ip": "192.0.0.1",
+ "cidr": "192.0.0.0/8",
+ "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
+ "created_at": "2016-10-10T14:35:47Z",
+ "description": "",
+ "ipv6_address_mode": None,
+ "ipv6_ra_mode": None,
+ "revision_number": 2,
+ "service_types": [],
+ "subnetpool_id": None,
+ "updated_at": "2016-10-10T14:35:47Z"
+ }
+ ]
+ }
+
+ FAKE_BULK_SUBNETS = copy.deepcopy(FAKE_SUBNETS)
+
+ FAKE_SUBNET_ID = "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+
+ FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+
+ def setUp(self):
+ super(TestSubnetsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.subnets_client = subnets_client.SubnetsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_subnet(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnets_client.create_subnet,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SUBNET,
+ bytes_body,
+ 201,
+ network_id=self.FAKE_NETWORK_ID,
+ ip_version=4,
+ cidr="192.168.199.0/24")
+
+ def _test_update_subnet(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnets_client.update_subnet,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATED_SUBNET,
+ bytes_body,
+ 200,
+ subnet_id=self.FAKE_SUBNET_ID,
+ name="fake_updated_subnet_name")
+
+ def _test_show_subnet(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnets_client.show_subnet,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SUBNET,
+ bytes_body,
+ 200,
+ subnet_id=self.FAKE_SUBNET_ID)
+
+ def _test_list_subnets(self, bytes_body=False):
+ self.check_service_client_function(
+ self.subnets_client.list_subnets,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SUBNETS,
+ bytes_body,
+ 200)
+
+ def _test_create_bulk_subnets(self, bytes_body=False):
+ kwargs = {
+ "subnets": [
+ {
+ "cidr": "192.168.199.0/24",
+ "ip_version": 4,
+ "network_id": "e6031bc2-901a-4c66-82da-f4c32ed89406"
+ },
+ {
+ "cidr": "10.56.4.0/22",
+ "ip_version": 4,
+ "network_id": "64239a54-dcc4-4b39-920b-b37c2144effa"
+ }
+ ]
+ }
+ self.check_service_client_function(
+ self.subnets_client.create_bulk_subnets,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SUBNETS,
+ bytes_body,
+ 201,
+ **kwargs)
+
+ def test_create_subnet_with_str_body(self):
+ self._test_create_subnet()
+
+ def test_create_subnet_with_bytes_body(self):
+ self._test_create_subnet(bytes_body=True)
+
+ def test_update_subnet_with_str_body(self):
+ self._test_update_subnet()
+
+ def test_update_subnet_with_bytes_body(self):
+ self._test_update_subnet(bytes_body=True)
+
+ def test_show_subnet_with_str_body(self):
+ self._test_show_subnet()
+
+ def test_show_subnet_with_bytes_body(self):
+ self._test_show_subnet(bytes_body=True)
+
+ def test_list_subnets_with_str_body(self):
+ self._test_list_subnets()
+
+ def test_list_subnets_with_bytes_body(self):
+ self._test_list_subnets(bytes_body=True)
+
+ def test_delete_subnet(self):
+ self.check_service_client_function(
+ self.subnets_client.delete_subnet,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ subnet_id=self.FAKE_SUBNET_ID)
+
+ def test_create_bulk_subnets_with_str_body(self):
+ self._test_create_bulk_subnets()
+
+ def test_create_bulk_subnets_with_bytes_body(self):
+ self._test_create_bulk_subnets(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_tags_client.py b/tempest/tests/lib/services/network/test_tags_client.py
new file mode 100644
index 0000000..dbe50a0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_tags_client.py
@@ -0,0 +1,123 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.lib.services.network import tags_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTagsClient(base.BaseServiceTest):
+
+ FAKE_TAGS = {
+ "tags": [
+ "red",
+ "blue"
+ ]
+ }
+
+ FAKE_RESOURCE_TYPE = 'network'
+
+ FAKE_RESOURCE_ID = '7a8f904b-c1ed-4446-a87d-60440c02934b'
+
+ def setUp(self):
+ super(TestTagsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tags_client.TagsClient(
+ fake_auth, 'network', 'regionOne')
+
+ def _test_update_all_tags(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_TAGS,
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tags=self.FAKE_TAGS)
+
+ def _test_check_tag_existence(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_tag_existence,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {},
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=204)
+
+ def _test_create_tag(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_tag,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=201)
+
+ def _test_list_tags(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tags,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TAGS,
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID)
+
+ def test_update_all_tags_with_str_body(self):
+ self._test_update_all_tags()
+
+ def test_update_all_tags_with_bytes_body(self):
+ self._test_update_all_tags(bytes_body=True)
+
+ def test_delete_all_tags(self):
+ self.check_service_client_function(
+ self.client.delete_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ status=204)
+
+ def test_check_tag_existence_with_str_body(self):
+ self._test_check_tag_existence()
+
+ def test_check_tag_existence_with_bytes_body(self):
+ self._test_check_tag_existence(bytes_body=True)
+
+ def test_create_tag_with_str_body(self):
+ self._test_create_tag()
+
+ def test_create_tag_with_bytes_body(self):
+ self._test_create_tag(bytes_body=True)
+
+ def test_list_tags_with_str_body(self):
+ self._test_list_tags()
+
+ def test_list_tags_with_bytes_body(self):
+ self._test_list_tags(bytes_body=True)
+
+ def test_delete_tag(self):
+ self.check_service_client_function(
+ self.client.delete_tag,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=204)
diff --git a/tempest/api/orchestration/__init__.py b/tempest/tests/lib/services/object_storage/__init__.py
similarity index 100%
copy from tempest/api/orchestration/__init__.py
copy to tempest/tests/lib/services/object_storage/__init__.py
diff --git a/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py b/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py
new file mode 100644
index 0000000..08028c3
--- /dev/null
+++ b/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py
@@ -0,0 +1,66 @@
+# Copyright 2017 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.object_storage import bulk_middleware_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestBulkMiddlewareClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestBulkMiddlewareClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = bulk_middleware_client.BulkMiddlewareClient(
+ fake_auth, 'object-storage', 'regionOne')
+
+ def test_upload_archive(self):
+ url = 'test_path?extract-archive=tar'
+ data = 'test_data'
+ self.check_service_client_function(
+ self.client.upload_archive,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ mock_args=[url, data, {}],
+ resp_as_string=True,
+ upload_path='test_path', data=data, archive_file_format='tar')
+
+ def test_delete_bulk_data(self):
+ url = '?bulk-delete'
+ data = 'test_data'
+ self.check_service_client_function(
+ self.client.delete_bulk_data,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ mock_args=[url, {}, data],
+ resp_as_string=True,
+ data=data)
+
+ def _test_delete_bulk_data_with_post(self, status):
+ url = '?bulk-delete'
+ data = 'test_data'
+ self.check_service_client_function(
+ self.client.delete_bulk_data_with_post,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ mock_args=[url, data, {}],
+ resp_as_string=True,
+ status=status,
+ data=data)
+
+ def test_delete_bulk_data_with_post_200(self):
+ self._test_delete_bulk_data_with_post(200)
+
+ def test_delete_bulk_data_with_post_204(self):
+ self._test_delete_bulk_data_with_post(204)
diff --git a/tempest/tests/lib/services/object_storage/test_capabilities_client.py b/tempest/tests/lib/services/object_storage/test_capabilities_client.py
new file mode 100644
index 0000000..b7f972a
--- /dev/null
+++ b/tempest/tests/lib/services/object_storage/test_capabilities_client.py
@@ -0,0 +1,54 @@
+# Copyright 2016 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.lib.services.object_storage import capabilities_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestCapabilitiesClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestCapabilitiesClient, self).setUp()
+ self.fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.url = self.fake_auth.base_url(None)
+ self.client = capabilities_client.CapabilitiesClient(
+ self.fake_auth, 'swift', 'region1')
+
+ def _test_list_capabilities(self, bytes_body=False):
+ resp = {
+ "swift": {
+ "version": "1.11.0"
+ },
+ "slo": {
+ "max_manifest_segments": 1000,
+ "max_manifest_size": 2097152,
+ "min_segment_size": 1
+ },
+ "staticweb": {},
+ "tempurl": {}
+ }
+ self.check_service_client_function(
+ self.client.list_capabilities,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp,
+ bytes_body)
+
+ def test_list_capabilities_with_str_body(self):
+ self._test_list_capabilities()
+
+ def test_list_capabilities_with_bytes_body(self):
+ self._test_list_capabilities(True)
diff --git a/tempest/tests/lib/services/test_clients.py b/tempest/tests/lib/services/test_clients.py
index a837199..6d0f27a 100644
--- a/tempest/tests/lib/services/test_clients.py
+++ b/tempest/tests/lib/services/test_clients.py
@@ -16,6 +16,7 @@
import fixtures
import mock
+import six
import testtools
from tempest.lib import auth
@@ -258,6 +259,58 @@
clients.ServiceClients(creds, identity_uri=uri,
client_parameters=params)
+ def test___init___plugin_service_clients_cannot_load(self):
+ creds = fake_credentials.FakeKeystoneV3Credentials()
+ uri = 'fake_uri'
+ fake_service_clients = {
+ 'service1': [{'name': 'client1',
+ 'service_version': 'client1.v1',
+ 'module_path': 'I cannot load this',
+ 'client_names': ['SomeClient1']}],
+ 'service2': [{'name': 'client2',
+ 'service_version': 'client2.v1',
+ 'module_path': 'This neither',
+ 'client_names': ['SomeClient1']}]}
+ msg = "(?=.*{0})(?=.*{1})".format(
+ *[x[1][0]['module_path'] for x in six.iteritems(
+ fake_service_clients)])
+ self.useFixture(fixtures.MockPatchObject(
+ clients.ClientsRegistry(), 'get_service_clients',
+ return_value=fake_service_clients))
+ with testtools.ExpectedException(
+ testtools.MultipleExceptions, value_re=msg):
+ clients.ServiceClients(creds, identity_uri=uri)
+
+ def test___init___plugin_service_clients_name_conflict(self):
+ creds = fake_credentials.FakeKeystoneV3Credentials()
+ uri = 'fake_uri'
+ fake_service_clients = {
+ 'serviceA': [{'name': 'client1',
+ 'service_version': 'client1.v1',
+ 'module_path': 'fake_path_1',
+ 'client_names': ['SomeClient1']}],
+ 'serviceB': [{'name': 'client1',
+ 'service_version': 'client1.v2',
+ 'module_path': 'fake_path_2',
+ 'client_names': ['SomeClient2']}],
+ 'serviceC': [{'name': 'client1',
+ 'service_version': 'client1.v1',
+ 'module_path': 'fake_path_2',
+ 'client_names': ['SomeClient1']}],
+ 'serviceD': [{'name': 'client1',
+ 'service_version': 'client1.v2',
+ 'module_path': 'fake_path_2',
+ 'client_names': ['SomeClient2']}]}
+ msg = "(?=.*{0})(?=.*{1})".format(
+ *[x[1][0]['service_version'] for x in six.iteritems(
+ fake_service_clients)])
+ self.useFixture(fixtures.MockPatchObject(
+ clients.ClientsRegistry(), 'get_service_clients',
+ return_value=fake_service_clients))
+ with testtools.ExpectedException(
+ testtools.MultipleExceptions, value_re=msg):
+ clients.ServiceClients(creds, identity_uri=uri)
+
def _get_manager(self, init_region='fake_region'):
# Get a manager to invoke _setup_parameters on
creds = fake_credentials.FakeKeystoneV2Credentials()
diff --git a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py b/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
new file mode 100644
index 0000000..770565c
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
@@ -0,0 +1,51 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v2 import availability_zone_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestAvailabilityZoneClient(base.BaseServiceTest):
+
+ FAKE_AZ_LIST = {
+ "availabilityZoneInfo": [
+ {
+ "zoneState": {
+ "available": True
+ },
+ "zoneName": "nova"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestAvailabilityZoneClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = availability_zone_client.AvailabilityZoneClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_list_availability_zones(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_availability_zones,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_AZ_LIST,
+ bytes_body)
+
+ def test_list_availability_zones_with_str_body(self):
+ self._test_list_availability_zones()
+
+ def test_list_availability_zones_with_bytes_body(self):
+ self._test_list_availability_zones(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_backups_client.py b/tempest/tests/lib/services/volume/v2/test_backups_client.py
new file mode 100644
index 0000000..14e5fb0
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_backups_client.py
@@ -0,0 +1,117 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v2 import backups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestBackupsClient(base.BaseServiceTest):
+
+ FAKE_BACKUP_LIST = {
+ "backups": [
+ {
+ "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+ "links": [
+ {
+ "href": "fake-url-1",
+ "rel": "self"
+ },
+ {
+ "href": "fake-url-2",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "backup001"
+ }
+ ]
+ }
+
+ FAKE_BACKUP_LIST_WITH_DETAIL = {
+ "backups": [
+ {
+ "availability_zone": "az1",
+ "container": "volumebackups",
+ "created_at": "2013-04-02T10:35:27.000000",
+ "description": None,
+ "fail_reason": None,
+ "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+ "links": [
+ {
+ "href": "fake-url-1",
+ "rel": "self"
+ },
+ {
+ "href": "fake-url-2",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "backup001",
+ "object_count": 22,
+ "size": 1,
+ "status": "available",
+ "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
+ "is_incremental": True,
+ "has_dependent_backups": False
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestBackupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = backups_client.BackupsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_list_backups(self, detail=False, mock_args='backups',
+ bytes_body=False, **params):
+ if detail:
+ resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
+ else:
+ resp_body = self.FAKE_BACKUP_LIST
+ self.check_service_client_function(
+ self.client.list_backups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ to_utf=bytes_body,
+ mock_args=[mock_args],
+ detail=detail,
+ **params)
+
+ def test_list_backups_with_str_body(self):
+ self._test_list_backups()
+
+ def test_list_backups_with_bytes_body(self):
+ self._test_list_backups(bytes_body=True)
+
+ def test_list_backups_with_detail_with_str_body(self):
+ mock_args = "backups/detail"
+ self._test_list_backups(detail=True, mock_args=mock_args)
+
+ def test_list_backups_with_detail_with_bytes_body(self):
+ mock_args = "backups/detail"
+ self._test_list_backups(detail=True, mock_args=mock_args,
+ bytes_body=True)
+
+ def test_list_backups_with_params(self):
+ # Run the test separately for each param, to avoid assertion error
+ # resulting from randomized params order.
+ mock_args = 'backups?sort_key=name'
+ self._test_list_backups(mock_args=mock_args, sort_key='name')
+
+ mock_args = 'backups/detail?limit=10'
+ self._test_list_backups(detail=True, mock_args=mock_args,
+ bytes_body=True, limit=10)
diff --git a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py b/tempest/tests/lib/services/volume/v2/test_capabilities_client.py
new file mode 100644
index 0000000..3d3f1e1
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_capabilities_client.py
@@ -0,0 +1,77 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v2 import capabilities_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestCapabilitiesClient(base.BaseServiceTest):
+
+ FAKE_BACKEND_CAPABILITIES = {
+ "namespace": "OS::Storage::Capabilities::fake",
+ "vendor_name": "OpenStack",
+ "volume_backend_name": "lvmdriver-1",
+ "pool_name": "pool",
+ "driver_version": "2.0.0",
+ "storage_protocol": "iSCSI",
+ "display_name": "Capabilities of Cinder LVM driver",
+ "description": (
+ "These are volume type options provided by Cinder LVM driver."),
+ "visibility": "public",
+ "replication_targets": [],
+ "properties": {
+ "compression": {
+ "title": "Compression",
+ "description": "Enables compression.",
+ "type": "boolean"
+ },
+ "qos": {
+ "title": "QoS",
+ "description": "Enables QoS.",
+ "type": "boolean"
+ },
+ "replication": {
+ "title": "Replication",
+ "description": "Enables replication.",
+ "type": "boolean"
+ },
+ "thin_provisioning": {
+ "title": "Thin Provisioning",
+ "description": "Sets thin provisioning.",
+ "type": "boolean"
+ }
+ }
+ }
+
+ def setUp(self):
+ super(TestCapabilitiesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = capabilities_client.CapabilitiesClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_show_backend_capabilities(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_backend_capabilities,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_BACKEND_CAPABILITIES,
+ bytes_body,
+ host='lvmdriver-1')
+
+ def test_show_backend_capabilities_with_str_body(self):
+ self._test_show_backend_capabilities()
+
+ def test_show_backend_capabilities_with_bytes_body(self):
+ self._test_show_backend_capabilities(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_extensions_client.py b/tempest/tests/lib/services/volume/v2/test_extensions_client.py
new file mode 100644
index 0000000..c0ee421
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_extensions_client.py
@@ -0,0 +1,70 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v2 import extensions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestExtensionsClient(base.BaseServiceTest):
+
+ FAKE_EXTENSION_LIST = {
+ "extensions": [
+ {
+ "updated": "2012-03-12T00:00:00+00:00",
+ "name": "QuotaClasses",
+ "links": [],
+ "namespace": "fake-namespace-1",
+ "alias": "os-quota-class-sets",
+ "description": "Quota classes management support."
+ },
+ {
+ "updated": "2013-05-29T00:00:00+00:00",
+ "name": "VolumeTransfer",
+ "links": [],
+ "namespace": "fake-namespace-2",
+ "alias": "os-volume-transfer",
+ "description": "Volume transfer management support."
+ },
+ {
+ "updated": "2014-02-10T00:00:00+00:00",
+ "name": "VolumeManage",
+ "links": [],
+ "namespace": "fake-namespace-3",
+ "alias": "os-volume-manage",
+ "description": "Manage existing backend storage by Cinder."
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestExtensionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = extensions_client.ExtensionsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_list_extensions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_extensions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_EXTENSION_LIST,
+ bytes_body)
+
+ def test_list_extensions_with_str_body(self):
+ self._test_list_extensions()
+
+ def test_list_extensions_with_bytes_body(self):
+ self._test_list_extensions(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_limits_client.py b/tempest/tests/lib/services/volume/v2/test_limits_client.py
new file mode 100644
index 0000000..202054c
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_limits_client.py
@@ -0,0 +1,59 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v2 import limits_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestLimitsClient(base.BaseServiceTest):
+
+ FAKE_LIMIT_INFO = {
+ "limits": {
+ "rate": [],
+ "absolute": {
+ "totalSnapshotsUsed": 0,
+ "maxTotalBackups": 10,
+ "maxTotalVolumeGigabytes": 1000,
+ "maxTotalSnapshots": 10,
+ "maxTotalBackupGigabytes": 1000,
+ "totalBackupGigabytesUsed": 0,
+ "maxTotalVolumes": 10,
+ "totalVolumesUsed": 0,
+ "totalBackupsUsed": 0,
+ "totalGigabytesUsed": 0
+ }
+ }
+ }
+
+ def setUp(self):
+ super(TestLimitsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = limits_client.LimitsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_show_limits(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_limits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIMIT_INFO,
+ bytes_body)
+
+ def test_show_limits_with_str_body(self):
+ self._test_show_limits()
+
+ def test_show_limits_with_bytes_body(self):
+ self._test_show_limits(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
new file mode 100644
index 0000000..8a5f25f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
@@ -0,0 +1,83 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v2 import scheduler_stats_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSchedulerStatsClient(base.BaseServiceTest):
+ FAKE_POOLS_LIST = {
+ "pools": [
+ {
+ "name": "pool1",
+ "capabilities": {
+ "updated": "2014-10-28T00:00:00-00:00",
+ "total_capacity": 1024,
+ "free_capacity": 100,
+ "volume_backend_name": "pool1",
+ "reserved_percentage": 0,
+ "driver_version": "1.0.0",
+ "storage_protocol": "iSCSI",
+ "QoS_support": False
+ }
+ },
+ {
+ "name": "pool2",
+ "capabilities": {
+ "updated": "2014-10-28T00:00:00-00:00",
+ "total_capacity": 512,
+ "free_capacity": 200,
+ "volume_backend_name": "pool2",
+ "reserved_percentage": 0,
+ "driver_version": "1.0.2",
+ "storage_protocol": "iSER",
+ "QoS_support": True
+ }
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestSchedulerStatsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = scheduler_stats_client.SchedulerStatsClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_list_pools(self, bytes_body=False, detail=False):
+ resp_body = []
+ if detail:
+ resp_body = self.FAKE_POOLS_LIST
+ else:
+ resp_body = {'pools': [{'name': pool['name']}
+ for pool in self.FAKE_POOLS_LIST['pools']]}
+ self.check_service_client_function(
+ self.client.list_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ bytes_body,
+ detail=detail)
+
+ def test_list_pools_with_str_body(self):
+ self._test_list_pools()
+
+ def test_list_pools_with_str_body_and_detail(self):
+ self._test_list_pools(detail=True)
+
+ def test_list_pools_with_bytes_body(self):
+ self._test_list_pools(bytes_body=True)
+
+ def test_list_pools_with_bytes_body_and_detail(self):
+ self._test_list_pools(bytes_body=True, detail=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
new file mode 100644
index 0000000..3fe8970
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
@@ -0,0 +1,83 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 mock
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.volume.v2 import snapshot_manage_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSnapshotManageClient(base.BaseServiceTest):
+
+ SNAPSHOT_MANAGE_REQUEST = {
+ "snapshot": {
+ "description": "snapshot-manage-description",
+ "metadata": None,
+ "ref": {
+ "source-name": "_snapshot-22b71da0-94f9-4aca-ad45-7522b3fa96bb"
+ },
+ "name": "snapshot-managed",
+ "volume_id": "7c064b34-1e4b-40bd-93ca-4ac5a973661b"
+ }
+ }
+
+ SNAPSHOT_MANAGE_RESPONSE = {
+ "snapshot": {
+ "status": "creating",
+ "description": "snapshot-manage-description",
+ "updated_at": None,
+ "volume_id": "32bafcc8-7109-42cd-8342-70d8de2bedef",
+ "id": "8fd6eb9d-0a82-456d-b1ec-dea4ac7f1ee2",
+ "size": 1,
+ "name": "snapshot-managed",
+ "created_at": "2017-07-11T10:07:58.000000",
+ "metadata": {}
+ }
+ }
+
+ def setUp(self):
+ super(TestSnapshotManageClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = snapshot_manage_client.SnapshotManageClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_manage_snapshot(self, bytes_body=False):
+ payload = json.dumps(self.SNAPSHOT_MANAGE_REQUEST, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(snapshot_manage_client.json,
+ 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.manage_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.SNAPSHOT_MANAGE_RESPONSE,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-snapshot-manage', payload],
+ **self.SNAPSHOT_MANAGE_REQUEST['snapshot'])
+
+ def test_manage_snapshot_with_str_body(self):
+ self._test_manage_snapshot()
+
+ def test_manage_snapshot_with_bytes_body(self):
+ self._test_manage_snapshot(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py b/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
index 7d656f1..c9f57a0 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
@@ -72,6 +72,12 @@
]
}
+ FAKE_SNAPSHOT_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
def setUp(self):
super(TestSnapshotsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -142,6 +148,15 @@
self.FAKE_INFO_SNAPSHOT,
bytes_body, volume_type_id="cbc36478b0bd8e67e89")
+ def _test_show_snapshot_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_snapshot_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SNAPSHOT_METADATA_ITEM,
+ bytes_body,
+ snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+ id="key1")
+
def test_create_snapshot_with_str_body(self):
self._test_create_snapshot()
@@ -184,6 +199,12 @@
def test_update_snapshot_metadata_with_bytes_body(self):
self._test_update_snapshot_metadata(bytes_body=True)
+ def test_show_snapshot_metadata_item_with_str_body(self):
+ self._test_show_snapshot_metadata_item()
+
+ def test_show_snapshot_metadata_item_with_bytes_body(self):
+ self._test_show_snapshot_metadata_item(bytes_body=True)
+
def test_force_delete_snapshot(self):
self.check_service_client_function(
self.client.force_delete_snapshot,
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
index 0c59bf2..84f4992 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
@@ -13,6 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
+import mock
+from oslo_serialization import jsonutils as json
+
from tempest.lib.services.volume.v2 import transfers_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -20,11 +25,14 @@
class TestTransfersClient(base.BaseServiceTest):
- FAKE_LIST_VOLUME_TRANSFERS_WITH_DETAIL = {
- "transfers": [{
- "created_at": "2017-04-18T09:10:03.000000",
+ FAKE_VOLUME_TRANSFER_ID = "0e89cdd1-6249-421b-96d8-25fac0623d42"
+
+ FAKE_VOLUME_TRANSFER_INFO = {
+ "transfer": {
+ "id": FAKE_VOLUME_TRANSFER_ID,
+ "name": "fake-volume-transfer",
"volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747",
- "id": "0e89cdd1-6249-421b-96d8-25fac0623d42",
+ "created_at": "2017-04-18T09:10:03.000000",
"links": [
{
"href": "fake-url-1",
@@ -34,9 +42,8 @@
"href": "fake-url-2",
"rel": "bookmark"
}
- ],
- "name": "fake-volume-transfer"
- }]
+ ]
+ }
}
def setUp(self):
@@ -46,16 +53,106 @@
'volume',
'regionOne')
- def _test_list_volume_transfers_with_detail(self, bytes_body=False):
+ def _test_create_volume_transfer(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ resp_body['transfer'].update({"auth_key": "fake-auth-key"})
+ kwargs = {"name": "fake-volume-transfer",
+ "volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747"}
+ payload = json.dumps({"transfer": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.create_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ resp_body,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-volume-transfer', payload],
+ **kwargs)
+
+ def _test_accept_volume_transfer(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ resp_body['transfer'].pop('created_at')
+ kwargs = {"auth_key": "fake-auth-key"}
+ payload = json.dumps({"accept": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.accept_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ resp_body,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-volume-transfer/%s/accept' %
+ self.FAKE_VOLUME_TRANSFER_ID, payload],
+ transfer_id=self.FAKE_VOLUME_TRANSFER_ID,
+ **kwargs)
+
+ def _test_show_volume_transfer(self, bytes_body=False):
+ resp_body = self.FAKE_VOLUME_TRANSFER_INFO
+ self.check_service_client_function(
+ self.client.show_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ to_utf=bytes_body,
+ transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42")
+
+ def _test_list_volume_transfers(self, detail=False, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ if not detail:
+ resp_body['transfer'].pop('created_at')
+ resp_body = {"transfers": [resp_body['transfer']]}
self.check_service_client_function(
self.client.list_volume_transfers,
'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_LIST_VOLUME_TRANSFERS_WITH_DETAIL,
- bytes_body,
- detail=True)
+ resp_body,
+ to_utf=bytes_body,
+ detail=detail)
+
+ def test_create_volume_transfer_with_str_body(self):
+ self._test_create_volume_transfer()
+
+ def test_create_volume_transfer_with_bytes_body(self):
+ self._test_create_volume_transfer(bytes_body=True)
+
+ def test_accept_volume_transfer_with_str_body(self):
+ self._test_accept_volume_transfer()
+
+ def test_accept_volume_transfer_with_bytes_body(self):
+ self._test_accept_volume_transfer(bytes_body=True)
+
+ def test_show_volume_transfer_with_str_body(self):
+ self._test_show_volume_transfer()
+
+ def test_show_volume_transfer_with_bytes_body(self):
+ self._test_show_volume_transfer(bytes_body=True)
+
+ def test_list_volume_transfers_with_str_body(self):
+ self._test_list_volume_transfers()
+
+ def test_list_volume_transfers_with_bytes_body(self):
+ self._test_list_volume_transfers(bytes_body=True)
def test_list_volume_transfers_with_detail_with_str_body(self):
- self._test_list_volume_transfers_with_detail()
+ self._test_list_volume_transfers(detail=True)
def test_list_volume_transfers_with_detail_with_bytes_body(self):
- self._test_list_volume_transfers_with_detail(bytes_body=True)
+ self._test_list_volume_transfers(detail=True, bytes_body=True)
+
+ def test_delete_volume_transfer(self):
+ self.check_service_client_function(
+ self.client.delete_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=202,
+ transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42")
diff --git a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
new file mode 100644
index 0000000..ea4a9f9
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
@@ -0,0 +1,111 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 mock
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.volume.v2 import volume_manage_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumeManageClient(base.BaseServiceTest):
+
+ VOLUME_MANAGE_REQUEST = {
+ "volume": {
+ "host": "controller1@rbd#rbd",
+ "name": "volume-managed",
+ "availability_zone": "nova",
+ "bootable": False,
+ "metadata": None,
+ "ref": {
+ "source-name": "volume-2ce6ca46-e6c1-4fe5-8268-3a1c536fcbf3"
+ },
+ "volume_type": None,
+ "description": "volume-manage-description"
+ }
+ }
+
+ VOLUME_MANAGE_RESPONSE = {
+ "volume": {
+ "migration_status": None,
+ "attachments": [],
+ "links": [
+ {
+ "href": "fake-url-1",
+ "rel": "self"
+ },
+ {
+ "href": "fake-url-2",
+ "rel": "bookmark"
+ }
+ ],
+ "availability_zone": "nova",
+ "os-vol-host-attr:host": "controller1@rbd#rbd",
+ "encrypted": False,
+ "updated_at": None,
+ "replication_status": None,
+ "snapshot_id": None,
+ "id": "c07cd4a4-b52b-4511-a176-fbaa2011a227",
+ "size": 0,
+ "user_id": "142d8663efce464c89811c63e45bd82e",
+ "os-vol-tenant-attr:tenant_id": "f21a9c86d7114bf99c711f4874d80474",
+ "os-vol-mig-status-attr:migstat": None,
+ "metadata": {},
+ "status": "creating",
+ "description": "volume-manage-description",
+ "multiattach": False,
+ "source_volid": None,
+ "consistencygroup_id": None,
+ "os-vol-mig-status-attr:name_id": None,
+ "name": "volume-managed",
+ "bootable": "false",
+ "created_at": "2017-07-11T09:14:01.000000",
+ "volume_type": None
+ }
+ }
+
+ def setUp(self):
+ super(TestVolumeManageClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volume_manage_client.VolumeManageClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_manage_volume(self, bytes_body=False):
+ payload = json.dumps(self.VOLUME_MANAGE_REQUEST, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(volume_manage_client.json,
+ 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.manage_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.VOLUME_MANAGE_RESPONSE,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-volume-manage', payload],
+ **self.VOLUME_MANAGE_REQUEST['volume'])
+
+ def test_manage_volume_with_str_body(self):
+ self._test_manage_volume()
+
+ def test_manage_volume_with_bytes_body(self):
+ self._test_manage_volume(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
index 498b963..d7b042e 100644
--- a/tempest/tests/lib/services/volume/v2/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_serialization import jsonutils as json
+
from tempest.lib.services.volume.v2 import volumes_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -20,6 +22,25 @@
class TestVolumesClient(base.BaseServiceTest):
+ FAKE_VOLUME_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
+ FAKE_VOLUME_IMAGE_METADATA = {
+ "metadata": {
+ "container_format": "bare",
+ "min_ram": "0",
+ "disk_format": "raw",
+ "image_name": "xly-ubuntu16-server",
+ "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
+ "checksum": "008f5d22fe3cb825d714da79607a90f9",
+ "min_disk": "0",
+ "size": "8589934592"
+ }
+ }
+
def setUp(self):
super(TestVolumesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -27,6 +48,22 @@
'volume',
'regionOne')
+ def _test_retype_volume(self, bytes_body=False):
+ kwargs = {
+ "new_type": "dedup-tier-replication",
+ "migration_policy": "never"
+ }
+
+ self.check_service_client_function(
+ self.client.retype_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ to_utf=bytes_body,
+ status=202,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ **kwargs
+ )
+
def _test_force_detach_volume(self, bytes_body=False):
kwargs = {
'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
@@ -45,8 +82,46 @@
**kwargs
)
+ def _test_show_volume_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_METADATA_ITEM,
+ to_utf=bytes_body,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ id="key1")
+
+ def _test_show_volume_image_metadata(self, bytes_body=False):
+ fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
+ self.check_service_client_function(
+ self.client.show_volume_image_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_VOLUME_IMAGE_METADATA,
+ to_utf=bytes_body,
+ mock_args=['volumes/%s/action' % fake_volume_id,
+ json.dumps({"os-show_image_metadata": {}})],
+ volume_id=fake_volume_id)
+
def test_force_detach_volume_with_str_body(self):
self._test_force_detach_volume()
def test_force_detach_volume_with_bytes_body(self):
self._test_force_detach_volume(bytes_body=True)
+
+ def test_show_volume_metadata_item_with_str_body(self):
+ self._test_show_volume_metadata_item()
+
+ def test_show_volume_metadata_item_with_bytes_body(self):
+ self._test_show_volume_metadata_item(bytes_body=True)
+
+ def test_show_volume_image_metadata_with_str_body(self):
+ self._test_show_volume_image_metadata()
+
+ def test_show_volume_image_metadata_with_bytes_body(self):
+ self._test_show_volume_image_metadata(bytes_body=True)
+
+ def test_retype_volume_with_str_body(self):
+ self._test_retype_volume()
+
+ def test_retype_volume_with_bytes_body(self):
+ self._test_retype_volume(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
new file mode 100644
index 0000000..f1ce987
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -0,0 +1,50 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v3 import backups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestBackupsClient(base.BaseServiceTest):
+
+ FAKE_BACKUP_UPDATE = {
+ "backup": {
+ "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7",
+ "name": "fake-backup-name",
+ "links": "fake-links"
+ }
+ }
+
+ def setUp(self):
+ super(TestBackupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = backups_client.BackupsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_update_backup(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_backup,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_BACKUP_UPDATE,
+ bytes_body,
+ backup_id='4c65c15f-a5c5-464b-b92a-90e4c04636a7')
+
+ def test_update_backup_with_str_body(self):
+ self._test_update_backup()
+
+ def test_update_backup_with_bytes_body(self):
+ self._test_update_backup(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py b/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py
new file mode 100644
index 0000000..5ac5c08
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py
@@ -0,0 +1,141 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v3 import group_snapshots_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestGroupSnapshotsClient(base.BaseServiceTest):
+ FAKE_CREATE_GROUP_SNAPSHOT = {
+ "group_snapshot": {
+ "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ "name": "group-snapshot-001",
+ "description": "Test group snapshot 1"
+ }
+ }
+
+ FAKE_INFO_GROUP_SNAPSHOT = {
+ "group_snapshot": {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ "name": "group-snapshot-001",
+ "description": "Test group snapshot 1",
+ "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "status": "available",
+ "created_at": "20127-06-20T03:50:07Z"
+ }
+ }
+
+ FAKE_LIST_GROUP_SNAPSHOTS = {
+ "group_snapshots": [
+ {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ "name": "group-snapshot-001",
+ "description": "Test group snapshot 1",
+ "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "status": "available",
+ "created_at": "2017-06-20T03:50:07Z",
+ },
+ {
+ "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
+ "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ "name": "group-snapshot-002",
+ "description": "Test group snapshot 2",
+ "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "status": "available",
+ "created_at": "2017-06-19T01:52:47Z",
+ },
+ {
+ "id": "c5c4769e-213c-40a6-a568-8e797bb691d4",
+ "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ "name": "group-snapshot-003",
+ "description": "Test group snapshot 3",
+ "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "status": "available",
+ "created_at": "2017-06-18T06:34:32Z",
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestGroupSnapshotsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = group_snapshots_client.GroupSnapshotsClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_create_group_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP_SNAPSHOT,
+ bytes_body,
+ group_id="49c8c114-0d68-4e89-b8bc-3f5a674d54be",
+ status=202)
+
+ def _test_show_group_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_group_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_INFO_GROUP_SNAPSHOT,
+ bytes_body,
+ group_snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
+
+ def _test_list_group_snapshots(self, bytes_body=False, detail=False):
+ resp_body = []
+ if detail:
+ resp_body = self.FAKE_LIST_GROUP_SNAPSHOTS
+ else:
+ resp_body = {
+ 'group_snapshots': [{
+ 'id': group_snapshot['id'],
+ 'name': group_snapshot['name'],
+ 'group_type_id': group_snapshot['group_type_id']}
+ for group_snapshot in
+ self.FAKE_LIST_GROUP_SNAPSHOTS['group_snapshots']
+ ]
+ }
+ self.check_service_client_function(
+ self.client.list_group_snapshots,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ bytes_body,
+ detail=detail)
+
+ def test_create_group_snapshot_with_str_body(self):
+ self._test_create_group_snapshot()
+
+ def test_create_group_snapshot_with_bytes_body(self):
+ self._test_create_group_snapshot(bytes_body=True)
+
+ def test_show_group_snapshot_with_str_body(self):
+ self._test_show_group_snapshot()
+
+ def test_show_group_snapshot_with_bytes_body(self):
+ self._test_show_group_snapshot(bytes_body=True)
+
+ def test_list_group_snapshots_with_str_body(self):
+ self._test_list_group_snapshots()
+
+ def test_list_group_snapshots_with_bytes_body(self):
+ self._test_list_group_snapshots(bytes_body=True)
+
+ def test_delete_group_snapshot(self):
+ self.check_service_client_function(
+ self.client.delete_group_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ group_snapshot_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
+ status=202)
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
new file mode 100644
index 0000000..0f456a2
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v3 import group_types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestGroupTypesClient(base.BaseServiceTest):
+ FAKE_CREATE_GROUP_TYPE = {
+ "group_type": {
+ "name": "group-type-001",
+ "description": "Test group type 1",
+ "group_specs": {},
+ "is_public": True,
+ }
+ }
+
+ FAKE_INFO_GROUP_TYPE = {
+ "group_type": {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "name": "group-type-001",
+ "description": "Test group type 1",
+ "is_public": True,
+ "created_at": "20127-06-20T03:50:07Z",
+ "group_specs": {},
+ }
+ }
+
+ FAKE_LIST_GROUP_TYPES = {
+ "group_types": [
+ {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "name": "group-type-001",
+ "description": "Test group type 1",
+ "is_public": True,
+ "created_at": "2017-06-20T03:50:07Z",
+ "group_specs": {},
+ },
+ {
+ "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
+ "name": "group-type-002",
+ "description": "Test group type 2",
+ "is_public": True,
+ "created_at": "2017-06-19T01:52:47Z",
+ "group_specs": {},
+ },
+ {
+ "id": "c5c4769e-213c-40a6-a568-8e797bb691d4",
+ "name": "group-type-003",
+ "description": "Test group type 3",
+ "is_public": True,
+ "created_at": "2017-06-18T06:34:32Z",
+ "group_specs": {},
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestGroupTypesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = group_types_client.GroupTypesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_create_group_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group_type,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP_TYPE,
+ bytes_body,
+ status=202)
+
+ def _test_show_group_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_group_type,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_INFO_GROUP_TYPE,
+ bytes_body,
+ group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
+
+ def _test_list_group_types(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_group_types,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_GROUP_TYPES,
+ bytes_body)
+
+ def test_create_group_type_with_str_body(self):
+ self._test_create_group_type()
+
+ def test_create_group_type_with_bytes_body(self):
+ self._test_create_group_type(bytes_body=True)
+
+ def test_delete_group_type(self):
+ self.check_service_client_function(
+ self.client.delete_group_type,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b',
+ status=202)
+
+ def test_show_group_type_with_str_body(self):
+ self._test_show_group_type()
+
+ def test_show_group_type_with_bytes_body(self):
+ self._test_show_group_type(bytes_body=True)
+
+ def test_list_group_types_with_str_body(self):
+ self._test_list_group_types()
+
+ def test_list_group_types_with_bytes_body(self):
+ self._test_list_group_types(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_groups_client.py b/tempest/tests/lib/services/volume/v3/test_groups_client.py
new file mode 100644
index 0000000..0884e5a
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_groups_client.py
@@ -0,0 +1,186 @@
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v3 import groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestGroupsClient(base.BaseServiceTest):
+ FAKE_CREATE_GROUP = {
+ "group": {
+ "name": "group-001",
+ "description": "Test group 1",
+ "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
+ "availability_zone": "az1",
+ }
+ }
+
+ FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT = {
+ "create-from-src": {
+ "name": "group-002",
+ "description": "Test group 2",
+ "group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c",
+ }
+ }
+
+ FAKE_CREATE_GROUP_FROM_GROUP = {
+ "create-from-src": {
+ "name": "group-003",
+ "description": "Test group 3",
+ "source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e",
+ }
+ }
+
+ FAKE_UPDATE_GROUP = {
+ "group": {
+ "name": "new-group",
+ "description": "New test group",
+ "add_volumes": "27d45037-ade3-4a87-b729-dba3293c06f3,"
+ "6e7cd916-d961-41cc-b3bd-0601ca0c701f",
+ "remove_volumes": "4d580519-6467-448e-95e9-5b25c94d83c7,"
+ "ea22464c-f095-4a87-a31f-c5d34e0c6fc9"
+ }
+ }
+
+ FAKE_INFO_GROUP = {
+ "group": {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "name": "group-001",
+ "description": "Test group 1",
+ "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
+ "status": "available",
+ "availability_zone": "az1",
+ "created_at": "20127-06-20T03:50:07Z"
+ }
+ }
+
+ FAKE_LIST_GROUPS = {
+ "groups": [
+ {
+ "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
+ "name": "group-001",
+ "description": "Test group 1",
+ "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
+ "status": "available",
+ "availability_zone": "az1",
+ "created_at": "2017-06-20T03:50:07Z",
+ },
+ {
+ "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
+ "name": "group-002",
+ "description": "Test group 2",
+ "group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c",
+ "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
+ "status": "available",
+ "availability_zone": "az1",
+ "created_at": "2017-06-19T01:52:47Z",
+ },
+ {
+ "id": "c5c4769e-213c-40a6-a568-8e797bb691d4",
+ "name": "group-003",
+ "description": "Test group 3",
+ "source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e",
+ "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
+ "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
+ "status": "available",
+ "availability_zone": "az1",
+ "created_at": "2017-06-18T06:34:32Z",
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = groups_client.GroupsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_create_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP,
+ bytes_body,
+ status=202)
+
+ def _test_show_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_INFO_GROUP,
+ bytes_body,
+ group_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
+
+ def _test_list_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_GROUPS,
+ bytes_body,
+ detail=True)
+
+ def test_create_group_with_str_body(self):
+ self._test_create_group()
+
+ def test_create_group_with_bytes_body(self):
+ self._test_create_group(bytes_body=True)
+
+ def test_show_group_with_str_body(self):
+ self._test_show_group()
+
+ def test_show_group_with_bytes_body(self):
+ self._test_show_group(bytes_body=True)
+
+ def test_list_groups_with_str_body(self):
+ self._test_list_groups()
+
+ def test_list_groups_with_bytes_body(self):
+ self._test_list_groups(bytes_body=True)
+
+ def test_delete_group(self):
+ self.check_service_client_function(
+ self.client.delete_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
+ status=202)
+
+ def test_create_group_from_group_snapshot(self):
+ self.check_service_client_function(
+ self.client.create_group_from_source,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT,
+ status=202)
+
+ def test_create_group_from_group(self):
+ self.check_service_client_function(
+ self.client.create_group_from_source,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP_FROM_GROUP,
+ status=202)
+
+ def test_update_group(self):
+ self.check_service_client_function(
+ self.client.update_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
+ status=202,
+ **self.FAKE_UPDATE_GROUP['group'])
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
new file mode 100644
index 0000000..a515fd3
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -0,0 +1,48 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v3 import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumesClient(base.BaseServiceTest):
+
+ FAKE_VOLUME_SUMMARY = {
+ "volume-summary": {
+ "total_size": 20,
+ "total_count": 5
+ }
+ }
+
+ def setUp(self):
+ super(TestVolumesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volumes_client.VolumesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_show_volume_summary(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_summary,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_SUMMARY,
+ bytes_body)
+
+ def test_show_volume_summary_with_str_body(self):
+ self._test_show_volume_summary()
+
+ def test_show_volume_summary_with_bytes_body(self):
+ self._test_show_volume_summary(bytes_body=True)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index ac13a13..c3a792f 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -16,7 +16,7 @@
import copy
import datetime
-from oslotest import mockpatch
+import fixtures
import testtools
from tempest.lib import auth
@@ -82,9 +82,9 @@
def test_auth_data_property_when_cache_exists(self):
self.auth_provider.cache = 'foo'
- self.useFixture(mockpatch.PatchObject(self.auth_provider,
- 'is_expired',
- return_value=False))
+ self.useFixture(fixtures.MockPatchObject(self.auth_provider,
+ 'is_expired',
+ return_value=False))
self.assertEqual('foo', getattr(self.auth_provider, 'auth_data'))
def test_delete_auth_data_property_through_deleter(self):
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index 01b8a72..6c6f612 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -16,8 +16,8 @@
from tempest import clients
from tempest.common import credentials_factory as credentials
-from tempest.common import fixed_network
from tempest import config
+from tempest.lib.common import fixed_network
from tempest import test
from tempest.tests import base
from tempest.tests import fake_config
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 8b6472b..2fc84dc 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
from oslo_config import cfg
-from oslotest import mockpatch
import testtools
from tempest import config
@@ -95,8 +95,8 @@
'bad_service')
def test_services_decorator_with_service_valid_and_unavailable(self):
- self.useFixture(mockpatch.PatchObject(test.CONF.service_available,
- 'cinder', False))
+ self.useFixture(fixtures.MockPatchObject(test.CONF.service_available,
+ 'cinder', False))
self.assertRaises(testtools.TestCase.skipException,
self._test_services_helper, 'compute',
'volume')
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index f005c21..c04d933 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -180,3 +180,15 @@
'from oslo_config import cfg', './tempest/lib/decorators.py')))
self.assertTrue(list(checks.dont_use_config_in_tempest_lib(
'import tempest.config', './tempest/lib/common/rest_client.py')))
+
+ def test_unsupported_exception_attribute_PY3(self):
+ self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
+ "raise TestCase.failureException(e.message)"))), 1)
+ self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
+ "raise TestCase.failureException(ex.message)"))), 1)
+ self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
+ "raise TestCase.failureException(exc.message)"))), 1)
+ self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
+ "raise TestCase.failureException(exception.message)"))), 1)
+ self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
+ "raise TestCase.failureException(ee.message)"))), 0)
diff --git a/tempest/tests/test_microversions.py b/tempest/tests/test_microversions.py
index 173accb..ee6db71 100644
--- a/tempest/tests/test_microversions.py
+++ b/tempest/tests/test_microversions.py
@@ -13,6 +13,7 @@
# under the License.
from oslo_config import cfg
+import six
import testtools
from tempest.api.compute import base as compute_base
@@ -74,7 +75,7 @@
self.assertRaises(testtools.TestCase.skipException,
test_class.skip_checks)
except testtools.TestCase.skipException as e:
- raise testtools.TestCase.failureException(e.message)
+ raise testtools.TestCase.failureException(six.text_type(e))
def test_config_version_none_none(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2]
diff --git a/test-requirements.txt b/test-requirements.txt
index 13950bd..09c7685 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,10 +3,10 @@
# process, which may cause wedges in the gate later.
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
# needed for doc build
-sphinx>=1.5.1 # BSD
-oslosphinx>=4.7.0 # Apache-2.0
-reno>=1.8.0 # Apache-2.0
+sphinx>=1.6.2 # BSD
+openstackdocstheme>=1.16.0 # Apache-2.0
+reno!=2.3.1,>=1.8.0 # Apache-2.0
mock>=2.0 # BSD
-coverage>=4.0 # Apache-2.0
+coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
flake8-import-order==0.11 # LGPLv3
diff --git a/tools/check_logs.py b/tools/check_logs.py
index f82b387..fc21f75 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -25,7 +25,6 @@
import six.moves.urllib.request as urlreq
import yaml
-
# DEVSTACK_GATE_GRENADE is either unset if grenade is not running
# or a string describing what type of grenade run to perform.
is_grenade = os.environ.get('DEVSTACK_GATE_GRENADE') is not None
@@ -137,7 +136,7 @@
with open(WHITELIST_FILE) as stream:
loaded = yaml.safe_load(stream)
if loaded:
- for (name, l) in loaded.iteritems():
+ for (name, l) in six.iteritems(loaded):
for w in l:
assert 'module' in w, 'no module in %s' % name
assert 'message' in w, 'no message in %s' % name
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index 2ba8b16..1f2b88b 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -126,8 +126,8 @@
def print_stats(items, fname, verbose=False):
- errors = len(filter(lambda x: x.level == "ERROR", items))
- traces = len(filter(lambda x: x.level == "TRACE", items))
+ errors = len([x for x in items if x.level == "ERROR"])
+ traces = len([x for x in items if x.level == "TRACE"])
print("%d ERRORS found in %s" % (errors, fname))
print("%d TRACES found in %s" % (traces, fname))
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index acb29af..99df0d1 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -26,7 +26,15 @@
import json
import re
-import requests
+try:
+ # For Python 3.0 and later
+ from urllib.error import HTTPError as HTTPError
+ import urllib.request as urllib
+except ImportError:
+ # Fall back to Python 2's urllib2
+ import urllib2 as urllib
+ from urllib2 import HTTPError as HTTPError
+
url = 'https://review.openstack.org/projects/'
@@ -49,23 +57,31 @@
def has_tempest_plugin(proj):
- if proj.startswith('openstack/deb-'):
- return False
- r = requests.get(
- "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+ try:
+ r = urllib.urlopen(
+ "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+ except HTTPError as err:
+ if err.code == 404:
+ return False
p = re.compile('^tempest\.test_plugins', re.M)
- if p.findall(r.text):
+ if p.findall(r.read().decode('utf-8')):
return True
else:
False
-r = requests.get(url)
+r = urllib.urlopen(url)
# Gerrit prepends 4 garbage octets to the JSON, in order to counter
# cross-site scripting attacks. Therefore we must discard it so the
# json library won't choke.
-projects = sorted(filter(is_in_openstack_namespace, json.loads(r.text[4:])))
+projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:])))
-found_plugins = filter(has_tempest_plugin, projects)
+# Retrieve projects having no deb, ui or spec namespace as those namespaces
+# do not contains tempest plugins.
+projects_list = [i for i in projects if not (i.startswith('openstack/deb-') or
+ i.endswith('-ui') or
+ i.endswith('-specs'))]
+
+found_plugins = list(filter(has_tempest_plugin, projects_list))
# Every element of the found_plugins list begins with "openstack/".
# We drop those initial 10 octets when printing the list.
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
new file mode 100644
index 0000000..a4f706e
--- /dev/null
+++ b/tools/tempest-plugin-sanity.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+
+# Copyright 2017 Red Hat, Inc.
+# 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.
+
+# This script is intended to check the sanity of tempest plugins against
+# tempest master.
+# What it does:
+# * Creates the virtualenv
+# * Install tempest
+# * Retrive the project lists having tempest plugin if project name is
+# given.
+# * For each project in a list, It does:
+# * Clone the Project
+# * Install the Project and also installs dependencies from
+# test-requirements.txt.
+# * Create Tempest workspace
+# * List tempest plugins
+# * List tempest plugins tests
+# * Uninstall the project and its dependencies
+# * Again Install tempest
+# * Again repeat the step from cloning project
+#
+# If one of the step fails, The script will exit with failure.
+
+if [ "$1" == "-h" ]; then
+ echo -e "This script performs the sanity of tempest plugins to find
+configuration and dependency issues with the tempest.\n
+Usage: sh ./tools/tempest-plugin-sanity.sh [Run sanity on tempest plugins]"
+ exit 0
+fi
+
+set -ex
+
+# retrieve a list of projects having tempest plugins
+PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
+# List of projects having tempest plugin stale or unmaintained from long time
+BLACKLIST="trio2o"
+
+# Function to clone project using zuul-cloner or from git
+function clone_project() {
+ if [ -e /usr/zuul-env/bin/zuul-cloner ]; then
+ /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \
+ git://git.openstack.org \
+ openstack/"$1"
+
+ elif [ -e /usr/bin/git ]; then
+ /usr/bin/git clone git://git.openstack.org/openstack/"$1" \
+ openstack/"$1"
+
+ fi
+}
+
+# Create virtualenv to perform sanity operation
+SANITY_DIR=$(pwd)
+virtualenv "$SANITY_DIR"/.venv
+export TVENV="$SANITY_DIR/tools/with_venv.sh"
+cd "$SANITY_DIR"
+
+# Install tempest in a venv
+"$TVENV" pip install .
+
+# Function to install project
+function install_project() {
+ "$TVENV" pip install "$SANITY_DIR"/openstack/"$1"
+ # Check for test-requirements.txt file in a project then install it.
+ if [ -e "$SANITY_DIR"/openstack/"$1"/test-requirements.txt ]; then
+ "$TVENV" pip install -r "$SANITY_DIR"/openstack/"$1"/test-requirements.txt
+ fi
+}
+
+# Function to perform sanity checking on Tempest plugin
+function tempest_sanity() {
+ "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity
+ cd "$SANITY_DIR"/tempest_sanity
+ "$TVENV" tempest list-plugins
+ "$TVENV" tempest run -l
+ # Delete tempest workspace
+ "$TVENV" tempest workspace remove --name tempest_sanity --rmdir
+ cd "$SANITY_DIR"
+}
+
+# Function to uninstall project
+function uninstall_project() {
+ "$TVENV" pip uninstall -y "$SANITY_DIR"/openstack/"$1"
+ # Check for *requirements.txt file in a project then uninstall it.
+ if [ -e "$SANITY_DIR"/openstack/"$1"/*requirements.txt ]; then
+ "$TVENV" pip uninstall -y -r "$SANITY_DIR"/openstack/"$1"/*requirements.txt
+ fi
+ # Remove the project directory after sanity run
+ rm -fr "$SANITY_DIR"/openstack/"$1"
+}
+
+# Function to run sanity check on each project
+function plugin_sanity_check() {
+ clone_project "$1" && install_project "$1" && tempest_sanity "$1" \
+ && uninstall_project "$1" && "$TVENV" pip install .
+}
+
+# Log status
+passed_plugin=''
+failed_plugin=''
+# Perform sanity on all tempest plugin projects
+for project in $PROJECT_LIST; do
+ # Remove blacklisted tempest plugins
+ if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then
+ plugin_sanity_check $project && passed_plugin+=", $project" || \
+ failed_plugin+=", $project"
+ fi
+done
diff --git a/tox.ini b/tox.ini
index 892f834..6f37d00 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,7 @@
PYTHONWARNINGS=default::DeprecationWarning
BRANCH_NAME=master
CLIENT_NAME=tempest
-passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION
+passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
usedevelop = True
install_command =
{toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
@@ -185,3 +185,9 @@
# separately, outside of the requirements files.
deps = bindep
commands = bindep test
+
+[testenv:plugin-sanity-check]
+# perform tempest plugin sanity
+whitelist_externals = bash
+commands =
+ bash tools/tempest-plugin-sanity.sh