Merge "Update the home page"
diff --git a/.gitignore b/.gitignore
index efba45e..5b87cec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,13 @@
dist
build
.testrepository
+.idea
+.project
+.pydevproject
.coverage*
!.coveragerc
cover/
doc/source/_static/tempest.conf.sample
+
+# Files created by releasenotes build
+releasenotes/build
diff --git a/.mailmap b/.mailmap
index 5c37a5e..a43c0b9 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,9 +1,25 @@
-Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar-venkatesan <ravikumar.venkatesan@hp.com>
-Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
-Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
-Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
-Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
-Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
<jeblair@hp.com> <corvus@inaugust.com>
<jeblair@hp.com> <james.blair@rackspace.com>
+Adam Gandelman <adamg@ubuntu.com> Adam Gandelman <adamg@canonical.com>
+Andrea Frittoli (andreaf) <andrea.frittoli@hpe.com> Andrea Frittoli (andreaf) <andrea.frittoli@hp.com>
+Andrea Frittoli (andreaf) <andrea.frittoli@hpe.com> Andrea Frittoli <andrea.frittoli@hp.com>
+Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
+David Kranz <dkranz@redhat.com> David Kranz <david.kranz@qrclab.com>
+Ghanshyam <ghanshyam.mann@nectechnologies.in> Ghanshyam Mann <ghanshyam.mann@nectechnologies.in>
+Ghanshyam <ghanshyam.mann@nectechnologies.in> ghanshyam <ghanshyam.mann@nectechnologies.in>
+Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
+Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> Ken'ichi Ohmichi <oomichi@mxs.nes.nec.co.jp>
+Marc Koderer <marc@koderer.com> Marc Koderer <m.koderer@telekom.de>
+Masayuki Igawa <masayuki.igawa@gmail.com> Masayuki Igawa <igawa@mxs.nes.nec.co.jp>
+Masayuki Igawa <masayuki.igawa@gmail.com> Masayuki Igawa <mas-igawa@ut.jp.nec.com>
+Matthew Treinish <mtreinish@kortar.org> Matthew Treinish <treinish@linux.vnet.ibm.com>
+Nayna Patel <nayna.patel@hp.com> nayna-patel <nayna.patel@hp.com>
+ravikumar-venkatesan <ravikumar.venkatesan@hp.com> Ravikumar Venkatesan <ravikumar.venkatesan@hp.com>
+ravikumar-venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
+Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
+Sean Dague <sean@dague.net> Sean Dague <sdague@linux.vnet.ibm.com>
+Sean Dague <sean@dague.net> Sean Dague <sean.dague@samsung.com>
+Yuiko Takada <takada-yuiko@mxn.nes.nec.co.jp> YuikoTakada <takada-yuiko@mxn.nes.nec.co.jp>
+Zhi Kun Liu <zhikunli@cn.ibm.com> Liu, Zhi Kun <zhikunli@cn.ibm.com>
diff --git a/HACKING.rst b/HACKING.rst
index 0962f80..ec7ff6a 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -16,8 +16,11 @@
- [T107] Check that a service tag isn't in the module path
- [T108] Check no hyphen at the end of rand_name() argument
- [T109] Cannot use testtools.skip decorator; instead use
- decorators.skip_because from tempest-lib
+ decorators.skip_because from tempest.lib
- [T110] Check that service client names of GET should be consistent
+- [T111] Check that service client names of DELETE should be consistent
+- [T112] Check that tempest.lib should not import local tempest code
+- [T113] Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
- [N322] Method's default argument shouldn't be mutable
Test Data/Configuration
@@ -129,6 +132,7 @@
Set-up is split in a series of steps (setup stages), which can be overwritten
by test classes. Set-up stages are:
+
- `skip_checks`
- `setup_credentials`
- `setup_clients`
@@ -137,6 +141,7 @@
Tear-down is also split in a series of steps (teardown stages), which are
stacked for execution only if the corresponding setup stage had been
reached during the setup phase. Tear-down stages are:
+
- `clear_credentials` (defined in the base test class)
- `resource_cleanup`
@@ -154,33 +159,7 @@
Negative Tests
--------------
-Newly added negative tests should use the negative test framework. First step
-is to create an interface description in a python file under
-`tempest/api_schema/request/`. These descriptions consists of two important
-sections for the test (one of those is mandatory):
-
- - A resource (part of the URL of the request): Resources needed for a test
- must be created in `setUpClass` and registered with `set_resource` e.g.:
- `cls.set_resource("server", server['id'])`
-
- - A json schema: defines properties for a request.
-
-After that a test class must be added to automatically generate test scenarios
-out of the given interface description::
-
- load_tests = test.NegativeAutoTest.load_tests
-
- @test.SimpleNegativeAutoTest
- class SampleTestNegativeTestJSON(<your base class>, test.NegativeAutoTest):
- _service = 'compute'
- _schema = <your schema file>
-
-The class decorator `SimpleNegativeAutoTest` will automatically generate test
-cases out of the given schema in the attribute `_schema`.
-
-All negative tests should be added into a separate negative test file.
-If such a file doesn't exist for the particular resource being tested a new
-test file should be added.
+TODO: Write the guideline related to negative tests.
Test skips because of Known Bugs
--------------------------------
@@ -212,9 +191,9 @@
things to watch out for to try to avoid issues when running your tests in
parallel.
-- Resources outside of a tenant scope still have the potential to conflict. This
+- Resources outside of a project scope still have the potential to conflict. This
is a larger concern for the admin tests since most resources and actions that
- require admin privileges are outside of tenants.
+ 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
@@ -330,9 +309,9 @@
# The created server should be in the detailed list of all servers
...
-Tempest-lib includes a ``check-uuid`` tool that will test for the existence
+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-lib installed you run the tool against Tempest by calling from the
+tempest installed you run the tool against Tempest by calling from the
tempest repo::
check-uuid
diff --git a/README.rst b/README.rst
index 71e185f..650a1ed 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,14 @@
Tempest - The OpenStack Integration Test Suite
==============================================
+.. image:: https://img.shields.io/pypi/v/tempest.svg
+ :target: https://pypi.python.org/pypi/tempest/
+ :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/dm/tempest.svg
+ :target: https://pypi.python.org/pypi/tempest/
+ :alt: Downloads
+
This is a set of integration tests to be run against a live OpenStack
cluster. Tempest has batteries of tests for OpenStack API validation,
Scenarios, and other specific tests useful in validating an OpenStack
@@ -50,7 +58,7 @@
#. You first need to install Tempest. This is done with pip after you check out
the Tempest repo::
- $ git clone https://github.com/openstack/tempest/
+ $ git clone http://git.openstack.org/openstack/tempest
$ pip install tempest/
This can be done within a venv, but the assumption for this guide is that
@@ -63,14 +71,14 @@
it's recommended that you copy or rename tempest.conf.sample to tempest.conf
and make those changes to that file in /etc/tempest
-#. Setup a local working Tempest dir. This is done using the tempest init
+#. Setup a local working Tempest dir. This is done by using the tempest init
command::
- tempest init cloud-01
+ $ tempest init cloud-01
works the same as::
- mkdir cloud-01 && cd cloud-01 && tempest init
+ $ mkdir cloud-01 && cd cloud-01 && tempest init
This will create a new directory for running a single Tempest configuration.
If you'd like to run Tempest against multiple OpenStack deployments the idea
@@ -94,6 +102,45 @@
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
.. _ostestr: http://docs.openstack.org/developer/os-testr/
+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
+stable interface and there are no guarantees on the Python API unless otherwise
+stated.
+
+For more details refer to the library documentation here: :ref:`library`
+
+Release Versioning
+------------------
+Tempest's released versions are broken into 2 sets of information. Depending on
+how you intend to consume tempest you might need
+
+The version is a set of 3 numbers:
+
+X.Y.Z
+
+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
+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.
+
+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
+interface. When Y is incremented we've added functionality to the library
+interface and when Z is incremented it's a bug fix release for the library.
+Also note that both Y and Z are reset to 0 at each increment of X.
+
+.. _semver: http://semver.org/
+
Configuration
-------------
diff --git a/REVIEWING.rst b/REVIEWING.rst
index f7334ad..bd6018d 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -2,7 +2,7 @@
======================
To start read the `OpenStack Common Review Checklist
-<https://wiki.openstack.org/wiki/ReviewChecklist#Common_Review_Checklist>`_
+<http://docs.openstack.org/infra/manual/developers.html#peer-review>`_
Ensuring code is executed
diff --git a/data/tempest-plugins-registry.header b/data/tempest-plugins-registry.header
new file mode 100644
index 0000000..9821e8e
--- /dev/null
+++ b/data/tempest-plugins-registry.header
@@ -0,0 +1,23 @@
+..
+ Note to patch submitters: this file is covered by a periodic proposal
+ job. You should edit the files data/tempest-plugins-registry.footer
+ and data/tempest-plugins-registry.header instead of this one.
+
+==========================
+ Tempest Plugin Registry
+==========================
+
+Since we've created the external plugin mechanism, it's gotten used by
+a lot of projects. The following is a list of plugins that currently
+exist.
+
+Detected Plugins
+================
+
+The following are plugins that a script has found in the openstack/
+namespace, which includes but is not limited to official OpenStack
+projects.
+
++----------------------------+-------------------------------------------------------------------------+
+|Plugin Name |URL |
++----------------------------+-------------------------------------------------------------------------+
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index e428592..9a7ce15 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -9,23 +9,30 @@
config file which explains the purpose of each individual option. You can see
the sample config file here: :ref:`tempest-sampleconf`
-Auth/Credentials
+Test Credentials
----------------
-Tempest currently has two different ways in configuration to provide credentials
-to use when running Tempest. One is a traditional set of configuration options
-in the tempest.conf file. These options are clearly labelled in the ``identity``
-section and let you specify a set of credentials for a regular user, a global
-admin user, and an alternate user, consisting of a username, password, and
-project/tenant name.
+Tempest allows for configuring a set of admin credentials in the ``auth``
+section, via the following parameters:
-The other method to provide credentials is using the accounts.yaml file. This
-file is used to specify an arbitrary number of users available to run tests
-with. You can specify the location of the file in the ``auth`` section in the
+ #. ``admin_username``
+ #. ``admin_password``
+ #. ``admin_project_name``
+ #. ``admin_domain_name``
+
+Admin credentials are not mandatory to run Tempest, but when provided they
+can be used to:
+
+- Run tests for admin APIs
+- Generate test credentials on the fly (see `Dynamic Credentials`_)
+
+Tempest allows for configuring pre-provisioned test credentials as well.
+This can be done using the accounts.yaml file (see
+`Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
+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. Eventually the config
-options for providing credentials to Tempest will be deprecated and removed in
-favor of the accounts.yaml file.
+the accounts.yaml.sample file included in Tempest.
Keystone Connection Info
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -59,13 +66,13 @@
Dynamic Credentials (formerly known as Tenant isolation) was originally created
to enable running Tempest in parallel. For each test class it creates a unique
set of user credentials to use for the tests in the class. It can create up to
-three sets of username, password, and tenant/project names for a primary user,
-an admin user, and an alternate user. To enable and use dynamic credentials you
+three sets of username, password, and project names for a primary user,
+an admin user, and an alternate user. To enable and use dynamic credentials you
only need to configure two things:
#. A set of admin credentials with permissions to create users and
- tenants/projects. This is specified in the ``auth`` section with the
- ``admin_username``, ``admin_tenant_name``, ``admin_domain_name`` and
+ projects. This is specified in the ``auth`` section with the
+ ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and
``admin_password`` options
#. To enable dynamic credentials in the ``auth`` section with the
``use_dynamic_credentials`` option.
@@ -81,8 +88,9 @@
configured to use dynamic credentials.
-Pre-Provisioned Credentials (aka accounts.yaml or accounts file)
-""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+Pre-Provisioned Credentials
+"""""""""""""""""""""""""""
+
For a long time using dynamic credentials was the only method available if you
wanted to enable parallel execution of Tempest tests. However, this was
insufficient for certain use cases because of the admin credentials requirement
@@ -112,46 +120,11 @@
#. Set ``use_dynamic_credentials = False`` in the ``auth`` group
It is worth pointing out that each set of credentials in the accounts.yaml
-should have a unique tenant. This is required to provide proper isolation
+should have a unique project. This is required to provide proper isolation
to the tests using the credentials, and failure to do this will likely cause
unexpected failures in some tests.
-
-Legacy Credentials (aka credentials config options)
-"""""""""""""""""""""""""""""""""""""""""""""""""""
-**Starting in the Liberty release this mechanism was deprecated; it will be
-removed in a future release.**
-
-When Tempest was refactored to allow for locking test accounts, the original
-non-tenant isolated case was converted to internally work similarly to the
-accounts.yaml file. This mechanism was then called the legacy test accounts
-provider. To use the legacy test accounts provider you can specify the sets of
-credentials in the configuration file as detailed above with following nine
-options in the ``identity`` section:
-
- #. ``username``
- #. ``password``
- #. ``tenant_name``
- #. ``admin_username``
- #. ``admin_password``
- #. ``admin_tenant_name``
- #. ``alt_username``
- #. ``alt_password``
- #. ``alt_tenant_name``
-
-And in the ``auth`` section:
-
- #. ``use_dynamic_credentials = False``
- #. Comment out ``test_accounts_file`` or keep it empty.
-
-It only makes sense to use this if parallel execution isn't needed, since
-Tempest won't be able to properly isolate tests using this. Additionally, using
-the traditional config options for credentials is not able to provide
-credentials to tests requiring specific roles on accounts. This is because the
-config options do not give sufficient flexibility to describe the roles assigned
-to a user for running the tests. There are additional limitations with regard to
-network configuration when using this credential provider mechanism - see the
-`Networking`_ section below.
+Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
Compute
-------
@@ -241,11 +214,26 @@
run. This section covers the different methods of configuring Tempest to provide
a network when creating servers.
+The ``validation`` group gathers all the connection options to remotely access the
+created servers.
+
+To enable remote access to servers, at least the three following options need to be
+set:
+
+* The ``run_validation`` option needs be set to ``true``.
+
+* The ``connect_method`` option. Two connect methods are available: ``fixed`` and
+ ``floating``, the later being set by default.
+
+* The ``auth_method`` option. Currently, only authentication by keypair is
+ available.
+
+
Fixed Network Name
""""""""""""""""""
This is the simplest method of specifying how networks should be used. You can
just specify a single network name/label to use for all server creations. The
-limitation with this is that all tenants/projects and users must be able to see
+limitation with this is that all projects and users must be able to see
that network name/label if they are to perform a network list and be able to use
it.
@@ -267,10 +255,10 @@
"""""""""""""
If you are using an accounts file to provide credentials for running Tempest
then you can leverage it to also specify which network should be used with
-server creations on a per tenant/project and user pair basis. This provides
+server creations on a per project and user pair basis. This provides
the necessary flexibility to work with more intricate networking configurations
by enabling the user to specify exactly which network to use for which
-tenants/projects. You can refer to the accounts.yaml.sample file included in
+projects. You can refer to the accounts.yaml.sample file included in
the Tempest repo for the syntax around specifying networks in the file.
However, specifying a network is not required when using an accounts file. If
@@ -291,7 +279,7 @@
With Dynamic Credentials
""""""""""""""""""""""""
With dynamic credentials enabled and using nova-network, your only option for
-configuration is to either set a fixed network name or not. However, in most
+configuration is to either set a fixed network name or not. However, in most
cases it shouldn't matter because nova-network should have no problem booting a
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
@@ -360,11 +348,14 @@
service catalog should be in a standard format (which is going to be
standardized at the keystone level).
Tempest expects URLs in the Service catalog in the following format:
- * ``http://example.com:1234/<version-info>``
+
+ * ``http://example.com:1234/<version-info>``
+
Examples:
- * Good - ``http://example.com:1234/v2.0``
- * Wouldn’t work - ``http://example.com:1234/xyz/v2.0/``
- (adding prefix/suffix around version etc)
+
+ * Good - ``http://example.com:1234/v2.0``
+ * Wouldn’t work - ``http://example.com:1234/xyz/v2.0/``
+ (adding prefix/suffix around version etc)
Service Feature Configuration
-----------------------------
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 32e6e51..10364db 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -11,6 +11,9 @@
HACKING
REVIEWING
plugin
+ plugin-registry
+ library
+ microversion_testing
------------
Field Guides
diff --git a/doc/source/library.rst b/doc/source/library.rst
new file mode 100644
index 0000000..a89512c
--- /dev/null
+++ b/doc/source/library.rst
@@ -0,0 +1,68 @@
+.. _library:
+
+Tempest Library Documentation
+=============================
+
+Tempest provides a stable library interface that provides external tools or
+test suites an interface for reusing pieces of tempest code. Any public
+interface that lives in tempest/lib in the tempest repo is treated as a stable
+public interface and it should be safe to external consume that. Every effort
+goes into maintaining backwards compatibility with any change.
+The library is self contained and doesn't have any dependency on
+other tempest internals outside of lib (including no usage of tempest
+configuration).
+
+Stability
+---------
+Any code that lives in tempest/lib will be treated as a stable interface.
+This means that any public interface under the tempest/lib directory is
+expected to be a stable interface suitable for public consumption. However, for
+any interfaces outside of tempest/lib in the tempest tree (unless otherwise
+noted) or any private interfaces the same stability guarantees don't apply.
+
+Adding Interfaces
+'''''''''''''''''
+When adding an interface to tempest/lib we have to make sure there are no
+dependencies on any pieces of tempest outside of tempest/lib. This means if
+for example there is a dependency on the configuration file we need remove that.
+The other aspect when adding an interface is to make sure it's really an
+interface ready for external consumption and something we want to commit to
+supporting.
+
+Making changes
+''''''''''''''
+When making changes to tempest/lib you have to be conscious of the effect of
+any changes on external consumers. If your proposed changeset will change the
+default behaviour of any interface, or make something which previously worked
+not after your change, then it is not acceptable. Every effort needs to go into
+preserving backwards compatibility in changes.
+
+Reviewing
+'''''''''
+When reviewing a proposed change to tempest/lib code we need to be careful to
+ensure that we don't break backwards compatibility. For patches that change
+existing interfaces we have to be careful to make sure we don't break any
+external consumers. Some common red flags are:
+
+ * a change to an existing API requires a change outside the library directory
+ where the interface is being consumed
+ * a unit test has to be significantly changed to make the proposed change pass
+
+Testing
+'''''''
+When adding a new interface to the library we need to at a minimum have unit
+test coverage. A proposed change to add an interface to tempest/lib that
+doesn't have unit tests shouldn't be accepted. Ideally these unit tests will
+provide sufficient coverage to ensure a stable interface moving forward.
+
+Current Library APIs
+--------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ library/cli
+ library/decorators
+ library/rest_client
+ library/utils
+ library/api_microversion_testing
diff --git a/doc/source/library/api_microversion_testing.rst b/doc/source/library/api_microversion_testing.rst
new file mode 100644
index 0000000..b7a4ba8
--- /dev/null
+++ b/doc/source/library/api_microversion_testing.rst
@@ -0,0 +1,29 @@
+.. _api_microversion_testing:
+
+API Microversion Testing Support in Temepst
+===========================================
+
+---------------------------------------------
+Framework to support API Microversion testing
+---------------------------------------------
+
+Many of the OpenStack components have implemented API microversions.
+It is important to test those microversions in Tempest or external plugins.
+Tempest now provides stable interfaces to support to test the API microversions.
+Based on the microversion range coming from the combination of both configuration
+and each test case, APIs request will be made with selected microversion.
+
+This document explains the interfaces needed for microversion testing.
+
+
+The api_version_request module
+""""""""""""""""""""""""""""""
+
+.. automodule:: tempest.lib.common.api_version_request
+ :members:
+
+The api_version_utils module
+""""""""""""""""""""""""""""
+
+.. automodule:: tempest.lib.common.api_version_utils
+ :members:
diff --git a/doc/source/library/auth.rst b/doc/source/library/auth.rst
new file mode 100644
index 0000000..e1d92ed
--- /dev/null
+++ b/doc/source/library/auth.rst
@@ -0,0 +1,11 @@
+.. _auth:
+
+Authentication Framework Usage
+==============================
+
+---------------
+The auth module
+---------------
+
+.. automodule:: tempest.lib.auth
+ :members:
diff --git a/doc/source/library/cli.rst b/doc/source/library/cli.rst
new file mode 100644
index 0000000..6bd7881
--- /dev/null
+++ b/doc/source/library/cli.rst
@@ -0,0 +1,18 @@
+.. _cli:
+
+CLI Testing Framework Usage
+===========================
+
+-------------------
+The cli.base module
+-------------------
+
+.. automodule:: tempest.lib.cli.base
+ :members:
+
+----------------------------
+The cli.output_parser module
+----------------------------
+
+.. automodule:: tempest.lib.cli.output_parser
+ :members:
diff --git a/doc/source/library/decorators.rst b/doc/source/library/decorators.rst
new file mode 100644
index 0000000..a173967
--- /dev/null
+++ b/doc/source/library/decorators.rst
@@ -0,0 +1,13 @@
+.. _decorators:
+
+Decorators Usage Guide
+======================
+
+---------------------
+The decorators module
+---------------------
+
+.. automodule:: tempest.lib.decorators
+ :members:
+
+
diff --git a/doc/source/library/rest_client.rst b/doc/source/library/rest_client.rst
new file mode 100644
index 0000000..3045694
--- /dev/null
+++ b/doc/source/library/rest_client.rst
@@ -0,0 +1,11 @@
+.. _rest_client:
+
+Rest Client Usage
+=================
+
+----------------------
+The rest_client module
+----------------------
+
+.. automodule:: tempest.lib.common.rest_client
+ :members:
diff --git a/doc/source/library/utils.rst b/doc/source/library/utils.rst
new file mode 100644
index 0000000..bc2f79b
--- /dev/null
+++ b/doc/source/library/utils.rst
@@ -0,0 +1,11 @@
+.. _utils:
+
+Utils Usage
+===========
+
+---------------
+The misc module
+---------------
+
+.. automodule:: tempest.lib.common.utils.misc
+ :members:
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
new file mode 100644
index 0000000..3568470
--- /dev/null
+++ b/doc/source/microversion_testing.rst
@@ -0,0 +1,213 @@
+===================================
+How To Implement Microversion Tests
+===================================
+
+Tempest provides stable interfaces to test API Microversion.
+For Details, see: `API Microversion testing Framework`_
+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
+
+
+Configuration options for Microversion
+""""""""""""""""""""""""""""""""""""""
+
+* Add configuration options for specifying test target Microversions.
+ We need to specify test target Microversions because the supported
+ Microversions may be different between OpenStack clouds. For operating
+ multiple Microversion tests in a single Tempest operation, configuration
+ options should represent the range of test target Microversions.
+ New configuration options are:
+ * min_microversion
+ * max_microversion
+
+ Those should be defined under respective section of each service.
+ For example::
+
+ [compute]
+ min_microversion = None
+ max_microversion = latest
+
+
+How To Implement Microversion Tests
+"""""""""""""""""""""""""""""""""""
+
+Step1: Add skip logic based on configured Microversion range
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Add logic to skip the tests based on Tests class and configured Microversion
+range.
+api_version_utils.check_skip_with_microversion function can be used
+to automatically skip the tests which do not fall under configured
+Microversion range.
+For example::
+
+ class BaseTestCase1(api_version_utils.BaseMicroversionTest):
+
+ [..]
+ @classmethod
+ def skip_checks(cls):
+ super(BaseTestCase1, cls).skip_checks()
+ api_version_utils.check_skip_with_microversion(cls.min_microversion,
+ cls.max_microversion,
+ CONF.compute.min_microversion,
+ CONF.compute.max_microversion)
+
+Skip logic can be added in tests base class or any specific test class depends on
+tests class structure.
+
+Step2: Selected API request microversion
+''''''''''''''''''''''''''''''''''''''''
+
+Select appropriate Microversion which needs to be used
+to send with API request.
+api_version_utils.select_request_microversion function can be used
+to select the appropriate Microversion which will be used for API request.
+For example::
+
+ @classmethod
+ def resource_setup(cls):
+ super(BaseTestCase1, cls).resource_setup()
+ cls.request_microversion = (
+ api_version_utils.select_request_microversion(
+ cls.min_microversion,
+ CONF.compute.min_microversion))
+
+
+Step3: Set Microversion on Service Clients
+''''''''''''''''''''''''''''''''''''''''''
+
+Microversion selected by Test Class in previous step needs to be set on
+service clients so that APIs can be requested with selected Microversion.
+
+Microversion can be defined as global variable on service clients which
+can be set using fixture.
+Also Microversion header name needs to be defined on service clients which
+should be constant because it is not supposed to be changed by project
+as per API contract.
+For example::
+
+ COMPUTE_MICROVERSION = None
+
+ class BaseClient1(rest_client.RestClient):
+ api_microversion_header_name = 'X-OpenStack-Nova-API-Version'
+
+Now test class can set the selected Microversion on required service clients
+using fixture which can take care of resetting the same once tests is completed.
+For example::
+
+ def setUp(self):
+ super(BaseTestCase1, self).setUp()
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ self.request_microversion))
+
+Service clients needs to add set Microversion in API request header which
+can be done by overriding the get_headers() method of rest_client.
+For example::
+
+ COMPUTE_MICROVERSION = None
+
+ class BaseClient1(rest_client.RestClient):
+ api_microversion_header_name = 'X-OpenStack-Nova-API-Version'
+
+ def get_headers(self):
+ headers = super(BaseClient1, self).get_headers()
+ if COMPUTE_MICROVERSION:
+ headers[self.api_microversion_header_name] = COMPUTE_MICROVERSION
+ return headers
+
+
+Step4: Separate Test classes for each Microversion
+''''''''''''''''''''''''''''''''''''''''''''''''''
+
+This is last step to implement Microversion test class.
+
+For any Microversion tests, basically we need to implement a
+separate test class. In addition, each test class defines its
+Microversion range with class variable like min_microversion
+and max_microversion. Tests will be valid for that defined range.
+If that range is out of configured Microversion range then, test
+will be skipped.
+
+*NOTE: Microversion testing is supported at test class level not at individual
+test case level.*
+For example:
+
+Below test is applicable for Microversion from 2.2 till 2.9::
+
+ class BaseTestCase1(api_version_utils.BaseMicroversionTest,
+ tempest.test.BaseTestCase):
+
+ [..]
+
+
+ class Test1(BaseTestCase1):
+ min_microversion = '2.2'
+ max_microversion = '2.9'
+
+ [..]
+
+Below test is applicable for Microversion from 2.10 till latest::
+
+ class Test2(BaseTestCase1):
+ min_microversion = '2.10'
+ max_microversion = 'latest'
+
+ [..]
+
+
+
+
+Notes about Compute Microversion Tests
+""""""""""""""""""""""""""""""""""""""
+
+Some of the compute Microversion tests have been already implemented
+with the Microversion testing framework. So for further tests only
+step 4 is needed.
+
+Along with that JSON response schema might need versioning if needed.
+
+Compute service clients strictly validate the response against defined JSON
+schema and does not allow additional elements in response.
+So if that Microversion changed the API response then schema needs to be versioned.
+New JSON schema file needs to be defined with new response attributes and service
+client methods will select the schema based on requested microversion.
+
+If Microversion tests are implemented randomly meaning not
+in sequence order(v2.20 tests added and previous Microversion tests are not yet added)
+then, still schema might need to be version for older Microversion if they changed
+the response.
+This is because Nova Microversion includes all the previous Microversions behavior.
+
+For Example:
+ Implementing the v2.20 Microversion tests before v2.9 and 2.19-
+ v2.20 API request will respond as latest behavior of Nova till v2.20,
+ and in v2.9 and 2.19, server response has been changed so response schema needs
+ to be versioned accordingly.
+
+That can be done by using the get_schema method in below module:
+
+The base_compute_client module
+''''''''''''''''''''''''''''''
+
+.. automodule:: tempest.lib.services.compute.base_compute_client
+ :members:
+
+
+Microversion tests implemented in Tempest
+"""""""""""""""""""""""""""""""""""""""""
+
+* Compute
+
+ * `2.1`_
+
+ .. _2.1: http://docs.openstack.org/developer/nova/api_microversion_history.html#id1
+
+ * `2.2`_
+
+ .. _2.2: http://docs.openstack.org/developer/nova/api_microversion_history.html#id2
+
+ * `2.10`_
+
+ .. _2.10: http://docs.openstack.org/developer/nova/api_microversion_history.html#id9
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
new file mode 100644
index 0000000..517e5b8
--- /dev/null
+++ b/doc/source/plugin-registry.rst
@@ -0,0 +1,23 @@
+..
+ Note to patch submitters: this file is covered by a periodic proposal
+ job. You should edit the files data/tempest-plugins-registry.footer
+ data/tempest-plugins-registry.header instead of this one.
+
+==========================
+ Tempest Plugin Registry
+==========================
+
+Since we've created the external plugin mechanism, it's gotten used by
+a lot of projects. The following is a list of plugins that currently
+exist.
+
+Detected Plugins
+================
+
+The following will list plugins that a script has found in the openstack/
+namespace, which includes but is not limited to official OpenStack
+projects.
+
++----------------------------+-------------------------------------------------------------------------+
+|Plugin Name |URL |
++----------------------------+-------------------------------------------------------------------------+
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 29653a6..9640469 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -11,14 +11,26 @@
=================
Creating a plugin is fairly straightforward and doesn't require much additional
-effort on top of creating a test suite using tempest-lib. One thing to note with
+effort on top of creating a test suite using tempest.lib. One thing to note with
doing this is that the interfaces exposed by tempest are not considered stable
(with the exception of configuration variables which ever effort goes into
ensuring backwards compatibility). You should not need to import anything from
-tempest itself except where explicitly noted. If there is an interface from
-tempest that you need to rely on in your plugin it likely needs to be migrated
-to tempest-lib. In that situation, file a bug, push a migration patch, etc. to
-expedite providing the interface in a reliable manner.
+tempest itself except where explicitly noted.
+
+Stable Tempest APIs plugins may use
+-----------------------------------
+
+As noted above, several tempest APIs are acceptable to use from plugins, while
+others are not. A list of stable APIs available to plugins is provided below:
+
+* tempest.lib.*
+* tempest.config
+* tempest.test_discover.plugins
+
+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
+that situation, file a bug, push a migration patch, etc. to expedite providing
+the interface in a reliable manner.
Plugin Cookiecutter
-------------------
@@ -55,6 +67,37 @@
tempest.test_plugins =
plugin_name = module.path:PluginClass
+Standalone Plugin vs In-repo Plugin
+-----------------------------------
+
+Since all that's required for a plugin to be detected by tempest is a valid
+setuptools entry point in the proper namespace there is no difference from the
+tempest perspective on either creating a separate python package to
+house the plugin or adding the code to an existing python project. However,
+there are tradeoffs to consider when deciding which approach to take when
+creating a new plugin.
+
+If you create a separate python project for your plugin this makes a lot of
+things much easier. Firstly it makes packaging and versioning much simpler, you
+can easily decouple the requirements for the plugin from the requirements for
+the other project. It lets you version the plugin independently and maintain a
+single version of the test code across project release boundaries (see the
+`Branchless Tempest Spec`_ for more details on this). It also greatly
+simplifies the install time story for external users. Instead of having to
+install the right version of a project in the same python namespace as tempest
+they simply need to pip install the plugin in that namespace. It also means
+that users don't have to worry about inadvertently installing a tempest plugin
+when they install another package.
+
+.. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
+
+The sole advantage to integrating a plugin into an existing python project is
+that it enables you to land code changes at the same time you land test changes
+in the plugin. This reduces some of the burden on contributors by not having
+to land 2 changes to add a new API feature and then test it and doing it as a
+single combined commit.
+
+
Plugin Class
============
@@ -71,11 +114,6 @@
Then you need to ensure you locally define all of the methods in the abstract
class, you can refer to the api doc below for a reference of what that entails.
-Also, note eventually this abstract class will likely live in tempest-lib, when
-that migration occurs a deprecation shim will be added to tempest so as to not
-break any existing plugins. But, when that occurs migrating to using tempest-lib
-as the source for the abstract class will be prudent.
-
Abstract Plugin Class
---------------------
diff --git a/etc/accounts.yaml.sample b/etc/accounts.yaml.sample
index decc659..3dbed79 100644
--- a/etc/accounts.yaml.sample
+++ b/etc/accounts.yaml.sample
@@ -3,7 +3,25 @@
# This is required to provide isolation between test for running in parallel
#
# Valid fields for credentials are defined in the descendants of
-# auth.Credentials - see KeystoneV[2|3]Credentials.CONF_ATTRIBUTES
+# lib.auth.Credentials - see KeystoneV[2|3]Credentials.ATTRIBUTES
+#
+# The fields in KeystoneV3Credentials behave as follows:
+#
+# tenant_[id|name] also sets project_[id|name].
+#
+# project_[id|name] also sets tenant_[id|name].
+#
+# Providing distinct values for both tenant_[id|name] and project_[id|name]
+# will result in an InvalidCredentials exception.
+#
+# The value of project_domain_[id|name] is used for user_domain_[id|name] if
+# the latter is not specified.
+#
+# The value of user_domain_[id|name] is used for project_domain_[id|name] if
+# the latter is not specified.
+#
+# The value of domain_[id|name] is used for project_domain_[id|name] if not
+# specified and user_domain_[id|name] if not specified.
- username: 'user_1'
tenant_name: 'test_tenant_1'
diff --git a/etc/javelin-resources.yaml.sample b/etc/javelin-resources.yaml.sample
index fb270a4..1565686 100644
--- a/etc/javelin-resources.yaml.sample
+++ b/etc/javelin-resources.yaml.sample
@@ -61,5 +61,3 @@
owner: javelin
file: /etc/hosts
swift_role: Member
-
-telemetry: true
\ No newline at end of file
diff --git a/openstack-common.conf b/openstack-common.conf
deleted file mode 100644
index acb1437..0000000
--- a/openstack-common.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-[DEFAULT]
-
-# The list of modules to copy from openstack-common
-module=install_venv_common
-module=with_venv
-module=install_venv
-
-# The base module to hold the copy of openstack.common
-base=tempest
diff --git a/tempest/api/messaging/__init__.py b/releasenotes/notes/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to releasenotes/notes/.placeholder
diff --git a/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml b/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml
new file mode 100644
index 0000000..217c2f6
--- /dev/null
+++ b/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml
@@ -0,0 +1,13 @@
+---
+other:
+ - OpenStack Releases Supported at this time are the same as in the
+ previous release 9,
+ **Kilo** and
+ **Liberty**.
+
+
+ The release under current development as of this tag is Mitaka,
+ meaning that every Tempest commit is also tested against master during
+ the Mitaka cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Mitaka (or future releases)
+ cloud.
diff --git a/releasenotes/notes/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml b/releasenotes/notes/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml
new file mode 100644
index 0000000..09ff15d
--- /dev/null
+++ b/releasenotes/notes/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+ This release is marking the start of Mitaka release support in tempest
+other:
+ - OpenStack Releases Supported at this time are **Kilo**, **Liberty**,
+ **Mitaka**
+
+ The release under current development as of this tag is Newton,
+ meaning that every Tempest commit is also tested against master during
+ the Newton cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Newton (or future releases)
+ cloud.
diff --git a/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml b/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
new file mode 100644
index 0000000..9baf035
--- /dev/null
+++ b/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+ This release is marking the end of Kilo release support in Tempest
+other:
+ - OpenStack Releases Supported after this release are **Liberty**
+ and **Mitaka**
+
+ The release under current development as of this tag is Newton,
+ meaning that every Tempest commit is also tested against master during
+ the Newton cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Newton (or future releases)
+ cloud.
diff --git a/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml b/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml
new file mode 100644
index 0000000..0ed3130
--- /dev/null
+++ b/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml
@@ -0,0 +1,11 @@
+---
+prelude: |
+ This release includes the addition of the stable library interface for
+ tempest. This behaves just as tempest-lib did prior to this, but instead
+ 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
+
+features:
+ - Tempest library interface
diff --git a/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
new file mode 100644
index 0000000..297279f
--- /dev/null
+++ b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - Tempest library auth interface now supports scope. Scope allows to control
+ the scope of tokens requested via the identity API. Identity V2 supports
+ unscoped and project scoped tokens, but only the latter are implemented.
+ Identity V3 supports unscoped, project and domain scoped token, all three
+ are available.
\ No newline at end of file
diff --git a/releasenotes/notes/api-microversion-testing-support-2ceddd2255670932.yaml b/releasenotes/notes/api-microversion-testing-support-2ceddd2255670932.yaml
new file mode 100644
index 0000000..e98671a
--- /dev/null
+++ b/releasenotes/notes/api-microversion-testing-support-2ceddd2255670932.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Tempest library interface addition(API Microversion testing interfaces).
\ No newline at end of file
diff --git a/releasenotes/notes/compute-microversion-support-e0b23f960f894b9b.yaml b/releasenotes/notes/compute-microversion-support-e0b23f960f894b9b.yaml
new file mode 100644
index 0000000..de1b35e
--- /dev/null
+++ b/releasenotes/notes/compute-microversion-support-e0b23f960f894b9b.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Compute Microversion testing support in Service Clients.
diff --git a/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
new file mode 100644
index 0000000..89b3f41
--- /dev/null
+++ b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - The deprecated legacy credential provider has been removed. The only way to
+ configure credentials in tempest now is to use the dynamic or preprovisioned
+ credential providers
diff --git a/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml b/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml
new file mode 100644
index 0000000..0bc9af5
--- /dev/null
+++ b/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml
@@ -0,0 +1,3 @@
+---
+other:
+ - Start using reno for managing release notes.
diff --git a/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml b/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml
new file mode 100644
index 0000000..eb45523
--- /dev/null
+++ b/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - The RestClient (in tempest.lib.common.rest_client) now supports POSTing
+ and PUTing data with chunked transfer encoding. Just pass an `iterable`
+ object as the `body` argument and set the `chunked` argument to `True`.
+ - A new generator called `chunkify` is added in
+ tempest.lib.common.utils.data_utils that yields fixed-size chunks (slices)
+ from a Python sequence.
+
diff --git a/tempest/api/messaging/__init__.py b/releasenotes/source/_static/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to releasenotes/source/_static/.placeholder
diff --git a/tempest/api/messaging/__init__.py b/releasenotes/source/_templates/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to releasenotes/source/_templates/.placeholder
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..4522a17
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,277 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+# tempest Release Notes documentation build configuration file, created by
+# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'oslosphinx',
+ 'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'tempest Release Notes'
+copyright = u'2016, tempest Developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from tempest.version import version_info as tempest_version
+# The full version, including alpha/beta/rc tags.
+release = tempest_version.version_string_with_vcs()
+# The short X.Y version.
+version = tempest_version.canonical_version_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# 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
+# documentation.
+# 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".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'tempestReleaseNotesdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ ('index', 'olso.configReleaseNotes.tex',
+ u'olso.config Release Notes Documentation',
+ u'tempest Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'olso.configreleasenotes',
+ u'tempest Release Notes Documentation',
+ [u'tempest Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'tempestReleaseNotes',
+ u'tempest Release Notes Documentation',
+ u'tempest Developers', 'olso.configReleaseNotes',
+ 'An OpenStack library for parsing configuration options from the command'
+ ' line and configuration files.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..2c22408
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,17 @@
+===========================
+ tempest Release Notes
+===========================
+
+ .. toctree::
+ :maxdepth: 1
+
+ v12.0.0
+ v11.0.0
+ v10.0.0
+ unreleased
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..5860a46
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+==========================
+ Unreleased Release Notes
+==========================
+
+.. release-notes::
diff --git a/releasenotes/source/v10.0.0.rst b/releasenotes/source/v10.0.0.rst
new file mode 100644
index 0000000..38ed2ef
--- /dev/null
+++ b/releasenotes/source/v10.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v10.0.0 Release Notes
+=====================
+
+.. release-notes:: 10.0.0 Release Notes
+ :version: 10.0.0
diff --git a/releasenotes/source/v11.0.0.rst b/releasenotes/source/v11.0.0.rst
new file mode 100644
index 0000000..84b145d
--- /dev/null
+++ b/releasenotes/source/v11.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v11.0.0 Release Notes
+=====================
+
+.. release-notes:: 11.0.0 Release Notes
+ :version: 11.0.0
diff --git a/releasenotes/source/v12.0.0.rst b/releasenotes/source/v12.0.0.rst
new file mode 100644
index 0000000..0bc8343
--- /dev/null
+++ b/releasenotes/source/v12.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v12.0.0 Release Notes
+=====================
+
+.. release-notes:: 12.0.0 Release Notes
+ :version: 12.0.0
diff --git a/requirements.txt b/requirements.txt
index 43f8ff7..f41d2a7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,26 +2,24 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
-cliff>=1.15.0 # Apache-2.0
-anyjson>=0.3.3 # BSD
-httplib2>=0.7.5 # MIT
+cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
testtools>=1.4.0 # MIT
-paramiko>=1.13.0 # LGPL
+paramiko>=2.0 # LGPL
netaddr!=0.7.16,>=0.7.12 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
pyOpenSSL>=0.14 # Apache-2.0
-oslo.concurrency>=2.3.0 # Apache-2.0
-oslo.config>=3.4.0 # Apache-2.0
+oslo.concurrency>=3.8.0 # Apache-2.0
+oslo.config>=3.9.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.4.0 # Apache-2.0
+oslo.utils>=3.5.0 # Apache-2.0
six>=1.9.0 # MIT
-iso8601>=0.1.9 # MIT
-fixtures>=1.3.1 # Apache-2.0/BSD
+fixtures>=3.0.0 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
-tempest-lib>=0.13.0 # Apache-2.0
PyYAML>=3.1.0 # MIT
-stevedore>=1.5.0 # Apache-2.0
+stevedore>=1.10.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
+os-testr>=0.7.0 # Apache-2.0
+urllib3>=1.15.1 # MIT
diff --git a/run_tempest.sh b/run_tempest.sh
index 8c8f25f..af01734 100755
--- a/run_tempest.sh
+++ b/run_tempest.sh
@@ -103,22 +103,25 @@
fi
if [ $update -eq 1 ]; then
echo "Updating virtualenv..."
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
+ $venv/bin/pip install -U -r requirements.txt
fi
if [ -e ${venv} ]; then
wrapper="${with_venv}"
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
wrapper="${with_venv}"
+ ${wrapper} pip install -U -r requirements.txt
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
wrapper=${with_venv}
+ ${wrapper} pip install -U -r requirements.txt
fi
fi
fi
diff --git a/run_tests.sh b/run_tests.sh
index 908056f..22314b6 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -114,22 +114,25 @@
fi
if [ $update -eq 1 ]; then
echo "Updating virtualenv..."
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
+ $venv/bin/pip install -U -r requirements.txt -r test-requirements.txt
fi
if [ -e ${venv} ]; then
wrapper="${with_venv}"
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
wrapper="${with_venv}"
+ ${wrapper} pip install -U -r requirements.txt -r test-requirements.txt
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
- python tools/install_venv.py $installvenvopts
+ virtualenv $installvenvopts $venv
wrapper=${with_venv}
+ ${wrapper} pip install -U -r requirements.txt -r test-requirements.txt
fi
fi
fi
diff --git a/setup.cfg b/setup.cfg
index 8f7b319..24e0214 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -32,6 +32,8 @@
tempest-cleanup = tempest.cmd.cleanup:main
tempest-account-generator = tempest.cmd.account_generator:main
tempest = tempest.cmd.main:main
+ skip-tracker = tempest.lib.cmd.skip_tracker:main
+ check-uuid = tempest.lib.cmd.check_uuid:run
tempest.cm =
account-generator = tempest.cmd.account_generator:TempestAccountGenerator
init = tempest.cmd.init:TempestInit
diff --git a/tempest/api/baremetal/admin/base.py b/tempest/api/baremetal/admin/base.py
index 80b69b9..f7891dd 100644
--- a/tempest/api/baremetal/admin/base.py
+++ b/tempest/api/baremetal/admin/base.py
@@ -12,10 +12,9 @@
import functools
-from tempest_lib import exceptions as lib_exc
-
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/baremetal/admin/test_chassis.py b/tempest/api/baremetal/admin/test_chassis.py
index 29fc64c..339aaea 100644
--- a/tempest/api/baremetal/admin/test_chassis.py
+++ b/tempest/api/baremetal/admin/test_chassis.py
@@ -12,10 +12,10 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/baremetal/admin/test_nodes.py b/tempest/api/baremetal/admin/test_nodes.py
index b6dee18..2c44665 100644
--- a/tempest/api/baremetal/admin/test_nodes.py
+++ b/tempest/api/baremetal/admin/test_nodes.py
@@ -11,11 +11,11 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -138,14 +138,14 @@
body = self.client.get_node_boot_device(self.node['uuid'])
self.assertIn('boot_device', body)
self.assertIn('persistent', body)
- self.assertTrue(isinstance(body['boot_device'], six.string_types))
- self.assertTrue(isinstance(body['persistent'], bool))
+ self.assertIsInstance(body['boot_device'], six.string_types)
+ self.assertIsInstance(body['persistent'], bool)
@test.idempotent_id('3622bc6f-3589-4bc2-89f3-50419c66b133')
def test_get_node_supported_boot_devices(self):
body = self.client.get_node_supported_boot_devices(self.node['uuid'])
self.assertIn('supported_boot_devices', body)
- self.assertTrue(isinstance(body['supported_boot_devices'], list))
+ self.assertIsInstance(body['supported_boot_devices'], list)
@test.idempotent_id('f63b6288-1137-4426-8cfe-0d5b7eb87c06')
def test_get_console(self):
diff --git a/tempest/api/baremetal/admin/test_ports.py b/tempest/api/baremetal/admin/test_ports.py
index 180b848..ce519c1 100644
--- a/tempest/api/baremetal/admin/test_ports.py
+++ b/tempest/api/baremetal/admin/test_ports.py
@@ -11,10 +11,10 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/baremetal/admin/test_ports_negative.py b/tempest/api/baremetal/admin/test_ports_negative.py
index 610758a..5e3a33f 100644
--- a/tempest/api/baremetal/admin/test_ports_negative.py
+++ b/tempest/api/baremetal/admin/test_ports_negative.py
@@ -10,10 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -38,7 +37,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('30277ee8-0c60-4f1d-b125-0e51c2f43369')
def test_create_port_nonexsistent_node_id(self):
- node_id = str(data_utils.rand_uuid())
+ node_id = data_utils.rand_uuid()
address = data_utils.rand_mac_address()
self.assertRaises(lib_exc.BadRequest, self.create_port,
node_id=node_id, address=address)
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 9f7bbae..671b139 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -13,10 +13,10 @@
# under the License.
from oslo_log import log
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
LOG = log.getLogger(__name__)
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 1d83fec..a2b1e2f 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 181533b..6b75aee 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_availability_zone_negative.py b/tempest/api/compute/admin/test_availability_zone_negative.py
index fe979d4..c60de1e 100644
--- a/tempest/api/compute/admin/test_availability_zone_negative.py
+++ b/tempest/api/compute/admin/test_availability_zone_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index 8d745c9..05bba21 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 1ef8f67..95e7ef1 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -92,7 +91,7 @@
@test.idempotent_id('94c9bb4e-2c2a-4f3c-bb1f-5f0daf918e6d')
def test_create_flavor_with_uuid_id(self):
- flavor_id = str(uuid.uuid4())
+ flavor_id = data_utils.rand_uuid()
new_flavor_id = self._create_flavor(flavor_id)
self.assertEqual(new_flavor_id, flavor_id)
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 5070fd7..1b7eb12 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -146,4 +143,4 @@
self.assertRaises(lib_exc.NotFound,
self.client.remove_flavor_access,
new_flavor['id'],
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index 14646e8..b0ab9be 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index f6ea3a4..a9e9644 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -49,7 +49,7 @@
@test.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 retured
+ # successful and no hosts will be returned
hosts = self.client.list_hosts(zone='xxx')['hosts']
self.assertEqual(0, len(hosts))
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index 65ada4d..8366945 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 0e8012a..9c6df7f 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -39,7 +36,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
def test_show_nonexistent_hypervisor(self):
- nonexistent_hypervisor_id = str(uuid.uuid4())
+ nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
@@ -71,7 +68,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
def test_show_servers_with_nonexistent_hypervisor(self):
- nonexistent_hypervisor_id = str(uuid.uuid4())
+ nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
@@ -88,7 +85,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('f60aa680-9a3a-4c7d-90e1-fae3a4891303')
def test_get_nonexistent_hypervisor_uptime(self):
- nonexistent_hypervisor_id = str(uuid.uuid4())
+ nonexistent_hypervisor_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
index eea3103..b908502 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -16,9 +16,9 @@
import datetime
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
new file mode 100644
index 0000000..dfa7c39
--- /dev/null
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -0,0 +1,76 @@
+# Copyright 2016 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.compute.keypairs import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class KeyPairsV210TestJSON(base.BaseKeypairTest):
+ credentials = ['primary', 'admin']
+ min_microversion = '2.10'
+
+ @classmethod
+ def setup_clients(cls):
+ super(KeyPairsV210TestJSON, cls).setup_clients()
+ cls.client = cls.os_adm.keypairs_client
+ cls.non_admin_client = cls.os.keypairs_client
+
+ def _create_and_check_keypairs(self, user_id):
+ key_list = list()
+ for i in range(2):
+ k_name = data_utils.rand_name('keypair')
+ keypair = self._create_keypair(k_name,
+ keypair_type='ssh',
+ user_id=user_id)
+ self.assertEqual(k_name, keypair['name'],
+ "The created keypair name is not equal "
+ "to the requested name!")
+ self.assertEqual(user_id, keypair['user_id'],
+ "The created keypair is not for requested user!")
+ keypair.pop('private_key', None)
+ keypair.pop('user_id')
+ key_list.append(keypair)
+ return key_list
+
+ @test.idempotent_id('3c8484af-cfb3-48f6-b8ba-d5d58bbf3eac')
+ def test_admin_manage_keypairs_for_other_users(self):
+ user_id = self.non_admin_client.user_id
+ key_list = self._create_and_check_keypairs(user_id)
+ first_keyname = key_list[0]['name']
+ keypair_detail = self.client.show_keypair(first_keyname,
+ user_id=user_id)['keypair']
+ self.assertEqual(first_keyname, keypair_detail['name'])
+ self.assertEqual(user_id, keypair_detail['user_id'],
+ "The fetched keypair is not for requested user!")
+ # Create a admin keypair
+ admin_k_name = data_utils.rand_name('keypair')
+ admin_keypair = self._create_keypair(admin_k_name, keypair_type='ssh')
+ admin_keypair.pop('private_key', None)
+ admin_keypair.pop('user_id')
+
+ # Admin fetch keypairs list of non admin user
+ keypairs = self.client.list_keypairs(user_id=user_id)['keypairs']
+ fetched_list = [keypair['keypair'] for keypair in keypairs]
+
+ # Check admin keypair is not present in non admin user keypairs list
+ self.assertNotIn(admin_keypair, fetched_list,
+ "The fetched user keypairs has admin keypair!")
+
+ # Now check if all the created keypairs are in the fetched list
+ missing_kps = [kp for kp in key_list if kp not in fetched_list]
+ self.assertFalse(missing_kps,
+ "Failed to find keypairs %s in fetched list"
+ % ', '.join(m_key['name'] for m_key in missing_kps))
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 653a3cd..94635ff 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -19,6 +19,7 @@
from tempest.api.compute import base
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
@@ -62,7 +63,7 @@
def _get_host_for_server(self, server_id):
return self._get_server_details(server_id)[self._host_key]
- def _migrate_server_to(self, server_id, dest_host, volume_backed):
+ def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
block_migration = (CONF.compute_feature_enabled.
block_migration_for_live_migration and
not volume_backed)
@@ -76,14 +77,12 @@
if host != target_host:
return target_host
- def _get_server_status(self, server_id):
- return self._get_server_details(server_id)['status']
-
def _volume_clean_up(self, server_id, volume_id):
body = self.volumes_client.show_volume(volume_id)['volume']
if body['status'] == 'in-use':
self.servers_client.detach_volume(server_id, volume_id)
- self.volumes_client.wait_for_volume_status(volume_id, 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume_id, 'available')
self.volumes_client.delete_volume(volume_id)
def _test_live_migration(self, state='ACTIVE', volume_backed=False):
@@ -129,13 +128,11 @@
@test.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
- @testtools.skipUnless(CONF.compute_feature_enabled
- .live_migrate_paused_instances,
- 'Live migration of paused instances is not '
- 'available.')
def test_live_block_migration_paused(self):
self._test_live_migration(state='PAUSED')
+ @decorators.skip_because(bug="1549511",
+ condition=CONF.service_available.neutron)
@test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
@test.services('volume')
def test_volume_backed_live_migration(self):
@@ -156,14 +153,15 @@
volume = self.volumes_client.create_volume(
display_name='test')['volume']
- self.volumes_client.wait_for_volume_status(volume['id'],
- 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
self.addCleanup(self._volume_clean_up, server_id, volume['id'])
# Attach the volume to the server
self.servers_client.attach_volume(server_id, volumeId=volume['id'],
device='/dev/xvdb')
- self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'in-use')
self._migrate_server_to(server_id, target_host)
waiters.wait_for_server_status(self.servers_client,
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index f81d665..6113c04 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -15,8 +15,10 @@
import testtools
from tempest.api.compute import base
+from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions
from tempest import test
CONF = config.CONF
@@ -28,6 +30,7 @@
def setup_clients(cls):
super(MigrationsAdminTest, cls).setup_clients()
cls.client = cls.os_adm.migrations_client
+ cls.flavors_admin_client = cls.os_adm.flavors_client
@test.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
def test_list_migrations(self):
@@ -53,3 +56,50 @@
instance_uuids = [x['instance_uuid'] for x in body]
self.assertIn(server_id, instance_uuids)
+
+ def _flavor_clean_up(self, flavor_id):
+ try:
+ self.flavors_admin_client.delete_flavor(flavor_id)
+ self.flavors_admin_client.wait_for_resource_deletion(flavor_id)
+ except exceptions.NotFound:
+ pass
+
+ @test.idempotent_id('33f1fec3-ba18-4470-8e4e-1d888e7c3593')
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ def test_resize_server_revert_deleted_flavor(self):
+ # Tests that we can revert the resize on an instance whose original
+ # flavor has been deleted.
+
+ # First we have to create a flavor that we can delete so make a copy
+ # of the normal flavor from which we'd create a server.
+ flavor = self.flavors_admin_client.show_flavor(
+ self.flavor_ref)['flavor']
+ flavor = self.flavors_admin_client.create_flavor(
+ name=data_utils.rand_name('test_resize_flavor_'),
+ ram=flavor['ram'],
+ disk=flavor['disk'],
+ vcpus=flavor['vcpus']
+ )['flavor']
+ self.addCleanup(self._flavor_clean_up, flavor['id'])
+
+ # Now boot a server with the copied flavor.
+ server = self.create_test_server(
+ wait_until='ACTIVE', flavor=flavor['id'])
+
+ # Delete the flavor we used to boot the instance.
+ self._flavor_clean_up(flavor['id'])
+
+ # Now resize the server and wait for it to go into verify state.
+ self.servers_client.resize_server(server['id'], self.flavor_ref_alt)
+ waiters.wait_for_server_status(self.servers_client, server['id'],
+ 'VERIFY_RESIZE')
+
+ # Now revert the resize, it should be OK even though the original
+ # flavor used to boot the server was deleted.
+ self.servers_client.revert_resize_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
+ 'ACTIVE')
+
+ server = self.servers_client.show_server(server['id'])['server']
+ self.assertEqual(flavor['id'], server['flavor']['id'])
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 2907e26..b1f0755 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -185,7 +185,7 @@
# increment all of the values for updating the default quota class
for quota, default in six.iteritems(body):
# NOTE(sdague): we need to increment a lot, otherwise
- # there is a real chance that we go from -1 (unlimitted)
+ # there is a real chance that we go from -1 (unlimited)
# to a very small number which causes issues.
body[quota] = default + 100
LOG.debug("update limits for the default quota class set")
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index bdbfde4..d6aba5b 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
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 74f3caa..ce350b6 100644
--- a/tempest/api/compute/admin/test_security_group_default_rules.py
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 49c7318..3eb6d94 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -12,13 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.compute import base
from tempest.common import compute
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import decorators
from tempest import test
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 23b8a6c..7437c14 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -12,9 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
@@ -22,6 +19,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -68,7 +66,11 @@
flavor_id = self._get_unused_flavor_id()
quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
['quota_set'])
- ram = int(quota_set['ram']) + 1
+ ram = int(quota_set['ram'])
+ if ram == -1:
+ raise self.skipException("default ram quota set is -1,"
+ " cannot test overlimit")
+ ram += 1
vcpus = 8
disk = 10
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
@@ -93,7 +95,11 @@
ram = 512
quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
['quota_set'])
- vcpus = int(quota_set['cores']) + 1
+ vcpus = int(quota_set['cores'])
+ if vcpus == -1:
+ raise self.skipException("default cores quota set is -1,"
+ " cannot test overlimit")
+ vcpus += 1
disk = 10
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
ram=ram, vcpus=vcpus,
@@ -139,7 +145,7 @@
# migrate a non existent server
self.assertRaises(lib_exc.NotFound,
self.client.migrate_server,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.idempotent_id('b0b17f83-d14e-4fc4-8f31-bcc9f3cfa629')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 814a876..1bbde98 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest import config
from tempest import test
@@ -34,6 +36,9 @@
server_id)['server']['OS-EXT-SRV-ATTR:host']
@test.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("SameHostFilter"),
+ 'SameHostFilter is not available.')
def test_create_servers_on_same_host(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
@@ -45,6 +50,9 @@
self.assertEqual(host01, host02)
@test.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
@@ -56,6 +64,9 @@
self.assertNotEqual(host01, host02)
@test.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts_with_list_of_servers(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index e57401a..710cac4 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index e5c17ca..a4ed8dc 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -16,8 +16,8 @@
import datetime
from tempest.api.compute import base
+from tempest.lib import exceptions as e
from tempest import test
-from tempest_lib import exceptions as e
# Time that waits for until returning valid response
# TODO(takmatsu): Ideally this value would come from configuration.
@@ -59,7 +59,9 @@
return True
except e.InvalidHTTPResponseBody:
return False
- test.call_until_true(is_valid, duration, 1)
+ self.assertEqual(test.call_until_true(is_valid, duration, 1), True,
+ "%s not return valid response in %s secs" % (
+ func.__name__, duration))
return self.resp
@test.idempotent_id('062c8ae9-9912-4249-8b51-e38d664e926e')
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
index e9b4ad4..315116e 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
@@ -14,9 +14,9 @@
# under the License.
import datetime
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/api_microversion_fixture.py b/tempest/api/compute/api_microversion_fixture.py
new file mode 100644
index 0000000..695af52
--- /dev/null
+++ b/tempest/api/compute/api_microversion_fixture.py
@@ -0,0 +1,31 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from tempest.lib.services.compute import base_compute_client
+
+
+class APIMicroversionFixture(fixtures.Fixture):
+
+ def __init__(self, compute_microversion):
+ self.compute_microversion = compute_microversion
+
+ def _setUp(self):
+ super(APIMicroversionFixture, self)._setUp()
+ base_compute_client.COMPUTE_MICROVERSION = self.compute_microversion
+ self.addCleanup(self._reset_compute_microversion)
+
+ def _reset_compute_microversion(self):
+ base_compute_client.COMPUTE_MICROVERSION = None
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 0856983..77ed7cd 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -16,14 +16,15 @@
import time
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
-from tempest.common import api_version_utils
+from tempest.api.compute import api_microversion_fixture
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib.common import api_version_utils
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -46,8 +47,8 @@
super(BaseV2ComputeTest, cls).skip_checks()
if not CONF.service_available.nova:
raise cls.skipException("Nova is not available")
- cfg_min_version = CONF.compute_feature_enabled.min_microversion
- cfg_max_version = CONF.compute_feature_enabled.max_microversion
+ cfg_min_version = CONF.compute.min_microversion
+ cfg_max_version = CONF.compute.max_microversion
api_version_utils.check_skip_with_microversion(cls.min_microversion,
cls.max_microversion,
cfg_min_version,
@@ -56,13 +57,6 @@
@classmethod
def setup_credentials(cls):
cls.set_network_resources()
- cls.request_microversion = (
- api_version_utils.select_request_microversion(
- cls.min_microversion,
- CONF.compute_feature_enabled.min_microversion))
- if cls.request_microversion:
- cls.services_microversion = {
- CONF.compute.catalog_type: cls.request_microversion}
super(BaseV2ComputeTest, cls).setup_credentials()
@classmethod
@@ -108,6 +102,10 @@
@classmethod
def resource_setup(cls):
super(BaseV2ComputeTest, cls).resource_setup()
+ cls.request_microversion = (
+ api_version_utils.select_request_microversion(
+ cls.min_microversion,
+ CONF.compute.min_microversion))
cls.build_interval = CONF.compute.build_interval
cls.build_timeout = CONF.compute.build_timeout
cls.image_ref = CONF.compute.image_ref
@@ -371,6 +369,11 @@
else:
raise exceptions.InvalidConfiguration()
+ def setUp(self):
+ super(BaseV2ComputeTest, self).setUp()
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ self.request_microversion))
+
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin API tests."""
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 5b90641..3d7f4f8 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -13,13 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.floating_ips import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
+CONF = config.CONF
+
class FloatingIPsTestJSON(base.BaseFloatingIPsTest):
server_id = None
@@ -39,7 +41,8 @@
server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
# Floating IP creation
- body = cls.client.create_floating_ip()['floating_ip']
+ body = cls.client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
cls.floating_ip_id = body['id']
cls.floating_ip = body['ip']
@@ -63,7 +66,8 @@
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
- body = self.client.create_floating_ip()['floating_ip']
+ body = self.client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
floating_ip_id_allocated = body['id']
self.addCleanup(self.client.delete_floating_ip,
floating_ip_id_allocated)
@@ -79,7 +83,8 @@
# Positive test:Deletion of valid floating IP from project
# should be successful
# Creating the floating IP that is to be deleted in this method
- floating_ip_body = self.client.create_floating_ip()['floating_ip']
+ floating_ip_body = self.client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(self._try_delete_floating_ip, floating_ip_body['id'])
# Deleting the floating IP from the project
self.client.delete_floating_ip(floating_ip_body['id'])
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 0223c0d..f71f046 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
@@ -13,13 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.floating_ips import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -48,7 +45,7 @@
while True:
cls.non_exist_id = data_utils.rand_int_id(start=999)
if CONF.service_available.neutron:
- cls.non_exist_id = str(uuid.uuid4())
+ cls.non_exist_id = data_utils.rand_uuid()
if cls.non_exist_id not in cls.floating_ip_ids:
break
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 d003967..5738293 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -14,8 +14,11 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class FloatingIPDetailsTestJSON(base.BaseV2ComputeTest):
@@ -31,7 +34,8 @@
cls.floating_ip = []
cls.floating_ip_id = []
for i in range(3):
- body = cls.client.create_floating_ip()['floating_ip']
+ body = cls.client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
cls.floating_ip.append(body)
cls.floating_ip_id.append(body['id'])
@@ -57,7 +61,8 @@
def test_get_floating_ip_details(self):
# Positive test:Should be able to GET the details of floatingIP
# Creating a floating IP for which details are to be checked
- body = self.client.create_floating_ip()['floating_ip']
+ body = self.client.create_floating_ip(
+ pool=CONF.network.floating_network_name)['floating_ip']
floating_ip_id = body['id']
self.addCleanup(self.client.delete_floating_ip,
floating_ip_id)
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 75b6b55..ea56ae9 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
@@ -13,13 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -40,7 +37,7 @@
# of non-existent floating IP
# Creating a non-existent floatingIP id
if CONF.service_available.neutron:
- non_exist_id = str(uuid.uuid4())
+ non_exist_id = data_utils.rand_uuid()
else:
non_exist_id = data_utils.rand_int_id(start=999)
self.assertRaises(lib_exc.NotFound,
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 85d137b..9cb9e03 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 8f6ede9..e91944f 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -68,7 +67,7 @@
resp = {}
resp['status'] = None
self.assertRaises(lib_exc.NotFound, self.create_image_from_server,
- '!@#$%^&*()', name=name, meta=meta)
+ '!@$^&*()', name=name, meta=meta)
@test.attr(type=['negative'])
@test.idempotent_id('ec176029-73dc-4037-8d72-2e4ff60cf538')
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 2fc9ef8..d9b80e1 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -15,12 +15,12 @@
# under the License.
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/images/test_list_image_filters_negative.py b/tempest/api/compute/images/test_list_image_filters_negative.py
index 34d26e2..2689f88 100644
--- a/tempest/api/compute/images/test_list_image_filters_negative.py
+++ b/tempest/api/compute/images/test_list_image_filters_negative.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/keypairs/base.py b/tempest/api/compute/keypairs/base.py
index ebfb724..ad7f958 100644
--- a/tempest/api/compute/keypairs/base.py
+++ b/tempest/api/compute/keypairs/base.py
@@ -24,15 +24,21 @@
super(BaseKeypairTest, cls).setup_clients()
cls.client = cls.keypairs_client
- def _delete_keypair(self, keypair_name):
- self.client.delete_keypair(keypair_name)
+ def _delete_keypair(self, keypair_name, **params):
+ self.client.delete_keypair(keypair_name, **params)
- def _create_keypair(self, keypair_name, pub_key=None, keypair_type=None):
+ def _create_keypair(self, keypair_name,
+ pub_key=None, keypair_type=None,
+ user_id=None):
kwargs = {'name': keypair_name}
+ delete_params = {}
if pub_key:
kwargs.update({'public_key': pub_key})
if keypair_type:
kwargs.update({'type': keypair_type})
+ if user_id:
+ kwargs.update({'user_id': user_id})
+ delete_params['user_id'] = user_id
body = self.client.create_keypair(**kwargs)['keypair']
- self.addCleanup(self._delete_keypair, keypair_name)
+ self.addCleanup(self._delete_keypair, keypair_name, **delete_params)
return body
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 0ab78fb..2a6139b 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.keypairs import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index 73e852f..66bc241 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -31,7 +30,6 @@
def setup_clients(cls):
super(AbsoluteLimitsNegativeTestJSON, cls).setup_clients()
cls.client = cls.limits_client
- cls.server_client = cls.servers_client
@test.attr(type=['negative'])
@test.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 816038a..853ef31 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 81a02be..f6353c8 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
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 120d327..5125e2b 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -13,13 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index a6ccdd3..fdf55e5 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -15,11 +15,11 @@
import time
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.common.utils import net_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -45,6 +45,9 @@
def setup_clients(cls):
super(AttachInterfacesTestJSON, cls).setup_clients()
cls.client = cls.os.interfaces_client
+ cls.networks_client = cls.os.networks_client
+ cls.subnets_client = cls.os.subnets_client
+ cls.ports_client = cls.os.ports_client
def wait_for_interface_status(self, server, port_id, status):
"""Waits for an interface to reach a given status."""
@@ -108,6 +111,37 @@
self._check_interface(iface, network_id=network_id)
return iface
+ def _test_create_interface_by_port_id(self, server, ifs):
+ network_id = ifs[0]['net_id']
+ port = self.ports_client.create_port(network_id=network_id)
+ port_id = port['port']['id']
+ self.addCleanup(self.ports_client.delete_port, port_id)
+ iface = self.client.create_interface(
+ server['id'], port_id=port_id)['interfaceAttachment']
+ iface = self.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface, port_id=port_id)
+ return iface
+
+ def _test_create_interface_by_fixed_ips(self, server, ifs):
+ network_id = ifs[0]['net_id']
+ subnet_id = ifs[0]['fixed_ips'][0]['subnet_id']
+ ip_list = net_utils.get_unused_ip_addresses(self.ports_client,
+ self.subnets_client,
+ network_id,
+ subnet_id,
+ 1)
+
+ fixed_ips = [{'ip_address': ip_list[0]}]
+ iface = self.client.create_interface(
+ server['id'], net_id=network_id,
+ fixed_ips=fixed_ips)['interfaceAttachment']
+ self.addCleanup(self.ports_client.delete_port, iface['port_id'])
+ iface = self.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface, fixed_ip=ip_list[0])
+ return iface
+
def _test_show_interface(self, server, ifs):
iface = ifs[0]
_iface = self.client.show_interface(
@@ -167,6 +201,12 @@
iface = self._test_create_interface_by_network_id(server, ifs)
ifs.append(iface)
+ iface = self._test_create_interface_by_port_id(server, ifs)
+ ifs.append(iface)
+
+ iface = self._test_create_interface_by_fixed_ips(server, ifs)
+ ifs.append(iface)
+
_ifs = (self.client.list_interfaces(server['id'])
['interfaceAttachments'])
self._compare_iface_list(ifs, _ifs)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index f719bfc..07423ff 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -38,7 +38,6 @@
def setup_clients(cls):
super(ServersTestJSON, cls).setup_clients()
cls.client = cls.servers_client
- cls.network_client = cls.os.network_client
cls.networks_client = cls.os.networks_client
cls.subnets_client = cls.os.subnets_client
@@ -120,7 +119,9 @@
self.get_server_ip(self.server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.client)
self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
@test.idempotent_id('ac1ad47f-984b-4441-9274-c9079b7a0666')
@@ -132,7 +133,9 @@
self.get_server_ip(self.server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.client)
self.assertTrue(linux_client.hostname_equals_servername(self.name))
@test.idempotent_id('ed20d3fb-9d1f-4329-b160-543fbd5d9811')
@@ -201,10 +204,6 @@
@test.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2')
@testtools.skipUnless(CONF.service_available.neutron,
'Neutron service must be available.')
- # The below skipUnless should be removed once Kilo-eol happens.
- @testtools.skipUnless(CONF.compute_feature_enabled.
- allow_duplicate_networks,
- 'Duplicate networks must be allowed')
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.
@@ -323,7 +322,9 @@
self.get_server_ip(server_no_eph_disk),
self.ssh_user,
admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server_no_eph_disk,
+ servers_client=self.client)
partition_num = len(linux_client.get_partitions().split('\n'))
# Explicit server deletion necessary for Juno compatibility
@@ -341,7 +342,9 @@
self.get_server_ip(server_with_eph_disk),
self.ssh_user,
admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server_with_eph_disk,
+ servers_client=self.client)
partition_num_emph = len(linux_client.get_partitions().split('\n'))
self.assertEqual(partition_num + 1, partition_num_emph)
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 6796bb5..079465d 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common import waiters
from tempest import config
from tempest import test
@@ -84,16 +85,8 @@
def test_delete_server_while_in_shelved_state(self):
# Delete a server while it's VM state is Shelved
server = self.create_test_server(wait_until='ACTIVE')
- self.client.shelve_server(server['id'])
+ compute.shelve_server(self.client, server['id'])
- offload_time = CONF.compute.shelved_offload_time
- if offload_time >= 0:
- waiters.wait_for_server_status(self.client, server['id'],
- 'SHELVED_OFFLOADED',
- extra_timeout=offload_time)
- else:
- waiters.wait_for_server_status(self.client, server['id'],
- 'SHELVED')
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
index ac66d05..54ec6aa 100644
--- a/tempest/api/compute/servers/test_instance_actions_negative.py
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 37f322f..c1fbb12 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -13,15 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
-from tempest.api import utils
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -90,7 +88,7 @@
wait_until='ACTIVE')
@test.idempotent_id('05e8a8e7-9659-459a-989d-92c2f501f4ba')
- @utils.skip_unless_attr('multiple_images', 'Only one image found')
+ @decorators.skip_unless_attr('multiple_images', 'Only one image found')
def test_list_servers_filter_by_image(self):
# Filter the list of servers by image
params = {'image': self.image_ref}
@@ -175,7 +173,7 @@
len([x for x in servers['servers'] if 'id' in x]))
@test.idempotent_id('b3304c3b-97df-46d2-8cd3-e2b6659724e7')
- @utils.skip_unless_attr('multiple_images', 'Only one image found')
+ @decorators.skip_unless_attr('multiple_images', 'Only one image found')
def test_list_servers_detailed_filter_by_image(self):
# Filter the detailed list of servers by image
params = {'image': self.image_ref}
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index f205ddf..b18789e 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -14,10 +14,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_multiple_create_negative.py b/tempest/api/compute/servers/test_multiple_create_negative.py
index 3d8a732..e5b4f46 100644
--- a/tempest/api/compute/servers/test_multiple_create_negative.py
+++ b/tempest/api/compute/servers/test_multiple_create_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 66e85a6..66aec84 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -16,15 +16,16 @@
import logging
from six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -90,7 +91,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user,
- new_password)
+ new_password,
+ server=server,
+ servers_client=self.client)
linux_client.validate_authentication()
def _test_reboot_server(self, reboot_type):
@@ -101,7 +104,9 @@
self.get_server_ip(server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
boot_time = linux_client.get_boot_time()
self.client.reboot_server(self.server_id, type=reboot_type)
@@ -113,7 +118,9 @@
self.get_server_ip(server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
new_boot_time = linux_client.get_boot_time()
self.assertTrue(new_boot_time > boot_time,
'%s > %s' % (new_boot_time, boot_time))
@@ -182,7 +189,9 @@
self.get_server_ip(rebuilt_server),
self.ssh_user,
password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=rebuilt_server,
+ servers_client=self.client)
linux_client.validate_authentication()
@test.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
@@ -217,6 +226,35 @@
self.client.start_server(self.server_id)
+ @test.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
+ @test.services('volume')
+ def test_rebuild_server_with_volume_attached(self):
+ # create a new volume and attach it to the server
+ volume = self.volumes_client.create_volume(
+ size=CONF.volume.volume_size)
+ volume = volume['volume']
+ self.addCleanup(self.volumes_client.delete_volume, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+
+ self.client.attach_volume(self.server_id, volumeId=volume['id'])
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ volume['id'], 'available')
+ self.addCleanup(self.client.detach_volume,
+ self.server_id, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'in-use')
+
+ # run general rebuild test
+ 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'])
+ vol_after_rebuild = vol_after_rebuild['volume']
+ self.assertEqual('in-use', vol_after_rebuild['status'])
+ self.assertEqual(self.server_id,
+ vol_after_rebuild['attachments'][0]['server_id'])
+
def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
@@ -303,7 +341,8 @@
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
- self.os.image_client.wait_for_image_status(image1_id, 'active')
+ waiters.wait_for_image_status(self.os.image_client,
+ image1_id, 'active')
backup2 = data_utils.rand_name('backup-2')
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
@@ -313,7 +352,8 @@
name=backup2).response
image2_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(self.os.image_client.delete_image, image2_id)
- self.os.image_client.wait_for_image_status(image2_id, 'active')
+ waiters.wait_for_image_status(self.os.image_client,
+ image2_id, 'active')
# verify they have been created
properties = {
@@ -440,20 +480,8 @@
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
def test_shelve_unshelve_server(self):
- self.client.shelve_server(self.server_id)
-
- offload_time = CONF.compute.shelved_offload_time
- if offload_time >= 0:
- waiters.wait_for_server_status(self.client, self.server_id,
- 'SHELVED_OFFLOADED',
- extra_timeout=offload_time)
- else:
- waiters.wait_for_server_status(self.client, self.server_id,
- 'SHELVED')
-
- self.client.shelve_offload_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id,
- 'SHELVED_OFFLOADED')
+ 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'
diff --git a/tempest/api/compute/servers/test_server_addresses_negative.py b/tempest/api/compute/servers/test_server_addresses_negative.py
index 3503dc2..b4753e1 100644
--- a/tempest/api/compute/servers/test_server_addresses_negative.py
+++ b/tempest/api/compute/servers/test_server_addresses_negative.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index c23b365..e32f6b0 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import test
@@ -81,13 +79,6 @@
policy = ['anti-affinity']
self._create_delete_server_group(policy)
- @decorators.skip_because(bug="1324348")
- @test.idempotent_id('6d9bae05-eb32-425d-a673-e14e1b1c6306')
- def test_create_delete_server_group_with_multiple_policies(self):
- # Create and Delete the server-group with multiple policies
- policies = ['affinity', 'affinity']
- self._create_delete_server_group(policies)
-
@test.idempotent_id('154dc5a4-a2fe-44b5-b99e-f15806a4a113')
def test_create_delete_multiple_server_groups_with_same_name_policy(self):
# Create and Delete the server-groups with same name and same policy
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index 18d80be..cbe70e2 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index dad8e90..baa4f9a 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -14,13 +14,13 @@
# under the License.
import base64
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -66,7 +66,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user, password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
self.assertEqual(file_contents,
linux_client.exec_command(
'sudo cat %s' % file_path))
@@ -130,7 +132,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user, password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
for i in person:
self.assertEqual(base64.b64decode(i['contents']),
linux_client.exec_command(
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 12b824f..32faf7d 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -41,7 +41,8 @@
super(ServerRescueTestJSON, cls).resource_setup()
# Floating IP creation
- body = cls.floating_ips_client.create_floating_ip()['floating_ip']
+ 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()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 5afb4d1..8d63b6b 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -13,13 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 2f79d47..e91857a 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -80,7 +80,7 @@
self.assertEqual(key_name, server['key_name'])
def _update_server_name(self, server_id, status, prefix_name='server'):
- # The server name should be changed to the the provided value
+ # The server name should be changed to the provided value
new_name = data_utils.rand_name(prefix_name)
# Update the server with a new name
@@ -95,7 +95,7 @@
@test.idempotent_id('5e6ccff8-349d-4852-a8b3-055df7988dd2')
def test_update_server_name(self):
- # The server name should be changed to the the provided value
+ # The server name should be changed to the provided value
server = self.create_test_server(wait_until='ACTIVE')
# Update instance name with non-ASCII characters
prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
@@ -103,7 +103,7 @@
@test.idempotent_id('6ac19cb1-27a3-40ec-b350-810bdc04c08e')
def test_update_server_name_in_stop_state(self):
- # The server name should be changed to the the provided value
+ # The server name should be changed to the provided value
server = self.create_test_server(wait_until='ACTIVE')
self.client.stop_server(server['id'])
waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 681b5db..10ea31d 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -15,13 +15,14 @@
import sys
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -29,8 +30,6 @@
class ServersNegativeTestJSON(base.BaseV2ComputeTest):
- credentials = ['primary', 'alt']
-
def setUp(self):
super(ServersNegativeTestJSON, self).setUp()
try:
@@ -47,7 +46,6 @@
def setup_clients(cls):
super(ServersNegativeTestJSON, cls).setup_clients()
cls.client = cls.servers_client
- cls.alt_client = cls.os_alt.servers_client
@classmethod
def resource_setup(cls):
@@ -271,16 +269,6 @@
self.server_id, name=new_name)
@test.attr(type=['negative'])
- @test.idempotent_id('543d84c1-dd2e-4c6d-8cb2-b9da0efaa384')
- def test_update_server_of_another_tenant(self):
- # Update name of a server that belongs to another tenant
-
- new_name = self.server_id + '_new'
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.update_server, self.server_id,
- name=new_name)
-
- @test.attr(type=['negative'])
@test.idempotent_id('5c8e244c-dada-4590-9944-749c455b431f')
def test_update_server_name_length_exceeds_256(self):
# Update name of server exceed the name length limit
@@ -301,14 +289,6 @@
nonexistent_server)
@test.attr(type=['negative'])
- @test.idempotent_id('5c75009d-3eea-423e-bea3-61b09fd25f9c')
- def test_delete_a_server_of_another_tenant(self):
- # Delete a server that belongs to another tenant
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.delete_server,
- self.server_id)
-
- @test.attr(type=['negative'])
@test.idempotent_id('75f79124-277c-45e6-a373-a1d6803f4cc4')
def test_delete_server_pass_negative_id(self):
# Pass an invalid string parameter to delete server
@@ -474,18 +454,7 @@
@test.attr(type=['negative'])
def test_shelve_shelved_server(self):
# shelve a shelved server.
- self.client.shelve_server(self.server_id)
-
- offload_time = CONF.compute.shelved_offload_time
- if offload_time >= 0:
- waiters.wait_for_server_status(self.client,
- self.server_id,
- 'SHELVED_OFFLOADED',
- extra_timeout=offload_time)
- else:
- waiters.wait_for_server_status(self.client,
- self.server_id,
- 'SHELVED')
+ compute.shelve_server(self.client, self.server_id)
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
@@ -519,3 +488,45 @@
self.assertRaises(lib_exc.Conflict,
self.client.unshelve_server,
self.server_id)
+
+
+class ServersNegativeTestMultiTenantJSON(base.BaseV2ComputeTest):
+
+ credentials = ['primary', 'alt']
+
+ def setUp(self):
+ super(ServersNegativeTestMultiTenantJSON, self).setUp()
+ try:
+ waiters.wait_for_server_status(self.client, self.server_id,
+ 'ACTIVE')
+ except Exception:
+ self.__class__.server_id = self.rebuild_server(self.server_id)
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServersNegativeTestMultiTenantJSON, cls).setup_clients()
+ cls.alt_client = cls.os_alt.servers_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(ServersNegativeTestMultiTenantJSON, cls).resource_setup()
+ server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('543d84c1-dd2e-4c6d-8cb2-b9da0efaa384')
+ def test_update_server_of_another_tenant(self):
+ # Update name of a server that belongs to another tenant
+
+ new_name = self.server_id + '_new'
+ self.assertRaises(lib_exc.NotFound,
+ self.alt_client.update_server, self.server_id,
+ name=new_name)
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('5c75009d-3eea-423e-bea3-61b09fd25f9c')
+ def test_delete_a_server_of_another_tenant(self):
+ # Delete a server that belongs to another tenant
+ self.assertRaises(lib_exc.NotFound,
+ self.alt_client.delete_server,
+ self.server_id)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 7aa6d34..e620e03 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -14,10 +14,11 @@
# under the License.
import netaddr
-from tempest_lib import decorators
+import testtools
from tempest.api.compute import base
from tempest import config
+from tempest.lib import exceptions
from tempest import test
CONF = config.CONF
@@ -42,20 +43,26 @@
server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
- @decorators.skip_because(bug="1183436",
- condition=CONF.service_available.neutron)
@test.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016')
@test.services('network')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
# for a given server_id
- output = self.client.list_virtual_interfaces(self.server_id)
- self.assertIsNotNone(output)
- virt_ifaces = output
- self.assertNotEqual(0, len(virt_ifaces['virtual_interfaces']),
- 'Expected virtual interfaces, got 0 interfaces.')
- for virt_iface in virt_ifaces['virtual_interfaces']:
- mac_address = virt_iface['mac_address']
- self.assertTrue(netaddr.valid_mac(mac_address),
- "Invalid mac address detected. mac address: %s"
- % mac_address)
+
+ if CONF.service_available.neutron:
+ # TODO(mriedem): After a microversion implements the API for
+ # neutron, a 400 should be a failure for nova-network and neutron.
+ with testtools.ExpectedException(exceptions.BadRequest):
+ 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']),
+ 'Expected virtual interfaces, got 0 '
+ 'interfaces.')
+ for virt_iface in virt_ifaces['virtual_interfaces']:
+ mac_address = virt_iface['mac_address']
+ self.assertTrue(netaddr.valid_mac(mac_address),
+ "Invalid mac address detected. mac address: %s"
+ % mac_address)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index 577a673..912b0a1 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -13,11 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -40,7 +38,7 @@
def test_list_virtual_interfaces_invalid_server_id(self):
# Negative test: Should not be able to GET virtual interfaces
# for an invalid server_id
- invalid_server_id = str(uuid.uuid4())
+ invalid_server_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.client.list_virtual_interfaces,
invalid_server_id)
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
deleted file mode 100644
index bf4396d..0000000
--- a/tempest/api/compute/test_authorization.py
+++ /dev/null
@@ -1,451 +0,0 @@
-# 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 six
-
-from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
-
-from tempest.api.compute import base
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-LOG = logging.getLogger(__name__)
-
-
-class AuthorizationTestJSON(base.BaseV2ComputeTest):
-
- credentials = ['primary', 'alt']
-
- @classmethod
- def skip_checks(cls):
- super(AuthorizationTestJSON, cls).skip_checks()
- if not CONF.service_available.glance:
- raise cls.skipException('Glance is not available.')
-
- @classmethod
- def setup_credentials(cls):
- # No network resources required for this test
- cls.set_network_resources()
- super(AuthorizationTestJSON, cls).setup_credentials()
-
- @classmethod
- def setup_clients(cls):
- super(AuthorizationTestJSON, cls).setup_clients()
- cls.client = cls.os.servers_client
- cls.compute_images_client = cls.os.compute_images_client
- cls.glance_client = cls.os.image_client
- cls.keypairs_client = cls.os.keypairs_client
- cls.security_client = cls.os.compute_security_groups_client
- cls.rule_client = cls.os.compute_security_group_rules_client
-
- cls.alt_client = cls.alt_manager.servers_client
- cls.alt_compute_images_client = cls.alt_manager.compute_images_client
- cls.alt_keypairs_client = cls.alt_manager.keypairs_client
- cls.alt_security_client = (
- cls.alt_manager.compute_security_groups_client)
- cls.alt_rule_client = (
- cls.alt_manager.compute_security_group_rules_client)
-
- @classmethod
- def resource_setup(cls):
- super(AuthorizationTestJSON, cls).resource_setup()
- server = cls.create_test_server(wait_until='ACTIVE')
- cls.server = cls.client.show_server(server['id'])['server']
-
- name = data_utils.rand_name('image')
- body = cls.glance_client.create_image(name=name,
- container_format='bare',
- disk_format='raw',
- is_public=False)['image']
- image_id = body['id']
- image_file = six.StringIO(('*' * 1024))
- body = cls.glance_client.update_image(image_id,
- data=image_file)['image']
- cls.glance_client.wait_for_image_status(image_id, 'active')
- cls.image = cls.compute_images_client.show_image(image_id)['image']
-
- cls.keypairname = data_utils.rand_name('keypair')
- cls.keypairs_client.create_keypair(name=cls.keypairname)
-
- name = data_utils.rand_name('security')
- description = data_utils.rand_name('description')
- cls.security_group = cls.security_client.create_security_group(
- name=name, description=description)['security_group']
-
- parent_group_id = cls.security_group['id']
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 22
- cls.rule = cls.rule_client.create_security_group_rule(
- parent_group_id=parent_group_id, ip_protocol=ip_protocol,
- from_port=from_port, to_port=to_port)['security_group_rule']
-
- @classmethod
- def resource_cleanup(cls):
- if hasattr(cls, 'image'):
- cls.compute_images_client.delete_image(cls.image['id'])
- if hasattr(cls, 'keypairname'):
- cls.keypairs_client.delete_keypair(cls.keypairname)
- if hasattr(cls, 'security_group'):
- cls.security_client.delete_security_group(cls.security_group['id'])
- super(AuthorizationTestJSON, cls).resource_cleanup()
-
- @test.idempotent_id('56816e4a-bd34-47b5-aee9-268c3efeb5d4')
- def test_get_server_for_alt_account_fails(self):
- # A GET request for a server on another user's account should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.show_server,
- self.server['id'])
-
- @test.idempotent_id('fb8a4870-6d9d-44ad-8375-95d52e98d9f6')
- def test_delete_server_for_alt_account_fails(self):
- # A DELETE request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.delete_server,
- self.server['id'])
-
- @test.idempotent_id('d792f91f-1d49-4eb5-b1ff-b229c4b9dc64')
- def test_update_server_for_alt_account_fails(self):
- # An update server request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.update_server,
- self.server['id'], name='test')
-
- @test.idempotent_id('488f24df-d7f7-4207-949a-f17fcb8e8769')
- def test_list_server_addresses_for_alt_account_fails(self):
- # A list addresses request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.list_addresses,
- self.server['id'])
-
- @test.idempotent_id('00b442d0-2e72-40e7-9b1f-31772e36da01')
- def test_list_server_addresses_by_network_for_alt_account_fails(self):
- # A list address/network request for another user's server should fail
- server_id = self.server['id']
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.list_addresses_by_network, server_id,
- 'public')
-
- @test.idempotent_id('cc90b35a-19f0-45d2-b680-2aabf934aa22')
- def test_list_servers_with_alternate_tenant(self):
- # A list on servers from one tenant should not
- # show on alternate tenant
- # Listing servers from alternate tenant
- alt_server_ids = []
- body = self.alt_client.list_servers()
- alt_server_ids = [s['id'] for s in body['servers']]
- self.assertNotIn(self.server['id'], alt_server_ids)
-
- @test.idempotent_id('376dbc16-0779-4384-a723-752774799641')
- def test_change_password_for_alt_account_fails(self):
- # A change password request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.change_password,
- self.server['id'], adminPass='newpass')
-
- @test.idempotent_id('14cb5ff5-f646-45ca-8f51-09081d6c0c24')
- def test_reboot_server_for_alt_account_fails(self):
- # A reboot request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.reboot_server,
- self.server['id'], type='HARD')
-
- @test.idempotent_id('8a0bce51-cd00-480b-88ba-dbc7d8408a37')
- def test_rebuild_server_for_alt_account_fails(self):
- # A rebuild request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.rebuild_server,
- self.server['id'], self.image_ref_alt)
-
- @test.idempotent_id('e4da647e-f982-4e61-9dad-1d1abebfb933')
- def test_resize_server_for_alt_account_fails(self):
- # A resize request for another user's server should fail
- self.assertRaises(lib_exc.NotFound, self.alt_client.resize_server,
- self.server['id'], self.flavor_ref_alt)
-
- @test.idempotent_id('a9fe8112-0ffa-4902-b061-f892bd5fe0d3')
- def test_create_image_for_alt_account_fails(self):
- # A create image request for another user's server should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_compute_images_client.create_image,
- self.server['id'], name='testImage')
-
- @test.idempotent_id('95d445f6-babc-4f2e-aea3-aa24ec5e7f0d')
- def test_create_server_with_unauthorized_image(self):
- # Server creation with another user's image should fail
- self.assertRaises(lib_exc.BadRequest, self.alt_client.create_server,
- name='test', imageRef=self.image['id'],
- flavorRef=self.flavor_ref)
-
- @test.idempotent_id('acf8724b-142b-4044-82c3-78d31a533f24')
- def test_create_server_fails_when_tenant_incorrect(self):
- # BUG(sdague): this test should fail because of bad auth url,
- # which means that when we run with a service catalog without
- # project_id in the urls, it should fail to fail, and thus
- # fail the test. It does not.
- #
- # The 400 BadRequest is clearly ambiguous, and something else
- # is wrong about this request. This should be fixed.
- #
- # A create server request should fail if the tenant id does not match
- # the current user
- # Change the base URL to impersonate another user
- self.alt_client.auth_provider.set_alt_auth_data(
- request_part='url',
- auth_data=self.client.auth_provider.auth_data
- )
- self.assertRaises(lib_exc.BadRequest,
- self.alt_client.create_server, name='test',
- imageRef=self.image['id'], flavorRef=self.flavor_ref)
-
- @test.idempotent_id('f03d1ded-7fd4-4d29-bc13-e2391f29c625')
- def test_create_keypair_in_analt_user_tenant(self):
- """create keypair should not function for alternate tenant
-
- POST {alt_service_url}/os-keypairs
-
- Attempt to create a keypair against an alternate tenant by
- changing using a different tenant's service url. This should
- return a BadRequest. This tests basic tenant isolation protections.
-
- NOTE(sdague): if the environment does not use project_id in
- the service urls, this test is not valid. Skip under these
- conditions.
-
- """
- if self.alt_keypairs_client.base_url == self.keypairs_client.base_url:
- raise self.skipException("Service urls don't include project_id")
-
- k_name = data_utils.rand_name('keypair')
- try:
- # Change the base URL to impersonate another user
- self.alt_keypairs_client.auth_provider.set_alt_auth_data(
- request_part='url',
- auth_data=self.keypairs_client.auth_provider.auth_data
- )
- resp = {}
- resp['status'] = None
- self.assertRaises(lib_exc.BadRequest,
- self.alt_keypairs_client.create_keypair,
- name=k_name)
- finally:
- # Next request the base_url is back to normal
- if (resp['status'] is not None):
- self.alt_keypairs_client.delete_keypair(k_name)
- LOG.error("Create keypair request should not happen "
- "if the tenant id does not match the current user")
-
- @test.idempotent_id('85bcdd8f-56b4-4868-ae56-63fbf6f7e405')
- def test_get_keypair_of_alt_account_fails(self):
- # A GET request for another user's keypair should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_keypairs_client.show_keypair,
- self.keypairname)
-
- @test.idempotent_id('6d841683-a8e0-43da-a1b8-b339f7692b61')
- def test_delete_keypair_of_alt_account_fails(self):
- # A DELETE request for another user's keypair should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_keypairs_client.delete_keypair,
- self.keypairname)
-
- @test.idempotent_id('fcb2e144-36e3-4dfb-9f9f-e72fcdec5656')
- def test_get_image_for_alt_account_fails(self):
- # A GET request for an image on another user's account should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_compute_images_client.show_image,
- self.image['id'])
-
- @test.idempotent_id('9facb962-f043-4a9d-b9ee-166a32dea098')
- def test_delete_image_for_alt_account_fails(self):
- # A DELETE request for another user's image should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_compute_images_client.delete_image,
- self.image['id'])
-
- @test.idempotent_id('752c917e-83be-499d-a422-3559127f7d3c')
- def test_create_security_group_in_analt_user_tenant(self):
- """create security group should not function for alternate tenant
-
- POST {alt_service_url}/os-security-groups
-
- Attempt to create a security group against an alternate tenant
- by changing using a different tenant's service url. This
- should return a BadRequest. This tests basic tenant isolation
- protections.
-
- NOTE(sdague): if the environment does not use project_id in
- the service urls, this test is not valid. Skip under these
- conditions.
-
- """
- if self.alt_security_client.base_url == self.security_client.base_url:
- raise self.skipException("Service urls don't include project_id")
-
- s_name = data_utils.rand_name('security')
- s_description = data_utils.rand_name('security')
- try:
- # Change the base URL to impersonate another user
- self.alt_security_client.auth_provider.set_alt_auth_data(
- request_part='url',
- auth_data=self.security_client.auth_provider.auth_data
- )
- resp = {}
- resp['status'] = None
- self.assertRaises(lib_exc.BadRequest,
- self.alt_security_client.create_security_group,
- name=s_name, description=s_description)
- finally:
- # Next request the base_url is back to normal
- if resp['status'] is not None:
- self.alt_security_client.delete_security_group(resp['id'])
- LOG.error("Create Security Group request should not happen if"
- "the tenant id does not match the current user")
-
- @test.idempotent_id('9db3590f-4d15-4e5f-985e-b28514919a6f')
- def test_get_security_group_of_alt_account_fails(self):
- # A GET request for another user's security group should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_security_client.show_security_group,
- self.security_group['id'])
-
- @test.idempotent_id('155387a5-2bbc-4acf-ab06-698dae537ea5')
- def test_delete_security_group_of_alt_account_fails(self):
- # A DELETE request for another user's security group should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_security_client.delete_security_group,
- self.security_group['id'])
-
- @test.idempotent_id('b2b76de0-210a-4089-b921-591c9ec552f6')
- def test_create_security_group_rule_in_analt_user_tenant(self):
- """create security group rule should not function for alternate tenant
-
- POST {alt_service_url}/os-security-group-rules
-
- Attempt to create a security group rule against an alternate
- tenant by changing using a different tenant's service
- url. This should return a BadRequest. This tests basic tenant
- isolation protections.
-
- NOTE(sdague): if the environment does not use project_id in
- the service urls, this test is not valid. Skip under these
- conditions.
-
- """
- if self.alt_security_client.base_url == self.security_client.base_url:
- raise self.skipException("Service urls don't include project_id")
-
- parent_group_id = self.security_group['id']
- ip_protocol = 'icmp'
- from_port = -1
- to_port = -1
- try:
- # Change the base URL to impersonate another user
- self.alt_rule_client.auth_provider.set_alt_auth_data(
- request_part='url',
- auth_data=self.rule_client.auth_provider.auth_data
- )
- resp = {}
- resp['status'] = None
- self.assertRaises(lib_exc.BadRequest,
- self.alt_rule_client.
- create_security_group_rule,
- parent_group_id=parent_group_id,
- ip_protocol=ip_protocol,
- from_port=from_port, to_port=to_port)
- finally:
- # Next request the base_url is back to normal
- if resp['status'] is not None:
- self.alt_rule_client.delete_security_group_rule(resp['id'])
- LOG.error("Create security group rule request should not "
- "happen if the tenant id does not match the"
- " current user")
-
- @test.idempotent_id('c6044177-37ef-4ce4-b12c-270ddf26d7da')
- def test_delete_security_group_rule_of_alt_account_fails(self):
- # A DELETE request for another user's security group rule
- # should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_rule_client.delete_security_group_rule,
- self.rule['id'])
-
- @test.idempotent_id('c5f52351-53d9-4fc9-83e5-917f7f5e3d71')
- def test_set_metadata_of_alt_account_server_fails(self):
- # A set metadata for another user's server should fail
- req_metadata = {'meta1': 'data1', 'meta2': 'data2'}
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.set_server_metadata,
- self.server['id'],
- req_metadata)
-
- @test.idempotent_id('fb6f51e9-df15-4939-898d-1aca38c258f0')
- def test_set_metadata_of_alt_account_image_fails(self):
- # A set metadata for another user's image should fail
- req_metadata = {'meta1': 'value1', 'meta2': 'value2'}
- self.assertRaises(lib_exc.NotFound,
- self.alt_compute_images_client.set_image_metadata,
- self.image['id'], req_metadata)
-
- @test.idempotent_id('dea1936a-473d-49f2-92ad-97bb7aded22e')
- def test_get_metadata_of_alt_account_server_fails(self):
- # A get metadata for another user's server should fail
- req_metadata = {'meta1': 'data1'}
- self.client.set_server_metadata(self.server['id'], req_metadata)
- self.addCleanup(self.client.delete_server_metadata_item,
- self.server['id'], 'meta1')
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.show_server_metadata_item,
- self.server['id'], 'meta1')
-
- @test.idempotent_id('16b2d724-0d3b-4216-a9fa-97bd4d9cf670')
- def test_get_metadata_of_alt_account_image_fails(self):
- # A get metadata for another user's image should fail
- req_metadata = {'meta1': 'value1'}
- self.addCleanup(self.compute_images_client.delete_image_metadata_item,
- self.image['id'], 'meta1')
- self.compute_images_client.set_image_metadata(self.image['id'],
- req_metadata)
- self.assertRaises(
- lib_exc.NotFound,
- self.alt_compute_images_client.show_image_metadata_item,
- self.image['id'], 'meta1')
-
- @test.idempotent_id('79531e2e-e721-493c-8b30-a35db36fdaa6')
- def test_delete_metadata_of_alt_account_server_fails(self):
- # A delete metadata for another user's server should fail
- req_metadata = {'meta1': 'data1'}
- self.addCleanup(self.client.delete_server_metadata_item,
- self.server['id'], 'meta1')
- self.client.set_server_metadata(self.server['id'], req_metadata)
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.delete_server_metadata_item,
- self.server['id'], 'meta1')
-
- @test.idempotent_id('a5175dcf-cef8-43d6-9b77-3cb707d62e94')
- def test_delete_metadata_of_alt_account_image_fails(self):
- # A delete metadata for another user's image should fail
- req_metadata = {'meta1': 'data1'}
- self.addCleanup(self.compute_images_client.delete_image_metadata_item,
- self.image['id'], 'meta1')
- self.compute_images_client.set_image_metadata(self.image['id'],
- req_metadata)
- self.assertRaises(
- lib_exc.NotFound,
- self.alt_compute_images_client.delete_image_metadata_item,
- self.image['id'], 'meta1')
-
- @test.idempotent_id('b0c1e7a0-8853-40fd-8384-01f93d116cae')
- def test_get_console_output_of_alt_account_server_fails(self):
- # A Get Console Output for another user's server should fail
- self.assertRaises(lib_exc.NotFound,
- self.alt_client.get_console_output,
- self.server['id'], length=10)
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/test_live_block_migration_negative.py
index 2cd85f2..dc57396 100644
--- a/tempest/api/compute/test_live_block_migration_negative.py
+++ b/tempest/api/compute/test_live_block_migration_negative.py
@@ -13,12 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 43f4c97..122e7cc 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -79,8 +79,8 @@
@test.idempotent_id('cd65d997-f7e4-4966-a7e9-d5001b674fdc')
def test_compare_tenant_quotas_with_default_quotas(self):
# Tenants are created with the default quota values
- defualt_quota_set = \
+ default_quota_set = \
self.client.show_default_quota_set(self.tenant_id)['quota_set']
tenant_quota_set = (self.client.show_quota_set(self.tenant_id)
['quota_set'])
- self.assertEqual(defualt_quota_set, tenant_quota_set)
+ self.assertEqual(default_quota_set, tenant_quota_set)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 01a8e58..fa3fdfe 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
@@ -25,6 +26,7 @@
class AttachVolumeTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.19'
def __init__(self, *args, **kwargs):
super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
@@ -52,7 +54,8 @@
def _detach(self, server_id, volume_id):
if self.attachment:
self.servers_client.detach_volume(server_id, volume_id)
- self.volumes_client.wait_for_volume_status(volume_id, 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume_id, 'available')
def _delete_volume(self):
# Delete the created Volumes
@@ -61,7 +64,7 @@
self.volumes_client.wait_for_resource_deletion(self.volume['id'])
self.volume = None
- def _create_and_attach(self):
+ def _create_and_attach(self, shelve_server=False):
# Start a server and wait for it to become ready
self.admin_pass = self.image_ssh_password
self.server = self.create_test_server(
@@ -77,15 +80,32 @@
self.volume = self.volumes_client.create_volume(
size=CONF.volume.volume_size, display_name='test')['volume']
self.addCleanup(self._delete_volume)
- self.volumes_client.wait_for_volume_status(self.volume['id'],
- 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume['id'], 'available')
+
+ if shelve_server:
+ # NOTE(andreaf) If we are going to shelve a server, we should
+ # check first whether the server is ssh-able. Otherwise we won't
+ # be able to distinguish failures introduced by shelve from
+ # pre-existing ones. Also it's good to wait for cloud-init to be
+ # done and sshd server to be running before shelving to avoid
+ # breaking the VM
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(self.server),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'])
+ linux_client.validate_authentication()
+ # If validation went ok, shelve the server
+ compute.shelve_server(self.servers_client, self.server['id'])
# Attach the volume to the server
self.attachment = self.servers_client.attach_volume(
self.server['id'],
volumeId=self.volume['id'],
device='/dev/%s' % self.device)['volumeAttachment']
- self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use')
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume['id'], 'in-use')
self.addCleanup(self._detach, self.server['id'], self.volume['id'])
@@ -109,7 +129,9 @@
self.get_server_ip(self.server),
self.image_ssh_user,
self.admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
partitions = linux_client.get_partitions()
self.assertIn(self.device, partitions)
@@ -128,7 +150,9 @@
self.get_server_ip(self.server),
self.image_ssh_user,
self.admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
partitions = linux_client.get_partitions()
self.assertNotIn(self.device, partitions)
@@ -150,3 +174,68 @@
self.assertEqual(self.server['id'], body['serverId'])
self.assertEqual(self.volume['id'], body['volumeId'])
self.assertEqual(self.attachment['id'], body['id'])
+
+
+class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
+ """Testing volume with shelved instance.
+
+ This test checks the attaching and detaching volumes from
+ a shelved or shelved ofload instance.
+ """
+
+ min_microversion = '2.20'
+ max_microversion = 'latest'
+
+ def _unshelve_server_and_check_volumes(self, number_of_partition):
+ # Unshelve the instance and check that there are expected volumes
+ self.servers_client.unshelve_server(self.server['id'])
+ waiters.wait_for_server_status(self.servers_client,
+ self.server['id'],
+ 'ACTIVE')
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(self.server['id']),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
+
+ command = 'grep vd /proc/partitions | wc -l'
+ nb_partitions = linux_client.exec_command(command).strip()
+ self.assertEqual(number_of_partition, nb_partitions)
+
+ @test.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
+ @testtools.skipUnless(CONF.validation.run_validation,
+ 'SSH required for this test')
+ def test_attach_volume_shelved_or_offload_server(self):
+ self._create_and_attach(shelve_server=True)
+
+ # Unshelve the instance and check that there are two volumes
+ self._unshelve_server_and_check_volumes('2')
+
+ # Get Volume attachment of the server
+ volume_attachment = self.servers_client.show_volume_attachment(
+ self.server['id'],
+ self.attachment['id'])['volumeAttachment']
+ self.assertEqual(self.server['id'], volume_attachment['serverId'])
+ self.assertEqual(self.attachment['id'], volume_attachment['id'])
+ # Check the mountpoint is not None after unshelve server even in
+ # case of shelved_offloaded.
+ self.assertIsNotNone(volume_attachment['device'])
+
+ @test.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
+ @testtools.skipUnless(CONF.validation.run_validation,
+ 'SSH required for this test')
+ def test_detach_volume_shelved_or_offload_server(self):
+ self._create_and_attach(shelve_server=True)
+
+ # Detach the volume
+ self._detach(self.server['id'], self.volume['id'])
+ self.attachment = None
+
+ # Unshelve the instance and check that there is only one volume
+ self._unshelve_server_and_check_volumes('1')
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index 990e429..f709c91 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -27,7 +27,7 @@
# ensure that the backing file for the volume group that Nova uses
# has space for at least 3 1G volumes!
# If you are running a Devstack environment, ensure that the
- # VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc
+ # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 01a0baf..92f5ea8 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -13,13 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -46,7 +43,7 @@
# Creating a nonexistent volume id
# Trying to GET a non existent volume
self.assertRaises(lib_exc.NotFound, self.client.show_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('54a34226-d910-4b00-9ef8-8683e6c55846')
@@ -55,7 +52,7 @@
# Creating nonexistent volume id
# Trying to DELETE a non existent volume
self.assertRaises(lib_exc.NotFound, self.client.delete_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('5125ae14-152b-40a7-b3c5-eae15e9022ef')
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index b6d0c48..164caaf 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -16,10 +16,10 @@
import copy
import six
-from tempest_lib import exceptions as lib_exc
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -183,27 +183,6 @@
('5.3.0', copy.deepcopy(BASE_CDH_DESC)),
('5', copy.deepcopy(BASE_CDH_DESC))
]),
- 'mapr': OrderedDict([
- ('4.0.1.mrv2', {
- 'NODES': {
- 'master1': {
- 'count': 1,
- 'node_processes': ['CLDB', 'FileServer', 'ZooKeeper',
- 'NodeManager', 'ResourceManager',
- 'HistoryServer', 'Oozie']
- },
- 'worker1': {
- 'count': 1,
- 'node_processes': ['FileServer', 'NodeManager', 'Pig']
- }
- },
- 'cluster_configs': {
- 'Hive': {
- 'Hive Version': '0.13',
- }
- }
- })
- ]),
}
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
index f75b867..bb7a0a4 100644
--- a/tempest/api/database/flavors/test_flavors.py
+++ b/tempest/api/database/flavors/test_flavors.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.database import base
+from tempest.lib import decorators
from tempest import test
@@ -26,6 +27,7 @@
@test.attr(type='smoke')
@test.idempotent_id('c94b825e-0132-4686-8049-8a4a2bc09525')
+ @decorators.skip_because(bug='1567134')
def test_get_db_flavor(self):
# The expected flavor details should be returned
flavor = (self.client.show_db_flavor(self.db_flavor_ref)
@@ -37,6 +39,7 @@
@test.attr(type='smoke')
@test.idempotent_id('685025d6-0cec-4673-8a8d-995cb8e0d3bb')
+ @decorators.skip_because(bug='1567134')
def test_list_db_flavors(self):
flavor = (self.client.show_db_flavor(self.db_flavor_ref)
['flavor'])
@@ -58,6 +61,7 @@
@test.attr(type='smoke')
@test.idempotent_id('afb2667f-4ec2-4925-bcb7-313fdcffb80d')
@test.services('compute')
+ @decorators.skip_because(bug='1567134')
def test_compare_db_flavors_with_os(self):
db_flavors = self.client.list_db_flavors()['flavors']
os_flavors = (self.os_flavors_client.list_flavors(detail=True)
diff --git a/tempest/api/database/flavors/test_flavors_negative.py b/tempest/api/database/flavors/test_flavors_negative.py
index 3dee96f..cd2981b 100644
--- a/tempest/api/database/flavors/test_flavors_negative.py
+++ b/tempest/api/database/flavors/test_flavors_negative.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.database import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index 23a1958..fd56285 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -116,7 +113,7 @@
@test.idempotent_id('38373691-8551-453a-b074-4260ad8298ef')
def test_delete_role_non_existent(self):
# Attempt to delete a non existent role should fail
- non_existent_role = str(uuid.uuid4().hex)
+ non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role,
non_existent_role)
@@ -147,7 +144,7 @@
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()
- non_existent_role = str(uuid.uuid4().hex)
+ non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.assign_user_role,
tenant['id'], user['id'], non_existent_role)
@@ -156,7 +153,7 @@
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()
- non_existent_tenant = str(uuid.uuid4().hex)
+ non_existent_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.assign_user_role,
non_existent_tenant, user['id'], role['id'])
@@ -206,7 +203,7 @@
self.roles_client.assign_user_role(tenant['id'],
user['id'],
role['id'])
- non_existent_role = str(uuid.uuid4().hex)
+ non_existent_role = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.delete_user_role,
tenant['id'], user['id'], non_existent_role)
@@ -218,7 +215,7 @@
self.roles_client.assign_user_role(tenant['id'],
user['id'],
role['id'])
- non_existent_tenant = str(uuid.uuid4().hex)
+ non_existent_tenant = data_utils.rand_uuid_hex()
self.assertRaises(lib_exc.NotFound, self.roles_client.delete_user_role,
non_existent_tenant, user['id'], role['id'])
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index 5685922..fe83759 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -14,10 +14,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_tenant_negative.py b/tempest/api/identity/admin/v2/test_tenant_negative.py
index a02dbc1..5169dae 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -71,7 +68,7 @@
def test_delete_non_existent_tenant(self):
# Attempt to delete a non existent tenant should fail
self.assertRaises(lib_exc.NotFound, self.tenants_client.delete_tenant,
- str(uuid.uuid4().hex))
+ data_utils.rand_uuid_hex())
@test.attr(type=['negative'])
@test.idempotent_id('af16f44b-a849-46cb-9f13-a751c388f739')
@@ -131,7 +128,7 @@
def test_update_non_existent_tenant(self):
# Attempt to update a non existent tenant should fail
self.assertRaises(lib_exc.NotFound, self.tenants_client.update_tenant,
- str(uuid.uuid4().hex))
+ data_utils.rand_uuid_hex())
@test.attr(type=['negative'])
@test.idempotent_id('41704dc5-c5f7-4f79-abfa-76e6fedc570b')
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 60c4e97..d860d2f 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from testtools import matchers
from tempest.api.identity import base
@@ -207,9 +209,11 @@
update_user = self.users_client.update_user_password(
self.data.user['id'], password=new_pass)['user']
self.assertEqual(update_user['id'], self.data.user['id'])
-
- # Validate the updated password
- # Get a token
+ # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure
+ # we are passing the second boundary.
+ time.sleep(1)
+ # Validate the updated password through getting a token.
body = self.token_client.auth(self.data.user['name'], new_pass,
self.data.tenant['name'])
self.assertTrue('id' in body['token'])
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 0a5d0c9..5fda4c14 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -86,13 +83,14 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
self.alt_user, self.alt_password,
self.data.tenant['id'], self.alt_email)
- # Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
-
@test.attr(type=['negative'])
@test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
def test_create_user_with_enabled_non_bool(self):
@@ -109,7 +107,7 @@
def test_update_user_for_non_existent_user(self):
# Attempt to update a user non-existent user should fail
user_name = data_utils.rand_name('user')
- non_existent_id = str(uuid.uuid4())
+ non_existent_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.users_client.update_user,
non_existent_id, name=user_name)
@@ -122,11 +120,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac')
@@ -162,11 +161,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829')
@@ -232,8 +232,11 @@
# Request to get list of users without a valid token should fail
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.list_users)
- self.client.auth_provider.clear_auth()
@test.attr(type=['negative'])
@test.idempotent_id('f5d39046-fc5f-425c-b29e-bac2632da28e')
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index b81bff7..7c2e8e0 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -31,20 +31,20 @@
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_password()
for i in range(2):
- cls.project = cls.client.create_project(
+ cls.project = cls.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'))['project']
cls.projects.append(cls.project['id'])
- cls.user_body = cls.client.create_user(
+ cls.user_body = cls.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.projects[0])['user']
@classmethod
def resource_cleanup(cls):
- cls.client.delete_user(cls.user_body['id'])
+ cls.users_client.delete_user(cls.user_body['id'])
for p in cls.projects:
- cls.client.delete_project(p)
+ cls.projects_client.delete_project(p)
super(CredentialsTestJSON, cls).resource_cleanup()
def _delete_credential(self, cred_id):
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 53861ca..18a50d0 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -10,12 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import auth
-
from tempest.api.identity import base
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import auth
from tempest import manager
from tempest import test
@@ -32,23 +31,23 @@
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
@test.idempotent_id('d6110661-6a71-49a7-a453-b5e26640ff6d')
def test_default_project_id(self):
# create a domain
dom_name = data_utils.rand_name('dom')
- domain_body = self.client.create_domain(dom_name)['domain']
+ domain_body = self.domains_client.create_domain(dom_name)['domain']
dom_id = domain_body['id']
self.addCleanup(self._delete_domain, dom_id)
# create a project in the domain
proj_name = data_utils.rand_name('proj')
- proj_body = self.client.create_project(proj_name,
- domain_id=dom_id)['project']
+ proj_body = self.projects_client.create_project(
+ proj_name, domain_id=dom_id)['project']
proj_id = proj_body['id']
- self.addCleanup(self.client.delete_project, proj_id)
+ self.addCleanup(self.projects_client.delete_project, proj_id)
self.assertEqual(proj_body['domain_id'], dom_id,
"project " + proj_name +
"doesn't have domain id " + dom_id)
@@ -56,11 +55,13 @@
# create a user in the domain, with the previous project as his
# default project
user_name = data_utils.rand_name('user')
- user_body = self.client.create_user(user_name, password=user_name,
- domain_id=dom_id,
- default_project_id=proj_id)['user']
+ user_body = self.users_client.create_user(
+ user_name,
+ password=user_name,
+ domain_id=dom_id,
+ default_project_id=proj_id)['user']
user_id = user_body['id']
- self.addCleanup(self.client.delete_user, user_id)
+ self.addCleanup(self.users_client.delete_user, user_id)
self.assertEqual(user_body['domain_id'], dom_id,
"user " + user_name +
"doesn't have domain id " + dom_id)
@@ -70,8 +71,8 @@
admin_role_id = admin_role['id']
# grant the admin role to the user on his project
- self.client.assign_user_role_on_project(proj_id, user_id,
- admin_role_id)
+ self.roles_client.assign_user_role_on_project(proj_id, user_id,
+ admin_role_id)
# create a new client with user's credentials (NOTE: unscoped token!)
creds = auth.KeystoneV3Credentials(username=user_name,
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 15bea28..27ff15d 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -26,11 +26,11 @@
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
# Asserting that the domain is not found in the list
# after deletion
- body = self.client.list_domains()['domains']
+ body = self.domains_client.list_domains()['domains']
domains_list = [d['id'] for d in body]
self.assertNotIn(domain_id, domains_list)
@@ -40,14 +40,14 @@
domain_ids = list()
fetched_ids = list()
for _ in range(3):
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'))['domain']
# Delete the domain at the end of this method
self.addCleanup(self._delete_domain, domain['id'])
domain_ids.append(domain['id'])
# List and Verify Domains
- body = self.client.list_domains()['domains']
+ body = self.domains_client.list_domains()['domains']
for d in body:
fetched_ids.append(d['id'])
missing_doms = [d for d in domain_ids if d not in fetched_ids]
@@ -58,7 +58,7 @@
def test_create_update_delete_domain(self):
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
d_name, description=d_desc)['domain']
self.addCleanup(self._delete_domain, domain['id'])
self.assertIn('id', domain)
@@ -73,7 +73,7 @@
new_desc = data_utils.rand_name('new-desc')
new_name = data_utils.rand_name('new-name')
- updated_domain = self.client.update_domain(
+ updated_domain = self.domains_client.update_domain(
domain['id'], name=new_name, description=new_desc)['domain']
self.assertIn('id', updated_domain)
self.assertIn('description', updated_domain)
@@ -85,7 +85,8 @@
self.assertEqual(new_desc, updated_domain['description'])
self.assertEqual(True, updated_domain['enabled'])
- fetched_domain = self.client.get_domain(domain['id'])['domain']
+ fetched_domain = self.domains_client.show_domain(
+ domain['id'])['domain']
self.assertEqual(new_name, fetched_domain['name'])
self.assertEqual(new_desc, fetched_domain['description'])
self.assertEqual(True, fetched_domain['enabled'])
@@ -95,9 +96,9 @@
# Create domain with enabled status as false
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
d_name, description=d_desc, enabled=False)['domain']
- self.addCleanup(self.client.delete_domain, domain['id'])
+ self.addCleanup(self.domains_client.delete_domain, domain['id'])
self.assertEqual(d_name, domain['name'])
self.assertFalse(domain['enabled'])
self.assertEqual(d_desc, domain['description'])
@@ -106,7 +107,7 @@
def test_create_domain_without_description(self):
# Create domain only with name
d_name = data_utils.rand_name('domain')
- domain = self.client.create_domain(d_name)['domain']
+ domain = self.domains_client.create_domain(d_name)['domain']
self.addCleanup(self._delete_domain, domain['id'])
self.assertIn('id', domain)
expected_data = {'name': d_name, 'enabled': True}
@@ -124,6 +125,6 @@
@test.attr(type='smoke')
@test.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
def test_default_domain_exists(self):
- domain = self.client.get_domain(self.domain_id)['domain']
+ domain = self.domains_client.show_domain(self.domain_id)['domain']
self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 9eb3149..4330cab 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -14,11 +14,10 @@
# under the License.
from tempest.api.identity import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
-
class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@@ -28,45 +27,46 @@
def test_delete_active_domain(self):
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(d_name,
- description=d_desc)['domain']
+ domain = self.domains_client.create_domain(
+ d_name,
+ description=d_desc)['domain']
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
# domain need to be disabled before deleting
- self.assertRaises(lib_exc.Forbidden, self.client.delete_domain,
+ self.assertRaises(lib_exc.Forbidden, self.domains_client.delete_domain,
domain_id)
@test.attr(type=['negative'])
@test.idempotent_id('9018461d-7d24-408d-b3fe-ae37e8cd5c9e')
def test_create_domain_with_empty_name(self):
# Domain name should not be empty
- self.assertRaises(lib_exc.BadRequest, self.client.create_domain,
- name='')
+ self.assertRaises(lib_exc.BadRequest,
+ self.domains_client.create_domain, name='')
@test.attr(type=['negative'])
@test.idempotent_id('37b1bbf2-d664-4785-9a11-333438586eae')
def test_create_domain_with_name_length_over_64(self):
# Domain name length should not ne greater than 64 characters
d_name = 'a' * 65
- self.assertRaises(lib_exc.BadRequest, self.client.create_domain,
- d_name)
+ self.assertRaises(lib_exc.BadRequest,
+ self.domains_client.create_domain, d_name)
@test.attr(type=['negative'])
@test.idempotent_id('43781c07-764f-4cf2-a405-953c1916f605')
def test_delete_non_existent_domain(self):
# Attempt to delete a non existent domain should fail
- self.assertRaises(lib_exc.NotFound, self.client.delete_domain,
+ self.assertRaises(lib_exc.NotFound, self.domains_client.delete_domain,
data_utils.rand_uuid_hex())
@test.attr(type=['negative'])
@test.idempotent_id('e6f9e4a2-4f36-4be8-bdbc-4e199ae29427')
def test_domain_create_duplicate(self):
domain_name = data_utils.rand_name('domain-dup')
- domain = self.client.create_domain(domain_name)['domain']
+ domain = self.domains_client.create_domain(domain_name)['domain']
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
# Domain name should be unique
self.assertRaises(
- lib_exc.Conflict, self.client.create_domain, domain_name)
+ lib_exc.Conflict, self.domains_client.create_domain, domain_name)
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 2ac832e..50cf258 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -71,7 +71,7 @@
', '.join(str(e) for e in missing_endpoints))
@test.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37')
- def test_create_list_delete_endpoint(self):
+ def test_create_list_show_delete_endpoint(self):
region = data_utils.rand_name('region')
url = data_utils.rand_url()
interface = 'public'
@@ -79,16 +79,32 @@
interface=interface,
url=url, region=region,
enabled=True)['endpoint']
+
+ self.setup_endpoints.append(endpoint)
# Asserting Create Endpoint response body
self.assertIn('id', endpoint)
self.assertEqual(region, endpoint['region'])
self.assertEqual(url, endpoint['url'])
+
# Checking if created endpoint is present in the list of endpoints
fetched_endpoints = self.client.list_endpoints()['endpoints']
fetched_endpoints_id = [e['id'] for e in fetched_endpoints]
self.assertIn(endpoint['id'], fetched_endpoints_id)
+
+ # Show endpoint
+ 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(interface, fetched_endpoint['interface'])
+ self.assertEqual(url, fetched_endpoint['url'])
+ self.assertEqual(region, fetched_endpoint['region'])
+ self.assertEqual(True, fetched_endpoint['enabled'])
+
# Deleting the endpoint created in this method
self.client.delete_endpoint(endpoint['id'])
+ self.setup_endpoints.remove(endpoint)
+
# Checking whether endpoint is deleted successfully
fetched_endpoints = self.client.list_endpoints()['endpoints']
fetched_endpoints_id = [e['id'] for e in fetched_endpoints]
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 372254f..b16605e 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 03b8b29..a3ada21 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -20,12 +20,18 @@
class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
+ @classmethod
+ def resource_setup(cls):
+ super(GroupsV3TestJSON, cls).resource_setup()
+ cls.data.setup_test_domain()
+
@test.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
def test_group_create_update_get(self):
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, description=description)['group']
+ name=name, domain_id=self.data.domain['id'],
+ description=description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
self.assertEqual(group['name'], name)
self.assertEqual(group['description'], description)
@@ -47,7 +53,8 @@
name = data_utils.rand_name('Group')
old_description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, description=old_description)['group']
+ name=name, domain_id=self.data.domain['id'],
+ description=old_description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
new_name = data_utils.rand_name('UpdateGroup')
@@ -61,16 +68,17 @@
@test.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339')
def test_group_users_add_list_delete(self):
name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(name=name)['group']
+ group = self.groups_client.create_group(
+ name=name, domain_id=self.data.domain['id'])['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
# add user into group
users = []
for i in range(3):
name = data_utils.rand_name('User')
password = data_utils.rand_password()
- user = self.client.create_user(name, password)['user']
+ user = self.users_client.create_user(name, password)['user']
users.append(user)
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list users in group
@@ -87,19 +95,20 @@
@test.idempotent_id('64573281-d26a-4a52-b899-503cb0f4e4ec')
def test_list_user_groups(self):
# create a user
- user = self.client.create_user(
+ user = self.users_client.create_user(
data_utils.rand_name('User'), data_utils.rand_password())['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# create two groups, and add user into them
groups = []
for i in range(2):
name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(name=name)['group']
+ group = self.groups_client.create_group(
+ name=name, domain_id=self.data.domain['id'])['group']
groups.append(group)
self.addCleanup(self.groups_client.delete_group, group['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
- user_groups = self.client.list_user_groups(user['id'])['groups']
+ user_groups = self.users_client.list_user_groups(user['id'])['groups']
self.assertEqual(sorted(groups), sorted(user_groups))
self.assertEqual(2, len(user_groups))
@@ -112,7 +121,8 @@
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, description=description)['group']
+ name=name, domain_id=self.data.domain['id'],
+ description=description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
group_ids.append(group['id'])
# List and Verify Groups
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
new file mode 100644
index 0000000..fe20349
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -0,0 +1,147 @@
+# 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.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class BaseInheritsV3Test(base.BaseIdentityV3AdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseInheritsV3Test, 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()
+ u_name = data_utils.rand_name('user-')
+ u_desc = '%s description' % u_name
+ u_email = '%s@testmail.tm' % u_name
+ u_password = data_utils.rand_name('pass-')
+ cls.domain = cls.domains_client.create_domain(
+ data_utils.rand_name('domain-'),
+ description=data_utils.rand_name('domain-desc-'))['domain']
+ cls.project = cls.projects_client.create_project(
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'),
+ domain_id=cls.domain['id'])['project']
+ cls.group = cls.groups_client.create_group(
+ name=data_utils.rand_name('group-'), project_id=cls.project['id'],
+ domain_id=cls.domain['id'])['group']
+ cls.user = cls.users_client.create_user(
+ u_name, description=u_desc, password=u_password,
+ email=u_email, project_id=cls.project['id'],
+ domain_id=cls.domain['id'])['user']
+
+ @classmethod
+ def resource_cleanup(cls):
+ cls.groups_client.delete_group(cls.group['id'])
+ cls.users_client.delete_user(cls.user['id'])
+ cls.projects_client.delete_project(cls.project['id'])
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
+ super(BaseInheritsV3Test, 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):
+
+ @test.idempotent_id('4e6f0366-97c8-423c-b2be-41eae6ac91c8')
+ def test_inherit_assign_list_check_revoke_roles_on_domains_user(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on domains user
+ self.roles_client.assign_inherited_role_on_domains_user(
+ self.domain['id'], self.user['id'], src_role['id'])
+ # list role on domains user
+ roles = self.roles_client.\
+ list_inherited_project_role_for_user_on_domain(
+ self.domain['id'], self.user['id'])['roles']
+
+ fetched_role_ids = [i['id'] for i in roles]
+ self._list_assertions(roles, fetched_role_ids,
+ src_role['id'])
+
+ # Check role on domains user
+ self.roles_client.check_user_inherited_project_role_on_domain(
+ self.domain['id'], self.user['id'], src_role['id'])
+ # Revoke role from domains user.
+ self.roles_client.revoke_inherited_role_from_user_on_domain(
+ self.domain['id'], self.user['id'], src_role['id'])
+
+ @test.idempotent_id('c7a8dda2-be50-4fb4-9a9c-e830771078b1')
+ def test_inherit_assign_list_check_revoke_roles_on_domains_group(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on domains group
+ self.roles_client.assign_inherited_role_on_domains_group(
+ self.domain['id'], self.group['id'], src_role['id'])
+ # List role on domains group
+ roles = self.roles_client.\
+ list_inherited_project_role_for_group_on_domain(
+ self.domain['id'], self.group['id'])['roles']
+
+ fetched_role_ids = [i['id'] for i in roles]
+ self._list_assertions(roles, fetched_role_ids,
+ src_role['id'])
+
+ # Check role on domains group
+ self.roles_client.check_group_inherited_project_role_on_domain(
+ self.domain['id'], self.group['id'], src_role['id'])
+ # Revoke role from domains group
+ self.roles_client.revoke_inherited_role_from_group_on_domain(
+ self.domain['id'], self.group['id'], src_role['id'])
+
+ @test.idempotent_id('18b70e45-7687-4b72-8277-b8f1a47d7591')
+ def test_inherit_assign_check_revoke_roles_on_projects_user(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on projects user
+ self.roles_client.assign_inherited_role_on_projects_user(
+ self.project['id'], self.user['id'], src_role['id'])
+ # Check role on projects user
+ self.roles_client.check_user_has_flag_on_inherited_to_project(
+ self.project['id'], self.user['id'], src_role['id'])
+ # Revoke role from projects user
+ self.roles_client.revoke_inherited_role_from_user_on_project(
+ self.project['id'], self.user['id'], src_role['id'])
+
+ @test.idempotent_id('26021436-d5a4-4256-943c-ded01e0d4b45')
+ def test_inherit_assign_check_revoke_roles_on_projects_group(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on projects group
+ self.roles_client.assign_inherited_role_on_projects_group(
+ self.project['id'], self.group['id'], src_role['id'])
+ # Check role on projects group
+ self.roles_client.check_group_has_flag_on_inherited_to_project(
+ self.project['id'], self.group['id'], src_role['id'])
+ # Revoke role from projects group
+ self.roles_client.revoke_inherited_role_from_group_on_project(
+ self.project['id'], self.group['id'], src_role['id'])
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 5185fea..928437c 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -27,25 +27,25 @@
cls.data.setup_test_domain()
# Create project with domain
cls.p1_name = data_utils.rand_name('project')
- cls.p1 = cls.client.create_project(
+ cls.p1 = cls.projects_client.create_project(
cls.p1_name, enabled=False,
domain_id=cls.data.domain['id'])['project']
cls.data.projects.append(cls.p1)
cls.project_ids.append(cls.p1['id'])
# Create default project
p2_name = data_utils.rand_name('project')
- cls.p2 = cls.client.create_project(p2_name)['project']
+ cls.p2 = cls.projects_client.create_project(p2_name)['project']
cls.data.projects.append(cls.p2)
cls.project_ids.append(cls.p2['id'])
@test.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
def test_projects_list(self):
# List projects
- list_projects = self.client.list_projects()['projects']
+ list_projects = self.projects_client.list_projects()['projects']
for p in self.project_ids:
- get_project = self.client.get_project(p)['project']
- self.assertIn(get_project, list_projects)
+ show_project = self.projects_client.show_project(p)['project']
+ self.assertIn(show_project, list_projects)
@test.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
def test_list_projects_with_domains(self):
@@ -64,6 +64,6 @@
self._list_projects_with_params({'name': self.p1_name}, 'name')
def _list_projects_with_params(self, params, key):
- body = self.client.list_projects(params)['projects']
+ body = self.projects_client.list_projects(params)['projects']
self.assertIn(self.p1[key], map(lambda x: x[key], body))
self.assertNotIn(self.p2[key], map(lambda x: x[key], body))
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 4921c00..5b27ab1 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -25,7 +25,7 @@
# assert the response based on expected and not_expected
# expected: user expected in the list response
# not_expected: user, which should not be present in list response
- body = self.client.list_users(params)['users']
+ body = self.users_client.list_users(params)['users']
self.assertIn(expected[key], map(lambda x: x[key], body))
self.assertNotIn(not_expected[key],
map(lambda x: x[key], body))
@@ -39,13 +39,13 @@
cls.data.setup_test_domain()
# Create user with Domain
u1_name = data_utils.rand_name('test_user')
- cls.domain_enabled_user = cls.client.create_user(
+ cls.domain_enabled_user = cls.users_client.create_user(
u1_name, password=alt_password,
email=cls.alt_email, domain_id=cls.data.domain['id'])['user']
cls.data.users.append(cls.domain_enabled_user)
# Create default not enabled user
u2_name = data_utils.rand_name('test_user')
- cls.non_domain_enabled_user = cls.client.create_user(
+ cls.non_domain_enabled_user = cls.users_client.create_user(
u2_name, password=alt_password,
email=cls.alt_email, enabled=False)['user']
cls.data.users.append(cls.non_domain_enabled_user)
@@ -77,7 +77,7 @@
@test.idempotent_id('b30d4651-a2ea-4666-8551-0c0e49692635')
def test_list_users(self):
# List users
- body = self.client.list_users()['users']
+ body = self.users_client.list_users()['users']
fetched_ids = [u['id'] for u in body]
missing_users = [u['id'] for u in self.data.users
if u['id'] not in fetched_ids]
@@ -88,7 +88,7 @@
@test.idempotent_id('b4baa3ae-ac00-4b4e-9e27-80deaad7771f')
def test_get_user(self):
# Get a user detail
- user = self.client.show_user(self.data.users[0]['id'])['user']
+ user = self.users_client.show_user(self.data.users[0]['id'])['user']
self.assertEqual(self.data.users[0]['id'], user['id'])
self.assertEqual(self.data.users[0]['name'], user['name'])
self.assertEqual(self.alt_email, user['email'])
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index af9497c..607bebe 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -25,14 +25,14 @@
# Create project with a description
project_name = data_utils.rand_name('project')
project_desc = data_utils.rand_name('desc')
- project = self.client.create_project(
+ project = self.projects_client.create_project(
project_name, description=project_desc)['project']
self.data.projects.append(project)
project_id = project['id']
desc1 = project['description']
self.assertEqual(desc1, project_desc, 'Description should have '
'been sent in response for create')
- body = self.client.get_project(project_id)['project']
+ body = self.projects_client.show_project(project_id)['project']
desc2 = body['description']
self.assertEqual(desc2, project_desc, 'Description does not appear'
'to be set')
@@ -42,13 +42,13 @@
# Create project with a domain
self.data.setup_test_domain()
project_name = data_utils.rand_name('project')
- project = self.client.create_project(
+ project = self.projects_client.create_project(
project_name, domain_id=self.data.domain['id'])['project']
self.data.projects.append(project)
project_id = project['id']
self.assertEqual(project_name, project['name'])
self.assertEqual(self.data.domain['id'], project['domain_id'])
- body = self.client.get_project(project_id)['project']
+ body = self.projects_client.show_project(project_id)['project']
self.assertEqual(project_name, body['name'])
self.assertEqual(self.data.domain['id'], body['domain_id'])
@@ -56,13 +56,13 @@
def test_project_create_enabled(self):
# Create a project that is enabled
project_name = data_utils.rand_name('project')
- project = self.client.create_project(
+ project = self.projects_client.create_project(
project_name, enabled=True)['project']
self.data.projects.append(project)
project_id = project['id']
en1 = project['enabled']
self.assertTrue(en1, 'Enable should be True in response')
- body = self.client.get_project(project_id)['project']
+ body = self.projects_client.show_project(project_id)['project']
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
@@ -70,13 +70,13 @@
def test_project_create_not_enabled(self):
# Create a project that is not enabled
project_name = data_utils.rand_name('project')
- project = self.client.create_project(
+ project = self.projects_client.create_project(
project_name, enabled=False)['project']
self.data.projects.append(project)
en1 = project['enabled']
self.assertEqual('false', str(en1).lower(),
'Enable should be False in response')
- body = self.client.get_project(project['id'])['project']
+ body = self.projects_client.show_project(project['id'])['project']
en2 = body['enabled']
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
@@ -85,18 +85,18 @@
def test_project_update_name(self):
# Update name attribute of a project
p_name1 = data_utils.rand_name('project')
- project = self.client.create_project(p_name1)['project']
+ project = self.projects_client.create_project(p_name1)['project']
self.data.projects.append(project)
resp1_name = project['name']
p_name2 = data_utils.rand_name('project2')
- body = self.client.update_project(project['id'],
- name=p_name2)['project']
+ body = self.projects_client.update_project(project['id'],
+ name=p_name2)['project']
resp2_name = body['name']
self.assertNotEqual(resp1_name, resp2_name)
- body = self.client.get_project(project['id'])['project']
+ body = self.projects_client.show_project(project['id'])['project']
resp3_name = body['name']
self.assertNotEqual(resp1_name, resp3_name)
@@ -108,18 +108,18 @@
# Update description attribute of a project
p_name = data_utils.rand_name('project')
p_desc = data_utils.rand_name('desc')
- project = self.client.create_project(
+ project = self.projects_client.create_project(
p_name, description=p_desc)['project']
self.data.projects.append(project)
resp1_desc = project['description']
p_desc2 = data_utils.rand_name('desc2')
- body = self.client.update_project(
+ body = self.projects_client.update_project(
project['id'], description=p_desc2)['project']
resp2_desc = body['description']
self.assertNotEqual(resp1_desc, resp2_desc)
- body = self.client.get_project(project['id'])['project']
+ body = self.projects_client.show_project(project['id'])['project']
resp3_desc = body['description']
self.assertNotEqual(resp1_desc, resp3_desc)
@@ -131,18 +131,19 @@
# Update the enabled attribute of a project
p_name = data_utils.rand_name('project')
p_en = False
- project = self.client.create_project(p_name, enabled=p_en)['project']
+ project = self.projects_client.create_project(p_name,
+ enabled=p_en)['project']
self.data.projects.append(project)
resp1_en = project['enabled']
p_en2 = True
- body = self.client.update_project(
- project['id'], enabled=p_en2)['project']
+ body = self.projects_client.update_project(project['id'],
+ enabled=p_en2)['project']
resp2_en = body['enabled']
self.assertNotEqual(resp1_en, resp2_en)
- body = self.client.get_project(project['id'])['project']
+ body = self.projects_client.show_project(project['id'])['project']
resp3_en = body['enabled']
self.assertNotEqual(resp1_en, resp3_en)
@@ -154,7 +155,7 @@
# Associate a user to a project
# Create a Project
p_name = data_utils.rand_name('project')
- project = self.client.create_project(p_name)['project']
+ project = self.projects_client.create_project(p_name)['project']
self.data.projects.append(project)
# Create a User
@@ -162,14 +163,14 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=project['id'])['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Get User To validate the user details
- new_user_get = self.client.show_user(user['id'])['user']
+ new_user_get = self.users_client.show_user(user['id'])['user']
# Assert response body of GET
self.assertEqual(u_name, new_user_get['name'])
self.assertEqual(u_desc, new_user_get['description'])
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 9b60d54..fb4a8cf 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -27,18 +26,18 @@
def test_list_projects_by_unauthorized_user(self):
# Non-admin user should not be able to list projects
self.assertRaises(lib_exc.Forbidden,
- self.non_admin_client.list_projects)
+ self.non_admin_projects_client.list_projects)
@test.attr(type=['negative'])
@test.idempotent_id('874c3e84-d174-4348-a16b-8c01f599561b')
def test_project_create_duplicate(self):
# Project names should be unique
project_name = data_utils.rand_name('project-dup')
- project = self.client.create_project(project_name)['project']
+ project = self.projects_client.create_project(project_name)['project']
self.data.projects.append(project)
- self.assertRaises(
- lib_exc.Conflict, self.client.create_project, project_name)
+ self.assertRaises(lib_exc.Conflict,
+ self.projects_client.create_project, project_name)
@test.attr(type=['negative'])
@test.idempotent_id('8fba9de2-3e1f-4e77-812a-60cb68f8df13')
@@ -46,38 +45,39 @@
# Non-admin user should not be authorized to create a project
project_name = data_utils.rand_name('project')
self.assertRaises(
- lib_exc.Forbidden, self.non_admin_client.create_project,
+ lib_exc.Forbidden, self.non_admin_projects_client.create_project,
project_name)
@test.attr(type=['negative'])
@test.idempotent_id('7828db17-95e5-475b-9432-9a51b4aa79a9')
def test_create_project_with_empty_name(self):
# Project name should not be empty
- self.assertRaises(lib_exc.BadRequest, self.client.create_project,
- name='')
+ self.assertRaises(lib_exc.BadRequest,
+ self.projects_client.create_project, name='')
@test.attr(type=['negative'])
@test.idempotent_id('502b6ceb-b0c8-4422-bf53-f08fdb21e2f0')
def test_create_projects_name_length_over_64(self):
# Project name length should not be greater than 64 characters
project_name = 'a' * 65
- self.assertRaises(lib_exc.BadRequest, self.client.create_project,
- project_name)
+ self.assertRaises(lib_exc.BadRequest,
+ self.projects_client.create_project, project_name)
@test.attr(type=['negative'])
@test.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
def test_project_delete_by_unauthorized_user(self):
# Non-admin user should not be able to delete a project
project_name = data_utils.rand_name('project')
- project = self.client.create_project(project_name)['project']
+ project = self.projects_client.create_project(project_name)['project']
self.data.projects.append(project)
self.assertRaises(
- lib_exc.Forbidden, self.non_admin_client.delete_project,
+ lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
project['id'])
@test.attr(type=['negative'])
@test.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb')
def test_delete_non_existent_project(self):
# Attempt to delete a non existent project should fail
- self.assertRaises(lib_exc.NotFound, self.client.delete_project,
+ self.assertRaises(lib_exc.NotFound,
+ self.projects_client.delete_project,
data_utils.rand_uuid_hex())
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index 8bba3cb..ece36b9 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index f1f06ee..12ef369 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,40 +25,40 @@
super(RolesV3TestJSON, cls).resource_setup()
for _ in range(3):
role_name = data_utils.rand_name(name='role')
- role = cls.client.create_role(name=role_name)['role']
+ role = cls.roles_client.create_role(name=role_name)['role']
cls.data.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
cls.u_password = data_utils.rand_password()
- cls.domain = cls.client.create_domain(
+ cls.domain = cls.domains_client.create_domain(
data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'))['domain']
- cls.project = cls.client.create_project(
+ cls.project = cls.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'),
domain_id=cls.domain['id'])['project']
cls.group_body = cls.groups_client.create_group(
name=data_utils.rand_name('Group'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
- cls.user_body = cls.client.create_user(
+ cls.user_body = cls.users_client.create_user(
u_name, description=u_desc, password=cls.u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
- cls.role = cls.client.create_role(
+ cls.role = cls.roles_client.create_role(
name=data_utils.rand_name('Role'))['role']
@classmethod
def resource_cleanup(cls):
- cls.client.delete_role(cls.role['id'])
+ cls.roles_client.delete_role(cls.role['id'])
cls.groups_client.delete_group(cls.group_body['id'])
- cls.client.delete_user(cls.user_body['id'])
- cls.client.delete_project(cls.project['id'])
+ cls.users_client.delete_user(cls.user_body['id'])
+ cls.projects_client.delete_project(cls.project['id'])
# NOTE(harika-vakadi): It is necessary to disable the domain
# before deleting,or else it would result in unauthorized error
- cls.client.update_domain(cls.domain['id'], enabled=False)
- cls.client.delete_domain(cls.domain['id'])
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
super(RolesV3TestJSON, cls).resource_cleanup()
def _list_assertions(self, body, fetched_role_ids, role_id):
@@ -67,34 +67,35 @@
@test.attr(type='smoke')
@test.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
- def test_role_create_update_get_list(self):
+ def test_role_create_update_show_list(self):
r_name = data_utils.rand_name('Role')
- role = self.client.create_role(name=r_name)['role']
- self.addCleanup(self.client.delete_role, role['id'])
+ role = self.roles_client.create_role(name=r_name)['role']
+ self.addCleanup(self.roles_client.delete_role, role['id'])
self.assertIn('name', role)
self.assertEqual(role['name'], r_name)
new_name = data_utils.rand_name('NewRole')
- updated_role = self.client.update_role(role['id'],
- name=new_name)['role']
+ updated_role = self.roles_client.update_role(role['id'],
+ name=new_name)['role']
self.assertIn('name', updated_role)
self.assertIn('id', updated_role)
self.assertIn('links', updated_role)
self.assertNotEqual(r_name, updated_role['name'])
- new_role = self.client.show_role(role['id'])['role']
+ new_role = self.roles_client.show_role(role['id'])['role']
self.assertEqual(new_name, new_role['name'])
self.assertEqual(updated_role['id'], new_role['id'])
- roles = self.client.list_roles()['roles']
+ roles = self.roles_client.list_roles()['roles']
self.assertIn(role['id'], [r['id'] for r in roles])
@test.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f')
def test_grant_list_revoke_role_to_user_on_project(self):
- self.client.assign_user_role_on_project(
- self.project['id'], self.user_body['id'], self.role['id'])
+ self.roles_client.assign_user_role_on_project(self.project['id'],
+ self.user_body['id'],
+ self.role['id'])
- roles = self.client.list_user_roles_on_project(
+ roles = self.roles_client.list_user_roles_on_project(
self.project['id'], self.user_body['id'])['roles']
for i in roles:
@@ -103,18 +104,18 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_user_role_existence_on_project(
+ self.roles_client.check_user_role_existence_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
- self.client.delete_role_from_user_on_project(
+ self.roles_client.delete_role_from_user_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
@test.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd')
def test_grant_list_revoke_role_to_user_on_domain(self):
- self.client.assign_user_role_on_domain(
+ self.roles_client.assign_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- roles = self.client.list_user_roles_on_domain(
+ roles = self.roles_client.list_user_roles_on_domain(
self.domain['id'], self.user_body['id'])['roles']
for i in roles:
@@ -123,19 +124,19 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_user_role_existence_on_domain(
+ self.roles_client.check_user_role_existence_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- self.client.delete_role_from_user_on_domain(
+ self.roles_client.delete_role_from_user_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@test.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
def test_grant_list_revoke_role_to_group_on_project(self):
# Grant role to group on project
- self.client.assign_group_role_on_project(
+ self.roles_client.assign_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
# List group roles on project
- roles = self.client.list_group_roles_on_project(
+ roles = self.roles_client.list_group_roles_on_project(
self.project['id'], self.group_body['id'])['roles']
for i in roles:
@@ -157,19 +158,19 @@
self.assertEqual(len(roles), 1)
self.assertEqual(roles[0]['id'], self.role['id'])
- self.client.check_role_from_group_on_project_existence(
+ self.roles_client.check_role_from_group_on_project_existence(
self.project['id'], self.group_body['id'], self.role['id'])
# Revoke role to group on project
- self.client.delete_role_from_group_on_project(
+ self.roles_client.delete_role_from_group_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
@test.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
def test_grant_list_revoke_role_to_group_on_domain(self):
- self.client.assign_group_role_on_domain(
+ self.roles_client.assign_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
- roles = self.client.list_group_roles_on_domain(
+ roles = self.roles_client.list_group_roles_on_domain(
self.domain['id'], self.group_body['id'])['roles']
for i in roles:
@@ -178,15 +179,15 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_role_from_group_on_domain_existence(
+ self.roles_client.check_role_from_group_on_domain_existence(
self.domain['id'], self.group_body['id'], self.role['id'])
- self.client.delete_role_from_group_on_domain(
+ self.roles_client.delete_role_from_group_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
@test.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
def test_list_roles(self):
# Return a list of all roles
- body = self.client.list_roles()['roles']
+ body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.data.roles]
self.assertEqual(len(found), len(self.data.roles))
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index c6e3df4..2c3cae5 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index b1446cf..6f12939 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -30,10 +29,10 @@
u_desc = '%s-description' % u_name
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email)['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Perform Authentication
resp = self.token.auth(user_id=user['id'],
password=u_password).response
@@ -61,30 +60,34 @@
# Create a user.
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
- user = self.client.create_user(user_name,
- password=user_password)['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ user = self.users_client.create_user(user_name,
+ password=user_password)['user']
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Create a couple projects
project1_name = data_utils.rand_name(name='project')
- project1 = self.client.create_project(project1_name)['project']
- self.addCleanup(self.client.delete_project, project1['id'])
+ project1 = self.projects_client.create_project(
+ project1_name)['project']
+ self.addCleanup(self.projects_client.delete_project, project1['id'])
project2_name = data_utils.rand_name(name='project')
- project2 = self.client.create_project(project2_name)['project']
- self.addCleanup(self.client.delete_project, project2['id'])
+ project2 = self.projects_client.create_project(
+ project2_name)['project']
+ self.addCleanup(self.projects_client.delete_project, project2['id'])
# Create a role
role_name = data_utils.rand_name(name='role')
- role = self.client.create_role(name=role_name)['role']
- self.addCleanup(self.client.delete_role, role['id'])
+ role = self.roles_client.create_role(name=role_name)['role']
+ self.addCleanup(self.roles_client.delete_role, role['id'])
# Grant the user the role on both projects.
- self.client.assign_user_role(project1['id'], user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project1['id'],
+ user['id'],
+ role['id'])
- self.client.assign_user_role(project2['id'], user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project2['id'],
+ user['id'],
+ role['id'])
# Get an unscoped token.
token_auth = self.token.auth(user_id=user['id'],
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 85961b4..09ae468 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -14,13 +14,13 @@
import re
from oslo_utils import timeutils
-from tempest_lib import exceptions as lib_exc
from tempest.api.identity import base
from tempest import clients
from tempest.common import credentials_factory as common_creds
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -46,8 +46,8 @@
def create_trustor_and_roles(self):
# create a project that trusts will be granted on
self.trustor_project_name = data_utils.rand_name(name='project')
- project = self.client.create_project(self.trustor_project_name,
- domain_id='default')['project']
+ project = self.projects_client.create_project(
+ self.trustor_project_name, domain_id='default')['project']
self.trustor_project_id = project['id']
self.assertIsNotNone(self.trustor_project_id)
@@ -56,7 +56,7 @@
u_desc = self.trustor_username + 'description'
u_email = self.trustor_username + '@testmail.xx'
self.trustor_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
self.trustor_username,
description=u_desc,
password=self.trustor_password,
@@ -69,19 +69,22 @@
self.delegated_role = data_utils.rand_name('DelegatedRole')
self.not_delegated_role = data_utils.rand_name('NotDelegatedRole')
- role = self.client.create_role(name=self.delegated_role)['role']
+ role = self.roles_client.create_role(name=self.delegated_role)['role']
self.delegated_role_id = role['id']
- role = self.client.create_role(name=self.not_delegated_role)['role']
+ role = self.roles_client.create_role(
+ name=self.not_delegated_role)['role']
self.not_delegated_role_id = role['id']
# Assign roles to trustor
- self.client.assign_user_role(self.trustor_project_id,
- self.trustor_user_id,
- self.delegated_role_id)
- self.client.assign_user_role(self.trustor_project_id,
- self.trustor_user_id,
- self.not_delegated_role_id)
+ self.roles_client.assign_user_role_on_project(
+ self.trustor_project_id,
+ self.trustor_user_id,
+ self.delegated_role_id)
+ self.roles_client.assign_user_role_on_project(
+ self.trustor_project_id,
+ self.trustor_user_id,
+ self.not_delegated_role_id)
# Get trustee user ID, use the demo user
trustee_username = self.non_admin_client.user
@@ -97,17 +100,17 @@
tenant_name=self.trustor_project_name,
project_domain_id='default')
os = clients.Manager(credentials=creds)
- self.trustor_client = os.identity_v3_client
+ self.trustor_client = os.trusts_client
def cleanup_user_and_roles(self):
if self.trustor_user_id:
- self.client.delete_user(self.trustor_user_id)
+ self.users_client.delete_user(self.trustor_user_id)
if self.trustor_project_id:
- self.client.delete_project(self.trustor_project_id)
+ self.projects_client.delete_project(self.trustor_project_id)
if self.delegated_role_id:
- self.client.delete_role(self.delegated_role_id)
+ self.roles_client.delete_role(self.delegated_role_id)
if self.not_delegated_role_id:
- self.client.delete_role(self.not_delegated_role_id)
+ self.roles_client.delete_role(self.not_delegated_role_id)
def create_trust(self, impersonate=True, expires=None):
@@ -139,8 +142,8 @@
self.assertEqual(self.delegated_role, trust['roles'][0]['name'])
self.assertEqual(1, len(trust['roles']))
- def get_trust(self):
- trust_get = self.trustor_client.get_trust(self.trust_id)['trust']
+ def show_trust(self):
+ trust_get = self.trustor_client.show_trust(self.trust_id)['trust']
return trust_get
def validate_role(self, role):
@@ -155,12 +158,12 @@
def check_trust_roles(self):
# Check we find the delegated role
- roles_get = self.trustor_client.get_trust_roles(
+ roles_get = self.trustor_client.list_trust_roles(
self.trust_id)['roles']
self.assertEqual(1, len(roles_get))
self.validate_role(roles_get[0])
- role_get = self.trustor_client.get_trust_role(
+ role_get = self.trustor_client.show_trust_role(
self.trust_id, self.delegated_role_id)['role']
self.validate_role(role_get)
@@ -169,7 +172,7 @@
# And that we don't find not_delegated_role
self.assertRaises(lib_exc.NotFound,
- self.trustor_client.get_trust_role,
+ self.trustor_client.show_trust_role,
self.trust_id,
self.not_delegated_role_id)
@@ -181,7 +184,7 @@
def delete_trust(self):
self.trustor_client.delete_trust(self.trust_id)
self.assertRaises(lib_exc.NotFound,
- self.trustor_client.get_trust,
+ self.trustor_client.show_trust,
self.trust_id)
self.trust_id = None
@@ -200,7 +203,7 @@
trust = self.create_trust()
self.validate_trust(trust)
- trust_get = self.get_trust()
+ trust_get = self.show_trust()
self.validate_trust(trust_get)
self.check_trust_roles()
@@ -212,7 +215,7 @@
trust = self.create_trust(impersonate=False)
self.validate_trust(trust, impersonate=False)
- trust_get = self.get_trust()
+ trust_get = self.show_trust()
self.validate_trust(trust_get, impersonate=False)
self.check_trust_roles()
@@ -236,7 +239,7 @@
trust = self.create_trust(expires=expires_str)
self.validate_trust(trust, expires=expires_str)
- trust_get = self.get_trust()
+ trust_get = self.show_trust()
self.validate_trust(trust_get, expires=expires_str)
@@ -255,7 +258,7 @@
@test.idempotent_id('6268b345-87ca-47c0-9ce3-37792b43403a')
def test_get_trusts_query(self):
self.create_trust()
- trusts_get = self.trustor_client.get_trusts(
+ trusts_get = self.trustor_client.list_trusts(
trustor_user_id=self.trustor_user_id)['trusts']
self.assertEqual(1, len(trusts_get))
self.validate_trust(trusts_get[0], summary=True)
@@ -264,7 +267,7 @@
@test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
def test_get_trusts_all(self):
self.create_trust()
- trusts_get = self.client.get_trusts()['trusts']
+ trusts_get = self.trusts_client.list_trusts()['trusts']
trusts = [t for t in trusts_get
if t['id'] == self.trust_id]
self.assertEqual(1, len(trusts))
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 894a7a9..371da9c 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -30,22 +30,22 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False)['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Creating second project for updation
- project = self.client.create_project(
+ project = self.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'))['project']
# Delete the Project at the end of this method
- self.addCleanup(self.client.delete_project, project['id'])
+ self.addCleanup(self.projects_client.delete_project, project['id'])
# 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.client.update_user(
+ 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']
@@ -56,7 +56,7 @@
self.assertEqual(u_email2, update_user['email'])
self.assertEqual(False, update_user['enabled'])
# GET by id after updation
- new_user_get = self.client.show_user(user['id'])['user']
+ 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'])
@@ -70,28 +70,24 @@
# Creating User to check password updation
u_name = data_utils.rand_name('user')
original_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, password=original_password)['user']
# Delete the User at the end all test methods
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Update user with new password
new_password = data_utils.rand_password()
- self.client.update_user_password(user['id'], password=new_password,
- original_password=original_password)
- # TODO(lbragstad): Sleeping after the response status has been checked
- # and the body loaded as JSON allows requests to fail-fast. The sleep
- # is necessary because keystone will err on the side of security and
- # invalidate tokens within a small margin of error (within the same
- # wall clock second) after a revocation event is issued (such as a
- # password change). Remove this once keystone and Fernet support
- # sub-second precision, see bug 1517697 for more details.
+ self.users_client.update_user_password(
+ user['id'], password=new_password,
+ original_password=original_password)
+ # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure
+ # we are passing the second boundary.
time.sleep(1)
resp = self.token.auth(user_id=user['id'],
password=new_password).response
subject_token = resp['x-subject-token']
# Perform GET Token to verify and confirm password is updated
token_details = self.client.show_token(subject_token)['token']
- self.assertEqual(resp['x-subject-token'], subject_token)
self.assertEqual(token_details['user']['id'], user['id'])
self.assertEqual(token_details['user']['name'], u_name)
@@ -100,43 +96,45 @@
# List the projects that a user has access upon
assigned_project_ids = list()
fetched_project_ids = list()
- u_project = self.client.create_project(
+ u_project = self.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'))['project']
# Delete the Project at the end of this method
- self.addCleanup(self.client.delete_project, u_project['id'])
+ self.addCleanup(self.projects_client.delete_project, u_project['id'])
# Create a user.
u_name = data_utils.rand_name('user')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user_body = self.client.create_user(
+ user_body = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False, project_id=u_project['id'])['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user_body['id'])
+ self.addCleanup(self.users_client.delete_user, user_body['id'])
# Creating Role
- role_body = self.client.create_role(
+ role_body = self.roles_client.create_role(
name=data_utils.rand_name('role'))['role']
# Delete the Role at the end of this method
- self.addCleanup(self.client.delete_role, role_body['id'])
+ self.addCleanup(self.roles_client.delete_role, role_body['id'])
- user = self.client.show_user(user_body['id'])['user']
- role = self.client.show_role(role_body['id'])['role']
+ user = self.users_client.show_user(user_body['id'])['user']
+ role = self.roles_client.show_role(role_body['id'])['role']
for i in range(2):
# Creating project so as to assign role
- project_body = self.client.create_project(
+ project_body = self.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'))['project']
- project = self.client.get_project(project_body['id'])['project']
+ project = self.projects_client.show_project(
+ project_body['id'])['project']
# Delete the Project at the end of this method
- self.addCleanup(self.client.delete_project, project_body['id'])
+ self.addCleanup(
+ self.projects_client.delete_project, project_body['id'])
# Assigning roles to user on project
- self.client.assign_user_role(project['id'],
- user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project['id'],
+ user['id'],
+ role['id'])
assigned_project_ids.append(project['id'])
- body = self.client.list_user_projects(user['id'])['projects']
+ body = self.users_client.list_user_projects(user['id'])['projects']
for i in body:
fetched_project_ids.append(i['id'])
# verifying the project ids in list
@@ -152,5 +150,5 @@
def test_get_user(self):
# Get a user detail
self.data.setup_test_user()
- user = self.client.show_user(self.data.user['id'])['user']
+ user = self.users_client.show_user(self.data.user['id'])['user']
self.assertEqual(self.data.user['id'], user['id'])
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 39c89a5..1375db1 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -29,7 +28,7 @@
u_name = data_utils.rand_name('user')
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- self.assertRaises(lib_exc.NotFound, self.client.create_user,
+ self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
u_name, u_password,
email=u_email,
domain_id=data_utils.rand_uuid_hex())
@@ -39,7 +38,7 @@
def test_authentication_for_disabled_user(self):
# Attempt to authenticate for disabled user should fail
self.data.setup_test_user()
- self.disable_user(self.data.user['name'])
+ self.disable_user(self.data.user['name'], self.data.user['domain_id'])
self.assertRaises(lib_exc.Unauthorized, self.token.auth,
username=self.data.user['name'],
password=self.data.user_password,
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 91d620c..3bcae17 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -14,10 +14,10 @@
# under the License.
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -37,8 +37,12 @@
cls.tenants_client.update_tenant(tenant['id'], enabled=False)
@classmethod
- def get_user_by_name(cls, name):
- users = cls.users_client.list_users()['users']
+ def get_user_by_name(cls, name, domain_id=None):
+ if domain_id:
+ params = {'domain_id': domain_id}
+ users = cls.users_client.list_users(params)['users']
+ else:
+ users = cls.users_client.list_users()['users']
user = [u for u in users if u['name'] == name]
if len(user) > 0:
return user[0]
@@ -48,7 +52,7 @@
try:
tenants = cls.tenants_client.list_tenants()['tenants']
except AttributeError:
- tenants = cls.client.list_projects()['projects']
+ tenants = cls.projects_client.list_projects()['projects']
tenant = [t for t in tenants if t['name'] == name]
if len(tenant) > 0:
return tenant[0]
@@ -75,7 +79,6 @@
cls.non_admin_client = cls.os.identity_public_client
cls.non_admin_token_client = cls.os.token_client
cls.non_admin_tenants_client = cls.os.tenants_public_client
- cls.non_admin_roles_client = cls.os.roles_public_client
cls.non_admin_users_client = cls.os.users_public_client
@classmethod
@@ -103,14 +106,14 @@
cls.non_admin_roles_client = cls.os.roles_client
cls.users_client = cls.os_adm.users_client
cls.non_admin_users_client = cls.os.users_client
- cls.services_client = cls.os_adm.services_v2_client
- cls.endpoints_client = cls.os_adm.endpoints_v2_client
+ cls.services_client = cls.os_adm.identity_services_client
+ cls.endpoints_client = cls.os_adm.endpoints_client
@classmethod
def resource_setup(cls):
super(BaseIdentityV2AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV2(cls.client, cls.tenants_client,
- cls.users_client, cls.roles_client)
+ cls.data = DataGeneratorV2(cls.tenants_client, cls.users_client,
+ cls.roles_client)
@classmethod
def resource_cleanup(cls):
@@ -130,7 +133,9 @@
def setup_clients(cls):
super(BaseIdentityV3Test, cls).setup_clients()
cls.non_admin_client = cls.os.identity_v3_client
+ cls.non_admin_users_client = cls.os.users_v3_client
cls.non_admin_token = cls.os.token_v3_client
+ cls.non_admin_projects_client = cls.os.projects_client
@classmethod
def resource_cleanup(cls):
@@ -145,18 +150,24 @@
def setup_clients(cls):
super(BaseIdentityV3AdminTest, cls).setup_clients()
cls.client = cls.os_adm.identity_v3_client
+ cls.domains_client = cls.os_adm.domains_client
+ cls.users_client = cls.os_adm.users_v3_client
+ cls.trusts_client = cls.os_adm.trusts_client
+ cls.roles_client = cls.os_adm.roles_v3_client
cls.token = cls.os_adm.token_v3_client
- cls.endpoints_client = cls.os_adm.endpoints_client
+ cls.endpoints_client = cls.os_adm.endpoints_v3_client
cls.regions_client = cls.os_adm.regions_client
- cls.services_client = cls.os_adm.identity_services_client
+ cls.services_client = cls.os_adm.identity_services_v3_client
cls.policies_client = cls.os_adm.policies_client
cls.creds_client = cls.os_adm.credentials_client
cls.groups_client = cls.os_adm.groups_client
+ cls.projects_client = cls.os_adm.projects_client
@classmethod
def resource_setup(cls):
super(BaseIdentityV3AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV3(cls.client)
+ cls.data = DataGeneratorV3(cls.projects_client, cls.users_client,
+ cls.roles_client, cls.domains_client)
@classmethod
def resource_cleanup(cls):
@@ -164,46 +175,25 @@
super(BaseIdentityV3AdminTest, cls).resource_cleanup()
@classmethod
- def get_user_by_name(cls, name):
- users = cls.client.list_users()['users']
- user = [u for u in users if u['name'] == name]
- if len(user) > 0:
- return user[0]
-
- @classmethod
- def get_tenant_by_name(cls, name):
- tenants = cls.client.list_projects()['projects']
- tenant = [t for t in tenants if t['name'] == name]
- if len(tenant) > 0:
- return tenant[0]
-
- @classmethod
- def get_role_by_name(cls, name):
- roles = cls.client.list_roles()['roles']
- role = [r for r in roles if r['name'] == name]
- if len(role) > 0:
- return role[0]
-
- @classmethod
- def disable_user(cls, user_name):
- user = cls.get_user_by_name(user_name)
- cls.client.update_user(user['id'], user_name, enabled=False)
+ def disable_user(cls, user_name, domain_id=None):
+ user = cls.get_user_by_name(user_name, domain_id)
+ cls.users_client.update_user(user['id'], user_name, enabled=False)
def delete_domain(self, domain_id):
# NOTE(mpavlase) It is necessary to disable the domain before deleting
# otherwise it raises Forbidden exception
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
class BaseDataGenerator(object):
- def __init__(self, client, projects_client=None,
- users_client=None, roles_client=None):
- self.client = client
- self.projects_client = projects_client or client
- self.users_client = users_client or client
- self.roles_client = roles_client or client
+ def __init__(self, projects_client, users_client, roles_client,
+ domains_client=None):
+ self.projects_client = projects_client
+ self.users_client = users_client
+ self.roles_client = roles_client
+ self.domains_client = domains_client
self.user_password = None
self.user = None
@@ -252,8 +242,9 @@
for role in self.roles:
self._try_wrapper(self.roles_client.delete_role, role)
for domain in self.domains:
- self._try_wrapper(self.client.update_domain, domain, enabled=False)
- self._try_wrapper(self.client.delete_domain, domain)
+ self._try_wrapper(self.domains_client.update_domain, domain,
+ enabled=False)
+ self._try_wrapper(self.domains_client.delete_domain, domain)
class DataGeneratorV2(BaseDataGenerator):
@@ -287,7 +278,7 @@
def setup_test_domain(self):
"""Set up a test domain."""
- self.domain = self.client.create_domain(
+ self.domain = self.domains_client.create_domain(
name=data_utils.rand_name('test_domain'),
description=data_utils.rand_name('desc'))['domain']
self.domains.append(self.domain)
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index bd49326..8600980 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/v2/test_tenants.py b/tempest/api/identity/v2/test_tenants.py
index 4e31557..b742e69 100644
--- a/tempest/api/identity/v2/test_tenants.py
+++ b/tempest/api/identity/v2/test_tenants.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index a59a1a0..79f2576 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -13,14 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
-
from tempest.api.identity import base
-from tempest import manager
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import test
@@ -36,33 +33,32 @@
@test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity Client with new credentials, since
- # current non_admin_client token will be revoked after updating
- # password
- self.non_admin_users_client_for_cleanup = copy.copy(
- self.non_admin_users_client)
- self.non_admin_users_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
- old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_own_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
+ old_pass = self.creds.credentials.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_users_client_for_cleanup.update_user_own_password,
- user_id, original_password=new_pass, password=old_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
+
# user updates own password
self.non_admin_users_client.update_user_own_password(
user_id, password=new_pass, original_password=old_pass)
- # TODO(lbragstad): Sleeping after the response status has been checked
- # and the body loaded as JSON allows requests to fail-fast. The sleep
- # is necessary because keystone will err on the side of security and
- # invalidate tokens within a small margin of error (within the same
- # wall clock second) after a revocation event is issued (such as a
- # password change). Remove this once keystone and Fernet support
- # sub-second precision.
+ # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure
+ # we are passing the second boundary.
time.sleep(1)
# check authorization with new password
diff --git a/tempest/api/identity/v3/test_projects.py b/tempest/api/identity/v3/test_projects.py
index a547b06..1574ab7 100644
--- a/tempest/api/identity/v3/test_projects.py
+++ b/tempest/api/identity/v3/test_projects.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -27,17 +26,21 @@
def test_list_projects_returns_only_authorized_projects(self):
alt_project_name =\
self.alt_manager.credentials.credentials.project_name
- resp = self.non_admin_client.list_user_projects(
+ resp = self.non_admin_users_client.list_user_projects(
self.os.credentials.user_id)
# check that user can see only that projects that he presents in so
# user can successfully authenticate using his credentials and
# project name from received projects list
for project in resp['projects']:
+ # 'user_domain_id' needs to be specified otherwise tempest.lib
+ # assumes it to be 'default'
token_id, body = self.non_admin_token.get_token(
username=self.os.credentials.username,
+ user_domain_id=self.os.credentials.user_domain_id,
password=self.os.credentials.password,
project_name=project['name'],
+ project_domain_id=project['domain_id'],
auth_data=True)
self.assertNotEmpty(token_id)
self.assertEqual(body['project']['id'], project['id'])
@@ -49,5 +52,7 @@
lib_exc.Unauthorized,
self.non_admin_token.get_token,
username=self.os.credentials.username,
+ user_domain_id=self.os.credentials.user_domain_id,
password=self.os.credentials.password,
- project_name=alt_project_name)
+ project_name=alt_project_name,
+ project_domain_id=project['domain_id'])
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 3151763..d5bed96 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -28,10 +28,15 @@
user_id = creds.user_id
username = creds.username
password = creds.password
+ user_domain_id = creds.user_domain_id
- token_id, resp = self.non_admin_token.get_token(user_id=user_id,
- password=password,
- auth_data=True)
+ # 'user_domain_id' needs to be specified otherwise tempest.lib assumes
+ # it to be 'default'
+ token_id, resp = self.non_admin_token.get_token(
+ user_id=user_id,
+ user_domain_id=user_domain_id,
+ password=password,
+ auth_data=True)
self.assertNotEmpty(token_id)
self.assertIsInstance(token_id, six.string_types)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 93814d3..76b46c0 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -13,14 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
-
from tempest.api.identity import base
-from tempest import manager
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import test
@@ -36,35 +33,33 @@
@test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity V3 Client with new credentials, since
- # current non_admin_client token will be revoked after updating
- # password
- self.non_admin_client_for_cleanup = copy.copy(self.non_admin_client)
- self.non_admin_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
+
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_client_for_cleanup.update_user_password,
- user_id,
- password=old_pass,
- original_password=new_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
# user updates own password
- self.non_admin_client.update_user_password(
+ self.non_admin_users_client.update_user_password(
user_id, password=new_pass, original_password=old_pass)
- # TODO(lbragstad): Sleeping after the response status has been checked
- # and the body loaded as JSON allows requests to fail-fast. The sleep
- # is necessary because keystone will err on the side of security and
- # invalidate tokens within a small margin of error (within the same
- # wall clock second) after a revocation event is issued (such as a
- # password change). Remove this once keystone and Fernet support
- # sub-second precision.
+ # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure
+ # we are passing the second boundary.
time.sleep(1)
# check authorization with new password
diff --git a/tempest/api/image/admin/v2/test_images.py b/tempest/api/image/admin/v2/test_images.py
index b171da3..80da7a1 100644
--- a/tempest/api/image/admin/v2/test_images.py
+++ b/tempest/api/image/admin/v2/test_images.py
@@ -14,13 +14,13 @@
# under the License.
from six import moves
-from tempest_lib.common.utils import data_utils
import testtools
from tempest.api.image import base
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
CONF = config.CONF
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index ade7b67..0683936 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -13,10 +13,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 50f5048..16a4ba6 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 1a84d06..6d5559d 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -17,6 +17,7 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest import test
@@ -32,7 +33,7 @@
if container_format in a_formats and container_format != disk_format:
msg = ("The container format and the disk format don't match. "
- "Contaiter format: %(container)s, Disk format: %(disk)s." %
+ "Container format: %(container)s, Disk format: %(disk)s." %
{'container': container_format, 'disk': disk_format})
raise exceptions.InvalidConfiguration(message=msg)
@@ -95,7 +96,7 @@
image_id = body.get('id')
self.assertEqual('New Http Image', body.get('name'))
self.assertFalse(body.get('is_public'))
- self.client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(self.client, image_id, 'active')
self.client.show_image(image_id)
@test.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
@@ -305,7 +306,7 @@
@test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'value1'}
self.assertEqual(expected, resp_metadata['properties'])
@@ -313,12 +314,12 @@
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
- metadata = self.client.get_image_meta(self.image_id)
+ metadata = self.client.check_image(self.image_id)
self.assertEqual(metadata['properties'], {'key1': 'value1'})
metadata['properties'].update(req_metadata)
metadata = self.client.update_image(
self.image_id, properties=metadata['properties'])['image']
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'alt1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index f16b80e..babee74 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index eb90719..388eb08 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -10,9 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index efb7b8b..de8299e 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -15,8 +15,8 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
class MetadataNamespacesTest(base.BaseV2ImageTest):
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 485942e..14de8fd 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -14,11 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -39,7 +37,7 @@
@test.idempotent_id('668743d5-08ad-4480-b2b8-15da34f81d9f')
def test_get_non_existent_image(self):
# get the non-existent image
- non_existent_id = str(uuid.uuid4())
+ non_existent_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.show_image,
non_existent_id)
@@ -73,7 +71,7 @@
@test.idempotent_id('6fe40f1c-57bd-4918-89cc-8500f850f3de')
def test_delete_non_existing_image(self):
# delete non-existent image
- non_existent_image_id = str(uuid.uuid4())
+ non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
non_existent_image_id)
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index a3f4ca8..dd5650f 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -12,12 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -28,7 +25,7 @@
def test_update_tags_for_non_existing_image(self):
# Update tag with non existing image.
tag = data_utils.rand_name('tag')
- non_exist_image = str(uuid.uuid4())
+ non_exist_image = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.add_image_tag,
non_exist_image, tag)
diff --git a/tempest/api/messaging/base.py b/tempest/api/messaging/base.py
deleted file mode 100644
index a324c37..0000000
--- a/tempest/api/messaging/base.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-
-class BaseMessagingTest(test.BaseTestCase):
-
- """Base class for the Messaging (Zaqar) tests
-
- It is assumed that the following option is defined in the
- [service_available] section of etc/tempest.conf
-
- messaging as True
- """
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseMessagingTest, cls).skip_checks()
- if not CONF.service_available.zaqar:
- raise cls.skipException("Zaqar support is required")
-
- @classmethod
- def setup_clients(cls):
- super(BaseMessagingTest, cls).setup_clients()
- cls.client = cls.os.messaging_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseMessagingTest, cls).resource_setup()
- cls.messaging_cfg = CONF.messaging
-
- @classmethod
- def create_queue(cls, queue_name):
- """Wrapper utility that returns a test queue."""
- resp, body = cls.client.create_queue(queue_name)
- return resp, body
-
- @classmethod
- def delete_queue(cls, queue_name):
- """Wrapper utility that deletes a test queue."""
- resp, body = cls.client.delete_queue(queue_name)
- return resp, body
-
- @classmethod
- def check_queue_exists(cls, queue_name):
- """Wrapper utility that checks the existence of a test queue."""
- resp, body = cls.client.show_queue(queue_name)
- return resp, body
-
- @classmethod
- def check_queue_exists_head(cls, queue_name):
- """Wrapper utility checks the head of a queue via http HEAD."""
- resp, body = cls.client.head_queue(queue_name)
- return resp, body
-
- @classmethod
- def list_queues(cls):
- """Wrapper utility that lists queues."""
- resp, body = cls.client.list_queues()
- return resp, body
-
- @classmethod
- def get_queue_stats(cls, queue_name):
- """Wrapper utility that returns the queue stats."""
- resp, body = cls.client.show_queue_stats(queue_name)
- return resp, body
-
- @classmethod
- def get_queue_metadata(cls, queue_name):
- """Wrapper utility that gets a queue metadata."""
- resp, body = cls.client.show_queue_metadata(queue_name)
- return resp, body
-
- @classmethod
- def set_queue_metadata(cls, queue_name, rbody):
- """Wrapper utility that sets the metadata of a queue."""
- resp, body = cls.client.set_queue_metadata(queue_name, rbody)
- return resp, body
-
- @classmethod
- def post_messages(cls, queue_name, rbody):
- """Wrapper utility that posts messages to a queue."""
- resp, body = cls.client.post_messages(queue_name, rbody)
-
- return resp, body
-
- @classmethod
- def list_messages(cls, queue_name):
- """Wrapper utility that lists the messages in a queue."""
- resp, body = cls.client.list_messages(queue_name)
-
- return resp, body
-
- @classmethod
- def delete_messages(cls, message_uri):
- """Wrapper utility that deletes messages."""
- resp, body = cls.client.delete_messages(message_uri)
-
- return resp, body
-
- @classmethod
- def post_claims(cls, queue_name, rbody, url_params=False):
- """Wrapper utility that claims messages."""
- resp, body = cls.client.post_claims(
- queue_name, rbody, url_params=False)
-
- return resp, body
-
- @classmethod
- def query_claim(cls, claim_uri):
- """Wrapper utility that gets a claim."""
- resp, body = cls.client.query_claim(claim_uri)
-
- return resp, body
-
- @classmethod
- def update_claim(cls, claim_uri, rbody):
- """Wrapper utility that updates a claim."""
- resp, body = cls.client.update_claim(claim_uri, rbody)
-
- return resp, body
-
- @classmethod
- def release_claim(cls, claim_uri):
- """Wrapper utility that deletes a claim."""
- resp, body = cls.client.delete_claim(claim_uri)
-
- return resp, body
-
- @classmethod
- def generate_message_body(cls, repeat=1):
- """Wrapper utility that sets the metadata of a queue."""
- message_ttl = data_utils.\
- rand_int_id(start=60, end=CONF.messaging.max_message_ttl)
-
- key = data_utils.arbitrary_string(size=20, base_text='MessagingKey')
- value = data_utils.arbitrary_string(size=20,
- base_text='MessagingValue')
- message_body = {key: value}
-
- rbody = ([{'body': message_body, 'ttl': message_ttl}] * repeat)
- return rbody
diff --git a/tempest/api/messaging/test_claims.py b/tempest/api/messaging/test_claims.py
deleted file mode 100644
index 99edde1..0000000
--- a/tempest/api/messaging/test_claims.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
-
-from tempest.api.messaging import base
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class TestClaims(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestClaims, cls).resource_setup()
- cls.queue_name = data_utils.rand_name('Queues-Test')
- # Create Queue
- cls.create_queue(cls.queue_name)
-
- def _post_and_claim_messages(self, queue_name, repeat=1):
- # Post Messages
- message_body = self.generate_message_body(repeat=repeat)
- self.client.post_messages(queue_name=self.queue_name,
- rbody=message_body)
-
- # Post Claim
- claim_ttl = data_utils.rand_int_id(start=60,
- end=CONF.messaging.max_claim_ttl)
- claim_grace = data_utils.\
- rand_int_id(start=60, end=CONF.messaging.max_claim_grace)
- claim_body = {"ttl": claim_ttl, "grace": claim_grace}
- resp, body = self.client.post_claims(queue_name=self.queue_name,
- rbody=claim_body)
-
- return resp, body
-
- @test.attr(type='smoke')
- @test.idempotent_id('936cb1ca-b7af-44dd-a752-805e8c98156f')
- def test_post_claim(self):
- _, body = self._post_and_claim_messages(queue_name=self.queue_name)
- claimed_message_uri = body[0]['href']
-
- # Skipping this step till bug-1331517 is fixed
- # Get posted claim
- # self.client.query_claim(claimed_message_uri)
-
- # Delete Claimed message
- self.client.delete_messages(claimed_message_uri)
-
- @decorators.skip_because(bug="1331517")
- @test.attr(type='smoke')
- @test.idempotent_id('84e491f4-68c6-451f-9846-b8f868eb27c5')
- def test_query_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
-
- # Query Claim
- claim_uri = resp['location']
- self.client.query_claim(claim_uri)
-
- # Delete Claimed message
- claimed_message_uri = body[0]['href']
- self.delete_messages(claimed_message_uri)
-
- @decorators.skip_because(bug="1328111")
- @test.attr(type='smoke')
- @test.idempotent_id('420ef0c5-9bd6-4b82-b06d-d9da330fefd3')
- def test_update_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
-
- claim_uri = resp['location']
- claimed_message_uri = body[0]['href']
-
- # Update Claim
- claim_ttl = data_utils.rand_int_id(start=60,
- end=CONF.messaging.max_claim_ttl)
- update_rbody = {"ttl": claim_ttl}
-
- self.client.update_claim(claim_uri, rbody=update_rbody)
-
- # Verify claim ttl >= updated ttl value
- _, body = self.client.query_claim(claim_uri)
- updated_claim_ttl = body["ttl"]
- self.assertTrue(updated_claim_ttl >= claim_ttl)
-
- # Delete Claimed message
- self.client.delete_messages(claimed_message_uri)
-
- @test.attr(type='smoke')
- @test.idempotent_id('fd4c7921-cb3f-4ed8-9ac8-e8f1e74c44aa')
- def test_release_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
- claim_uri = resp['location']
-
- # Release Claim
- self.client.delete_claim(claim_uri)
-
- # Delete Claimed message
- # This will implicitly verify that the claim is deleted.
- message_uri = urlparse.urlparse(claim_uri).path
- self.client.delete_messages(message_uri)
-
- @classmethod
- def resource_cleanup(cls):
- cls.delete_queue(cls.queue_name)
- super(TestClaims, cls).resource_cleanup()
diff --git a/tempest/api/messaging/test_messages.py b/tempest/api/messaging/test_messages.py
deleted file mode 100644
index 7f4182a..0000000
--- a/tempest/api/messaging/test_messages.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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.messaging import base
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class TestMessages(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestMessages, cls).resource_setup()
- cls.queue_name = data_utils.rand_name('Queues-Test')
- # Create Queue
- cls.client.create_queue(cls.queue_name)
-
- def _post_messages(self, repeat=CONF.messaging.max_messages_per_page):
- message_body = self.generate_message_body(repeat=repeat)
- resp, body = self.post_messages(queue_name=self.queue_name,
- rbody=message_body)
- return resp, body
-
- @test.attr(type='smoke')
- @test.idempotent_id('93867172-a414-4eb3-a639-96e943c516b4')
- def test_post_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
-
- # Get on the posted messages
- message_uri = resp['location']
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('c967d59a-e919-41cb-994b-1c4300452c80')
- def test_list_messages(self):
- # Post Messages
- self._post_messages()
-
- # List Messages
- resp, _ = self.list_messages(queue_name=self.queue_name)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('2a68e3de-24df-47c3-9039-ec4156656bf8')
- def test_get_message(self):
- # Post Messages
- _, body = self._post_messages()
- message_uri = body['resources'][0]
-
- # Get posted message
- resp, _ = self.client.show_single_message(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('c4b0a30b-efda-4b87-a395-0c43140df74d')
- def test_get_multiple_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
- message_uri = resp['location']
-
- # Get posted messages
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('fc0fca47-dd8b-4ecc-8522-d9c191f9bc9f')
- def test_delete_single_message(self):
- # Post Messages
- _, body = self._post_messages()
- message_uri = body['resources'][0]
-
- # Delete posted message & verify the delete operration
- self.client.delete_messages(message_uri)
-
- message_uri = message_uri.replace('/messages/', '/messages?ids=')
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response has to be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('204', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('00cca069-5c8f-4b42-bff1-c577da2a4546')
- def test_delete_multiple_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
- message_uri = resp['location']
-
- # Delete multiple messages
- self.client.delete_messages(message_uri)
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response has to be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('204', resp['status'])
-
- @classmethod
- def resource_cleanup(cls):
- cls.delete_queue(cls.queue_name)
- super(TestMessages, cls).resource_cleanup()
diff --git a/tempest/api/messaging/test_queues.py b/tempest/api/messaging/test_queues.py
deleted file mode 100644
index dcb5450..0000000
--- a/tempest/api/messaging/test_queues.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 six import moves
-from tempest_lib import exceptions as lib_exc
-from testtools import matchers
-
-from tempest.api.messaging import base
-from tempest.common.utils import data_utils
-from tempest import test
-
-
-class TestQueues(base.BaseMessagingTest):
-
- @test.attr(type='smoke')
- @test.idempotent_id('9f1c4c72-80c5-4dac-acf3-188cef42e36c')
- def test_create_delete_queue(self):
- # Create & Delete Queue
- queue_name = data_utils.rand_name('test')
- _, body = self.create_queue(queue_name)
-
- self.addCleanup(self.client.delete_queue, queue_name)
- # NOTE(gmann): create_queue returns response status code as 201
- # so specifically checking the expected empty response body as
- # this is not going to be checked in response_checker().
- self.assertEqual('', body)
-
- self.delete_queue(queue_name)
- self.assertRaises(lib_exc.NotFound,
- self.client.show_queue,
- queue_name)
-
-
-class TestManageQueue(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestManageQueue, cls).resource_setup()
- cls.queues = list()
- for _ in moves.xrange(5):
- queue_name = data_utils.rand_name('Queues-Test')
- cls.queues.append(queue_name)
- # Create Queue
- cls.client.create_queue(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('ccd3d69e-f156-4c5f-8a12-b4f24bee44e1')
- def test_check_queue_existence(self):
- # Checking Queue Existence
- for queue_name in self.queues:
- self.check_queue_exists(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('e27634d8-9c8f-47d8-a677-655c47658d3e')
- def test_check_queue_head(self):
- # Checking Queue Existence by calling HEAD
- for queue_name in self.queues:
- self.check_queue_exists_head(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('0a0feeca-7768-4303-806d-82bbbb796ad3')
- def test_list_queues(self):
- # Listing queues
- _, body = self.list_queues()
- self.assertEqual(len(body['queues']), len(self.queues))
- for item in body['queues']:
- self.assertIn(item['name'], self.queues)
-
- @test.attr(type='smoke')
- @test.idempotent_id('8fb66602-077d-49d6-ae1a-5f2091739178')
- def test_get_queue_stats(self):
- # Retrieve random queue
- queue_name = self.queues[data_utils.rand_int_id(0,
- len(self.queues) - 1)]
- # Get Queue Stats for a newly created Queue
- _, body = self.get_queue_stats(queue_name)
- msgs = body['messages']
- for element in ('free', 'claimed', 'total'):
- self.assertEqual(0, msgs[element])
- for element in ('oldest', 'newest'):
- self.assertNotIn(element, msgs)
-
- @test.attr(type='smoke')
- @test.idempotent_id('0e2441e6-6593-4bdb-a3c0-20e66eeb3fff')
- def test_set_and_get_queue_metadata(self):
- # Retrieve random queue
- queue_name = self.queues[data_utils.rand_int_id(0,
- len(self.queues) - 1)]
- # Check the Queue has no metadata
- _, body = self.get_queue_metadata(queue_name)
- self.assertThat(body, matchers.HasLength(0))
- # Create metadata
- key3 = [0, 1, 2, 3, 4]
- key2 = data_utils.rand_name('value')
- req_body1 = dict()
- req_body1[data_utils.rand_name('key3')] = key3
- req_body1[data_utils.rand_name('key2')] = key2
- req_body = dict()
- req_body[data_utils.rand_name('key1')] = req_body1
- # Set Queue Metadata
- self.set_queue_metadata(queue_name, req_body)
-
- # Get Queue Metadata
- _, body = self.get_queue_metadata(queue_name)
- self.assertThat(body, matchers.Equals(req_body))
-
- @classmethod
- def resource_cleanup(cls):
- for queue_name in cls.queues:
- cls.client.delete_queue(queue_name)
- super(TestManageQueue, cls).resource_cleanup()
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index fcb6fce..d2ab237 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -36,12 +36,12 @@
@test.idempotent_id('5032b1fe-eb42-4a64-8f3b-6e189d8b5c7d')
def test_list_dhcp_agent_hosting_network(self):
- self.admin_client.list_dhcp_agent_hosting_network(
+ self.admin_networks_client.list_dhcp_agents_on_hosting_network(
self.network['id'])
@test.idempotent_id('30c48f98-e45d-4ffb-841c-b8aad57c7587')
def test_list_networks_hosted_by_one_dhcp(self):
- body = self.admin_client.list_dhcp_agent_hosting_network(
+ body = self.admin_networks_client.list_dhcp_agents_on_hosting_network(
self.network['id'])
agents = body['agents']
self.assertIsNotNone(agents)
diff --git a/tempest/api/network/admin/test_external_networks_negative.py b/tempest/api/network/admin/test_external_networks_negative.py
index d031108..57b144a 100644
--- a/tempest/api/network/admin/test_external_networks_negative.py
+++ b/tempest/api/network/admin/test_external_networks_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
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 6ad374b..baeaa0c 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -28,7 +28,6 @@
@classmethod
def setup_clients(cls):
super(FloatingIPAdminTestJSON, cls).setup_clients()
- cls.alt_client = cls.alt_manager.network_client
cls.alt_floating_ips_client = cls.alt_manager.floating_ips_client
@classmethod
@@ -68,7 +67,7 @@
body = self.floating_ips_client.list_floatingips()
floating_ip_ids = [f['id'] for f in body['floatingips']]
# Check that nonadmin user doesn't see floating ip created from admin
- # and floating ip that is created in another tenant (alt user)
+ # and floating ip that is created in another project (alt user)
self.assertIn(self.floating_ip['id'], floating_ip_ids)
self.assertNotIn(floating_ip_admin['floatingip']['id'],
floating_ip_ids)
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 5d12e17..7a4547a 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -74,14 +74,14 @@
# query and setup steps are only required if the extension is available
# and only if the router's default type is distributed.
if test.is_extension_enabled('dvr', 'network'):
- cls.is_dvr_router = cls.admin_client.show_router(
+ cls.is_dvr_router = cls.admin_routers_client.show_router(
cls.router['id'])['router'].get('distributed', False)
if cls.is_dvr_router:
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
cls.port = cls.create_port(cls.network)
- cls.client.add_router_interface_with_port_id(
- cls.router['id'], cls.port['id'])
+ cls.routers_client.add_router_interface(
+ cls.router['id'], port_id=cls.port['id'])
# NOTE: Sometimes we have seen this test fail with dvr in,
# multinode tests, since the dhcp port is not created before
# the test gets executed and so the router is not scheduled
@@ -92,15 +92,15 @@
external_gateway_info = {
'network_id': CONF.network.public_network_id,
'enable_snat': True}
- cls.admin_client.update_router_with_snat_gw_info(
+ cls.admin_routers_client.update_router_with_snat_gw_info(
cls.router['id'],
external_gateway_info=external_gateway_info)
@classmethod
def resource_cleanup(cls):
if cls.is_dvr_router:
- cls.client.remove_router_interface_with_port_id(
- cls.router['id'], cls.port['id'])
+ cls.routers_client.remove_router_interface(cls.router['id'],
+ port_id=cls.port['id'])
super(L3AgentSchedulerTestJSON, cls).resource_cleanup()
@test.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a')
@@ -114,7 +114,8 @@
self.agent['id'],
router_id=self.router['id'])
body = (
- self.admin_client.list_l3_agents_hosting_router(self.router['id']))
+ self.admin_routers_client.list_l3_agents_hosting_router(
+ self.router['id']))
for agent in body['agents']:
l3_agent_ids.append(agent['id'])
self.assertIn('agent_type', agent)
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 47da08c..7b037d5 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -14,8 +14,8 @@
# under the License.
from tempest.api.network import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
class QuotasNegativeTest(base.BaseAdminNetworkTest):
@@ -24,7 +24,7 @@
set network quota and exceed this quota
v2.0 of the API is assumed.
- It is also assumed that the per-tenant quota extension API is configured
+ It is also assumed that the per-project quota extension API is configured
in /etc/neutron/neutron.conf as follows:
quota_driver = neutron.db.quota_db.DbQuotaDriver
@@ -60,7 +60,7 @@
n2['network']['id'])
# Try to create a third network while the quota is two
- with self.assertRaisesRegexp(
+ with self.assertRaisesRegex(
lib_exc.Conflict,
"An object with that identifier already exists\\n" +
"Details.*Quota exceeded for resources: \['network'\].*"):
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index 45d35cf..ea3d59a 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -17,19 +17,20 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
class QuotasTest(base.BaseAdminNetworkTest):
"""Tests the following operations in the Neutron API:
- list quotas for tenants who have non-default quota values
- show quotas for a specified tenant
- update quotas for a specified tenant
- reset quotas to default values for a specified tenant
+ list quotas for projects who have non-default quota values
+ show quotas for a specified project
+ update quotas for a specified project
+ reset quotas to default values for a specified project
v2.0 of the API is assumed.
- It is also assumed that the per-tenant quota extension API is configured
+ It is also assumed that the per-project quota extension API is configured
in /etc/neutron/neutron.conf as follows:
quota_driver = neutron.db.quota_db.DbQuotaDriver
@@ -48,7 +49,7 @@
cls.identity_admin_client = cls.os_adm.identity_client
def _check_quotas(self, new_quotas):
- # Add a tenant to conduct the test
+ # Add a project to conduct the test
project = data_utils.rand_name('test_project_')
description = data_utils.rand_name('desc_')
project = self.identity_utils.create_project(name=project,
@@ -56,14 +57,14 @@
project_id = project['id']
self.addCleanup(self.identity_utils.delete_project, project_id)
- # Change quotas for tenant
+ # Change quotas for project
quota_set = self.admin_quotas_client.update_quotas(
project_id, **new_quotas)['quota']
- self.addCleanup(self.admin_quotas_client.reset_quotas, project_id)
+ self.addCleanup(self._cleanup_quotas, project_id)
for key, value in six.iteritems(new_quotas):
self.assertEqual(value, quota_set[key])
- # Confirm our tenant is listed among tenants with non default quotas
+ # Confirm our project is listed among projects with non default quotas
non_default_quotas = self.admin_quotas_client.list_quotas()
found = False
for qs in non_default_quotas['quotas']:
@@ -71,7 +72,7 @@
found = True
self.assertTrue(found)
- # Confirm from API quotas were changed as requested for tenant
+ # Confirm from API quotas were changed as requested for project
quota_set = self.admin_quotas_client.show_quotas(project_id)
quota_set = quota_set['quota']
for key, value in six.iteritems(new_quotas):
@@ -87,3 +88,12 @@
def test_quotas(self):
new_quotas = {'network': 0, 'security_group': 0}
self._check_quotas(new_quotas)
+
+ def _cleanup_quotas(self, project_id):
+ # try to clean up the resources.If it fails, then
+ # assume that everything was already deleted, so
+ # it is OK to continue.
+ try:
+ self.admin_quotas_client.reset_quotas(project_id)
+ except lib_exc.NotFound:
+ pass
diff --git a/tempest/api/network/admin/test_routers_dvr.py b/tempest/api/network/admin/test_routers_dvr.py
index 3e787af..36cb15f 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -34,8 +34,8 @@
# has a distributed attribute.
super(RoutersTestDVR, cls).resource_setup()
name = data_utils.rand_name('pretest-check')
- router = cls.admin_client.create_router(name)
- cls.admin_client.delete_router(router['router']['id'])
+ router = cls.admin_routers_client.create_router(name=name)
+ cls.admin_routers_client.delete_router(router['router']['id'])
if 'distributed' not in router['router']:
msg = "'distributed' flag not found. DVR Possibly not enabled"
raise cls.skipException(msg)
@@ -53,8 +53,9 @@
set to True
"""
name = data_utils.rand_name('router')
- router = self.admin_client.create_router(name, distributed=True)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name=name,
+ distributed=True)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertTrue(router['router']['distributed'])
@@ -72,8 +73,9 @@
as opposed to a "Distributed Virtual Router"
"""
name = data_utils.rand_name('router')
- router = self.admin_client.create_router(name, distributed=False)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name=name,
+ distributed=False)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertFalse(router['router']['distributed'])
@@ -93,11 +95,12 @@
"""
name = data_utils.rand_name('router')
# router needs to be in admin state down in order to be upgraded to DVR
- router = self.admin_client.create_router(name, distributed=False,
- admin_state_up=False)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name=name,
+ distributed=False,
+ admin_state_up=False)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertFalse(router['router']['distributed'])
- router = self.admin_client.update_router(router['router']['id'],
- distributed=True)
+ router = self.admin_routers_client.update_router(
+ router['router']['id'], distributed=True)
self.assertTrue(router['router']['distributed'])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index db4561f..9823345 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -14,11 +14,11 @@
# under the License.
import netaddr
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -32,11 +32,11 @@
Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
following options are defined in the [network] section of etc/tempest.conf:
- tenant_network_cidr with a block of cidr's from which smaller blocks
- can be allocated for tenant networks
+ project_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for project networks
- tenant_network_mask_bits with the mask bits to be used to partition the
- block defined by tenant-network_cidr
+ project_network_mask_bits with the mask bits to be used to partition
+ the block defined by project-network_cidr
Finally, it is assumed that the following option is defined in the
[service_available] section of etc/tempest.conf
@@ -67,10 +67,10 @@
@classmethod
def setup_clients(cls):
super(BaseNetworkTest, cls).setup_clients()
- cls.client = cls.os.network_client
cls.agents_client = cls.os.network_agents_client
cls.network_extensions_client = cls.os.network_extensions_client
cls.networks_client = cls.os.networks_client
+ cls.routers_client = cls.os.routers_client
cls.subnetpools_client = cls.os.subnetpools_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
@@ -175,12 +175,12 @@
ip_version = ip_version if ip_version is not None else cls._ip_version
gateway_not_set = gateway == ''
if ip_version == 4:
- cidr = cidr or netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = mask_bits or CONF.network.tenant_network_mask_bits
+ cidr = cidr or netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = mask_bits or CONF.network.project_network_mask_bits
elif ip_version == 6:
- cidr = (
- cidr or netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr))
- mask_bits = mask_bits or CONF.network.tenant_network_v6_mask_bits
+ cidr = (cidr or
+ netaddr.IPNetwork(CONF.network.project_network_v6_cidr))
+ mask_bits = mask_bits or CONF.network.project_network_v6_mask_bits
# Find a cidr that is not in use yet and create a subnet with it
for subnet_cidr in cidr.subnet(mask_bits):
if gateway_not_set:
@@ -231,8 +231,8 @@
ext_gw_info['network_id'] = external_network_id
if enable_snat is not None:
ext_gw_info['enable_snat'] = enable_snat
- body = cls.client.create_router(
- router_name, external_gateway_info=ext_gw_info,
+ body = cls.routers_client.create_router(
+ name=router_name, external_gateway_info=ext_gw_info,
admin_state_up=admin_state_up, **kwargs)
router = body['router']
cls.routers.append(router)
@@ -250,21 +250,22 @@
@classmethod
def create_router_interface(cls, router_id, subnet_id):
"""Wrapper utility that returns a router interface."""
- interface = cls.client.add_router_interface_with_subnet_id(
- router_id, subnet_id)
+ interface = cls.routers_client.add_router_interface(
+ router_id, subnet_id=subnet_id)
return interface
@classmethod
def delete_router(cls, router):
- body = cls.client.list_router_interfaces(router['id'])
+ body = cls.ports_client.list_ports(device_id=router['id'])
interfaces = body['ports']
for i in interfaces:
try:
- cls.client.remove_router_interface_with_subnet_id(
- router['id'], i['fixed_ips'][0]['subnet_id'])
+ cls.routers_client.remove_router_interface(
+ router['id'],
+ subnet_id=i['fixed_ips'][0]['subnet_id'])
except lib_exc.NotFound:
pass
- cls.client.delete_router(router['id'])
+ cls.routers_client.delete_router(router['id'])
class BaseAdminNetworkTest(BaseNetworkTest):
@@ -274,9 +275,9 @@
@classmethod
def setup_clients(cls):
super(BaseAdminNetworkTest, cls).setup_clients()
- cls.admin_client = cls.os_adm.network_client
cls.admin_agents_client = cls.os_adm.network_agents_client
cls.admin_networks_client = cls.os_adm.networks_client
+ cls.admin_routers_client = cls.os_adm.routers_client
cls.admin_subnets_client = cls.os_adm.subnets_client
cls.admin_ports_client = cls.os_adm.ports_client
cls.admin_quotas_client = cls.os_adm.network_quotas_client
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
index 739e6f9..807257f 100644
--- a/tempest/api/network/base_routers.py
+++ b/tempest/api/network/base_routers.py
@@ -33,31 +33,31 @@
self.addCleanup(self._cleanup_router, router)
return router
- def _delete_router(self, router_id, network_client=None):
- client = network_client or self.client
+ def _delete_router(self, router_id, routers_client=None):
+ client = routers_client or self.routers_client
client.delete_router(router_id)
# Asserting that the router is not found in the list
# after deletion
- list_body = self.client.list_routers()
+ list_body = self.routers_client.list_routers()
routers_list = list()
for router in list_body['routers']:
routers_list.append(router['id'])
self.assertNotIn(router_id, routers_list)
def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
- interface = self.client.add_router_interface_with_subnet_id(
- 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.client.remove_router_interface_with_subnet_id(
- 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.client.remove_router_interface_with_port_id(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_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 394aec1..b2892e5 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -100,7 +100,7 @@
@test.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4')
def test_update_port_with_cidr_address_pair(self):
# Update allowed address pair with cidr
- cidr = str(netaddr.IPNetwork(CONF.network.tenant_network_cidr))
+ cidr = str(netaddr.IPNetwork(CONF.network.project_network_cidr))
self._update_port_with_address(cidr)
@test.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933')
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 74c1d51..77008ab 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -17,11 +17,11 @@
import random
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -66,11 +66,10 @@
body = self.ports_client.list_ports()
ports = body['ports']
for port in ports:
- if (port['device_owner'].startswith('network:router_interface')
- and port['device_id'] in [r['id'] for r in self.routers]):
- self.client.remove_router_interface_with_port_id(
- port['device_id'], port['id']
- )
+ if (port['device_owner'].startswith('network:router_interface') and
+ port['device_id'] in [r['id'] for r in self.routers]):
+ self.routers_client.remove_router_interface(port['device_id'],
+ port_id=port['id'])
else:
if port['id'] in [p['id'] for p in self.ports]:
self.ports_client.delete_port(port['id'])
@@ -81,11 +80,11 @@
if subnet['id'] in [s['id'] for s in self.subnets]:
self.subnets_client.delete_subnet(subnet['id'])
self._remove_from_list_by_index(self.subnets, subnet)
- body = self.client.list_routers()
+ body = self.routers_client.list_routers()
routers = body['routers']
for router in routers:
if router['id'] in [r['id'] for r in self.routers]:
- self.client.delete_router(router['id'])
+ self.routers_client.delete_router(router['id'])
self._remove_from_list_by_index(self.routers, router)
def _get_ips_from_subnet(self, **kwargs):
@@ -339,12 +338,12 @@
fixed_ips=[
{'subnet_id': subnet['id'],
'ip_address': ip}])
- self.assertRaisesRegexp(lib_exc.Conflict,
- "object with that identifier already exists",
- self.create_port,
- self.network,
- fixed_ips=[{'subnet_id': subnet['id'],
- 'ip_address': ip}])
+ self.assertRaisesRegex(lib_exc.Conflict,
+ "object with that identifier already exists",
+ self.create_port,
+ self.network,
+ fixed_ips=[{'subnet_id': subnet['id'],
+ 'ip_address': ip}])
def _create_subnet_router(self, kwargs):
subnet = self.create_subnet(self.network, **kwargs)
diff --git a/tempest/api/network/test_extensions.py b/tempest/api/network/test_extensions.py
index d71d600..2c981a1 100644
--- a/tempest/api/network/test_extensions.py
+++ b/tempest/api/network/test_extensions.py
@@ -31,7 +31,7 @@
@test.attr(type='smoke')
@test.idempotent_id('ef28c7e6-e646-4979-9d67-deb207bc5564')
def test_list_show_extensions(self):
- # List available extensions for the tenant
+ # List available extensions for the project
expected_alias = ['security-group', 'l3_agent_scheduler',
'ext-gw-mode', 'binding', 'quotas',
'agent', 'dhcp_agent_scheduler', 'provider',
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index ce9c4be..2abbf93 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import netaddr
-
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.common.utils import net_utils
from tempest import config
from tempest import test
@@ -59,7 +58,6 @@
cls.router = cls.create_router(data_utils.rand_name('router-'),
external_network_id=cls.ext_net_id)
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
- cls.port = list()
# Create two ports one each for Creation and Updating of floatingIP
for i in range(2):
cls.create_port(cls.network)
@@ -193,8 +191,12 @@
@test.idempotent_id('45c4c683-ea97-41ef-9c51-5e9802f2f3d7')
def test_create_update_floatingip_with_port_multiple_ip_address(self):
# Find out ips that can be used for tests
- ips = list(netaddr.IPNetwork(self.subnet['cidr']))
- list_ips = [str(ip) for ip in ips[-3:-1]]
+ list_ips = net_utils.get_unused_ip_addresses(
+ self.ports_client,
+ self.subnets_client,
+ self.subnet['network_id'],
+ self.subnet['id'],
+ 2)
fixed_ips = [{'ip_address': list_ips[0]}, {'ip_address': list_ips[1]}]
# Create port
body = self.ports_client.create_port(network_id=self.network['id'],
diff --git a/tempest/api/network/test_floating_ips_negative.py b/tempest/api/network/test_floating_ips_negative.py
index f915615..963d99d 100644
--- a/tempest/api/network/test_floating_ips_negative.py
+++ b/tempest/api/network/test_floating_ips_negative.py
@@ -14,11 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1c446ef..a5fb25c 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -12,16 +12,15 @@
# 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 itertools
-
import netaddr
import six
-from tempest_lib import exceptions as lib_exc
+import testtools
from tempest.api.network import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -30,12 +29,12 @@
class NetworksTest(base.BaseNetworkTest):
"""Tests the following operations in the Neutron API:
- create a network for a tenant
- list tenant's networks
- show a tenant network details
- create a subnet for a tenant
- list tenant's subnets
- show a tenant subnet details
+ create a network for a project
+ list project's networks
+ show a project network details
+ create a subnet for a project
+ list project's subnets
+ show a project subnet details
network update
subnet update
delete a network also deletes its subnets
@@ -46,15 +45,15 @@
v2.0 of the Neutron API is assumed. It is also assumed that the following
options are defined in the [network] section of etc/tempest.conf:
- tenant_network_cidr with a block of cidr's from which smaller blocks
- can be allocated for tenant ipv4 subnets
+ project_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for project ipv4 subnets
- tenant_network_v6_cidr is the equivalent for ipv6 subnets
+ project_network_v6_cidr is the equivalent for ipv6 subnets
- tenant_network_mask_bits with the mask bits to be used to partition the
- block defined by tenant_network_cidr
+ project_network_mask_bits with the mask bits to be used to partition
+ the block defined by project_network_cidr
- tenant_network_v6_mask_bits is the equivalent for ipv6 subnets
+ project_network_v6_mask_bits is the equivalent for ipv6 subnets
"""
@classmethod
@@ -93,14 +92,14 @@
@classmethod
def _create_subnet_with_last_subnet_block(cls, network, ip_version):
- # Derive last subnet CIDR block from tenant CIDR and
+ # Derive last subnet CIDR block from project CIDR and
# create the subnet with that derived CIDR
if ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = CONF.network.project_network_mask_bits
elif ip_version == 6:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
- mask_bits = CONF.network.tenant_network_v6_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
+ mask_bits = CONF.network.project_network_v6_mask_bits
subnet_cidr = list(cidr.subnet(mask_bits))[-1]
gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
@@ -111,11 +110,11 @@
def _get_gateway_from_tempest_conf(cls, ip_version):
"""Return first subnet gateway for configured CIDR """
if ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = CONF.network.project_network_mask_bits
elif ip_version == 6:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
- mask_bits = CONF.network.tenant_network_v6_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
+ mask_bits = CONF.network.project_network_v6_mask_bits
if mask_bits >= cidr.prefixlen:
return netaddr.IPAddress(cidr) + 1
@@ -376,6 +375,9 @@
@test.attr(type='smoke')
@test.idempotent_id('af774677-42a9-4e4b-bb58-16fe6a5bc1ec')
+ @test.requires_ext(extension='external-net', service='network')
+ @testtools.skipUnless(CONF.network.public_network_id,
+ 'The public_network_id option must be specified.')
def test_external_network_visibility(self):
"""Verifies user can see external networks but not subnets."""
body = self.networks_client.list_networks(**{'router:external': True})
@@ -387,35 +389,30 @@
self.assertEmpty(nonexternal, "Found non-external networks"
" in filtered list (%s)." % nonexternal)
self.assertIn(CONF.network.public_network_id, networks)
-
- subnets_iter = (network['subnets']
- for network in body['networks']
- if not network['shared'])
- # subnets_iter is a list (iterator) of lists. This flattens it to a
- # list of UUIDs
- public_subnets_iter = itertools.chain(*subnets_iter)
- body = self.subnets_client.list_subnets()
- subnets = [sub['id'] for sub in body['subnets']
- if sub['id'] in public_subnets_iter]
- self.assertEmpty(subnets, "Public subnets visible")
+ # only check the public network ID because the other networks may
+ # belong to other tests and their state may have changed during this
+ # test
+ body = self.subnets_client.list_subnets(
+ network_id=CONF.network.public_network_id)
+ self.assertEmpty(body['subnets'], "Public subnets visible")
-class BulkNetworkOpsTestJSON(base.BaseNetworkTest):
+class BulkNetworkOpsTest(base.BaseNetworkTest):
"""Tests the following operations in the Neutron API:
bulk network creation
bulk subnet creation
bulk port creation
- list tenant's networks
+ list project's networks
v2.0 of the Neutron API is assumed. It is also assumed that the following
options are defined in the [network] section of etc/tempest.conf:
- tenant_network_cidr with a block of cidr's from which smaller blocks
- can be allocated for tenant networks
+ project_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for project networks
- tenant_network_mask_bits with the mask bits to be used to partition the
- block defined by tenant-network_cidr
+ project_network_mask_bits with the mask bits to be used to partition
+ the block defined by project-network_cidr
"""
def _delete_networks(self, created_networks):
@@ -451,7 +448,7 @@
# Creates 2 networks in one request
network_list = [{'name': data_utils.rand_name('network-')},
{'name': data_utils.rand_name('network-')}]
- body = self.client.create_bulk_network(networks=network_list)
+ body = self.networks_client.create_bulk_networks(networks=network_list)
created_networks = body['networks']
self.addCleanup(self._delete_networks, created_networks)
# Asserting that the networks are found in the list after creation
@@ -467,11 +464,11 @@
networks = [self.create_network(), self.create_network()]
# Creates 2 subnets in one request
if self._ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = CONF.network.project_network_mask_bits
else:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
- mask_bits = CONF.network.tenant_network_v6_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
+ mask_bits = CONF.network.project_network_v6_mask_bits
cidrs = [subnet_cidr for subnet_cidr in cidr.subnet(mask_bits)]
@@ -486,7 +483,7 @@
}
subnets_list.append(p1)
del subnets_list[1]['name']
- body = self.client.create_bulk_subnet(subnets=subnets_list)
+ body = self.subnets_client.create_bulk_subnets(subnets=subnets_list)
created_subnets = body['subnets']
self.addCleanup(self._delete_subnets, created_subnets)
# Asserting that the subnets are found in the list after creation
@@ -512,7 +509,7 @@
}
port_list.append(p1)
del port_list[1]['name']
- body = self.client.create_bulk_port(ports=port_list)
+ body = self.ports_client.create_bulk_ports(ports=port_list)
created_ports = body['ports']
self.addCleanup(self._delete_ports, created_ports)
# Asserting that the ports are found in the list after creation
@@ -523,16 +520,16 @@
self.assertIn(n['id'], ports_list)
-class BulkNetworkOpsIpV6TestJSON(BulkNetworkOpsTestJSON):
+class BulkNetworkOpsIpV6Test(BulkNetworkOpsTest):
_ip_version = 6
-class NetworksIpV6TestJSON(NetworksTest):
+class NetworksIpV6Test(NetworksTest):
_ip_version = 6
@test.idempotent_id('e41a4888-65a6-418c-a095-f7c2ef4ad59a')
def test_create_delete_subnet_with_gw(self):
- net = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
+ net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
gateway = str(netaddr.IPAddress(net.first + 2))
name = data_utils.rand_name('network-')
network = self.create_network(network_name=name)
@@ -542,7 +539,7 @@
@test.idempotent_id('ebb4fd95-524f-46af-83c1-0305b239338f')
def test_create_delete_subnet_with_default_gw(self):
- net = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
+ net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
gateway_ip = str(netaddr.IPAddress(net.first + 1))
name = data_utils.rand_name('network-')
network = self.create_network(network_name=name)
@@ -579,7 +576,7 @@
'Subnet are not in the same network')
-class NetworksIpV6TestAttrs(NetworksIpV6TestJSON):
+class NetworksIpV6TestAttrs(NetworksIpV6Test):
@classmethod
def skip_checks(cls):
@@ -621,7 +618,7 @@
subnet_ids = [subnet['id'] for subnet in subnets['subnets']]
self.assertNotIn(subnet_slaac['id'], subnet_ids,
"Subnet wasn't deleted")
- self.assertRaisesRegexp(
+ self.assertRaisesRegex(
lib_exc.Conflict,
"There are one or more ports still in use on the network",
self.networks_client.delete_network,
diff --git a/tempest/api/network/test_networks_negative.py b/tempest/api/network/test_networks_negative.py
index 0ef96a6..d87c2b6 100644
--- a/tempest/api/network/test_networks_negative.py
+++ b/tempest/api/network/test_networks_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 67f2c83..caf7f14 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -22,6 +22,7 @@
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -74,7 +75,7 @@
network2 = self.create_network(network_name=name)
network_list = [network1['id'], network2['id']]
port_list = [{'network_id': net_id} for net_id in network_list]
- body = self.client.create_bulk_port(ports=port_list)
+ body = self.ports_client.create_bulk_ports(ports=port_list)
created_ports = body['ports']
port1 = created_ports[0]
port2 = created_ports[1]
@@ -87,14 +88,16 @@
@classmethod
def _get_ipaddress_from_tempest_conf(cls):
- """Return first subnet gateway for configured CIDR """
+ """Return subnet with mask bits for configured CIDR """
if cls._ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
+ cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ cidr.prefixlen = CONF.network.project_network_mask_bits
elif cls._ip_version == 6:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
+ cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
+ cidr.prefixlen = CONF.network.project_network_v6_mask_bits
- return netaddr.IPAddress(cidr)
+ return cidr
@test.attr(type='smoke')
@test.idempotent_id('0435f278-40ae-48cb-a404-b8a087bc09b1')
@@ -102,9 +105,15 @@
network = self.create_network()
net_id = network['id']
address = self._get_ipaddress_from_tempest_conf()
- allocation_pools = {'allocation_pools': [{'start': str(address + 4),
- 'end': str(address + 6)}]}
- subnet = self.create_subnet(network, **allocation_pools)
+ if ((address.version == 4 and address.prefixlen >= 30) or
+ (address.version == 6 and address.prefixlen >= 126)):
+ msg = ("Subnet %s isn't large enough for the test" % address.cidr)
+ raise exceptions.InvalidConfiguration(message=msg)
+ allocation_pools = {'allocation_pools': [{'start': str(address[2]),
+ 'end': str(address[-2])}]}
+ subnet = self.create_subnet(network, cidr=address,
+ mask_bits=address.prefixlen,
+ **allocation_pools)
self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
body = self.ports_client.create_port(network_id=net_id)
self.addCleanup(self.ports_client.delete_port, body['port']['id'])
@@ -122,12 +131,15 @@
body = self.ports_client.show_port(self.port['id'])
port = body['port']
self.assertIn('id', port)
- # TODO(Santosh)- This is a temporary workaround to compare create_port
- # and show_port dict elements.Remove this once extra_dhcp_opts issue
- # gets fixed in neutron.( bug - 1365341.)
+ # NOTE(rfolco): created_at and updated_at may get inconsistent values
+ # due to possible delay between POST request and resource creation.
+ # TODO(rfolco): Neutron Bug #1365341 is fixed, can remove the key
+ # extra_dhcp_opts in the O release (K/L gate jobs still need it).
self.assertThat(self.port,
custom_matchers.MatchesDictExceptForKeys
- (port, excluded_keys=['extra_dhcp_opts']))
+ (port, excluded_keys=['extra_dhcp_opts',
+ 'created_at',
+ 'updated_at']))
@test.idempotent_id('45fcdaf2-dab0-4c13-ac6c-fcddfb579dbd')
def test_show_port_fields(self):
@@ -188,13 +200,13 @@
subnet = self.create_subnet(network)
self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
router = self.create_router(data_utils.rand_name('router-'))
- self.addCleanup(self.client.delete_router, router['id'])
+ self.addCleanup(self.routers_client.delete_router, router['id'])
port = self.ports_client.create_port(network_id=network['id'])
# Add router interface to port created above
- self.client.add_router_interface_with_port_id(
- router['id'], port['port']['id'])
- self.addCleanup(self.client.remove_router_interface_with_port_id,
- router['id'], port['port']['id'])
+ self.routers_client.add_router_interface(router['id'],
+ port_id=port['port']['id'])
+ self.addCleanup(self.routers_client.remove_router_interface,
+ router['id'], port_id=port['port']['id'])
# List ports filtered by router_id
port_list = self.ports_client.list_ports(device_id=router['id'])
ports = port_list['ports']
@@ -416,11 +428,7 @@
class PortsIpV6TestJSON(PortsTestJSON):
_ip_version = 6
- _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
- _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits
class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON):
_ip_version = 6
- _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
- _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 406ad44..46b068b 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -41,9 +41,9 @@
@classmethod
def resource_setup(cls):
super(RoutersTest, cls).resource_setup()
- cls.tenant_cidr = (CONF.network.tenant_network_cidr
+ cls.tenant_cidr = (CONF.network.project_network_cidr
if cls._ip_version == 4 else
- CONF.network.tenant_network_v6_cidr)
+ CONF.network.project_network_v6_cidr)
@test.attr(type='smoke')
@test.idempotent_id('f64403e2-8483-4b34-8ccd-b09a87bcc68c')
@@ -52,8 +52,8 @@
# NOTE(salv-orlando): Do not invoke self.create_router
# as we need to check the response code
name = data_utils.rand_name('router-')
- create_body = self.client.create_router(
- name, external_gateway_info={
+ create_body = self.routers_client.create_router(
+ name=name, external_gateway_info={
"network_id": CONF.network.public_network_id},
admin_state_up=False)
self.addCleanup(self._delete_router, create_body['router']['id'])
@@ -63,24 +63,25 @@
CONF.network.public_network_id)
self.assertEqual(create_body['router']['admin_state_up'], False)
# Show details of the created router
- show_body = self.client.show_router(create_body['router']['id'])
+ show_body = self.routers_client.show_router(
+ create_body['router']['id'])
self.assertEqual(show_body['router']['name'], name)
self.assertEqual(
show_body['router']['external_gateway_info']['network_id'],
CONF.network.public_network_id)
self.assertEqual(show_body['router']['admin_state_up'], False)
# List routers and verify if created router is there in response
- list_body = self.client.list_routers()
+ list_body = self.routers_client.list_routers()
routers_list = list()
for router in list_body['routers']:
routers_list.append(router['id'])
self.assertIn(create_body['router']['id'], routers_list)
# Update the name of router and verify if it is updated
updated_name = 'updated ' + name
- update_body = self.client.update_router(create_body['router']['id'],
- name=updated_name)
+ update_body = self.routers_client.update_router(
+ create_body['router']['id'], name=updated_name)
self.assertEqual(update_body['router']['name'], updated_name)
- show_body = self.client.show_router(
+ show_body = self.routers_client.show_router(
create_body['router']['id'])
self.assertEqual(show_body['router']['name'], updated_name)
@@ -95,9 +96,9 @@
self.addCleanup(self.identity_utils.delete_project, project_id)
name = data_utils.rand_name('router-')
- create_body = self.admin_client.create_router(name,
- tenant_id=project_id)
- self.addCleanup(self.admin_client.delete_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'])
@@ -122,9 +123,9 @@
external_gateway_info = {
'network_id': CONF.network.public_network_id,
'enable_snat': enable_snat}
- create_body = self.admin_client.create_router(
- name, external_gateway_info=external_gateway_info)
- self.addCleanup(self.admin_client.delete_router,
+ 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'],
@@ -137,8 +138,8 @@
subnet = self.create_subnet(network)
router = self._create_router(data_utils.rand_name('router-'))
# Add router interface with subnet id
- interface = self.client.add_router_interface_with_subnet_id(
- 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.assertIn('subnet_id', interface.keys())
@@ -158,8 +159,9 @@
port_body = self.ports_client.create_port(
network_id=network['id'])
# add router interface to port created above
- interface = self.client.add_router_interface_with_port_id(
- router['id'], port_body['port']['id'])
+ 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.assertIn('subnet_id', interface.keys())
@@ -171,7 +173,7 @@
router['id'])
def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
- show_body = self.admin_client.show_router(router_id)
+ 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)
@@ -197,7 +199,7 @@
@test.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79')
def test_update_router_set_gateway(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.client.update_router(
+ self.routers_client.update_router(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id})
@@ -211,7 +213,7 @@
@test.requires_ext(extension='ext-gw-mode', service='network')
def test_update_router_set_gateway_with_snat_explicit(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -226,7 +228,7 @@
@test.requires_ext(extension='ext-gw-mode', service='network')
def test_update_router_set_gateway_without_snat(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -242,7 +244,8 @@
router = self._create_router(
data_utils.rand_name('router-'),
external_network_id=CONF.network.public_network_id)
- self.client.update_router(router['id'], external_gateway_info={})
+ 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(
@@ -256,7 +259,7 @@
router = self._create_router(
data_utils.rand_name('router-'),
external_network_id=CONF.network.public_network_id)
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -269,9 +272,9 @@
@test.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c')
@test.requires_ext(extension='extraroute', service='network')
- def test_update_extra_route(self):
+ def test_update_delete_extra_route(self):
# Create different cidr for each subnet to avoid cidr duplicate
- # The cidr starts from tenant_cidr
+ # The cidr starts from project_cidr
next_cidr = netaddr.IPNetwork(self.tenant_cidr)
# Prepare to build several routes
test_routes = []
@@ -300,9 +303,9 @@
)
test_routes.sort(key=lambda x: x['destination'])
- extra_route = self.client.update_extra_routes(router['id'],
- routes=test_routes)
- show_body = self.client.show_router(router['id'])
+ extra_route = self.routers_client.update_extra_routes(
+ router['id'], routes=test_routes)
+ show_body = self.routers_client.show_router(router['id'])
# Assert the number of routes
self.assertEqual(routes_num, len(extra_route['router']['routes']))
self.assertEqual(routes_num, len(show_body['router']['routes']))
@@ -322,18 +325,23 @@
routes[i]['destination'])
self.assertEqual(test_routes[i]['nexthop'], routes[i]['nexthop'])
+ self.routers_client.delete_extra_routes(router['id'])
+ show_body_after_deletion = self.routers_client.show_router(
+ router['id'])
+ self.assertEmpty(show_body_after_deletion['router']['routes'])
+
def _delete_extra_routes(self, router_id):
- self.client.delete_extra_routes(router_id)
+ self.routers_client.delete_extra_routes(router_id)
@test.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
def test_update_router_admin_state(self):
router = self._create_router(data_utils.rand_name('router-'))
self.assertFalse(router['admin_state_up'])
# Update router admin state
- update_body = self.client.update_router(router['id'],
- admin_state_up=True)
+ update_body = self.routers_client.update_router(router['id'],
+ admin_state_up=True)
self.assertTrue(update_body['router']['admin_state_up'])
- show_body = self.client.show_router(router['id'])
+ show_body = self.routers_client.show_router(router['id'])
self.assertTrue(show_body['router']['admin_state_up'])
@test.attr(type='smoke')
@@ -356,6 +364,23 @@
self._verify_router_interface(router['id'], subnet02['id'],
interface02['port_id'])
+ @test.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff')
+ def test_router_interface_port_update_with_fixed_ip(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ router = self._create_router(data_utils.rand_name('router-'))
+ fixed_ip = [{'subnet_id': subnet['id']}]
+ interface = self._add_router_interface_with_subnet_id(router['id'],
+ subnet['id'])
+ self.assertIn('port_id', interface)
+ self.assertIn('subnet_id', interface)
+ port = self.ports_client.show_port(interface['port_id'])
+ self.assertEqual(port['port']['id'], interface['port_id'])
+ router_port = self.ports_client.update_port(port['port']['id'],
+ fixed_ips=fixed_ip)
+ self.assertEqual(subnet['id'],
+ router_port['port']['fixed_ips'][0]['subnet_id'])
+
def _verify_router_interface(self, router_id, subnet_id, port_id):
show_port_body = self.ports_client.show_port(port_id)
interface_port = show_port_body['port']
@@ -380,21 +405,21 @@
@test.idempotent_id('141297aa-3424-455d-aa8d-f2d95731e00a')
def test_create_distributed_router(self):
name = data_utils.rand_name('router')
- create_body = self.admin_client.create_router(
- name, distributed=True)
+ create_body = self.admin_routers_client.create_router(
+ name=name, distributed=True)
self.addCleanup(self._delete_router,
create_body['router']['id'],
- self.admin_client)
+ self.admin_routers_client)
self.assertTrue(create_body['router']['distributed'])
@test.idempotent_id('644d7a4a-01a1-4b68-bb8d-0c0042cb1729')
def test_convert_centralized_router(self):
router = self._create_router(data_utils.rand_name('router'))
self.assertNotIn('distributed', router)
- update_body = self.admin_client.update_router(router['id'],
- distributed=True)
+ update_body = self.admin_routers_client.update_router(router['id'],
+ distributed=True)
self.assertTrue(update_body['router']['distributed'])
- show_body = self.admin_client.show_router(router['id'])
+ show_body = self.admin_routers_client.show_router(router['id'])
self.assertTrue(show_body['router']['distributed'])
- show_body = self.client.show_router(router['id'])
+ show_body = self.routers_client.show_router(router['id'])
self.assertNotIn('distributed', show_body['router'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 90da6fd..cd9f6ad 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -14,11 +14,11 @@
# under the License.
import netaddr
-from tempest_lib import exceptions as lib_exc
from tempest.api.network import base_routers as base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -39,15 +39,15 @@
cls.router = cls.create_router(data_utils.rand_name('router-'))
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
- cls.tenant_cidr = (CONF.network.tenant_network_cidr
+ cls.tenant_cidr = (CONF.network.project_network_cidr
if cls._ip_version == 4 else
- CONF.network.tenant_network_v6_cidr)
+ CONF.network.project_network_v6_cidr)
@test.attr(type=['negative'])
@test.idempotent_id('37a94fc0-a834-45b9-bd23-9a81d2fd1e22')
def test_router_add_gateway_invalid_network_returns_404(self):
self.assertRaises(lib_exc.NotFound,
- self.client.update_router,
+ self.routers_client.update_router,
self.router['id'],
external_gateway_info={
'network_id': self.router['id']})
@@ -60,7 +60,7 @@
sub_cidr = netaddr.IPNetwork(self.tenant_cidr).next()
self.create_subnet(alt_network, cidr=sub_cidr)
self.assertRaises(lib_exc.BadRequest,
- self.client.update_router,
+ self.routers_client.update_router,
self.router['id'],
external_gateway_info={
'network_id': alt_network['id']})
@@ -84,31 +84,31 @@
@test.attr(type=['negative'])
@test.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20')
def test_router_remove_interface_in_use_returns_409(self):
- self.client.add_router_interface_with_subnet_id(
- self.router['id'], self.subnet['id'])
+ self.routers_client.add_router_interface(self.router['id'],
+ subnet_id=self.subnet['id'])
self.assertRaises(lib_exc.Conflict,
- self.client.delete_router,
+ self.routers_client.delete_router,
self.router['id'])
@test.attr(type=['negative'])
@test.idempotent_id('c2a70d72-8826-43a7-8208-0209e6360c47')
def test_show_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.show_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.show_router,
router)
@test.attr(type=['negative'])
@test.idempotent_id('b23d1569-8b0c-4169-8d4b-6abd34fad5c7')
def test_update_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.update_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.update_router,
router, name="new_name")
@test.attr(type=['negative'])
@test.idempotent_id('c7edc5ad-d09d-41e6-a344-5c0c31e2e3e4')
def test_delete_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.delete_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.delete_router,
router)
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 7d0765e..5312979 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -24,7 +24,7 @@
class SecGroupTest(base.BaseSecGroupTest):
- _tenant_network_cidr = CONF.network.tenant_network_cidr
+ _project_network_cidr = CONF.network.project_network_cidr
@classmethod
def skip_checks(cls):
@@ -71,7 +71,7 @@
@test.attr(type='smoke')
@test.idempotent_id('e30abd17-fef9-4739-8617-dc26da88e686')
def test_list_security_groups(self):
- # Verify the that security group belonging to tenant exist in list
+ # Verify the that security group belonging to project exist in list
body = self.security_groups_client.list_security_groups()
security_groups = body['security_groups']
found = None
@@ -210,7 +210,7 @@
protocol = 'tcp'
port_range_min = 76
port_range_max = 77
- ip_prefix = self._tenant_network_cidr
+ ip_prefix = self._project_network_cidr
self._create_verify_security_group_rule(sg_id, direction,
self.ethertype, protocol,
port_range_min,
@@ -239,4 +239,4 @@
class SecGroupIPv6Test(SecGroupTest):
_ip_version = 6
- _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
+ _project_network_cidr = CONF.network.project_network_v6_cidr
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index ff38e9e..a3b0a82 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -13,19 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base_security_groups as base
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
class NegativeSecGroupTest(base.BaseSecGroupTest):
- _tenant_network_cidr = CONF.network.tenant_network_cidr
+ _project_network_cidr = CONF.network.project_network_cidr
@classmethod
def skip_checks(cls):
@@ -37,7 +35,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('424fd5c3-9ddc-486a-b45f-39bf0c820fc6')
def test_show_non_existent_security_group(self):
- non_exist_id = str(uuid.uuid4())
+ non_exist_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound, self.security_groups_client.show_security_group,
non_exist_id)
@@ -45,7 +43,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('4c094c09-000b-4e41-8100-9617600c02a6')
def test_show_non_existent_security_group_rule(self):
- non_exist_id = str(uuid.uuid4())
+ non_exist_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
self.security_group_rules_client.show_security_group_rule,
@@ -54,7 +52,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('1f1bb89d-5664-4956-9fcd-83ee0fa603df')
def test_delete_non_existent_security_group(self):
- non_exist_id = str(uuid.uuid4())
+ non_exist_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.security_groups_client.delete_security_group,
non_exist_id
@@ -92,7 +90,7 @@
@test.idempotent_id('4bf786fd-2f02-443c-9716-5b98e159a49a')
def test_create_security_group_rule_with_non_existent_remote_groupid(self):
group_create_body, _ = self._create_security_group()
- non_exist_id = str(uuid.uuid4())
+ non_exist_id = data_utils.rand_uuid()
# Create rule with non existent remote_group_id
group_ids = ['bad_group_id', non_exist_id]
@@ -111,7 +109,7 @@
sg2_body, _ = self._create_security_group()
# Create rule specifying both remote_ip_prefix and remote_group_id
- prefix = self._tenant_network_cidr
+ prefix = self._project_network_cidr
self.assertRaises(
lib_exc.BadRequest,
self.security_group_rules_client.create_security_group_rule,
@@ -155,6 +153,7 @@
# Create rule for icmp protocol with invalid ports
states = [(1, 256, 'Invalid value for ICMP code'),
+ (-1, 25, 'Invalid value'),
(None, 6, 'ICMP type (port-range-min) is missing'),
(300, 1, 'Invalid value for ICMP type')]
for pmin, pmax, msg in states:
@@ -205,7 +204,7 @@
@test.idempotent_id('be308db6-a7cf-4d5c-9baf-71bafd73f35e')
def test_create_security_group_rule_with_non_existent_security_group(self):
# Create security group rules with not existing security group.
- non_existent_sg = str(uuid.uuid4())
+ non_existent_sg = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
self.security_group_rules_client.create_security_group_rule,
@@ -215,7 +214,7 @@
class NegativeSecGroupIPv6Test(NegativeSecGroupTest):
_ip_version = 6
- _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
+ _project_network_cidr = CONF.network.project_network_v6_cidr
@test.attr(type=['negative'])
@test.idempotent_id('7607439c-af73-499e-bf64-f687fd12a842')
@@ -224,11 +223,11 @@
# Create rule with bad remote_ip_prefix
pairs = ({'ethertype': 'IPv6',
- 'ip_prefix': CONF.network.tenant_network_cidr},
+ 'ip_prefix': CONF.network.project_network_cidr},
{'ethertype': 'IPv4',
- 'ip_prefix': CONF.network.tenant_network_v6_cidr})
+ 'ip_prefix': CONF.network.project_network_v6_cidr})
for pair in pairs:
- self.assertRaisesRegexp(
+ self.assertRaisesRegex(
lib_exc.BadRequest,
"Conflicting value ethertype",
self.security_group_rules_client.create_security_group_rule,
diff --git a/tempest/api/network/test_service_type_management.py b/tempest/api/network/test_service_type_management.py
index ad1ecc4..f49f082 100644
--- a/tempest/api/network/test_service_type_management.py
+++ b/tempest/api/network/test_service_type_management.py
@@ -10,9 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.network import base
+from tempest.lib import decorators
from tempest import test
diff --git a/tempest/api/network/test_subnetpools_extensions.py b/tempest/api/network/test_subnetpools_extensions.py
index e5d0462..c6cc8e2 100644
--- a/tempest/api/network/test_subnetpools_extensions.py
+++ b/tempest/api/network/test_subnetpools_extensions.py
@@ -15,8 +15,8 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
CONF = config.CONF
@@ -30,7 +30,7 @@
Lists subnet pool.
Show subnet pool details.
- v2.0 of the Neutron API is assumed. It is assumed that subnetpools
+ v2.0 of the Neutron API is assumed. It is assumed that subnet_allocation
options mentioned in the [network-feature-enabled] section and
default_network option mentioned in the [network] section of
etc/tempest.conf:
@@ -40,8 +40,8 @@
@classmethod
def skip_checks(cls):
super(SubnetPoolsTestJSON, cls).skip_checks()
- if not test.is_extension_enabled('subnetpools', 'network'):
- msg = "subnet pools extension not enabled."
+ if not test.is_extension_enabled('subnet_allocation', 'network'):
+ msg = "subnet_allocation extension not enabled."
raise cls.skipException(msg)
@test.attr(type='smoke')
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 2621581..044e8c1 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import custom_matchers
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index aee17d3..546bb06 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index 998c2bd..254a9b3 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 3bb47f0..0055bf9 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 896352b..01e5389 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 1cc9437..9d043e5 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib.common.utils import data_utils
-
from tempest.api.object_storage import base
+from tempest.lib.common.utils import data_utils
from tempest import test
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 18593f3..5b3ce79 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 0e39b7e..2a5cec6 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -16,13 +16,13 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
import testtools
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 1c9d582..9db8bde 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -13,11 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import time
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index 7d9e115..cb13271 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -17,10 +17,10 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index e8b035b..a88e4f4 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -20,7 +20,6 @@
import zlib
import six
-from six import moves
from tempest.api.object_storage import base
from tempest.common import custom_matchers
@@ -179,23 +178,14 @@
@test.idempotent_id('84dafe57-9666-4f6d-84c8-0814d37923b8')
def test_create_object_with_expect_continue(self):
# create object with expect_continue
+
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.arbitrary_string()
- metadata = {'Expect': '100-continue'}
- resp = self.object_client.create_object_continue(
- self.container_name,
- object_name,
- data,
- metadata=metadata)
- self.assertIn('status', resp)
- self.assertEqual(resp['status'], '100')
+ status, _ = self.object_client.create_object_continue(
+ self.container_name, object_name, data)
- self.object_client.create_object_continue(
- self.container_name,
- object_name,
- data,
- metadata=None)
+ self.assertEqual(status, 201)
# check uploaded content
_, body = self.object_client.get_object(self.container_name,
@@ -210,8 +200,8 @@
status, _, resp_headers = self.object_client.put_object_with_chunk(
container=self.container_name,
name=object_name,
- contents=moves.cStringIO(data),
- chunk_size=512)
+ contents=data_utils.chunkify(data, 512)
+ )
self.assertHeaders(resp_headers, 'Object', 'PUT')
# check uploaded content
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 5811cb8..752f0b4 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -15,11 +15,11 @@
import hashlib
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
# Each segment, except for the final one, must be at least 1 megabyte
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 6d06143..38fe697 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -17,10 +17,10 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index c93b5ed..d813263 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -12,11 +12,11 @@
import os.path
-from tempest_lib import exceptions as lib_exc
import yaml
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -46,7 +46,6 @@
cls.client = cls.orchestration_client
cls.servers_client = cls.os.servers_client
cls.keypairs_client = cls.os.keypairs_client
- cls.network_client = cls.os.network_client
cls.networks_client = cls.os.networks_client
cls.volumes_client = cls.os.volumes_client
cls.images_v2_client = cls.os.image_client_v2
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
index ffff580..61c271c 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
@@ -1,10 +1,15 @@
heat_template_version: 2013-05-23
+parameters:
+ volume_size:
+ type: number
+ default: 1
+
resources:
volume:
type: OS::Cinder::Volume
properties:
- size: 1
+ size: { get_param: volume_size }
description: a descriptive description
name: volume_name
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
index b660c19..0bc6d69 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
@@ -1,11 +1,16 @@
heat_template_version: 2013-05-23
+parameters:
+ volume_size:
+ type: number
+ default: 1
+
resources:
volume:
deletion_policy: 'Retain'
type: OS::Cinder::Volume
properties:
- size: 1
+ size: { get_param: volume_size }
description: a descriptive description
name: volume_name
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
index 2acf97b..d85aa96 100644
--- a/tempest/api/orchestration/stacks/test_limits.py
+++ b/tempest/api/orchestration/stacks/test_limits.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -33,7 +32,7 @@
Foo: bar''' % fill
ex = self.assertRaises(lib_exc.BadRequest, self.create_stack,
stack_name, template)
- self.assertIn('Template exceeds maximum allowed size', str(ex))
+ self.assertIn('exceeds maximum allowed size', str(ex))
@test.idempotent_id('d1b83e73-7cad-4a22-9839-036548c5387c')
def test_exceed_max_resources_per_stack(self):
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 09e863e..3f45634 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -30,6 +30,8 @@
@classmethod
def skip_checks(cls):
+ msg = "Skipped until Bug: 1547261 is resolved."
+ raise cls.skipException(msg)
super(NeutronResourcesTestJSON, cls).skip_checks()
if not CONF.service_available.neutron:
raise cls.skipException("Neutron support is required")
@@ -42,7 +44,6 @@
@classmethod
def setup_clients(cls):
super(NeutronResourcesTestJSON, cls).setup_clients()
- cls.network_client = cls.os.network_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
@@ -56,8 +57,8 @@
cls._create_keypair()['name'])
cls.external_network_id = CONF.network.public_network_id
- tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ 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
@@ -149,7 +150,7 @@
def test_created_router(self):
"""Verifies created router."""
router_id = self.test_resources.get('Router')['physical_resource_id']
- body = self.network_client.show_router(router_id)
+ body = self.routers_client.show_router(router_id)
router = body['router']
self.assertEqual(self.neutron_basic_template['resources'][
'Router']['properties']['name'], router['name'])
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
index ab45929..6a4e2b9 100644
--- a/tempest/api/orchestration/stacks/test_soft_conf.py
+++ b/tempest/api/orchestration/stacks/test_soft_conf.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/orchestration/stacks/test_templates_negative.py b/tempest/api/orchestration/stacks/test_templates_negative.py
index 4bd0f33..24e10dd 100644
--- a/tempest/api/orchestration/stacks/test_templates_negative.py
+++ b/tempest/api/orchestration/stacks/test_templates_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index e51551b..a5aaf6e 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -33,8 +32,7 @@
self.assertIsNotNone(volume_id)
volume = self.volumes_client.show_volume(volume_id)['volume']
self.assertEqual('available', volume.get('status'))
- self.assertEqual(template['resources']['volume']['properties'][
- 'size'], volume.get('size'))
+ self.assertEqual(CONF.volume.volume_size, volume.get('size'))
# Some volume properties have been renamed with Cinder v2
if CONF.volume_feature_enabled.api_v2:
@@ -52,8 +50,8 @@
def _outputs_verify(self, stack_identifier, template):
self.assertEqual('available',
self.get_stack_output(stack_identifier, 'status'))
- self.assertEqual(str(template['resources']['volume']['properties'][
- 'size']), self.get_stack_output(stack_identifier, 'size'))
+ 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'))
@@ -66,7 +64,12 @@
"""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)
+ 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
@@ -95,7 +98,12 @@
"""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)
+ 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
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
deleted file mode 100644
index ff06810..0000000
--- a/tempest/api/telemetry/base.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 time
-
-from oslo_utils import timeutils
-from tempest_lib import exceptions as lib_exc
-
-from tempest.common import compute
-from tempest.common.utils import data_utils
-from tempest.common import waiters
-from tempest import config
-from tempest import exceptions
-import tempest.test
-
-CONF = config.CONF
-
-
-class BaseTelemetryTest(tempest.test.BaseTestCase):
-
- """Base test case class for all Telemetry API tests."""
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseTelemetryTest, cls).skip_checks()
- if not CONF.service_available.ceilometer:
- raise cls.skipException("Ceilometer support is required")
-
- @classmethod
- def setup_credentials(cls):
- cls.set_network_resources()
- super(BaseTelemetryTest, cls).setup_credentials()
-
- @classmethod
- def setup_clients(cls):
- super(BaseTelemetryTest, cls).setup_clients()
- cls.telemetry_client = cls.os.telemetry_client
- cls.servers_client = cls.os.servers_client
- cls.flavors_client = cls.os.flavors_client
- cls.image_client = cls.os.image_client
- cls.image_client_v2 = cls.os.image_client_v2
-
- @classmethod
- def resource_setup(cls):
- super(BaseTelemetryTest, cls).resource_setup()
- cls.nova_notifications = ['memory', 'vcpus', 'disk.root.size',
- 'disk.ephemeral.size']
-
- cls.glance_notifications = ['image.size']
-
- cls.glance_v2_notifications = ['image.download', 'image.serve']
-
- cls.server_ids = []
- cls.image_ids = []
-
- @classmethod
- def create_server(cls):
- tenant_network = cls.get_tenant_network()
- body, server = compute.create_test_server(
- cls.os,
- tenant_network=tenant_network,
- name=data_utils.rand_name('ceilometer-instance'),
- wait_until='ACTIVE')
- cls.server_ids.append(body['id'])
- return body
-
- @classmethod
- def create_image(cls, client, **kwargs):
- body = client.create_image(name=data_utils.rand_name('image'),
- container_format='bare',
- disk_format='raw',
- **kwargs)
- # TODO(jswarren) Move ['image'] up to initial body value assignment
- # once both v1 and v2 glance clients include the full response
- # object.
- if 'image' in body:
- body = body['image']
- cls.image_ids.append(body['id'])
- return body
-
- @staticmethod
- def cleanup_resources(method, list_of_ids):
- for resource_id in list_of_ids:
- try:
- method(resource_id)
- except lib_exc.NotFound:
- pass
-
- @classmethod
- def wait_for_server_termination(cls, server_id):
- waiters.wait_for_server_termination(cls.servers_client,
- server_id)
-
- @classmethod
- def resource_cleanup(cls):
- cls.cleanup_resources(cls.servers_client.delete_server, cls.server_ids)
- cls.cleanup_resources(cls.wait_for_server_termination, cls.server_ids)
- cls.cleanup_resources(cls.image_client.delete_image, cls.image_ids)
- super(BaseTelemetryTest, cls).resource_cleanup()
-
- def await_samples(self, metric, query):
- """This method is to wait for sample to add it to database.
-
- There are long time delays when using Postgresql (or Mysql)
- database as ceilometer backend
- """
- timeout = CONF.compute.build_timeout
- start = timeutils.utcnow()
- while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
- body = self.telemetry_client.list_samples(metric, query)
- if body:
- return body
- time.sleep(CONF.compute.build_interval)
-
- raise exceptions.TimeoutException(
- 'Sample for metric:%s with query:%s has not been added to the '
- 'database within %d seconds' % (metric, query,
- CONF.compute.build_timeout))
-
-
-class BaseTelemetryAdminTest(BaseTelemetryTest):
- """Base test case class for admin Telemetry API tests."""
-
- credentials = ['primary', 'admin']
-
- @classmethod
- def setup_clients(cls):
- super(BaseTelemetryAdminTest, cls).setup_clients()
- cls.telemetry_admin_client = cls.os_adm.telemetry_client
-
- def await_events(self, query):
- timeout = CONF.compute.build_timeout
- start = timeutils.utcnow()
- while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
- body = self.telemetry_admin_client.list_events(query)
- if body:
- return body
- time.sleep(CONF.compute.build_interval)
-
- raise exceptions.TimeoutException(
- 'Event with query:%s has not been added to the '
- 'database within %d seconds' % (query, CONF.compute.build_timeout))
-
-
-class BaseAlarmingTest(tempest.test.BaseTestCase):
- """Base test case class for all Alarming API tests."""
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseAlarmingTest, cls).skip_checks()
- if not CONF.service_available.aodh:
- raise cls.skipException("Aodh support is required")
-
- @classmethod
- def setup_clients(cls):
- super(BaseAlarmingTest, cls).setup_clients()
- cls.alarming_client = cls.os.alarming_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseAlarmingTest, cls).resource_setup()
- cls.alarm_ids = []
-
- @classmethod
- def create_alarm(cls, **kwargs):
- body = cls.alarming_client.create_alarm(
- name=data_utils.rand_name('telemetry_alarm'),
- type='threshold', **kwargs)
- cls.alarm_ids.append(body['alarm_id'])
- return body
-
- @staticmethod
- def cleanup_resources(method, list_of_ids):
- for resource_id in list_of_ids:
- try:
- method(resource_id)
- except lib_exc.NotFound:
- pass
-
- @classmethod
- def resource_cleanup(cls):
- cls.cleanup_resources(cls.alarming_client.delete_alarm, cls.alarm_ids)
- super(BaseAlarmingTest, cls).resource_cleanup()
diff --git a/tempest/api/telemetry/test_alarming_api.py b/tempest/api/telemetry/test_alarming_api.py
deleted file mode 100644
index daa0939..0000000
--- a/tempest/api/telemetry/test_alarming_api.py
+++ /dev/null
@@ -1,111 +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_lib import exceptions as lib_exc
-
-from tempest.api.telemetry import base
-from tempest.common.utils import data_utils
-from tempest import test
-
-
-class TelemetryAlarmingAPITestJSON(base.BaseAlarmingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TelemetryAlarmingAPITestJSON, cls).resource_setup()
- cls.rule = {'meter_name': 'cpu_util',
- 'comparison_operator': 'gt',
- 'threshold': 80.0,
- 'period': 70}
- for i in range(2):
- cls.create_alarm(threshold_rule=cls.rule)
-
- @test.idempotent_id('1c918e06-210b-41eb-bd45-14676dd77cd6')
- def test_alarm_list(self):
- # List alarms
- alarm_list = self.alarming_client.list_alarms()
-
- # Verify created alarm in the list
- fetched_ids = [a['alarm_id'] for a in alarm_list]
- missing_alarms = [a for a in self.alarm_ids if a not in fetched_ids]
- self.assertEqual(0, len(missing_alarms),
- "Failed to find the following created alarm(s)"
- " in a fetched list: %s" %
- ', '.join(str(a) for a in missing_alarms))
-
- @test.idempotent_id('1297b095-39c1-4e74-8a1f-4ae998cedd67')
- def test_create_update_get_delete_alarm(self):
- # Create an alarm
- alarm_name = data_utils.rand_name('telemetry_alarm')
- body = self.alarming_client.create_alarm(
- name=alarm_name, type='threshold', threshold_rule=self.rule)
- self.assertEqual(alarm_name, body['name'])
- alarm_id = body['alarm_id']
- self.assertDictContainsSubset(self.rule, body['threshold_rule'])
- # Update alarm with new rule and new name
- new_rule = {'meter_name': 'cpu',
- 'comparison_operator': 'eq',
- 'threshold': 70.0,
- 'period': 60}
- alarm_name_updated = data_utils.rand_name('telemetry-alarm-update')
- body = self.alarming_client.update_alarm(
- alarm_id,
- threshold_rule=new_rule,
- name=alarm_name_updated,
- type='threshold')
- self.assertEqual(alarm_name_updated, body['name'])
- self.assertDictContainsSubset(new_rule, body['threshold_rule'])
- # Get and verify details of an alarm after update
- body = self.alarming_client.show_alarm(alarm_id)
- self.assertEqual(alarm_name_updated, body['name'])
- self.assertDictContainsSubset(new_rule, body['threshold_rule'])
- # Get history for the alarm and verify the same
- body = self.alarming_client.show_alarm_history(alarm_id)
- self.assertEqual("rule change", body[0]['type'])
- self.assertIn(alarm_name_updated, body[0]['detail'])
- self.assertEqual("creation", body[1]['type'])
- self.assertIn(alarm_name, body[1]['detail'])
- # Delete alarm and verify if deleted
- self.alarming_client.delete_alarm(alarm_id)
- self.assertRaises(lib_exc.NotFound,
- self.alarming_client.show_alarm, alarm_id)
-
- @test.idempotent_id('aca49486-70bb-4016-87e0-f6131374f741')
- def test_set_get_alarm_state(self):
- alarm_states = ['ok', 'alarm', 'insufficient data']
- alarm = self.create_alarm(threshold_rule=self.rule)
- # Set alarm state and verify
- new_state =\
- [elem for elem in alarm_states if elem != alarm['state']][0]
- state = self.alarming_client.alarm_set_state(alarm['alarm_id'],
- new_state)
- self.assertEqual(new_state, state.data)
- # Get alarm state and verify
- state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
- self.assertEqual(new_state, state.data)
-
- @test.idempotent_id('08d7e45a-1344-4e5c-ba6f-f6cbb77f55b9')
- def test_create_delete_alarm_with_combination_rule(self):
- rule = {"alarm_ids": self.alarm_ids,
- "operator": "or"}
- # Verifies alarm create
- alarm_name = data_utils.rand_name('combination_alarm')
- body = self.alarming_client.create_alarm(name=alarm_name,
- combination_rule=rule,
- type='combination')
- self.assertEqual(alarm_name, body['name'])
- alarm_id = body['alarm_id']
- self.assertDictContainsSubset(rule, body['combination_rule'])
- # Verify alarm delete
- self.alarming_client.delete_alarm(alarm_id)
- self.assertRaises(lib_exc.NotFound,
- self.alarming_client.show_alarm, alarm_id)
diff --git a/tempest/api/telemetry/test_alarming_api_negative.py b/tempest/api/telemetry/test_alarming_api_negative.py
deleted file mode 100644
index e945556..0000000
--- a/tempest/api/telemetry/test_alarming_api_negative.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2015 GlobalLogic. 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.telemetry import base
-from tempest.common.utils import data_utils
-from tempest import test
-from tempest_lib import exceptions as lib_exc
-
-import uuid
-
-
-class TelemetryAlarmingNegativeTest(base.BaseAlarmingTest):
- """Negative tests for show_alarm, update_alarm, show_alarm_history tests
-
- ** show non-existent alarm
- ** show the deleted alarm
- ** delete deleted alarm
- ** update deleted alarm
- """
-
- @test.attr(type=['negative'])
- @test.idempotent_id('668743d5-08ad-4480-b2b8-15da34f81e7d')
- def test_get_non_existent_alarm(self):
- # get the non-existent alarm
- non_existent_id = str(uuid.uuid4())
- self.assertRaises(lib_exc.NotFound, self.alarming_client.show_alarm,
- non_existent_id)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('ef45000d-0a72-4781-866d-4cb7bf2582ad')
- def test_get_update_show_history_delete_deleted_alarm(self):
- # get, update and delete the deleted alarm
- alarm_name = data_utils.rand_name('telemetry_alarm')
- rule = {'meter_name': 'cpu',
- 'comparison_operator': 'eq',
- 'threshold': 100.0,
- 'period': 90}
- body = self.alarming_client.create_alarm(
- name=alarm_name,
- type='threshold',
- threshold_rule=rule)
- alarm_id = body['alarm_id']
- self.alarming_client.delete_alarm(alarm_id)
- # get the deleted alarm
- self.assertRaises(lib_exc.NotFound, self.alarming_client.show_alarm,
- alarm_id)
-
- # update the deleted alarm
- updated_alarm_name = data_utils.rand_name('telemetry_alarm_updated')
- updated_rule = {'meter_name': 'cpu_new',
- 'comparison_operator': 'eq',
- 'threshold': 70,
- 'period': 50}
- self.assertRaises(lib_exc.NotFound, self.alarming_client.update_alarm,
- alarm_id, threshold_rule=updated_rule,
- name=updated_alarm_name,
- type='threshold')
- # delete the deleted alarm
- self.assertRaises(lib_exc.NotFound, self.alarming_client.delete_alarm,
- alarm_id)
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
deleted file mode 100644
index a575125..0000000
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ /dev/null
@@ -1,84 +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_lib import decorators
-import testtools
-
-from tempest.api.telemetry import base
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-
-class TelemetryNotificationAPITestJSON(base.BaseTelemetryTest):
-
- @test.idempotent_id('d7f8c1c8-d470-4731-8604-315d3956caad')
- @test.services('compute')
- def test_check_nova_notification(self):
-
- body = self.create_server()
-
- query = ('resource', 'eq', body['id'])
-
- for metric in self.nova_notifications:
- self.await_samples(metric, query)
-
- @test.attr(type="smoke")
- @test.idempotent_id('04b10bfe-a5dc-47af-b22f-0460426bf498')
- @test.services("image")
- @testtools.skipIf(not CONF.image_feature_enabled.api_v1,
- "Glance api v1 is disabled")
- def test_check_glance_v1_notifications(self):
- body = self.create_image(self.image_client, is_public=False)
- self.image_client.update_image(body['id'], data='data')
-
- query = 'resource', 'eq', body['id']
-
- self.image_client.delete_image(body['id'])
-
- for metric in self.glance_notifications:
- self.await_samples(metric, query)
-
- @test.attr(type="smoke")
- @test.idempotent_id('c240457d-d943-439b-8aea-85e26d64fe8e')
- @test.services("image")
- @testtools.skipIf(not CONF.image_feature_enabled.api_v2,
- "Glance api v2 is disabled")
- def test_check_glance_v2_notifications(self):
- body = self.create_image(self.image_client_v2, visibility='private')
-
- self.image_client_v2.store_image_file(body['id'], "file")
- self.image_client_v2.show_image_file(body['id'])
-
- query = 'resource', 'eq', body['id']
-
- for metric in self.glance_v2_notifications:
- self.await_samples(metric, query)
-
-
-class TelemetryNotificationAdminAPITestJSON(base.BaseTelemetryAdminTest):
-
- @test.idempotent_id('29604198-8b45-4fc0-8af8-1cae4f94ebe9')
- @test.services('compute')
- @decorators.skip_because(bug='1480490')
- def test_check_nova_notification_event_and_meter(self):
-
- body = self.create_server()
-
- if CONF.telemetry_feature_enabled.events:
- query = ('instance_id', 'eq', body['id'])
- self.await_events(query)
-
- query = ('resource', 'eq', body['id'])
- for metric in self.nova_notifications:
- self.await_samples(metric, query)
diff --git a/tempest/api/utils.py b/tempest/api/utils.py
deleted file mode 100644
index 00c93b7..0000000
--- a/tempest/api/utils.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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.
-
-"""Common utilities used in testing."""
-
-from tempest import test
-
-
-class skip_unless_attr(object):
- """Decorator that skips a test if a specified attr exists and is True."""
- def __init__(self, attr, msg=None):
- self.attr = attr
- self.message = msg or ("Test case attribute %s not found "
- "or False") % attr
-
- def __call__(self, func):
- def _skipper(*args, **kw):
- """Wrapped skipper function."""
- testobj = args[0]
- if not getattr(testobj, self.attr, False):
- raise test.BaseTestCase.skipException(self.message)
- func(*args, **kw)
- _skipper.__name__ = func.__name__
- _skipper.__doc__ = func.__doc__
- return _skipper
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 60e6e6c..00acc7d 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -13,6 +13,7 @@
import six
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -31,14 +32,9 @@
@classmethod
def resource_setup(cls):
super(VolumeMultiBackendV2Test, cls).resource_setup()
- # support 2 backends names, deprecated_for_removal.
- # keep support 2 backend names, in case they are not empty
- if CONF.volume.backend1_name and CONF.volume.backend2_name:
- cls.backend_names = {CONF.volume.backend1_name,
- CONF.volume.backend2_name}
- else:
- # read backend name from a list .
- cls.backend_names = set(CONF.volume.backend_names)
+
+ # read backend name from a list .
+ cls.backend_names = set(CONF.volume.backend_names)
cls.name_field = cls.special_fields['name_field']
cls.volume_type_id_list = []
@@ -80,8 +76,8 @@
else:
self.volume_id_list_without_prefix.append(
self.volume['id'])
- self.admin_volume_client.wait_for_volume_status(
- self.volume['id'], 'available')
+ waiters.wait_for_volume_status(self.admin_volume_client,
+ self.volume['id'], 'available')
@classmethod
def resource_cleanup(cls):
diff --git a/tempest/api/volume/test_qos.py b/tempest/api/volume/admin/test_qos.py
similarity index 100%
rename from tempest/api/volume/test_qos.py
rename to tempest/api/volume/admin/test_qos.py
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index f2bf613..26a5a45 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -15,6 +15,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -41,18 +42,17 @@
vol_name = data_utils.rand_name(cls.__name__ + '-Volume')
cls.name_field = cls.special_fields['name_field']
params = {cls.name_field: vol_name}
- cls.volume = \
- cls.volumes_client.create_volume(**params)['volume']
- cls.volumes_client.wait_for_volume_status(cls.volume['id'],
- 'available')
+ cls.volume = cls.volumes_client.create_volume(**params)['volume']
+ waiters.wait_for_volume_status(cls.volumes_client,
+ cls.volume['id'], 'available')
# Create a test shared snapshot for tests
snap_name = data_utils.rand_name(cls.__name__ + '-Snapshot')
params = {cls.name_field: snap_name}
cls.snapshot = cls.client.create_snapshot(
volume_id=cls.volume['id'], **params)['snapshot']
- cls.client.wait_for_snapshot_status(cls.snapshot['id'],
- 'available')
+ waiters.wait_for_snapshot_status(cls.client,
+ cls.snapshot['id'], 'available')
@classmethod
def resource_cleanup(cls):
diff --git a/tempest/api/volume/admin/test_volume_pools.py b/tempest/api/volume/admin/test_volume_pools.py
new file mode 100644
index 0000000..c662e8c
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_pools.py
@@ -0,0 +1,43 @@
+# Copyright 2016 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.volume import base
+from tempest import test
+
+
+class VolumePoolsAdminV2TestsJSON(base.BaseVolumeAdminTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(VolumePoolsAdminV2TestsJSON, cls).resource_setup()
+ # Create a test shared volume for tests
+ cls.volume = cls.create_volume()
+
+ @test.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
+ def test_get_pools_without_details(self):
+ volume_info = self.admin_volume_client. \
+ show_volume(self.volume['id'])['volume']
+ cinder_pools = self.admin_volume_client.show_pools()['pools']
+ self.assertIn(volume_info['os-vol-host-attr:host'],
+ [pool['name'] for pool in cinder_pools])
+
+ @test.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced')
+ def test_get_pools_with_details(self):
+ volume_info = self.admin_volume_client. \
+ show_volume(self.volume['id'])['volume']
+ cinder_pools = self.admin_volume_client.\
+ show_pools(detail=True)['pools']
+ self.assertIn(volume_info['os-vol-host-attr:host'],
+ [pool['name'] for pool in cinder_pools])
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 9185553..a43ee8e 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 2b7ee45..755365d 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -12,11 +12,20 @@
# 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 import config
from tempest import test
+CONF = config.CONF
+
+
+def _get_host(host):
+ if CONF.volume_feature_enabled.volume_services:
+ host = host.split('@')[0]
+ return host
+
+
class VolumesServicesV2TestJSON(base.BaseVolumeAdminTest):
"""Tests Volume Services API.
@@ -28,7 +37,10 @@
super(VolumesServicesV2TestJSON, cls).resource_setup()
cls.services = (cls.admin_volume_services_client.list_services()
['services'])
- cls.host_name = cls.services[0]['host']
+ # NOTE: Cinder service-list API returns the list contains
+ # "<host name>@<driver name>" like "nova-compute01@lvmdriver-1".
+ # So here picks <host name> up as a host.
+ cls.host_name = _get_host(cls.services[0]['host'])
cls.binary_name = cls.services[0]['binary']
@test.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d')
@@ -48,7 +60,7 @@
@test.idempotent_id('178710e4-7596-4e08-9333-745cb8bc4f8d')
def test_get_service_by_host_name(self):
services_on_host = [service for service in self.services if
- service['host'] == self.host_name]
+ _get_host(service['host']) == self.host_name]
services = (self.admin_volume_services_client.list_services(
host=self.host_name)['services'])
@@ -68,7 +80,7 @@
host=self.host_name, binary=self.binary_name))['services']
self.assertEqual(1, len(services))
- self.assertEqual(self.host_name, services[0]['host'])
+ 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_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index c66207f..b7f70ba 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index c032d9c..6fc6f1c 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -15,6 +15,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -27,9 +28,6 @@
self.volumes_client.delete_volume(volume_id)
self.volumes_client.wait_for_resource_deletion(volume_id)
- def _delete_volume_type(self, volume_type_id):
- self.volume_types_client.delete_volume_type(volume_type_id)
-
@test.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54')
def test_volume_type_list(self):
# List volume types.
@@ -53,7 +51,8 @@
name=vol_type_name,
extra_specs=extra_specs)['volume_type']
volume_types.append(vol_type)
- self.addCleanup(self._delete_volume_type, vol_type['id'])
+ self.addCleanup(self.volume_types_client.delete_volume_type,
+ vol_type['id'])
params = {self.name_field: vol_name,
'volume_type': volume_types[0]['id']}
@@ -66,12 +65,14 @@
"to the requested name")
self.assertIsNotNone(volume['id'],
"Field volume id is empty or not found.")
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
# Update volume with new volume_type
self.volumes_client.retype_volume(volume['id'],
new_type=volume_types[1]['id'])
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
# Get volume details and Verify
fetched_volume = self.volumes_client.show_volume(
@@ -100,7 +101,8 @@
name=name,
extra_specs=extra_specs)['volume_type']
self.assertIn('id', body)
- self.addCleanup(self._delete_volume_type, body['id'])
+ self.addCleanup(self.volume_types_client.delete_volume_type,
+ body['id'])
self.assertIn('name', body)
self.assertEqual(body['name'], name,
"The created volume_type name is not equal "
@@ -127,7 +129,8 @@
name = data_utils.rand_name("volume-type")
body = self.volume_types_client.create_volume_type(
name=name)['volume_type']
- self.addCleanup(self._delete_volume_type, body['id'])
+ self.addCleanup(self.volume_types_client.delete_volume_type,
+ body['id'])
# Create encryption type
encryption_type = self.volume_types_client.create_encryption_type(
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 6483af3..f3e52e9 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -54,7 +51,7 @@
self.assertRaises(
lib_exc.BadRequest,
self.volume_types_client.update_volume_type_extra_specs,
- self.volume_type['id'], str(uuid.uuid4()),
+ self.volume_type['id'], data_utils.rand_uuid(),
extra_spec)
@test.idempotent_id('9bf7a657-b011-4aec-866d-81c496fbe5c8')
@@ -85,7 +82,7 @@
self.assertRaises(
lib_exc.NotFound,
self.volume_types_client.create_volume_type_extra_specs,
- str(uuid.uuid4()), extra_specs)
+ data_utils.rand_uuid(), extra_specs)
@test.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d')
def test_create_none_body(self):
@@ -111,7 +108,7 @@
self.assertRaises(
lib_exc.NotFound,
self.volume_types_client.delete_volume_type_extra_specs,
- str(uuid.uuid4()), extra_specs.keys()[0])
+ data_utils.rand_uuid(), extra_specs.keys()[0])
@test.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb')
def test_list_nonexistent_volume_type_id(self):
@@ -119,7 +116,7 @@
self.assertRaises(
lib_exc.NotFound,
self.volume_types_client.list_volume_types_extra_specs,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9')
def test_get_nonexistent_volume_type_id(self):
@@ -128,7 +125,7 @@
self.assertRaises(
lib_exc.NotFound,
self.volume_types_client.show_volume_type_extra_specs,
- str(uuid.uuid4()), extra_specs.keys()[0])
+ data_utils.rand_uuid(), extra_specs.keys()[0])
@test.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753')
def test_get_nonexistent_extra_spec_id(self):
@@ -137,7 +134,7 @@
self.assertRaises(
lib_exc.NotFound,
self.volume_types_client.show_volume_type_extra_specs,
- self.volume_type['id'], str(uuid.uuid4()))
+ self.volume_type['id'], data_utils.rand_uuid())
class ExtraSpecsNegativeV1Test(ExtraSpecsNegativeV2Test):
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index bc32fc9..aff5466 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -13,11 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -27,8 +25,8 @@
def test_create_with_nonexistent_volume_type(self):
# Should not be able to create volume with nonexistent volume_type.
self.name_field = self.special_fields['name_field']
- params = {self.name_field: str(uuid.uuid4()),
- 'volume_type': str(uuid.uuid4())}
+ params = {self.name_field: data_utils.rand_uuid(),
+ 'volume_type': data_utils.rand_uuid()}
self.assertRaises(lib_exc.NotFound,
self.volumes_client.create_volume, **params)
@@ -43,14 +41,14 @@
# Should not be able to get volume type with nonexistent type id.
self.assertRaises(lib_exc.NotFound,
self.volume_types_client.show_volume_type,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.idempotent_id('6b3926d2-7d73-4896-bc3d-e42dfd11a9f6')
def test_delete_nonexistent_type_id(self):
# Should not be able to delete volume type with nonexistent type id.
self.assertRaises(lib_exc.NotFound,
self.volume_types_client.delete_volume_type,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
class VolumeTypesNegativeV1Test(VolumeTypesNegativeV2Test):
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 253a3e1..bdb313f 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -15,6 +15,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils as utils
+from tempest.common import waiters
from tempest import test
@@ -35,7 +36,8 @@
params = {cls.name_field: vol_name}
cls.volume = cls.client.create_volume(**params)['volume']
- cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+ waiters.wait_for_volume_status(cls.client,
+ cls.volume['id'], 'available')
@classmethod
def resource_cleanup(cls):
@@ -61,7 +63,8 @@
vol_name = utils.rand_name('Volume')
params = {self.name_field: vol_name}
temp_volume = self.client.create_volume(**params)['volume']
- self.client.wait_for_volume_status(temp_volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client,
+ temp_volume['id'], 'available')
return temp_volume
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 4b2d3f3..b09cd2c 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -13,11 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
@@ -51,8 +51,8 @@
self.addCleanup(self.backups_adm_client.delete_backup,
backup['id'])
self.assertEqual(backup_name, backup['name'])
- self.admin_volume_client.wait_for_volume_status(
- self.volume['id'], 'available')
+ waiters.wait_for_volume_status(self.admin_volume_client,
+ self.volume['id'], 'available')
self.backups_adm_client.wait_for_backup_status(backup['id'],
'available')
@@ -75,8 +75,8 @@
self.assertEqual(backup['id'], restore['backup_id'])
self.backups_adm_client.wait_for_backup_status(backup['id'],
'available')
- self.admin_volume_client.wait_for_volume_status(
- restore['volume_id'], 'available')
+ waiters.wait_for_volume_status(self.admin_volume_client,
+ restore['volume_id'], 'available')
@decorators.skip_because(bug='1455043')
@test.idempotent_id('a99c54a1-dd80-4724-8a13-13bf58d4068d')
@@ -118,8 +118,8 @@
self.addCleanup(self.admin_volume_client.delete_volume,
restore['volume_id'])
self.assertEqual(import_backup['id'], restore['backup_id'])
- self.admin_volume_client.wait_for_volume_status(restore['volume_id'],
- 'available')
+ waiters.wait_for_volume_status(self.admin_volume_client,
+ restore['volume_id'], 'available')
# Verify if restored volume is there in volume list
volumes = self.admin_volume_client.list_volumes()['volumes']
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cc906e5..14819e3 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import compute
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -113,7 +113,8 @@
volume = cls.volumes_client.create_volume(**kwargs)['volume']
cls.volumes.append(volume)
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(cls.volumes_client,
+ volume['id'], 'available')
return volume
@classmethod
@@ -122,8 +123,8 @@
snapshot = cls.snapshots_client.create_snapshot(
volume_id=volume_id, **kwargs)['snapshot']
cls.snapshots.append(snapshot)
- cls.snapshots_client.wait_for_snapshot_status(snapshot['id'],
- 'available')
+ waiters.wait_for_snapshot_status(cls.snapshots_client,
+ snapshot['id'], 'available')
return snapshot
# NOTE(afazekas): these create_* and clean_* could be defined
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 7046dcf..866db3d 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -16,6 +16,7 @@
from testtools import matchers
from tempest.api.volume import base
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -51,8 +52,8 @@
volume_id=volume['id'])['transfer']
transfer_id = transfer['id']
auth_key = transfer['auth_key']
- self.client.wait_for_volume_status(volume['id'],
- 'awaiting-transfer')
+ waiters.wait_for_volume_status(self.client,
+ volume['id'], 'awaiting-transfer')
# Get a volume transfer
body = self.client.show_volume_transfer(transfer_id)['transfer']
@@ -66,7 +67,8 @@
# Accept a volume transfer by alt_tenant
body = self.alt_client.accept_volume_transfer(
transfer_id, auth_key=auth_key)['transfer']
- self.alt_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.alt_client,
+ volume['id'], 'available')
@test.idempotent_id('ab526943-b725-4c07-b875-8e8ef87a2c30')
def test_create_list_delete_volume_transfer(self):
@@ -78,8 +80,8 @@
body = self.client.create_volume_transfer(
volume_id=volume['id'])['transfer']
transfer_id = body['id']
- self.client.wait_for_volume_status(volume['id'],
- 'awaiting-transfer')
+ waiters.wait_for_volume_status(self.client,
+ volume['id'], 'awaiting-transfer')
# List all volume transfers (looking for the one we created)
body = self.client.list_volume_transfers()['transfers']
@@ -91,7 +93,7 @@
# Delete a volume transfer
self.client.delete_volume_transfer(transfer_id)
- self.client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client, volume['id'], 'available')
class VolumesV1TransfersTest(VolumesV2TransfersTest):
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 5f9ea7f..5975231 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -17,6 +17,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions
from tempest import test
import testtools
@@ -34,7 +35,6 @@
@classmethod
def resource_setup(cls):
super(VolumesV2ActionsTest, cls).resource_setup()
-
# Create a test shared instance
srv_name = data_utils.rand_name(cls.__name__ + '-Instance')
cls.server = cls.create_server(
@@ -43,7 +43,8 @@
# Create a test shared volume for attach/detach tests
cls.volume = cls.create_volume()
- cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+ waiters.wait_for_volume_status(cls.client,
+ cls.volume['id'], 'available')
@classmethod
def resource_cleanup(cls):
@@ -60,13 +61,15 @@
@test.services('compute')
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
- mountpoint = '/dev/vdc'
self.client.attach_volume(self.volume['id'],
instance_uuid=self.server['id'],
- mountpoint=mountpoint)
- self.client.wait_for_volume_status(self.volume['id'], 'in-use')
+ mountpoint='/dev/%s' %
+ CONF.compute.volume_device_name)
+ waiters.wait_for_volume_status(self.client,
+ self.volume['id'], 'in-use')
self.client.detach_volume(self.volume['id'])
- self.client.wait_for_volume_status(self.volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client,
+ self.volume['id'], 'available')
@test.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
@testtools.skipUnless(CONF.volume_feature_enabled.bootable,
@@ -87,21 +90,24 @@
@test.services('compute')
def test_get_volume_attachment(self):
# Verify that a volume's attachment information is retrieved
- mountpoint = '/dev/vdc'
self.client.attach_volume(self.volume['id'],
instance_uuid=self.server['id'],
- mountpoint=mountpoint)
- self.client.wait_for_volume_status(self.volume['id'], 'in-use')
+ mountpoint='/dev/%s' %
+ CONF.compute.volume_device_name)
+ waiters.wait_for_volume_status(self.client,
+ self.volume['id'], 'in-use')
# NOTE(gfidente): added in reverse order because functions will be
# called in reverse order to the order they are added (LIFO)
- self.addCleanup(self.client.wait_for_volume_status,
+ self.addCleanup(waiters.wait_for_volume_status, self.client,
self.volume['id'],
'available')
self.addCleanup(self.client.detach_volume, self.volume['id'])
volume = self.client.show_volume(self.volume['id'])['volume']
self.assertIn('attachments', volume)
attachment = self.client.get_attachment_from_volume(volume)
- self.assertEqual(mountpoint, attachment['device'])
+ self.assertEqual('/dev/%s' %
+ CONF.compute.volume_device_name,
+ attachment['device'])
self.assertEqual(self.server['id'], attachment['server_id'])
self.assertEqual(self.volume['id'], attachment['id'])
self.assertEqual(self.volume['id'], attachment['volume_id'])
@@ -118,9 +124,18 @@
self.volume['id'], image_name=image_name,
disk_format=CONF.volume.disk_format)['os-volume_upload_image']
image_id = body["image_id"]
- self.addCleanup(self.image_client.delete_image, image_id)
- self.image_client.wait_for_image_status(image_id, 'active')
- self.client.wait_for_volume_status(self.volume['id'], 'available')
+ self.addCleanup(self._cleanup_image, image_id)
+ waiters.wait_for_image_status(self.image_client, image_id, 'active')
+ waiters.wait_for_volume_status(self.client,
+ self.volume['id'], 'available')
+
+ def _cleanup_image(self, image_id):
+ # Ignores the image deletion
+ # in the case that image wasn't created in the first place
+ try:
+ self.image_client.delete_image(image_id)
+ except exceptions.NotFound:
+ pass
@test.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index ed1e5c5..1947779 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -33,7 +34,8 @@
self.volume = self.create_volume()
extend_size = int(self.volume['size']) + 1
self.client.extend_volume(self.volume['id'], new_size=extend_size)
- self.client.wait_for_volume_status(self.volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client,
+ self.volume['id'], 'available')
volume = self.client.show_volume(self.volume['id'])['volume']
self.assertEqual(int(volume['size']), extend_size)
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index aa3ef2f..5d83bb0 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -18,6 +18,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import test
@@ -53,7 +54,7 @@
volume = self.client.create_volume(**kwargs)['volume']
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
- self.client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client, volume['id'], 'available')
self.assertIn(self.name_field, volume)
self.assertEqual(volume[self.name_field], v_name,
"The created volume name is not equal "
@@ -113,7 +114,8 @@
new_volume = self.client.create_volume(**params)['volume']
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
- self.client.wait_for_volume_status(new_volume['id'], 'available')
+ waiters.wait_for_volume_status(self.client,
+ new_volume['id'], 'available')
params = {self.name_field: volume[self.name_field],
self.descrip_field: volume[self.descrip_field]}
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index ad6f556..77bfaf1 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -13,13 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -45,14 +42,14 @@
def test_volume_get_nonexistent_volume_id(self):
# Should not be able to get a non-existent volume
self.assertRaises(lib_exc.NotFound, self.client.show_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29')
def test_volume_delete_nonexistent_volume_id(self):
# Should not be able to delete a non-existent Volume
self.assertRaises(lib_exc.NotFound, self.client.delete_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('1ed83a8a-682d-4dfb-a30e-ee63ffd6c049')
@@ -99,7 +96,7 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
- size='1', volume_type=str(uuid.uuid4()),
+ size='1', volume_type=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -109,7 +106,7 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
- size='1', snapshot_id=str(uuid.uuid4()),
+ size='1', snapshot_id=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -119,7 +116,7 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
- size='1', source_volid=str(uuid.uuid4()),
+ size='1', source_volid=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -128,7 +125,8 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.update_volume,
- volume_id=str(uuid.uuid4()), display_name=v_name,
+ volume_id=data_utils.rand_uuid(),
+ display_name=v_name,
metadata=metadata)
@test.attr(type=['negative'])
@@ -189,7 +187,7 @@
self.assertRaises(lib_exc.NotFound,
self.client.attach_volume,
- str(uuid.uuid4()),
+ data_utils.rand_uuid(),
instance_uuid=server['id'],
mountpoint=self.mountpoint)
@@ -230,7 +228,7 @@
# Extend volume size when volume is nonexistent.
extend_size = int(self.volume['size']) + 1
self.assertRaises(lib_exc.NotFound, self.client.extend_volume,
- str(uuid.uuid4()), new_size=extend_size)
+ data_utils.rand_uuid(), new_size=extend_size)
@test.attr(type=['negative'])
@test.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
@@ -245,14 +243,14 @@
def test_reserve_volume_with_nonexistent_volume_id(self):
self.assertRaises(lib_exc.NotFound,
self.client.reserve_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c')
def test_unreserve_volume_with_nonexistent_volume_id(self):
self.assertRaises(lib_exc.NotFound,
self.client.unreserve_volume,
- str(uuid.uuid4()))
+ data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index c79235a..6707121 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -12,7 +12,9 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
@@ -33,11 +35,15 @@
cls.name_field = cls.special_fields['name_field']
cls.descrip_field = cls.special_fields['descrip_field']
+ # Create 2 snapshots
+ for _ in xrange(2):
+ cls.create_snapshot(cls.volume_origin['id'])
def _detach(self, volume_id):
"""Detach volume."""
self.volumes_client.detach_volume(volume_id)
- self.volumes_client.wait_for_volume_status(volume_id, 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume_id, 'available')
def _list_by_param_values_and_assert(self, with_detail=False, **params):
"""list or list_details with given params and validates result."""
@@ -56,6 +62,14 @@
('details' if with_detail else '', key)
self.assertEqual(params[key], snap[key], msg)
+ def _list_snapshots_by_param_limit(self, limit, expected_elements):
+ """list snapshots by limit param"""
+ # Get snapshots list using limit parameter
+ fetched_snap_list = self.snapshots_client.list_snapshots(
+ limit=limit)['snapshots']
+ # Validating filtered snapshots length equals to expected_elements
+ self.assertEqual(expected_elements, len(fetched_snap_list))
+
@test.idempotent_id('b467b54c-07a4-446d-a1cf-651dedcc3ff1')
@test.services('compute')
def test_snapshot_create_with_volume_in_use(self):
@@ -66,13 +80,12 @@
name=server_name,
wait_until='ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
- mountpoint = '/dev/%s' % CONF.compute.volume_device_name
self.servers_client.attach_volume(
server['id'], volumeId=self.volume_origin['id'],
- device=mountpoint)
- self.volumes_client.wait_for_volume_status(self.volume_origin['id'],
- 'in-use')
- self.addCleanup(self.volumes_client.wait_for_volume_status,
+ device='/dev/%s' % CONF.compute.volume_device_name)
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume_origin['id'], 'in-use')
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
self.volume_origin['id'], 'available')
self.addCleanup(self.servers_client.detach_volume, server['id'],
self.volume_origin['id'])
@@ -171,11 +184,31 @@
# NOTE(gfidente): size is required also when passing snapshot_id
volume = self.volumes_client.create_volume(
snapshot_id=snapshot['id'])['volume']
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
self.volumes_client.delete_volume(volume['id'])
self.volumes_client.wait_for_resource_deletion(volume['id'])
self.cleanup_snapshot(snapshot)
+ @test.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
+ def test_snapshot_list_param_limit(self):
+ # List returns limited elements
+ self._list_snapshots_by_param_limit(limit=1, expected_elements=1)
+
+ @test.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400')
+ def test_snapshot_list_param_limit_equals_infinite(self):
+ # List returns all elements when request limit exceeded
+ # snapshots number
+ snap_list = self.snapshots_client.list_snapshots()['snapshots']
+ self._list_snapshots_by_param_limit(limit=100000,
+ expected_elements=len(snap_list))
+
+ @decorators.skip_because(bug='1540893')
+ @test.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
+ def test_snapshot_list_param_limit_equals_zero(self):
+ # List returns zero elements
+ self._list_snapshots_by_param_limit(limit=0, expected_elements=0)
+
def cleanup_snapshot(self, snapshot):
# Delete the snapshot
self.snapshots_client.delete_snapshot(snapshot['id'])
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index d46c9b5..374979c 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -10,13 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -37,7 +34,8 @@
s_name = data_utils.rand_name('snap')
self.assertRaises(lib_exc.NotFound,
self.snapshots_client.create_snapshot,
- volume_id=str(uuid.uuid4()), display_name=s_name)
+ volume_id=data_utils.rand_uuid(),
+ display_name=s_name)
@test.attr(type=['negative'])
@test.idempotent_id('bb9da53e-d335-4309-9c15-7e76fd5e4d6d')
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 6568627..1fa54c2 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -14,9 +14,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import random
from six.moves.urllib import parse
from tempest.api.volume import base
+from tempest.lib import decorators
from tempest import test
@@ -185,3 +187,29 @@
@test.idempotent_id('af55e775-8e4b-4feb-8719-215c43b0238c')
def test_volume_list_pagination(self):
self._test_pagination('volumes', ids=self.volume_id_list, detail=False)
+
+ @test.idempotent_id('46eff077-100b-427f-914e-3db2abcdb7e2')
+ @decorators.skip_because(bug='1572765')
+ def test_volume_list_with_detail_param_marker(self):
+ # Choosing a random volume from a list of volumes for 'marker'
+ # parameter
+ random_volume = random.choice(self.volume_id_list)
+
+ params = {'marker': random_volume}
+
+ # Running volume list using marker parameter
+ vol_with_marker = self.client.list_volumes(detail=True,
+ params=params)['volumes']
+
+ # Fetching the index of the random volume from volume_id_list
+ index_marker = self.volume_id_list.index(random_volume)
+
+ # The expected list with marker parameter
+ verify_volume_list = self.volume_id_list[:index_marker]
+
+ failed_msg = "Failed to list volume details by marker"
+
+ # Validating the expected list is the same like the observed list
+ self.assertEqual(verify_volume_list,
+ map(lambda x: x['id'],
+ vol_with_marker[::-1]), failed_msg)
diff --git a/tempest/api_schema/response/messaging/__init__.py b/tempest/api_schema/response/messaging/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/response/messaging/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/response/messaging/v1/__init__.py b/tempest/api_schema/response/messaging/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/response/messaging/v1/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/response/messaging/v1/queues.py b/tempest/api_schema/response/messaging/v1/queues.py
deleted file mode 100644
index 09e0147..0000000
--- a/tempest/api_schema/response/messaging/v1/queues.py
+++ /dev/null
@@ -1,239 +0,0 @@
-
-# Copyright (c) 2014 Rackspace, 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.
-
-
-list_link = {
- 'type': 'object',
- 'properties': {
- 'rel': {'type': 'string'},
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- }
- },
- 'required': ['href', 'rel']
-}
-
-list_queue = {
- 'type': 'object',
- 'properties': {
- 'name': {'type': 'string'},
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'metadata': {'type': 'object'}
- },
- 'required': ['name', 'href']
-}
-
-list_queues = {
- 'status_code': [200, 204],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'links': {
- 'type': 'array',
- 'items': list_link,
- 'maxItems': 1
- },
- 'queues': {
- 'type': 'array',
- 'items': list_queue
- }
- },
- 'required': ['links', 'queues']
- }
-}
-
-age = {
- 'type': 'number',
- 'minimum': 0
-}
-
-message_link = {
- 'type': 'object',
- 'properties': {
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'age': age,
- 'created': {
- 'type': 'string',
- 'format': 'date-time'
- }
- },
- 'required': ['href', 'age', 'created']
-}
-
-messages = {
- 'type': 'object',
- 'properties': {
- 'free': {'type': 'number'},
- 'claimed': {'type': 'number'},
- 'total': {'type': 'number'},
- 'oldest': message_link,
- 'newest': message_link
- },
- 'required': ['free', 'claimed', 'total']
-}
-
-queue_stats = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'messages': messages
- },
- 'required': ['messages']
- }
-}
-
-resource_schema = {
- 'type': 'array',
- 'items': {
- 'type': 'string'
- },
- 'minItems': 1
-}
-
-post_messages = {
- 'status_code': [201],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'resources': resource_schema,
- 'partial': {'type': 'boolean'}
- }
- },
- 'required': ['resources', 'partial']
-}
-
-message_ttl = {
- 'type': 'number',
- 'minimum': 1
-}
-
-list_messages_links = {
- 'type': 'array',
- 'maxItems': 1,
- 'minItems': 1,
- 'items': {
- 'type': 'object',
- 'properties': {
- 'rel': {'type': 'string'},
- 'href': {'type': 'string'}
- },
- 'required': ['rel', 'href']
- }
-}
-
-list_messages_response = {
- 'type': 'array',
- 'minItems': 1,
- 'items': {
- 'type': 'object',
- 'properties': {
- 'href': {'type': 'string'},
- 'ttl': message_ttl,
- 'age': age,
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
- }
-}
-
-list_messages = {
- 'status_code': [200, 204],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'links': list_messages_links,
- 'messages': list_messages_response
- }
- },
- 'required': ['links', 'messages']
-}
-
-single_message = {
- 'type': 'object',
- 'properties': {
- 'href': {'type': 'string'},
- 'ttl': message_ttl,
- 'age': age,
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
-}
-
-get_single_message = {
- 'status_code': [200],
- 'response_body': single_message
-}
-
-get_multiple_messages = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'array',
- 'items': single_message,
- 'minItems': 1
- }
-}
-
-messages_claimed = {
- 'type': 'object',
- 'properties': {
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'ttl': message_ttl,
- 'age': {'type': 'number'},
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
-}
-
-claim_messages = {
- 'status_code': [201, 204],
- 'response_body': {
- 'type': 'array',
- 'items': messages_claimed,
- 'minItems': 1
- }
-}
-
-claim_ttl = {
- 'type': 'number',
- 'minimum': 1
-}
-
-query_claim = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'age': {'type': 'number'},
- 'ttl': claim_ttl,
- 'messages': {
- 'type': 'array',
- 'minItems': 1
- }
- },
- 'required': ['ttl', 'age', 'messages']
- }
-}
diff --git a/tempest/clients.py b/tempest/clients.py
index 50aa6f4..19f1a2a 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -16,76 +16,87 @@
import copy
from oslo_log import log as logging
-from tempest_lib.services.compute.agents_client import AgentsClient
-from tempest_lib.services.compute.aggregates_client import AggregatesClient
-from tempest_lib.services.compute.availability_zone_client import \
- AvailabilityZoneClient
-from tempest_lib.services.compute.baremetal_nodes_client import \
- BaremetalNodesClient
-from tempest_lib.services.compute.certificates_client import \
- CertificatesClient
-from tempest_lib.services.compute.extensions_client import \
- ExtensionsClient
-from tempest_lib.services.compute.fixed_ips_client import FixedIPsClient
-from tempest_lib.services.compute.flavors_client import FlavorsClient
-from tempest_lib.services.compute.floating_ip_pools_client import \
- FloatingIPPoolsClient
-from tempest_lib.services.compute.floating_ips_bulk_client import \
- FloatingIPsBulkClient
-from tempest_lib.services.compute.floating_ips_client import \
- FloatingIPsClient as ComputeFloatingIPsClient
-from tempest_lib.services.compute.hosts_client import HostsClient
-from tempest_lib.services.compute.hypervisor_client import \
- HypervisorClient
-from tempest_lib.services.compute.images_client import ImagesClient \
- as ComputeImagesClient
-from tempest_lib.services.compute.instance_usage_audit_log_client import \
- InstanceUsagesAuditLogClient
-from tempest_lib.services.compute.interfaces_client import InterfacesClient
-from tempest_lib.services.compute.limits_client import LimitsClient
-from tempest_lib.services.compute.migrations_client import MigrationsClient
-from tempest_lib.services.compute.networks_client import NetworksClient \
- as ComputeNetworksClient
-from tempest_lib.services.compute.quota_classes_client import \
- QuotaClassesClient
-from tempest_lib.services.compute.quotas_client import QuotasClient
-from tempest_lib.services.compute.security_group_default_rules_client import \
- SecurityGroupDefaultRulesClient
-from tempest_lib.services.compute.security_group_rules_client import \
- SecurityGroupRulesClient as ComputeSecurityGroupRulesClient
-from tempest_lib.services.compute.security_groups_client import \
- SecurityGroupsClient as ComputeSecurityGroupsClient
-from tempest_lib.services.compute.server_groups_client import \
- ServerGroupsClient
-from tempest_lib.services.compute.servers_client import ServersClient
-from tempest_lib.services.compute.services_client import ServicesClient
-from tempest_lib.services.compute.snapshots_client import \
- SnapshotsClient as ComputeSnapshotsClient
-from tempest_lib.services.compute.tenant_networks_client import \
- TenantNetworksClient
-from tempest_lib.services.compute.tenant_usages_client import \
- TenantUsagesClient
-from tempest_lib.services.compute.versions_client import VersionsClient
-from tempest_lib.services.compute.volumes_client import \
- VolumesClient as ComputeVolumesClient
-from tempest_lib.services.identity.v2.token_client import TokenClient
-from tempest_lib.services.identity.v3.token_client import V3TokenClient
-from tempest_lib.services.network.floating_ips_client import FloatingIPsClient
-from tempest_lib.services.network.metering_label_rules_client import \
- MeteringLabelRulesClient
-from tempest_lib.services.network.metering_labels_client import \
- MeteringLabelsClient
-from tempest_lib.services.network.networks_client import NetworksClient
-from tempest_lib.services.network.ports_client import PortsClient
-from tempest_lib.services.network.subnets_client import SubnetsClient
from tempest.common import negative_rest_client
from tempest import config
from tempest import exceptions
+from tempest.lib.services.compute.agents_client import AgentsClient
+from tempest.lib.services.compute.aggregates_client import AggregatesClient
+from tempest.lib.services.compute.availability_zone_client import \
+ AvailabilityZoneClient
+from tempest.lib.services.compute.baremetal_nodes_client import \
+ BaremetalNodesClient
+from tempest.lib.services.compute.certificates_client import \
+ CertificatesClient
+from tempest.lib.services.compute.extensions_client import \
+ ExtensionsClient
+from tempest.lib.services.compute.fixed_ips_client import FixedIPsClient
+from tempest.lib.services.compute.flavors_client import FlavorsClient
+from tempest.lib.services.compute.floating_ip_pools_client import \
+ FloatingIPPoolsClient
+from tempest.lib.services.compute.floating_ips_bulk_client import \
+ FloatingIPsBulkClient
+from tempest.lib.services.compute.floating_ips_client import \
+ FloatingIPsClient as ComputeFloatingIPsClient
+from tempest.lib.services.compute.hosts_client import HostsClient
+from tempest.lib.services.compute.hypervisor_client import \
+ HypervisorClient
+from tempest.lib.services.compute.images_client import ImagesClient \
+ as ComputeImagesClient
+from tempest.lib.services.compute.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClient
+from tempest.lib.services.compute.interfaces_client import InterfacesClient
+from tempest.lib.services.compute.keypairs_client import KeyPairsClient
+from tempest.lib.services.compute.limits_client import LimitsClient
+from tempest.lib.services.compute.migrations_client import MigrationsClient
+from tempest.lib.services.compute.networks_client import NetworksClient \
+ as ComputeNetworksClient
+from tempest.lib.services.compute.quota_classes_client import \
+ QuotaClassesClient
+from tempest.lib.services.compute.quotas_client import QuotasClient
+from tempest.lib.services.compute.security_group_default_rules_client import \
+ SecurityGroupDefaultRulesClient
+from tempest.lib.services.compute.security_group_rules_client import \
+ SecurityGroupRulesClient as ComputeSecurityGroupRulesClient
+from tempest.lib.services.compute.security_groups_client import \
+ SecurityGroupsClient as ComputeSecurityGroupsClient
+from tempest.lib.services.compute.server_groups_client import \
+ ServerGroupsClient
+from tempest.lib.services.compute.servers_client import ServersClient
+from tempest.lib.services.compute.services_client import ServicesClient
+from tempest.lib.services.compute.snapshots_client import \
+ SnapshotsClient as ComputeSnapshotsClient
+from tempest.lib.services.compute.tenant_networks_client import \
+ TenantNetworksClient
+from tempest.lib.services.compute.tenant_usages_client import \
+ TenantUsagesClient
+from tempest.lib.services.compute.versions_client import VersionsClient
+from tempest.lib.services.compute.volumes_client import \
+ VolumesClient as ComputeVolumesClient
+from tempest.lib.services.identity.v2.token_client import TokenClient
+from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.network.agents_client import AgentsClient \
+ as NetworkAgentsClient
+from tempest.lib.services.network.extensions_client import \
+ ExtensionsClient as NetworkExtensionsClient
+from tempest.lib.services.network.floating_ips_client import FloatingIPsClient
+from tempest.lib.services.network.metering_label_rules_client import \
+ MeteringLabelRulesClient
+from tempest.lib.services.network.metering_labels_client import \
+ MeteringLabelsClient
+from tempest.lib.services.network.networks_client import NetworksClient
+from tempest.lib.services.network.ports_client import PortsClient
+from tempest.lib.services.network.quotas_client import QuotasClient \
+ as NetworkQuotasClient
+from tempest.lib.services.network.security_group_rules_client import \
+ SecurityGroupRulesClient
+from tempest.lib.services.network.security_groups_client import \
+ SecurityGroupsClient
+from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
+from tempest.lib.services.network.subnets_client import SubnetsClient
from tempest import manager
from tempest.services.baremetal.v1.json.baremetal_client import \
BaremetalClient
-from tempest.services.compute.json.keypairs_client import KeyPairsClient
from tempest.services.data_processing.v1_1.data_processing_client import \
DataProcessingClient
from tempest.services.database.json.flavors_client import \
@@ -94,55 +105,40 @@
DatabaseLimitsClient
from tempest.services.database.json.versions_client import \
DatabaseVersionsClient
-from tempest.services.identity.v2.json.endpoints_client import \
- EndpointsClient as EndpointsV2Client
-from tempest.services.identity.v2.json.identity_client import \
- IdentityClient
-from tempest.services.identity.v2.json.roles_client import \
- RolesClient
+from tempest.services.identity.v2.json.endpoints_client import EndpointsClient
+from tempest.services.identity.v2.json.identity_client import IdentityClient
+from tempest.services.identity.v2.json.roles_client import RolesClient
from tempest.services.identity.v2.json.services_client import \
- ServicesClient as ServicesV2Client
-from tempest.services.identity.v2.json.tenants_client import \
- TenantsClient
-from tempest.services.identity.v2.json.users_client import \
- UsersClient
+ ServicesClient as IdentityServicesClient
+from tempest.services.identity.v2.json.tenants_client import TenantsClient
+from tempest.services.identity.v2.json.users_client import UsersClient
from tempest.services.identity.v3.json.credentials_client import \
- CredentialsClient as CredentialsV3Client
+ CredentialsClient
+from tempest.services.identity.v3.json.domains_client import DomainsClient
from tempest.services.identity.v3.json.endpoints_client import \
- EndPointClient as EndPointV3Client
-from tempest.services.identity.v3.json.groups_client import \
- GroupsClient as GroupsV3Client
-from tempest.services.identity.v3.json.identity_client import IdentityV3Client
-from tempest.services.identity.v3.json.policies_client import \
- PoliciesClient as PoliciesV3Client
-from tempest.services.identity.v3.json.regions_client import \
- RegionsClient as RegionsV3Client
+ EndPointsClient as EndPointsV3Client
+from tempest.services.identity.v3.json.groups_client import GroupsClient
+from tempest.services.identity.v3.json.identity_client import \
+ IdentityClient as IdentityV3Client
+from tempest.services.identity.v3.json.policies_client import PoliciesClient
+from tempest.services.identity.v3.json.projects_client import ProjectsClient
+from tempest.services.identity.v3.json.regions_client import RegionsClient
+from tempest.services.identity.v3.json.roles_client import \
+ RolesClient as RolesV3Client
from tempest.services.identity.v3.json.services_client import \
ServicesClient as IdentityServicesV3Client
+from tempest.services.identity.v3.json.trusts_client import TrustsClient
+from tempest.services.identity.v3.json.users_clients import \
+ UsersClient as UsersV3Client
from tempest.services.image.v1.json.images_client import ImagesClient
-from tempest.services.image.v2.json.images_client import ImagesClientV2
-from tempest.services.messaging.json.messaging_client import \
- MessagingClient
-from tempest.services.network.json.agents_client import AgentsClient \
- as NetworkAgentsClient
-from tempest.services.network.json.extensions_client import \
- ExtensionsClient as NetworkExtensionsClient
-from tempest.services.network.json.network_client import NetworkClient
-from tempest.services.network.json.quotas_client import QuotasClient \
- as NetworkQuotasClient
-from tempest.services.network.json.security_group_rules_client import \
- SecurityGroupRulesClient
-from tempest.services.network.json.security_groups_client import \
- SecurityGroupsClient
-from tempest.services.network.json.subnetpools_client import SubnetpoolsClient
+from tempest.services.image.v2.json.images_client import \
+ ImagesClient as ImagesV2Client
+from tempest.services.network.json.routers_client import RoutersClient
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
from tempest.services.orchestration.json.orchestration_client import \
OrchestrationClient
-from tempest.services.telemetry.json.alarming_client import AlarmingClient
-from tempest.services.telemetry.json.telemetry_client import \
- TelemetryClient
from tempest.services.volume.v1.json.admin.hosts_client import \
HostsClient as VolumeHostsClient
from tempest.services.volume.v1.json.admin.quotas_client import \
@@ -202,30 +198,15 @@
}
default_params_with_timeout_values.update(default_params)
- def __init__(self, credentials, service=None, api_microversions=None):
+ def __init__(self, credentials, service=None, scope='project'):
"""Initialization of Manager class.
Setup all services clients and make them available for tests cases.
:param credentials: type Credentials or TestResources
:param service: Service name
- :param api_microversions: This is dict of services catalog type
- and their microversion which will be set on respective
- services clients.
- {<service catalog type>: request_microversion}
- Example :
- {'compute': request_microversion}
- - request_microversion will be set on all compute
- service clients.
- OR
- {'compute': request_microversion,
- 'volume': request_microversion}
- - request_microversion of compute will be set on all
- compute service clients.
- - request_microversion of volume will be set on all
- volume service clients.
+ :param scope: default scope for tokens produced by the auth provider
"""
- super(Manager, self).__init__(credentials=credentials)
- self.api_microversions = api_microversions or {}
+ super(Manager, self).__init__(credentials=credentials, scope=scope)
self._set_compute_clients()
self._set_database_clients()
self._set_identity_clients()
@@ -254,14 +235,6 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
- self.network_client = NetworkClient(
- self.auth_provider,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type=CONF.network.endpoint_type,
- build_interval=CONF.network.build_interval,
- build_timeout=CONF.network.build_timeout,
- **self.default_params)
self.networks_client = NetworksClient(
self.auth_provider,
CONF.network.catalog_type,
@@ -326,6 +299,14 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
+ self.routers_client = RoutersClient(
+ self.auth_provider,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **self.default_params)
self.security_group_rules_client = SecurityGroupRulesClient(
self.auth_provider,
CONF.network.catalog_type,
@@ -342,25 +323,6 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
- self.messaging_client = MessagingClient(
- self.auth_provider,
- CONF.messaging.catalog_type,
- CONF.identity.region,
- **self.default_params_with_timeout_values)
- if CONF.service_available.ceilometer:
- self.telemetry_client = TelemetryClient(
- self.auth_provider,
- CONF.telemetry.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.telemetry.endpoint_type,
- **self.default_params_with_timeout_values)
- if CONF.service_available.aodh:
- self.alarming_client = AlarmingClient(
- self.auth_provider,
- CONF.alarming.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.alarming.endpoint_type,
- **self.default_params_with_timeout_values)
if CONF.service_available.glance:
self.image_client = ImagesClient(
self.auth_provider,
@@ -370,7 +332,7 @@
build_interval=CONF.image.build_interval,
build_timeout=CONF.image.build_timeout,
**self.default_params)
- self.image_client_v2 = ImagesClientV2(
+ self.image_client_v2 = ImagesV2Client(
self.auth_provider,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
@@ -395,8 +357,6 @@
self.negative_client = negative_rest_client.NegativeRestClient(
self.auth_provider, service, **self.default_params)
- self._set_api_microversions()
-
def _set_compute_clients(self):
params = {
'service': CONF.compute.catalog_type,
@@ -501,48 +461,53 @@
'region': CONF.identity.region
}
params.update(self.default_params_with_timeout_values)
+
+ # Clients below use the admin endpoint type of Keystone API v2
params_v2_admin = params.copy()
params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
- # Client uses admin endpoint type of Keystone API v2
- self.endpoints_v2_client = EndpointsV2Client(self.auth_provider,
- **params_v2_admin)
+ self.endpoints_client = EndpointsClient(self.auth_provider,
+ **params_v2_admin)
self.identity_client = IdentityClient(self.auth_provider,
**params_v2_admin)
self.tenants_client = TenantsClient(self.auth_provider,
**params_v2_admin)
- self.roles_client = RolesClient(self.auth_provider,
- **params_v2_admin)
- self.users_client = UsersClient(self.auth_provider,
- **params_v2_admin)
- self.services_v2_client = ServicesV2Client(self.auth_provider,
- **params_v2_admin)
+ self.roles_client = RolesClient(self.auth_provider, **params_v2_admin)
+ self.users_client = UsersClient(self.auth_provider, **params_v2_admin)
+ self.identity_services_client = IdentityServicesClient(
+ self.auth_provider, **params_v2_admin)
+
+ # Clients below use the public endpoint type of Keystone API v2
params_v2_public = params.copy()
params_v2_public['endpoint_type'] = (
CONF.identity.v2_public_endpoint_type)
- # Client uses public endpoint type of Keystone API v2
self.identity_public_client = IdentityClient(self.auth_provider,
**params_v2_public)
self.tenants_public_client = TenantsClient(self.auth_provider,
**params_v2_public)
- self.roles_public_client = RolesClient(self.auth_provider,
- **params_v2_public)
self.users_public_client = UsersClient(self.auth_provider,
**params_v2_public)
+
+ # Clients below use the endpoint type of Keystone API v3
params_v3 = params.copy()
params_v3['endpoint_type'] = CONF.identity.v3_endpoint_type
- # Clients below use the endpoint type of Keystone API v3
+ self.domains_client = DomainsClient(self.auth_provider,
+ **params_v3)
self.identity_v3_client = IdentityV3Client(self.auth_provider,
**params_v3)
- self.endpoints_client = EndPointV3Client(self.auth_provider,
- **params_v3)
- self.identity_services_client = IdentityServicesV3Client(
+ self.trusts_client = TrustsClient(self.auth_provider, **params_v3)
+ self.users_v3_client = UsersV3Client(self.auth_provider, **params_v3)
+ self.endpoints_v3_client = EndPointsV3Client(self.auth_provider,
+ **params_v3)
+ self.roles_v3_client = RolesV3Client(self.auth_provider, **params_v3)
+ self.identity_services_v3_client = IdentityServicesV3Client(
self.auth_provider, **params_v3)
- self.policies_client = PoliciesV3Client(self.auth_provider,
- **params_v3)
- self.regions_client = RegionsV3Client(self.auth_provider, **params_v3)
- self.credentials_client = CredentialsV3Client(self.auth_provider,
- **params_v3)
- self.groups_client = GroupsV3Client(self.auth_provider, **params_v3)
+ self.policies_client = PoliciesClient(self.auth_provider, **params_v3)
+ self.projects_client = ProjectsClient(self.auth_provider, **params_v3)
+ self.regions_client = RegionsClient(self.auth_provider, **params_v3)
+ self.credentials_client = CredentialsClient(self.auth_provider,
+ **params_v3)
+ self.groups_client = GroupsClient(self.auth_provider, **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
# API version is marked as enabled
@@ -624,15 +589,3 @@
self.account_client = AccountClient(self.auth_provider, **params)
self.container_client = ContainerClient(self.auth_provider, **params)
self.object_client = ObjectClient(self.auth_provider, **params)
-
- def _set_api_microversions(self):
- service_clients = [x for x in self.__dict__ if x.endswith('_client')]
- for client in service_clients:
- client_obj = getattr(self, client)
- microversion = self.api_microversions.get(client_obj.service)
- if microversion:
- if hasattr(client_obj, 'set_api_microversion'):
- client_obj.set_api_microversion(microversion)
- else:
- LOG.debug("Need to implement set_api_microversion on %s"
- % client)
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index ddfc75d..5fab961 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -89,21 +89,21 @@
from cliff import command
from oslo_log import log as logging
-import tempest_lib.auth
-from tempest_lib.common.utils import data_utils
-import tempest_lib.exceptions
-from tempest_lib.services.network import networks_client
-from tempest_lib.services.network import subnets_client
import yaml
from tempest.common import identity
from tempest import config
from tempest import exceptions as exc
+import tempest.lib.auth
+from tempest.lib.common.utils import data_utils
+import tempest.lib.exceptions
+from tempest.lib.services.network import networks_client
+from tempest.lib.services.network import subnets_client
from tempest.services.identity.v2.json import identity_client
from tempest.services.identity.v2.json import roles_client
from tempest.services.identity.v2.json import tenants_client
from tempest.services.identity.v2.json import users_client
-from tempest.services.network.json import network_client
+from tempest.services.network.json import routers_client
LOG = None
CONF = config.CONF
@@ -121,7 +121,7 @@
def get_admin_clients(opts):
- _creds = tempest_lib.auth.KeystoneV2Credentials(
+ _creds = tempest.lib.auth.KeystoneV2Credentials(
username=opts.os_username,
password=opts.os_password,
tenant_name=opts.os_tenant_name)
@@ -131,7 +131,7 @@
'ca_certs': CONF.identity.ca_certificates_file,
'trace_requests': CONF.debug.trace_requests
}
- _auth = tempest_lib.auth.KeystoneV2AuthProvider(
+ _auth = tempest.lib.auth.KeystoneV2AuthProvider(
_creds, CONF.identity.uri, **auth_params)
params = {
'disable_ssl_certificate_validation':
@@ -169,20 +169,20 @@
endpoint_type='adminURL',
**params
)
- network_admin = None
networks_admin = None
+ routers_admin = None
subnets_admin = None
neutron_iso_networks = False
if (CONF.service_available.neutron and
CONF.auth.create_isolated_networks):
neutron_iso_networks = True
- network_admin = network_client.NetworkClient(
+ networks_admin = networks_client.NetworksClient(
_auth,
CONF.network.catalog_type,
CONF.network.region or CONF.identity.region,
endpoint_type='adminURL',
**params)
- networks_admin = networks_client.NetworksClient(
+ routers_admin = routers_client.RoutersClient(
_auth,
CONF.network.catalog_type,
CONF.network.region or CONF.identity.region,
@@ -195,12 +195,13 @@
endpoint_type='adminURL',
**params)
return (identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, network_admin, networks_admin, subnets_admin)
+ neutron_iso_networks, networks_admin, routers_admin,
+ subnets_admin)
def create_resources(opts, resources):
(identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, network_admin, networks_admin,
+ neutron_iso_networks, networks_admin, routers_admin,
subnets_admin) = get_admin_clients(opts)
roles = roles_admin.list_roles()['roles']
for u in resources['users']:
@@ -223,14 +224,14 @@
for u in resources['users']:
try:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
LOG.error("Tenant: %s - not found" % u['tenant'])
continue
while True:
try:
identity.get_user_by_username(tenants_admin,
tenant['id'], u['name'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
users_admin.create_user(
u['name'], u['pass'], tenant['id'],
"%s@%s" % (u['name'], tenant['id']),
@@ -246,35 +247,36 @@
for u in resources['users']:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
network_name, router_name = create_network_resources(
- network_admin, networks_admin, subnets_admin, tenant['id'],
- u['name'])
+ networks_admin, routers_admin, subnets_admin,
+ tenant['id'], u['name'])
u['network'] = network_name
u['router'] = router_name
LOG.info('Networks created')
for u in resources['users']:
try:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
LOG.error("Tenant: %s - not found" % u['tenant'])
continue
try:
user = identity.get_user_by_username(tenants_admin,
tenant['id'], u['name'])
- except tempest_lib.exceptions.NotFound:
- LOG.error("User: %s - not found" % u['user'])
+ except tempest.lib.exceptions.NotFound:
+ LOG.error("User: %s - not found" % u['name'])
continue
for r in u['role_ids']:
try:
roles_admin.assign_user_role(tenant['id'], user['id'], r)
- except tempest_lib.exceptions.Conflict:
+ except tempest.lib.exceptions.Conflict:
# don't care if it's already assigned
pass
LOG.info('Roles assigned')
LOG.info('Resources deployed successfully!')
-def create_network_resources(network_admin_client, networks_admin_client,
- subnets_admin_client, tenant_id, name):
+def create_network_resources(networks_admin_client,
+ routers_admin_client, subnets_admin_client,
+ tenant_id, name):
def _create_network(name):
resp_body = networks_admin_client.create_network(
@@ -282,8 +284,8 @@
return resp_body['network']
def _create_subnet(subnet_name, network_id):
- base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ base_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = CONF.network.project_network_mask_bits
for subnet_cidr in base_cidr.subnet(mask_bits):
try:
resp_body = subnets_admin_client.\
@@ -294,7 +296,7 @@
enable_dhcp=True,
ip_version=4)
break
- except tempest_lib.exceptions.BadRequest as e:
+ except tempest.lib.exceptions.BadRequest as e:
if 'overlaps with another subnet' not in str(e):
raise
else:
@@ -305,15 +307,15 @@
def _create_router(router_name):
external_net_id = dict(
network_id=CONF.network.public_network_id)
- resp_body = network_admin_client.create_router(
- router_name,
+ resp_body = routers_admin_client.create_router(
+ name=router_name,
external_gateway_info=external_net_id,
tenant_id=tenant_id)
return resp_body['router']
def _add_router_interface(router_id, subnet_id):
- network_admin_client.add_router_interface_with_subnet_id(
- router_id, subnet_id)
+ routers_admin_client.add_router_interface(router_id,
+ subnet_id=subnet_id)
network_name = name + "-network"
network = _create_network(network_name)
@@ -480,8 +482,8 @@
def main(opts=None):
setup_logging()
if not opts:
- LOG.warn("Use of: 'tempest-account-generator' is deprecated, "
- "please use: 'tempest account-generator'")
+ LOG.warning("Use of: 'tempest-account-generator' is deprecated, "
+ "please use: 'tempest account-generator'")
opts = get_options()
if opts.config_file:
config.CONF.set_config_path(opts.config_file)
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 7b73a61..caba4b5 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -77,7 +77,8 @@
def take_action(self, parsed_args):
try:
self.init(parsed_args)
- self._cleanup()
+ if not parsed_args.init_saved_state:
+ self._cleanup()
except Exception:
LOG.exception("Failure during cleanup")
traceback.print_exc()
@@ -116,7 +117,6 @@
if is_dry_run:
self.dry_run_data["_tenants_to_clean"] = {}
- f = open(DRY_RUN_JSON, 'w+')
admin_mgr = self.admin_mgr
# Always cleanup tempest and alt tempest tenants unless
@@ -145,9 +145,9 @@
svc.run()
if is_dry_run:
- f.write(json.dumps(self.dry_run_data, sort_keys=True,
- indent=2, separators=(',', ': ')))
- f.close()
+ with open(DRY_RUN_JSON, 'w+') as f:
+ f.write(json.dumps(self.dry_run_data, sort_keys=True,
+ indent=2, separators=(',', ': ')))
self._remove_admin_user_roles()
@@ -190,7 +190,7 @@
rl_cl = self.admin_mgr.roles_client
tenant = identity.get_tenant_by_name(tn_cl,
- CONF.auth.admin_tenant_name)
+ CONF.auth.admin_project_name)
self.admin_tenant_id = tenant['id']
user = identity.get_user_by_username(tn_cl, self.admin_tenant_id,
@@ -231,7 +231,6 @@
return 'Cleanup after tempest run'
def _add_admin(self, tenant_id):
- id_cl = self.admin_mgr.identity_client
rl_cl = self.admin_mgr.roles_client
needs_role = True
roles = rl_cl.list_user_roles(tenant_id, self.admin_id)['roles']
@@ -241,7 +240,7 @@
LOG.debug("User already had admin privilege for this tenant")
if needs_role:
LOG.debug("Adding admin privilege for : %s" % tenant_id)
- id_cl.assign_user_role(tenant_id, self.admin_id,
+ rl_cl.assign_user_role(tenant_id, self.admin_id,
self.admin_role_id)
self.admin_role_added.append(tenant_id)
@@ -281,16 +280,15 @@
svc = service(admin_mgr, **kwargs)
svc.run()
- f = open(SAVED_STATE_JSON, 'w+')
- f.write(json.dumps(data,
- sort_keys=True, indent=2, separators=(',', ': ')))
- f.close()
+ with open(SAVED_STATE_JSON, 'w+') as f:
+ f.write(json.dumps(data,
+ sort_keys=True, indent=2, separators=(',', ': ')))
def _load_json(self):
try:
- json_file = open(SAVED_STATE_JSON)
- self.json_data = json.load(json_file)
- json_file.close()
+ with open(SAVED_STATE_JSON) as json_file:
+ self.json_data = json.load(json_file)
+
except IOError as ex:
LOG.exception("Failed loading saved state, please be sure you"
" have first run cleanup with --init-saved-state "
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 8a47406..9e86b48 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -33,7 +33,6 @@
CONF_TENANTS = None
CONF_USERS = None
-IS_CEILOMETER = None
IS_CINDER = None
IS_GLANCE = None
IS_HEAT = None
@@ -51,14 +50,12 @@
global CONF_PUB_ROUTER
global CONF_TENANTS
global CONF_USERS
- global IS_CEILOMETER
global IS_CINDER
global IS_GLANCE
global IS_HEAT
global IS_NEUTRON
global IS_NOVA
- IS_CEILOMETER = CONF.service_available.ceilometer
IS_CINDER = CONF.service_available.cinder
IS_GLANCE = CONF.service_available.glance
IS_HEAT = CONF.service_available.heat
@@ -70,25 +67,25 @@
CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name
CONF_PUB_NETWORK = CONF.network.public_network_id
CONF_PUB_ROUTER = CONF.network.public_router_id
- CONF_TENANTS = [CONF.auth.admin_tenant_name,
- CONF.identity.tenant_name,
- CONF.identity.alt_tenant_name]
+ CONF_TENANTS = [CONF.auth.admin_project_name,
+ CONF.identity.project_name,
+ CONF.identity.alt_project_name]
CONF_USERS = [CONF.auth.admin_username, CONF.identity.username,
CONF.identity.alt_username]
if IS_NEUTRON:
CONF_PRIV_NETWORK = _get_network_id(CONF.compute.fixed_network_name,
- CONF.identity.tenant_name)
+ CONF.auth.admin_project_name)
CONF_NETWORKS = [CONF_PUB_NETWORK, CONF_PRIV_NETWORK]
-def _get_network_id(net_name, tenant_name):
+def _get_network_id(net_name, project_name):
am = credentials.AdminManager()
net_cl = am.networks_client
tn_cl = am.tenants_client
networks = net_cl.list_networks()
- tenant = identity.get_tenant_by_name(tn_cl, tenant_name)
+ tenant = identity.get_tenant_by_name(tn_cl, project_name)
t_id = tenant['id']
n_id = None
for net in networks['networks']:
@@ -382,7 +379,6 @@
class NetworkService(BaseService):
def __init__(self, manager, **kwargs):
super(NetworkService, self).__init__(kwargs)
- self.client = manager.network_client
self.networks_client = manager.networks_client
self.subnets_client = manager.subnets_client
self.ports_client = manager.ports_client
@@ -390,6 +386,7 @@
self.metering_labels_client = manager.metering_labels_client
self.metering_label_rules_client = manager.metering_label_rules_client
self.security_groups_client = manager.security_groups_client
+ self.routers_client = manager.routers_client
def _filter_by_conf_networks(self, item_list):
if not item_list or not all(('network_id' in i for i in item_list)):
@@ -449,7 +446,7 @@
class NetworkRouterService(NetworkService):
def list(self):
- client = self.client
+ client = self.routers_client
routers = client.list_routers(**self.tenant_filter)
routers = routers['routers']
if self.is_preserve:
@@ -460,17 +457,17 @@
return routers
def delete(self):
- client = self.client
+ client = self.routers_client
+ ports_client = self.ports_client
routers = self.list()
for router in routers:
try:
rid = router['id']
ports = [port for port
- in client.list_router_interfaces(rid)['ports']
+ in ports_client.list_ports(device_id=rid)['ports']
if port["device_owner"] == "network:router_interface"]
for port in ports:
- client.remove_router_interface_with_port_id(rid,
- port['id'])
+ client.remove_router_interface(rid, port_id=port['id'])
client.delete_router(rid)
except Exception:
LOG.exception("Delete Router exception.")
@@ -706,32 +703,6 @@
self.data['subnets'] = subnets
-# Telemetry services
-class TelemetryAlarmService(BaseService):
- def __init__(self, manager, **kwargs):
- super(TelemetryAlarmService, self).__init__(kwargs)
- self.client = manager.telemetry_client
-
- def list(self):
- client = self.client
- alarms = client.list_alarms()
- LOG.debug("List count, %s Alarms" % len(alarms))
- return alarms
-
- def delete(self):
- client = self.client
- alarms = self.list()
- for alarm in alarms:
- try:
- client.delete_alarm(alarm['id'])
- except Exception:
- LOG.exception("Delete Alarms exception.")
-
- def dry_run(self):
- alarms = self.list()
- self.data['alarms'] = alarms
-
-
# begin global services
class FlavorService(BaseService):
def __init__(self, manager, **kwargs):
@@ -909,7 +880,7 @@
if not self.is_save_state:
tenants = [tenant for tenant in tenants if (tenant['id']
not in self.saved_state_json['tenants'].keys()
- and tenant['name'] != CONF.auth.admin_tenant_name)]
+ and tenant['name'] != CONF.auth.admin_project_name)]
if self.is_preserve:
tenants = [tenant for tenant in tenants if tenant['name']
@@ -941,7 +912,7 @@
def __init__(self, manager, **kwargs):
super(DomainService, self).__init__(kwargs)
- self.client = manager.identity_v3_client
+ self.client = manager.domains_client
def list(self):
client = self.client
@@ -976,8 +947,8 @@
def get_tenant_cleanup_services():
tenant_services = []
- if IS_CEILOMETER:
- tenant_services.append(TelemetryAlarmService)
+ # TODO(gmann): Tempest should provide some plugin hook for cleanup
+ # script extension to plugin tests also.
if IS_NOVA:
tenant_services.append(ServerService)
tenant_services.append(KeyPairService)
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index ac67ce4..633b9e9 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -54,21 +54,28 @@
real_prefix = getattr(sys, 'real_prefix', None)
base_prefix = getattr(sys, 'base_prefix', None)
prefix = sys.prefix
+ global_conf_dir = '/etc/tempest'
if (real_prefix is None and
- (base_prefix is None or base_prefix == prefix)):
+ (base_prefix is None or base_prefix == prefix) and
+ os.path.isdir(global_conf_dir)):
# Probably not running in a virtual environment.
# NOTE(andreaf) we cannot distinguish this case from the case of
# a virtual environment created with virtualenv, and running python3.
# Also if it appears we are not in virtual env and fail to find
# global config: '/etc/tempest', fall back to
# '[sys.prefix]/etc/tempest'
- global_conf_dir = '/etc/tempest'
- if os.path.isdir(global_conf_dir):
- return global_conf_dir
- else:
- return os.path.join(prefix, 'etc/tempest')
+ return global_conf_dir
else:
- return os.path.join(prefix, 'etc/tempest')
+ conf_dir = os.path.join(prefix, 'etc/tempest')
+ if os.path.isdir(conf_dir):
+ return conf_dir
+ else:
+ # NOTE: The prefix is gotten from the path which pyconfig.h is
+ # installed under. Some envs contain it under /usr/include, not
+ # /user/local/include. Then prefix becomes /usr on such envs.
+ # However, etc/tempest is installed under /usr/local and the bove
+ # path logic mismatches. This is a workaround for such envs.
+ return os.path.join(prefix, 'local/etc/tempest')
class TempestInit(command.Command):
@@ -78,6 +85,10 @@
parser = super(TempestInit, self).get_parser(prog_name)
parser.add_argument('dir', nargs='?', default=os.getcwd())
parser.add_argument('--config-dir', '-c', default=None)
+ parser.add_argument('--show-global-config-dir', '-s',
+ action='store_true', dest='show_global_dir',
+ help="Print the global config dir location, "
+ "then exit")
return parser
def generate_testr_conf(self, local_path):
@@ -91,8 +102,7 @@
def update_local_conf(self, conf_path, lock_dir, log_dir):
config_parse = moves.configparser.SafeConfigParser()
config_parse.optionxform = str
- with open(conf_path, 'w+') as conf_file:
- config_parse.readfp(conf_file)
+ with open(conf_path, 'a+') as conf_file:
# Set local lock_dir in tempest conf
if not config_parse.has_section('oslo_concurrency'):
config_parse.add_section('oslo_concurrency')
@@ -101,6 +111,7 @@
config_parse.set('DEFAULT', 'log_dir', log_dir)
# Set default log filename to tempest.log
config_parse.set('DEFAULT', 'log_file', 'tempest.log')
+ config_parse.write(conf_file)
def copy_config(self, etc_dir, config_dir):
shutil.copytree(config_dir, etc_dir)
@@ -149,4 +160,7 @@
def take_action(self, parsed_args):
config_dir = parsed_args.config_dir or get_tempest_default_config_dir()
+ if parsed_args.show_global_dir:
+ print("Global config dir is located at: %s" % config_dir)
+ sys.exit(0)
self.create_working_dir(parsed_args.dir, config_dir)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 8012ad7..17ee618 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -113,31 +113,30 @@
import netaddr
from oslo_log import log as logging
-from oslo_utils import timeutils
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.compute import flavors_client
-from tempest_lib.services.compute import floating_ips_client
-from tempest_lib.services.compute import security_group_rules_client
-from tempest_lib.services.compute import security_groups_client
-from tempest_lib.services.compute import servers_client
-from tempest_lib.services.network import subnets_client
import yaml
from tempest.common import identity
from tempest.common import waiters
from tempest import config
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import flavors_client
+from tempest.lib.services.compute import floating_ips_client
+from tempest.lib.services.compute import security_group_rules_client
+from tempest.lib.services.compute import security_groups_client
+from tempest.lib.services.compute import servers_client
+from tempest.lib.services.network import networks_client
+from tempest.lib.services.network import ports_client
+from tempest.lib.services.network import subnets_client
from tempest.services.identity.v2.json import identity_client
from tempest.services.identity.v2.json import roles_client
from tempest.services.identity.v2.json import tenants_client
from tempest.services.identity.v2.json import users_client
from tempest.services.image.v2.json import images_client
-from tempest.services.network.json import network_client
+from tempest.services.network.json import routers_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
-from tempest.services.telemetry.json import alarming_client
-from tempest.services.telemetry.json import telemetry_client
from tempest.services.volume.v1.json import volumes_client
CONF = config.CONF
@@ -234,7 +233,7 @@
**object_storage_params)
self.containers = container_client.ContainerClient(
_auth, **object_storage_params)
- self.images = images_client.ImagesClientV2(
+ self.images = images_client.ImagesClient(
_auth,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
@@ -242,18 +241,6 @@
build_interval=CONF.image.build_interval,
build_timeout=CONF.image.build_timeout,
**default_params)
- self.telemetry = telemetry_client.TelemetryClient(
- _auth,
- CONF.telemetry.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.telemetry.endpoint_type,
- **default_params_with_timeout_values)
- self.alarming = alarming_client.AlarmingClient(
- _auth,
- CONF.alarm.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.alarm.endpoint_type,
- **default_params_with_timeout_values)
self.volumes = volumes_client.VolumesClient(
_auth,
CONF.volume.catalog_type,
@@ -262,7 +249,23 @@
build_interval=CONF.volume.build_interval,
build_timeout=CONF.volume.build_timeout,
**default_params)
- self.networks = network_client.NetworkClient(
+ self.networks = networks_client.NetworksClient(
+ _auth,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **default_params)
+ self.ports = ports_client.PortsClient(
+ _auth,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **default_params)
+ self.routers = routers_client.RoutersClient(
_auth,
CONF.network.catalog_type,
CONF.network.region or CONF.identity.region,
@@ -443,7 +446,6 @@
self.check_objects()
self.check_servers()
self.check_volumes()
- self.check_telemetry()
self.check_secgroups()
# validate neutron is enabled and ironic disabled:
@@ -545,27 +547,6 @@
found,
"Couldn't find expected secgroup %s" % secgroup['name'])
- def check_telemetry(self):
- """Check that ceilometer provides a sane sample.
-
- Confirm that there is more than one sample and that they have the
- expected metadata.
-
- If in check mode confirm that the oldest sample available is from
- before the upgrade.
- """
- if not self.res.get('telemetry'):
- return
- LOG.info("checking telemetry")
- for server in self.res['servers']:
- client = client_for_user(server['owner'])
- body = client.telemetry.list_samples(
- 'instance',
- query=('metadata.display_name', 'eq', server['name'])
- )
- self.assertTrue(len(body) >= 1, 'expecting at least one sample')
- self._confirm_telemetry_sample(server, body[-1])
-
def check_volumes(self):
"""Check that the volumes are still there and attached."""
if not self.res.get('volumes'):
@@ -584,26 +565,6 @@
self.assertEqual(vol_body['id'], attachment['volume_id'])
self.assertEqual(server_id, attachment['server_id'])
- def _confirm_telemetry_sample(self, server, sample):
- """Check this sample matches the expected resource metadata."""
- # Confirm display_name
- self.assertEqual(server['name'],
- sample['resource_metadata']['display_name'])
- # Confirm instance_type of flavor
- flavor = sample['resource_metadata'].get(
- 'flavor.name',
- sample['resource_metadata'].get('instance_type')
- )
- self.assertEqual(server['flavor'], flavor)
- # Confirm the oldest sample was created before upgrade.
- if OPTS.mode == 'check':
- oldest_timestamp = timeutils.normalize_time(
- timeutils.parse_isotime(sample['timestamp']))
- self.assertTrue(
- oldest_timestamp < JAVELIN_START,
- 'timestamp should come before start of second javelin run'
- )
-
def check_networking(self):
"""Check that the networks are still there."""
for res_type in ('networks', 'subnets', 'routers'):
@@ -739,10 +700,10 @@
def _get_router_namespace(client, network):
network_id = _get_resource_by_name(client.networks,
'networks', network)['id']
- n_body = client.networks.list_routers()
+ n_body = client.routers.list_routers()
for router in n_body['routers']:
router_id = router['id']
- r_body = client.networks.list_router_interfaces(router_id)
+ r_body = client.ports.list_ports(device_id=router_id)
for port in r_body['ports']:
if port['network_id'] == network_id:
return "qrouter-%s" % router_id
@@ -756,7 +717,7 @@
# we cannot assume they all have the same signature so we need to discard
# the unused response first value it two values are being returned.
body = get_resources()
- if type(body) == tuple:
+ if isinstance(body, tuple):
body = body[1]
if isinstance(body, dict):
body = body[resource]
@@ -824,12 +785,12 @@
client = client_for_user(router['owner'])
# only create a router if the name isn't here
- body = client.networks.list_routers()
+ body = client.routers.list_routers()
if any(item['name'] == router['name'] for item in body['routers']):
LOG.warning("Duplicated router name: %s" % router['name'])
continue
- client.networks.create_router(router['name'])
+ client.networks.create_router(name=router['name'])
def destroy_routers(routers):
@@ -841,9 +802,9 @@
for subnet in router['subnet']:
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
- client.networks.remove_router_interface_with_subnet_id(router_id,
- subnet_id)
- client.networks.delete_router(router_id)
+ client.routers.remove_router_interface(router_id,
+ subnet_id=subnet_id)
+ client.routers.delete_router(router_id)
def add_router_interface(routers):
@@ -856,13 +817,13 @@
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
# connect routers to their subnets
- client.networks.add_router_interface_with_subnet_id(router_id,
- subnet_id)
+ client.routers.add_router_interface(router_id,
+ subnet_id=subnet_id)
# connect routers to external network if set to "gateway"
if router['gateway']:
if CONF.network.public_network_id:
ext_net = CONF.network.public_network_id
- client.networks._update_router(
+ client.routers._update_router(
router_id, set_enable_snat=True,
external_gateway_info={"network_id": ext_net})
else:
@@ -1013,7 +974,7 @@
v_name = volume['name']
body = client.volumes.create_volume(size=size,
display_name=v_name)['volume']
- client.volumes.wait_for_volume_status(body['id'], 'available')
+ waiters.wait_for_volume_status(client.volumes, body['id'], 'available')
def destroy_volumes(volumes):
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
index 2d6664c..5c62ee3 100644
--- a/tempest/cmd/resources.yaml
+++ b/tempest/cmd/resources.yaml
@@ -93,4 +93,3 @@
name: javelin1
owner: javelin
file: /etc/hosts
-telemetry: true
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
old mode 100644
new mode 100755
index 943fe5b..9c8552f
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -23,6 +23,7 @@
# unittest in python 2.6 does not contain loader, so uses unittest2
from unittest2 import loader
import traceback
+import warnings
from cliff import command
from oslo_log import log as logging
@@ -74,7 +75,17 @@
class TempestRunStress(command.Command):
+ @staticmethod
+ def display_deprecation_warning():
+ warnings.simplefilter('once', category=DeprecationWarning)
+ warnings.warn(
+ 'Stress tests are deprecated and will be removed from Tempest '
+ 'in the Newton release.',
+ DeprecationWarning)
+ warnings.resetwarnings()
+
def get_parser(self, prog_name):
+ self.display_deprecation_warning()
pa = super(TempestRunStress, self).get_parser(prog_name)
pa = add_arguments(pa)
return pa
@@ -146,8 +157,7 @@
def main():
- LOG.warning("Deprecated: Use 'tempest run-stress' instead. "
- "The old entrypoint will be removed in a future release.")
+ TempestRunStress.display_deprecation_warning()
parser = argparse.ArgumentParser(description='Run stress tests')
pa = add_arguments(parser)
ns = pa.parse_args()
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 92aa19e..72ea894 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -15,8 +15,8 @@
# under the License.
import argparse
-import httplib2
import os
+import re
import sys
import traceback
@@ -29,6 +29,7 @@
from tempest import clients
from tempest.common import credentials_factory as credentials
from tempest import config
+import tempest.lib.common.http
CONF = config.CONF
@@ -77,9 +78,16 @@
not CONF.image_feature_enabled.api_v2, update)
+def _remove_version_project(url_path):
+ # The regex matches strings like /v2.0, /v3/, /v2.1/project-id/
+ return re.sub(r'/v\d+(\.\d+)?(/[^/]+)?', '', url_path)
+
+
def _get_unversioned_endpoint(base_url):
endpoint_parts = urlparse.urlparse(base_url)
- endpoint = endpoint_parts.scheme + '://' + endpoint_parts.netloc
+ new_path = _remove_version_project(endpoint_parts.path)
+ endpoint_parts = endpoint_parts._replace(path=new_path)
+ endpoint = urlparse.urlunparse(endpoint_parts)
return endpoint
@@ -89,15 +97,25 @@
'keystone': os.identity_client,
'cinder': os.volumes_client,
}
- client_dict[service].skip_path()
+ if service != 'keystone':
+ # Since keystone 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)
- dscv = CONF.identity.disable_ssl_certificate_validation
- ca_certs = CONF.identity.ca_certificates_file
- raw_http = httplib2.Http(disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs)
- __, body = raw_http.request(endpoint, 'GET')
+
+ http = tempest.lib.common.http.ClosingHttp(
+ CONF.identity.disable_ssl_certificate_validation,
+ CONF.identity.ca_certificates_file)
+
+ __, body = http.request(endpoint, 'GET')
client_dict[service].reset_path()
- body = json.loads(body)
+ try:
+ body = json.loads(body)
+ except ValueError:
+ LOG.error(
+ 'Failed to get a JSON response from unversioned endpoint %s '
+ '(versioned endpoint was %s). Response is:\n%s',
+ endpoint, client_dict[service].base_url, body[:100])
+ raise
if service == 'keystone':
versions = map(lambda x: x['id'], body['versions']['values'])
else:
@@ -264,12 +282,9 @@
'object_storage': 'swift',
'compute': 'nova',
'orchestration': 'heat',
- 'metering': 'ceilometer',
- 'telemetry': 'ceilometer',
'data_processing': 'sahara',
'baremetal': 'ironic',
'identity': 'keystone',
- 'messaging': 'zaqar',
'database': 'trove'
}
# Get catalog list for endpoints to use for validation
@@ -355,12 +370,19 @@
outfile = sys.stdout
if update:
conf_file = _get_config_file()
- if opts.output:
- outfile = open(opts.output, 'w+')
CONF_PARSER = moves.configparser.SafeConfigParser()
CONF_PARSER.optionxform = str
CONF_PARSER.readfp(conf_file)
- icreds = credentials.get_credentials_provider('verify_tempest_config')
+
+ # Indicate not to create network resources as part of getting credentials
+ net_resources = {
+ 'network': False,
+ 'router': False,
+ 'subnet': False,
+ 'dhcp': False
+ }
+ icreds = credentials.get_credentials_provider(
+ 'verify_tempest_config', network_resources=net_resources)
try:
os = clients.Manager(icreds.get_primary_creds())
services = check_service_availability(os, update)
@@ -379,8 +401,9 @@
display_results(results, update, replace)
if update:
conf_file.close()
- CONF_PARSER.write(outfile)
- outfile.close()
+ if opts.output:
+ with open(opts.output, 'w+') as outfile:
+ CONF_PARSER.write(outfile)
finally:
icreds.clear_creds()
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
deleted file mode 100644
index 392c9d0..0000000
--- a/tempest/common/commands.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# 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 shlex
-import subprocess
-
-from oslo_log import log as logging
-
-LOG = logging.getLogger(__name__)
-
-
-def copy_file_to_host(file_from, dest, host, username, pkey):
- dest = "%s@%s:%s" % (username, host, dest)
- cmd = "scp -v -o UserKnownHostsFile=/dev/null " \
- "-o StrictHostKeyChecking=no " \
- "-i %(pkey)s %(file1)s %(dest)s" % {'pkey': pkey,
- 'file1': file_from,
- 'dest': dest}
- args = shlex.split(cmd.encode('utf-8'))
- subprocess_args = {'stdout': subprocess.PIPE,
- 'stderr': subprocess.STDOUT}
- proc = subprocess.Popen(args, **subprocess_args)
- stdout, stderr = proc.communicate()
- if proc.returncode != 0:
- LOG.error(("Command {0} returned with exit status {1},"
- "output {2}, error {3}").format(cmd, proc.returncode,
- stdout, stderr))
- return stdout
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 73505e6..7ebc283 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -15,12 +15,12 @@
from oslo_log import log as logging
from oslo_utils import excutils
-from tempest_lib.common.utils import data_utils
from tempest.common import fixed_network
-from tempest.common import service_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common import rest_client
+from tempest.lib.common.utils import data_utils
CONF = config.CONF
@@ -49,10 +49,6 @@
# TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
- name = name
- flavor = flavor
- image_id = image_id
-
if name is None:
name = data_utils.rand_name(__name__ + "-instance")
if flavor is None:
@@ -102,8 +98,8 @@
volume = volumes_client.create_volume(
display_name=volume_name,
imageRef=image_id)
- volumes_client.wait_for_volume_status(volume['volume']['id'],
- 'available')
+ waiters.wait_for_volume_status(volumes_client,
+ volume['volume']['id'], 'available')
bd_map_v2 = [{
'uuid': volume['volume']['id'],
@@ -129,7 +125,7 @@
servers = \
[s for s in body_servers['servers'] if s['name'].startswith(name)]
else:
- body = service_client.ResponseBody(body.response, body['server'])
+ body = rest_client.ResponseBody(body.response, body['server'])
servers = [body]
# The name of the method to associate a floating IP to as server is too
@@ -152,14 +148,38 @@
except Exception:
with excutils.save_and_reraise_exception():
- if ('preserve_server_on_error' not in kwargs
- or kwargs['preserve_server_on_error'] is False):
- for server in servers:
- try:
- clients.servers_client.delete_server(
- server['id'])
- except Exception:
- LOG.exception('Deleting server %s failed'
- % server['id'])
+ for server in servers:
+ try:
+ clients.servers_client.delete_server(
+ server['id'])
+ except Exception:
+ LOG.exception('Deleting server %s failed'
+ % server['id'])
return body, servers
+
+
+def shelve_server(client, server_id, force_shelve_offload=False):
+ """Common wrapper utility to shelve server.
+
+ This method is a common wrapper to make server in 'SHELVED'
+ or 'SHELVED_OFFLOADED' state.
+
+ :param server_id: Server to make in shelve state
+ :param force_shelve_offload: Forcefully offload shelve server if it
+ is configured not to offload server
+ automatically after offload time.
+ """
+ client.shelve_server(server_id)
+
+ offload_time = CONF.compute.shelved_offload_time
+ if offload_time >= 0:
+ waiters.wait_for_server_status(client, server_id,
+ 'SHELVED_OFFLOADED',
+ extra_timeout=offload_time)
+ else:
+ waiters.wait_for_server_status(client, server_id, 'SHELVED')
+ if force_shelve_offload:
+ client.shelve_offload_server(server_id)
+ waiters.wait_for_server_status(client, server_id,
+ 'SHELVED_OFFLOADED')
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 6df7eb2..37c9727 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -14,9 +14,9 @@
from oslo_log import log as logging
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
from tempest.services.identity.v2.json import identity_client as v2_identity
LOG = logging.getLogger(__name__)
@@ -31,15 +31,13 @@
admin credentials used for generating credentials.
"""
- def __init__(self, identity_client, projects_client=None,
- roles_client=None, users_client=None):
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client):
# The client implies version and credentials
self.identity_client = identity_client
- # this is temporary until the v3 project client is
- # separated, then projects_client will become mandatory
- self.projects_client = projects_client or identity_client
- self.roles_client = roles_client or identity_client
- self.users_client = users_client or identity_client
+ self.users_client = users_client
+ self.projects_client = projects_client
+ self.roles_client = roles_client
def create_user(self, username, password, project, email):
user = self.users_client.create_user(
@@ -48,6 +46,9 @@
user = user['user']
return user
+ def delete_user(self, user_id):
+ self.users_client.delete_user(user_id)
+
@abc.abstractmethod
def create_project(self, name, description):
pass
@@ -70,8 +71,7 @@
msg = 'No "%s" role found' % role_name
raise lib_exc.NotFound(msg)
try:
- self.roles_client.assign_user_role(project['id'], user['id'],
- role['id'])
+ self._assign_user_role(project, user, role)
except lib_exc.Conflict:
LOG.debug("Role %s already assigned on project %s for user %s" % (
role['id'], project['id'], user['id']))
@@ -87,9 +87,6 @@
"""
pass
- def delete_user(self, user_id):
- self.users_client.delete_user(user_id)
-
def _list_roles(self):
roles = self.roles_client.list_roles()['roles']
return roles
@@ -97,18 +94,21 @@
class V2CredsClient(CredsClient):
- def __init__(self, identity_client, projects_client, roles_client,
- users_client):
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client):
super(V2CredsClient, self).__init__(identity_client,
projects_client,
- roles_client,
- users_client)
+ users_client,
+ roles_client)
def create_project(self, name, description):
tenant = self.projects_client.create_tenant(
name=name, description=description)['tenant']
return tenant
+ def delete_project(self, project_id):
+ self.projects_client.delete_tenant(project_id)
+
def get_credentials(self, user, project, password):
# User and project already include both ID and name here,
# so there's no need to use the fill_in mode
@@ -120,18 +120,23 @@
tenant_name=project['name'], tenant_id=project['id'],
password=password)
- def delete_project(self, project_id):
- self.projects_client.delete_tenant(project_id)
+ def _assign_user_role(self, project, user, role):
+ self.roles_client.assign_user_role(project['id'], user['id'],
+ role['id'])
class V3CredsClient(CredsClient):
- def __init__(self, identity_client, domain_name):
- super(V3CredsClient, self).__init__(identity_client)
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client, domains_client, domain_name):
+ super(V3CredsClient, self).__init__(identity_client, projects_client,
+ users_client, roles_client)
+ self.domains_client = domains_client
+
try:
# Domain names must be unique, in any case a list is returned,
# selecting the first (and only) element
- self.creds_domain = self.identity_client.list_domains(
+ self.creds_domain = self.domains_client.list_domains(
params={'name': domain_name})['domains'][0]
except lib_exc.NotFound:
# TODO(andrea) we could probably create the domain on the fly
@@ -139,11 +144,14 @@
raise lib_exc.InvalidCredentials(msg)
def create_project(self, name, description):
- project = self.identity_client.create_project(
+ project = self.projects_client.create_project(
name=name, description=description,
domain_id=self.creds_domain['id'])['project']
return project
+ def delete_project(self, project_id):
+ self.projects_client.delete_project(project_id)
+
def get_credentials(self, user, project, password):
# User, project and domain already include both ID and name here,
# so there's no need to use the fill_in mode.
@@ -157,21 +165,21 @@
project_domain_id=self.creds_domain['id'],
project_domain_name=self.creds_domain['name'])
- def delete_project(self, project_id):
- self.identity_client.delete_project(project_id)
-
- def _list_roles(self):
- roles = self.identity_client.list_roles()['roles']
- return roles
+ def _assign_user_role(self, project, user, role):
+ self.roles_client.assign_user_role_on_project(project['id'],
+ user['id'],
+ role['id'])
def get_creds_client(identity_client,
- projects_client=None,
- roles_client=None,
- users_client=None,
+ projects_client,
+ users_client,
+ roles_client,
+ domains_client=None,
project_domain_name=None):
if isinstance(identity_client, v2_identity.IdentityClient):
- return V2CredsClient(identity_client, projects_client, roles_client,
- users_client)
+ return V2CredsClient(identity_client, projects_client, users_client,
+ roles_client)
else:
- return V3CredsClient(identity_client, project_domain_name)
+ return V3CredsClient(identity_client, projects_client, users_client,
+ roles_client, domains_client, project_domain_name)
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index 9dd89ea..5cce0bb 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -15,9 +15,9 @@
import abc
import six
-from tempest_lib import auth
from tempest import exceptions
+from tempest.lib import auth
@six.add_metaclass(abc.ABCMeta)
@@ -88,6 +88,11 @@
def __getattr__(self, item):
return getattr(self._credentials, item)
+ def __str__(self):
+ _format = "Credentials: %s, Network: %s, Subnet: %s, Router: %s"
+ return _format % (self._credentials, self.network, self.subnet,
+ self.router)
+
def set_resources(self, **kwargs):
for key in kwargs.keys():
if hasattr(self, key):
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 24c1198..286e01f 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -12,21 +12,20 @@
# limitations under the License.
from oslo_concurrency import lockutils
-from tempest_lib import auth
from tempest import clients
-from tempest.common import cred_provider
from tempest.common import dynamic_creds
from tempest.common import preprov_creds
from tempest import config
from tempest import exceptions
+from tempest.lib import auth
CONF = config.CONF
"""This module provides factories of credential and credential providers
-Credentials providers and clients are (going to be) part of tempest-lib,
+Credentials providers and clients are (going to be) part of tempest.lib,
and so they may not hold any dependency to tempest configuration.
Methods in this module collect the relevant configuration details and pass
@@ -62,89 +61,6 @@
]))
-class LegacyCredentialProvider(cred_provider.CredentialProvider):
-
- def __init__(self, identity_version):
- """Credentials provider which returns credentials from tempest.conf
-
- Credentials provider which always returns the first and second
- configured accounts as primary and alt users.
- Credentials from tempest.conf are deprecated, and this credential
- provider is also accordingly.
-
- This credential provider can be used in case of serial test execution
- to preserve the current behaviour of the serial tempest run.
-
- :param identity_version: Version of the identity API
- :return: CredentialProvider
- """
- super(LegacyCredentialProvider, self).__init__(
- identity_version=identity_version)
- self._creds = {}
-
- def _unique_creds(self, cred_arg=None):
- """Verify that the configured credentials are valid and distinct """
- try:
- user = self.get_primary_creds()
- alt_user = self.get_alt_creds()
- return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
- except exceptions.InvalidCredentials as ic:
- msg = "At least one of the configured credentials is " \
- "not valid: %s" % ic.message
- raise exceptions.InvalidConfiguration(msg)
-
- def is_multi_user(self):
- return self._unique_creds('username')
-
- def is_multi_tenant(self):
- return self._unique_creds('tenant_id')
-
- def get_primary_creds(self):
- if self._creds.get('primary'):
- return self._creds.get('primary')
- primary_credential = get_configured_credentials(
- credential_type='user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['primary'] = cred_provider.TestResources(
- primary_credential)
- return self._creds['primary']
-
- def get_alt_creds(self):
- if self._creds.get('alt'):
- return self._creds.get('alt')
- alt_credential = get_configured_credentials(
- credential_type='alt_user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['alt'] = cred_provider.TestResources(
- alt_credential)
- return self._creds['alt']
-
- def clear_creds(self):
- self._creds = {}
-
- def get_admin_creds(self):
- if self._creds.get('admin'):
- return self._creds.get('admin')
- creds = get_configured_credentials(
- "identity_admin", fill_in=False)
- self._creds['admin'] = cred_provider.TestResources(creds)
- return self._creds['admin']
-
- def get_creds_by_roles(self, roles, force_new=False):
- msg = "Credentials being specified through the config file can not be"\
- " used with tests that specify using credentials by roles. "\
- "Either exclude/skip the tests doing this or use either an "\
- "test_accounts_file or dynamic credentials."
- raise exceptions.InvalidConfiguration(msg)
-
- def is_role_available(self, role):
- # NOTE(andreaf) LegacyCredentialProvider does not support credentials
- # by role, so returning always False.
- # Test that rely on credentials by role should use this to skip
- # when this is credential provider is used
- return False
-
-
# 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
@@ -157,8 +73,8 @@
# 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_credentials(
- 'identity_admin', fill_in=True, identity_version=identity_version)
+ admin_creds = get_configured_admin_credentials(
+ fill_in=True, identity_version=identity_version)
return dynamic_creds.DynamicCredentialProvider(
name=name,
network_resources=network_resources,
@@ -172,9 +88,8 @@
name=name, identity_version=identity_version,
**_get_preprov_provider_params())
else:
- # Dynamic credentials are disabled, and the account file is not
- # defined - we fall back on credentials configured in tempest.conf
- return LegacyCredentialProvider(identity_version=identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
# We want a helper function here to check and see if admin credentials
@@ -196,8 +111,8 @@
is_admin = False
else:
try:
- get_configured_credentials('identity_admin', fill_in=False,
- identity_version=identity_version)
+ get_configured_admin_credentials(fill_in=False,
+ identity_version=identity_version)
except exceptions.InvalidConfiguration:
is_admin = False
return is_admin
@@ -218,7 +133,9 @@
identity_version=identity_version, name='check_alt',
**_get_preprov_provider_params())
else:
- check_accounts = LegacyCredentialProvider(identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
+
try:
if not check_accounts.is_multi_user():
return False
@@ -246,39 +163,32 @@
# Read credentials from configuration, builds a Credentials object
# based on the specified or configured version
-def get_configured_credentials(credential_type, fill_in=True,
- identity_version=None):
+def get_configured_admin_credentials(fill_in=True, identity_version=None):
identity_version = identity_version or CONF.identity.auth_version
if identity_version not in ('v2', 'v3'):
raise exceptions.InvalidConfiguration(
'Unsupported auth version: %s' % identity_version)
- if credential_type not in CREDENTIAL_TYPES:
- raise exceptions.InvalidCredentials()
- conf_attributes = ['username', 'password', 'tenant_name']
+ conf_attributes = ['username', 'password',
+ 'project_name']
if identity_version == 'v3':
conf_attributes.append('domain_name')
# Read the parts of credentials from config
params = DEFAULT_PARAMS.copy()
- section, prefix = CREDENTIAL_TYPES[credential_type]
for attr in conf_attributes:
- _section = getattr(CONF, section)
- if prefix is None:
- params[attr] = getattr(_section, attr)
- else:
- params[attr] = getattr(_section, prefix + "_" + attr)
+ params[attr] = getattr(CONF.auth, 'admin_' + attr)
# Build and validate credentials. We are reading configured credentials,
# so validate them even if fill_in is False
credentials = get_credentials(fill_in=fill_in,
identity_version=identity_version, **params)
if not fill_in:
if not credentials.is_valid():
- msg = ("The %s credentials are incorrectly set in the config file."
- " Double check that all required values are assigned" %
- credential_type)
- raise exceptions.InvalidConfiguration(msg)
+ msg = ("The admin credentials are incorrectly set in the config "
+ "file for identity version %s. Double check that all "
+ "required values are assigned.")
+ raise exceptions.InvalidConfiguration(msg % identity_version)
return credentials
@@ -306,19 +216,10 @@
# === Credential / client managers
-class ConfiguredUserManager(clients.Manager):
- """Manager that uses user credentials for its managed client objects"""
-
- def __init__(self, service=None):
- super(ConfiguredUserManager, self).__init__(
- credentials=get_configured_credentials('user'),
- service=service)
-
-
class AdminManager(clients.Manager):
"""Manager that uses admin credentials for its managed client objects"""
def __init__(self, service=None):
super(AdminManager, self).__init__(
- credentials=get_configured_credentials('identity_admin'),
+ credentials=get_configured_admin_credentials(),
service=service)
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 95ad229..a63af38 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -15,7 +15,6 @@
import netaddr
from oslo_log import log as logging
import six
-from tempest_lib import exceptions as lib_exc
from tempest import clients
from tempest.common import cred_client
@@ -23,6 +22,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -57,11 +57,13 @@
self._creds = {}
self.ports = []
self.default_admin_creds = admin_creds
- (self.identity_admin_client, self.tenants_admin_client,
- self.roles_admin_client,
+ (self.identity_admin_client,
+ self.tenants_admin_client,
self.users_admin_client,
- self.network_admin_client,
+ self.roles_admin_client,
+ self.domains_admin_client,
self.networks_admin_client,
+ self.routers_admin_client,
self.subnets_admin_client,
self.ports_admin_client,
self.security_groups_admin_client) = self._get_admin_clients()
@@ -75,8 +77,9 @@
self.creds_client = cred_client.get_creds_client(
self.identity_admin_client,
self.tenants_admin_client,
- self.roles_admin_client,
self.users_admin_client,
+ self.roles_admin_client,
+ self.domains_admin_client,
self.creds_domain_name)
def _get_admin_clients(self):
@@ -88,13 +91,15 @@
"""
os = clients.Manager(self.default_admin_creds)
if self.identity_version == 'v2':
- return (os.identity_client, os.tenants_client, os.roles_client,
- os.users_client, os.network_client, os.networks_client,
- os.subnets_client, os.ports_client,
- os.security_groups_client)
+ 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)
else:
- return (os.identity_v3_client, None, None, None, os.network_client,
- os.networks_client, os.subnets_client, os.ports_client,
+ return (os.identity_v3_client, os.projects_client,
+ os.users_v3_client, os.roles_v3_client, os.domains_client,
+ os.networks_client, os.routers_client,
+ os.subnets_client, os.ports_client,
os.security_groups_client)
def _create_creds(self, suffix="", admin=False, roles=None):
@@ -149,6 +154,20 @@
return cred_provider.TestResources(creds)
def _create_network_resources(self, tenant_id):
+ """The function creates network resources in the given tenant.
+
+ The function checks if network_resources class member is empty,
+ In case it is, it will create a network, a subnet and a router for
+ the tenant according to the given tenant id parameter.
+ Otherwise it will create a network resource according
+ to the values from network_resources dict.
+
+ :param tenant_id: The tenant id to create resources for.
+ :type tenant_id: str
+ :raises: InvalidConfiguration, Exception
+ :returns: network resources(network,subnet,router)
+ :rtype: tuple
+ """
network = None
subnet = None
router = None
@@ -180,12 +199,19 @@
router = self._create_router(router_name, tenant_id)
self._add_router_interface(router['id'], subnet['id'])
except Exception:
- if router:
- self._clear_isolated_router(router['id'], router['name'])
- if subnet:
- self._clear_isolated_subnet(subnet['id'], subnet['name'])
- if network:
- self._clear_isolated_network(network['id'], network['name'])
+ try:
+ if router:
+ self._clear_isolated_router(router['id'], router['name'])
+ if subnet:
+ self._clear_isolated_subnet(subnet['id'], subnet['name'])
+ if network:
+ self._clear_isolated_network(network['id'],
+ network['name'])
+ except Exception as cleanup_exception:
+ msg = "There was an exception trying to setup network " \
+ "resources for tenant %s, and this error happened " \
+ "trying to clean them up: %s"
+ LOG.warning(msg % (tenant_id, cleanup_exception))
raise
return network, subnet, router
@@ -195,8 +221,8 @@
return resp_body['network']
def _create_subnet(self, subnet_name, tenant_id, network_id):
- base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ base_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ mask_bits = CONF.network.project_network_mask_bits
for subnet_cidr in base_cidr.subnet(mask_bits):
try:
if self.network_resources:
@@ -226,15 +252,15 @@
def _create_router(self, router_name, tenant_id):
external_net_id = dict(
network_id=CONF.network.public_network_id)
- resp_body = self.network_admin_client.create_router(
- router_name,
+ resp_body = self.routers_admin_client.create_router(
+ name=router_name,
external_gateway_info=external_net_id,
tenant_id=tenant_id)
return resp_body['router']
def _add_router_interface(self, router_id, subnet_id):
- self.network_admin_client.add_router_interface_with_subnet_id(
- router_id, subnet_id)
+ self.routers_admin_client.add_router_interface(router_id,
+ subnet_id=subnet_id)
def get_credentials(self, credential_type):
if self._creds.get(str(credential_type)):
@@ -284,9 +310,9 @@
return self.get_credentials(roles)
def _clear_isolated_router(self, router_id, router_name):
- net_client = self.network_admin_client
+ client = self.routers_admin_client
try:
- net_client.delete_router(router_id)
+ client.delete_router(router_id)
except lib_exc.NotFound:
LOG.warning('router with name: %s not found for delete' %
router_name)
@@ -320,7 +346,7 @@
(secgroup['name'], secgroup['id']))
def _clear_isolated_net_resources(self):
- net_client = self.network_admin_client
+ client = self.routers_admin_client
for cred in self._creds:
creds = self._creds.get(cred)
if (not creds or not any([creds.router, creds.network,
@@ -333,8 +359,9 @@
if (not self.network_resources or
(self.network_resources.get('router') and creds.subnet)):
try:
- net_client.remove_router_interface_with_subnet_id(
- creds.router['id'], creds.subnet['id'])
+ client.remove_router_interface(
+ creds.router['id'],
+ subnet_id=creds.subnet['id'])
except lib_exc.NotFound:
LOG.warning('router with name: %s not found for delete' %
creds.router['name'])
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index 3fc1365..5f0685e 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -13,9 +13,8 @@
import copy
from oslo_log import log as logging
-from tempest_lib.common.utils import misc as misc_utils
-
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
LOG = logging.getLogger(__name__)
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 3a51f2e..0647edb 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -169,7 +169,7 @@
expected_result = generator_result[2]
element = path.pop()
if len(path) > 0:
- schema_snip = reduce(dict.get, path, schema)
+ schema_snip = six.moves.reduce(dict.get, path, schema)
schema_snip[element] = invalid_snippet
else:
schema[element] = invalid_snippet
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 800e977..00062de 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -47,7 +47,6 @@
self.endpoint_scheme = endpoint_parts.scheme
self.endpoint_hostname = endpoint_parts.hostname
self.endpoint_port = endpoint_parts.port
- self.endpoint_path = endpoint_parts.path
self.connection_class = self._get_connection_class(
self.endpoint_scheme)
@@ -307,13 +306,32 @@
def connect(self):
"""Connect to SSL port and apply per-connection parameters."""
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- if self.timeout is not None:
- # '0' microseconds
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
- struct.pack('LL', self.timeout, 0))
- self.sock = OpenSSLConnectionDelegator(self.context, sock)
- self.sock.connect((self.host, self.port))
+ try:
+ addresses = socket.getaddrinfo(self.host,
+ self.port,
+ socket.AF_UNSPEC,
+ socket.SOCK_STREAM)
+ except OSError as msg:
+ raise exc.RestClientException(msg)
+ for res in addresses:
+ af, socktype, proto, canonname, sa = res
+ sock = socket.socket(af, socket.SOCK_STREAM)
+
+ if self.timeout is not None:
+ # '0' microseconds
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
+ struct.pack('LL', self.timeout, 0))
+ self.sock = OpenSSLConnectionDelegator(self.context, sock)
+ try:
+ self.sock.connect(sa)
+ except OSError as msg:
+ if self.sock:
+ self.sock = None
+ continue
+ break
+ if self.sock is None:
+ # Happen only when all results have failed.
+ raise exc.RestClientException('Cannot connect to %s' % self.host)
def close(self):
if self.sock:
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index 2179363..469defe 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
+from tempest.lib import exceptions as lib_exc
def get_tenant_by_name(client, tenant_name):
diff --git a/tempest/common/negative_rest_client.py b/tempest/common/negative_rest_client.py
index d97411c..3495a24 100644
--- a/tempest/common/negative_rest_client.py
+++ b/tempest/common/negative_rest_client.py
@@ -15,30 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common import service_client
from tempest import config
+from tempest.lib.common import rest_client
CONF = config.CONF
-class NegativeRestClient(service_client.ServiceClient):
+class NegativeRestClient(rest_client.RestClient):
"""Version of RestClient that does not raise exceptions."""
- def __init__(self, auth_provider, service,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ def __init__(self, auth_provider, service, **kwargs):
region, endpoint_type = self._get_region_and_endpoint_type(service)
super(NegativeRestClient, self).__init__(
- auth_provider,
- service,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, service, region, endpoint_type=endpoint_type,
+ **kwargs)
def _get_region_and_endpoint_type(self, service):
"""Returns the region for a specific service"""
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index 34af31e..7350222 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -18,14 +18,14 @@
from oslo_concurrency import lockutils
from oslo_log import log as logging
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
import yaml
from tempest import clients
from tempest.common import cred_provider
from tempest.common import fixed_network
from tempest import exceptions
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
@@ -43,6 +43,11 @@
class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
+ # Exclude from the hash fields specific to v2 or v3 identity API
+ # i.e. only include user*, project*, tenant* and password
+ HASH_CRED_FIELDS = (set(auth.KeystoneV2Credentials.ATTRIBUTES) &
+ set(auth.KeystoneV3Credentials.ATTRIBUTES))
+
def __init__(self, identity_version, test_accounts_file,
accounts_lock_dir, name=None, credentials_domain=None,
admin_role=None, object_storage_operator_role=None,
@@ -104,6 +109,7 @@
object_storage_operator_role=None,
object_storage_reseller_admin_role=None):
hash_dict = {'roles': {}, 'creds': {}, 'networks': {}}
+
# Loop over the accounts read from the yaml file
for account in accounts:
roles = []
@@ -116,7 +122,9 @@
if 'resources' in account:
resources = account.pop('resources')
temp_hash = hashlib.md5()
- temp_hash.update(six.text_type(account).encode('utf-8'))
+ account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
+ if k in cls.HASH_CRED_FIELDS)
+ temp_hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash_key = temp_hash.hexdigest()
hash_dict['creds'][temp_hash_key] = account
for role in roles:
@@ -219,9 +227,9 @@
else:
hashes = self.hash_dict['creds'].keys()
# NOTE(mtreinish): admin is a special case because of the increased
- # privlege set which could potentially cause issues on tests where that
- # is not expected. So unless the admin role isn't specified do not
- # allocate admin.
+ # privilege set which could potentially cause issues on tests where
+ # that is not expected. So unless the admin role isn't specified do
+ # not allocate admin.
admin_hashes = self.hash_dict['roles'].get(self.admin_role,
None)
if ((not roles or self.admin_role not in roles) and
@@ -262,13 +270,13 @@
for _hash in self.hash_dict['creds']:
# Comparing on the attributes that are expected in the YAML
init_attributes = creds.get_init_attributes()
+ # Only use the attributes initially used to calculate the hash
+ init_attributes = [x for x in init_attributes if
+ x in self.HASH_CRED_FIELDS]
hash_attributes = self.hash_dict['creds'][_hash].copy()
- if ('user_domain_name' in init_attributes and 'user_domain_name'
- not in hash_attributes):
- # Allow for the case of domain_name populated from config
- domain_name = self.credentials_domain
- hash_attributes['user_domain_name'] = domain_name
- if all([getattr(creds, k) == hash_attributes[k] for
+ # NOTE(andreaf) Not all fields may be available on all credentials
+ # so defaulting to None for that case.
+ if all([getattr(creds, k, None) == hash_attributes.get(k, None) for
k in init_attributes]):
return _hash
raise AttributeError('Invalid credentials %s' % creds)
@@ -331,6 +339,7 @@
def _wrap_creds_with_network(self, hash):
creds_dict = self.hash_dict['creds'][hash]
# Make sure a domain scope if defined for users in case of V3
+ # Make sure a tenant is available in case of V2
creds_dict = self._extend_credentials(creds_dict)
# This just builds a Credentials object, it does not validate
# nor fill with missing fields.
@@ -350,10 +359,20 @@
return net_creds
def _extend_credentials(self, creds_dict):
- # In case of v3, adds a user_domain_name field to the creds
- # dict if not defined
+ # Add or remove credential domain fields to fit the identity version
+ domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
+ if 'domain' in x)
+ msg = 'Assuming they are valid in the default domain.'
if self.identity_version == 'v3':
- user_domain_fields = set(['user_domain_name', 'user_domain_id'])
- if not user_domain_fields.intersection(set(creds_dict.keys())):
- creds_dict['user_domain_name'] = self.credentials_domain
+ if not domain_fields.intersection(set(creds_dict.keys())):
+ msg = 'Using credentials %s for v3 API calls. ' + msg
+ LOG.warning(msg, self._sanitize_creds(creds_dict))
+ creds_dict['domain_name'] = self.credentials_domain
+ if self.identity_version == 'v2':
+ if domain_fields.intersection(set(creds_dict.keys())):
+ msg = 'Using credentials %s for v2 API calls. ' + msg
+ LOG.warning(msg, self._sanitize_creds(creds_dict))
+ # Remove all valid domain attributes
+ for attr in domain_fields.intersection(set(creds_dict.keys())):
+ creds_dict.pop(attr)
return creds_dict
diff --git a/tempest/common/service_client.py b/tempest/common/service_client.py
deleted file mode 100644
index b3a5a09..0000000
--- a/tempest/common/service_client.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest_lib.common import rest_client
-
-
-class ServiceClient(rest_client.RestClient):
-
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
-
- dscv = disable_ssl_certificate_validation
- params = {
- 'disable_ssl_certificate_validation': dscv,
- 'ca_certs': ca_certs,
- 'trace_requests': trace_requests
- }
-
- if endpoint_type is not None:
- params.update({'endpoint_type': endpoint_type})
- if build_interval is not None:
- params.update({'build_interval': build_interval})
- if build_timeout is not None:
- params.update({'build_timeout': build_timeout})
- super(ServiceClient, self).__init__(auth_provider, service, region,
- **params)
-
-
-class ResponseBody(dict):
- """Class that wraps an http response and dict body into a single value.
-
- Callers that receive this object will normally use it as a dict but
- can extract the response if needed.
- """
-
- def __init__(self, response, body=None):
- body_data = body or {}
- self.update(body_data)
- self.response = response
-
- def __str__(self):
- body = super(ResponseBody, self).__str__()
- return "response: %s\nBody: %s" % (self.response, body)
-
-
-class ResponseBodyData(object):
- """Class that wraps an http response and string data into a single value"""
-
- def __init__(self, response, data):
- self.response = response
- self.data = data
-
- def __str__(self):
- return "response: %s\nBody: %s" % (self.response, self.data)
-
-
-class ResponseBodyList(list):
- """Class that wraps an http response and list body into a single value.
-
- Callers that receive this object will normally use it as a list but
- can extract the response if needed.
- """
-
- def __init__(self, response, body=None):
- body_data = body or []
- self.extend(body_data)
- self.response = response
-
- def __str__(self):
- body = super(ResponseBodyList, self).__str__()
- return "response: %s\nBody: %s" % (self.response, body)
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 81b8110..b6565d1 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -15,8 +15,7 @@
from functools import partial
from tempest import config
-
-from tempest_lib.common.utils import data_utils as lib_data_utils
+from tempest.lib.common.utils import data_utils as lib_data_utils
CONF = config.CONF
@@ -27,8 +26,6 @@
class DataUtils(object):
def __getattr__(self, attr):
- if attr in self.__dict__:
- return self.__dict__[attr]
if attr == 'rand_name':
# NOTE(flwang): This is a proxy to generate a random name that
diff --git a/tempest/common/utils/file_utils.py b/tempest/common/utils/file_utils.py
deleted file mode 100644
index 43083f4..0000000
--- a/tempest/common/utils/file_utils.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-
-
-def have_effective_read_access(path):
- try:
- fh = open(path, "rb")
- except IOError:
- return False
- fh.close()
- return True
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 2e233c5..3f573b7 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -12,29 +12,78 @@
import netaddr
import re
+import six
+import sys
import time
from oslo_log import log as logging
-from tempest_lib.common import ssh
from tempest import config
from tempest import exceptions
+from tempest.lib.common import ssh
+from tempest.lib.common.utils import misc as misc_utils
+import tempest.lib.exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
+def debug_ssh(function):
+ """Decorator to generate extra debug info in case off SSH failure"""
+ def wrapper(self, *args, **kwargs):
+ try:
+ return function(self, *args, **kwargs)
+ except tempest.lib.exceptions.SSHTimeout:
+ try:
+ original_exception = sys.exc_info()
+ caller = misc_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.log_console 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
+
+
class RemoteClient(object):
- def __init__(self, ip_address, username, password=None, pkey=None):
+ def __init__(self, ip_address, username, password=None, pkey=None,
+ server=None, servers_client=None):
+ """Executes commands in a VM over ssh
+
+ :param ip_address: IP address to ssh to
+ :param username: ssh username
+ :param password: ssh password (optional)
+ :param pkey: ssh public key (optional)
+ :param server: server dict, used for debugging purposes
+ :param servers_client: servers client, used for debugging purposes
+ """
+ self.server = server
+ self.servers_client = servers_client
ssh_timeout = CONF.validation.ssh_timeout
connect_timeout = CONF.validation.connect_timeout
+ self.log_console = CONF.compute_feature_enabled.console_output
self.ssh_client = ssh.Client(ip_address, username, password,
ssh_timeout, pkey=pkey,
channel_timeout=connect_timeout)
+ @debug_ssh
def exec_command(self, cmd):
# Shell options below add more clearness on failures,
# path is extended for some non-cirros guest oses (centos7)
@@ -42,6 +91,7 @@
LOG.debug("Remote command: %s" % cmd)
return self.ssh_client.exec_command(cmd)
+ @debug_ssh
def validate_authentication(self):
"""Validate ssh connection and authentication
@@ -101,7 +151,12 @@
cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
return self.exec_command(cmd).strip().lower()
- def get_nic_name(self, address):
+ 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()
+
+ 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()
@@ -112,7 +167,7 @@
def assign_static_ip(self, nic, addr):
cmd = "sudo ip addr add {ip}/{mask} dev {nic}".format(
- ip=addr, mask=CONF.network.tenant_network_mask_bits,
+ ip=addr, mask=CONF.network.project_network_mask_bits,
nic=nic
)
return self.exec_command(cmd)
@@ -141,7 +196,7 @@
def _renew_lease_udhcpc(self, fixed_ip=None):
"""Renews DHCP lease via udhcpc client. """
file_path = '/var/run/udhcpc.'
- nic_name = self.get_nic_name(fixed_ip)
+ nic_name = self.get_nic_name_by_ip(fixed_ip)
pid = self.exec_command('cat {path}{nic}.pid'.
format(path=file_path, nic=nic_name))
pid = pid.strip()
@@ -178,4 +233,10 @@
def make_fs(self, dev_name, fs='ext4'):
cmd_mkfs = 'sudo /usr/sbin/mke2fs -t %s /dev/%s' % (fs, dev_name)
- self.exec_command(cmd_mkfs)
+ try:
+ self.exec_command(cmd_mkfs)
+ except tempest.lib.exceptions.SSHExecCommandFailed:
+ LOG.error("Couldn't mke2fs")
+ cmd_why = 'sudo ls -lR /dev'
+ LOG.info("Contents of /dev: %s" % self.exec_command(cmd_why))
+ raise
diff --git a/tempest/common/utils/net_utils.py b/tempest/common/utils/net_utils.py
new file mode 100644
index 0000000..d98fb32
--- /dev/null
+++ b/tempest/common/utils/net_utils.py
@@ -0,0 +1,48 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company
+#
+# 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 itertools
+import netaddr
+
+from tempest.lib import exceptions as lib_exc
+
+
+def get_unused_ip_addresses(ports_client, subnets_client,
+ network_id, subnet_id, count):
+
+ """Return a list with the specified number of unused IP addresses
+
+ This method uses the given ports_client to find the specified number of
+ unused IP addresses on the given subnet using the supplied subnets_client
+ """
+
+ ports = ports_client.list_ports(network_id=network_id)['ports']
+ subnet = subnets_client.show_subnet(subnet_id)
+ ip_net = netaddr.IPNetwork(subnet['subnet']['cidr'])
+ subnet_set = netaddr.IPSet(ip_net.iter_hosts())
+ alloc_set = netaddr.IPSet()
+
+ # prune out any addresses already allocated to existing ports
+ for port in ports:
+ for fixed_ip in port.get('fixed_ips'):
+ alloc_set.add(fixed_ip['ip_address'])
+
+ av_set = subnet_set - alloc_set
+ ip_list = [str(ip) for ip in itertools.islice(av_set, count)]
+
+ if len(ip_list) != count:
+ msg = "Insufficient IP addresses available"
+ raise lib_exc.BadRequest(message=msg)
+
+ return ip_list
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index 9457a60..c3c9a41 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -14,9 +14,9 @@
from oslo_log import log as logging
from tempest import config
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -59,7 +59,9 @@
create_ssh_security_group(os, add_rule)
if validation_resources['floating_ip']:
floating_client = os.compute_floating_ips_client
- validation_data.update(floating_client.create_floating_ip())
+ validation_data.update(
+ floating_client.create_floating_ip(
+ pool=CONF.network.floating_network_name))
return validation_data
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 867d3f6..23d7f88 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -14,11 +14,12 @@
import time
from oslo_log import log as logging
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
+from tempest.services.image.v1.json import images_client as images_v1_client
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -122,42 +123,44 @@
The client should have a show_image(image_id) method to get the image.
The client should also have build_interval and build_timeout attributes.
"""
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
- if 'image' in image:
- image = image['image']
- start = int(time.time())
+ if isinstance(client, images_v1_client.ImagesClient):
+ # The 'check_image' method is used here because the show_image method
+ # returns image details plus the image itself which is very expensive.
+ # The 'check_image' method returns just image details.
+ show_image = client.check_image
+ else:
+ show_image = client.show_image
- while image['status'] != status:
- time.sleep(client.build_interval)
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
+ current_status = 'An unknown status'
+ start = int(time.time())
+ while int(time.time()) - start < client.build_timeout:
+ image = show_image(image_id)
+ # Compute image client returns response wrapped in 'image' element
+ # which is not case with Glance image client.
if 'image' in image:
image = image['image']
- status_curr = image['status']
- if status_curr == 'ERROR':
+
+ current_status = image['status']
+ if current_status == status:
+ return
+ if current_status.lower() == 'killed':
+ raise exceptions.ImageKilledException(image_id=image_id,
+ status=status)
+ if current_status.lower() == 'error':
raise exceptions.AddImageException(image_id=image_id)
- # check the status again to avoid a false negative where we hit
- # the timeout at the same time that the image reached the expected
- # status
- if status_curr == status:
- return
+ time.sleep(client.build_interval)
- if int(time.time()) - start >= client.build_timeout:
- message = ('Image %(image_id)s failed to reach %(status)s state'
- '(current state %(status_curr)s) '
- 'within the required time (%(timeout)s s).' %
- {'image_id': image_id,
- 'status': status,
- 'status_curr': status_curr,
- 'timeout': client.build_timeout})
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
+ message = ('Image %(image_id)s failed to reach %(status)s state '
+ '(current state %(current_status)s) within the required '
+ 'time (%(timeout)s s).' % {'image_id': image_id,
+ 'status': status,
+ 'current_status': current_status,
+ 'timeout': client.build_timeout})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
def wait_for_volume_status(client, volume_id, status):
diff --git a/tempest/config.py b/tempest/config.py
index 6942172..71b25d0 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,6 +15,7 @@
from __future__ import print_function
+import functools
import logging as std_logging
import os
import tempfile
@@ -22,6 +23,7 @@
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
+import testtools
from tempest.test_discover import plugins
@@ -56,7 +58,7 @@
"number of concurrent test processes."),
cfg.BoolOpt('use_dynamic_credentials',
default=True,
- help="Allows test cases to create/destroy tenants and "
+ help="Allows test cases to create/destroy projects and "
"users. This option requires that OpenStack Identity "
"API admin credentials are known. If false, isolated "
"test cases and parallel execution, can still be "
@@ -81,23 +83,26 @@
default=True,
help="If use_dynamic_credentials is set to True and Neutron "
"is enabled Tempest will try to create a usable network, "
- "subnet, and router when needed for each tenant it "
+ "subnet, and router when needed for each project it "
"creates. However in some neutron configurations, like "
"with VLAN provider networks, this doesn't work. So if "
"set to False the isolated networks will not be created"),
cfg.StrOpt('admin_username',
help="Username for an administrative user. This is needed for "
- "authenticating requests made by tenant isolation to "
+ "authenticating requests made by project isolation to "
"create users and projects",
deprecated_group='identity'),
- cfg.StrOpt('admin_tenant_name',
- help="Tenant name to use for an administrative user. This is "
- "needed for authenticating requests made by tenant "
+ cfg.StrOpt('admin_project_name',
+ help="Project name to use for an administrative user. This is "
+ "needed for authenticating requests made by project "
"isolation to create users and projects",
- deprecated_group='identity'),
+ deprecated_opts=[cfg.DeprecatedOpt('admin_tenant_name',
+ group='auth'),
+ cfg.DeprecatedOpt('admin_tenant_name',
+ group='identity')]),
cfg.StrOpt('admin_password',
- help="Password to use for an administrative user. This is "
- "needed for authenticating requests made by tenant "
+ help="Password to use for an administrative user. This is "
+ "needed for authenticating requests made by project "
"isolation to create users and projects",
secret=True,
deprecated_group='identity'),
@@ -155,39 +160,9 @@
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for OpenStack Identity "
"(Keystone) API v3"),
- cfg.StrOpt('username',
- help="Username to use for Nova API requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('tenant_name',
- help="Tenant name to use for Nova API requests.",
- deprecated_for_removal=True),
cfg.StrOpt('admin_role',
default='admin',
help="Role required to administrate keystone."),
- cfg.StrOpt('password',
- help="API key to use when authenticating.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('domain_name',
- help="Domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_username',
- help="Username of alternate user to use for Nova API "
- "requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_tenant_name',
- help="Alternate user's Tenant name to use for Nova API "
- "requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_password',
- help="API key to use when authenticating as alternate user.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('alt_domain_name',
- help="Alternate domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
cfg.StrOpt('default_domain_id',
default='default',
help="ID of the default domain"),
@@ -246,10 +221,10 @@
"no OS-EXT-STS extension available"),
cfg.StrOpt('fixed_network_name',
help="Name of the fixed network that is visible to all test "
- "tenants. If multiple networks are available for a tenant"
- " this is the network which will be used for creating "
- "servers if tempest does not create a network or a "
- "network is not specified elsewhere. It may be used for "
+ "projects. If multiple networks are available for a "
+ "project, this is the network which will be used for "
+ "creating servers if tempest does not create a network or "
+ "s network is not specified elsewhere. It may be used for "
"ssh validation only if floating IPs are disabled."),
cfg.StrOpt('catalog_type',
default='compute',
@@ -281,13 +256,7 @@
help=('The minimum number of compute nodes expected. This will '
'be utilized by some multinode specific tests to ensure '
'that requests match the expected size of the cluster '
- 'you are testing with.'))
-]
-
-compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
- title="Enabled Compute Service Features")
-
-ComputeFeaturesGroup = [
+ 'you are testing with.')),
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
@@ -296,7 +265,8 @@
"min_microversion and max_microversion. "
"If both values are not specified, Tempest avoids tests "
"which require a microversion. Valid values are string "
- "with format 'X.Y' or string 'latest'"),
+ "with format 'X.Y' or string 'latest'",
+ deprecated_group='compute-feature-enabled'),
cfg.StrOpt('max_microversion',
default=None,
help="Upper version of the test target microversion range. "
@@ -305,7 +275,14 @@
"min_microversion and max_microversion. "
"If both values are not specified, Tempest avoids tests "
"which require a microversion. Valid values are string "
- "with format 'X.Y' or string 'latest'"),
+ "with format 'X.Y' or string 'latest'",
+ deprecated_group='compute-feature-enabled'),
+]
+
+compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
+ title="Enabled Compute Service Features")
+
+ComputeFeaturesGroup = [
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),
@@ -352,13 +329,6 @@
help="Does the test environment block migration support "
"cinder iSCSI volumes. Note, libvirt doesn't support this, "
"see https://bugs.launchpad.net/nova/+bug/1398999"),
- # TODO(gilliard): Remove live_migrate_paused_instances at juno-eol.
- cfg.BoolOpt('live_migrate_paused_instances',
- default=False,
- help="Does the test system allow live-migration of paused "
- "instances? Note, this is more than just the ANDing of "
- "paused and live_migrate, but all 3 should be set to True "
- "to run those tests"),
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
@@ -394,29 +364,24 @@
cfg.BoolOpt('personality',
default=True,
help='Does the test environment support server personality'),
- # TODO(mriedem): Remove preserve_ports once juno-eol happens.
- cfg.BoolOpt('preserve_ports',
- default=False,
- help='Does Nova preserve preexisting ports from Neutron '
- 'when deleting an instance? This should be set to True '
- 'if testing Kilo+ Nova.'),
cfg.BoolOpt('attach_encrypted_volume',
default=True,
help='Does the test environment support attaching an '
'encrypted volume to a running server instance? This may '
'depend on the combination of compute_driver in nova and '
'the volume_driver(s) in cinder.'),
- # TODO(mriedem): Remove allow_duplicate_networks once kilo-eol happens
- # since the option was removed from nova in Liberty and is the default
- # behavior starting in Liberty.
- cfg.BoolOpt('allow_duplicate_networks',
- default=False,
- help='Does the test environment support creating instances '
- 'with multiple ports on the same network? This is only '
- 'valid when using Neutron.'),
cfg.BoolOpt('config_drive',
default=True,
help='Enable special configuration drive with metadata.'),
+ cfg.ListOpt('scheduler_available_filters',
+ default=['all'],
+ help="A list of enabled filters that nova will accept as hints"
+ " to the scheduler when creating a server. A special "
+ "entry 'all' indicates all filters are enabled. Empty "
+ "list indicates all filters are disabled. The full "
+ "available list of filters is in nova.conf: "
+ "DEFAULT.scheduler_available_filters"),
+
]
@@ -495,21 +460,26 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the network service."),
- cfg.StrOpt('tenant_network_cidr',
+ cfg.StrOpt('project_network_cidr',
+ deprecated_name='tenant_network_cidr',
default="10.100.0.0/16",
- help="The cidr block to allocate tenant ipv4 subnets from"),
- cfg.IntOpt('tenant_network_mask_bits',
+ help="The cidr block to allocate project ipv4 subnets from"),
+ cfg.IntOpt('project_network_mask_bits',
+ deprecated_name='tenant_network_mask_bits',
default=28,
- help="The mask bits for tenant ipv4 subnets"),
- cfg.StrOpt('tenant_network_v6_cidr',
+ help="The mask bits for project ipv4 subnets"),
+ cfg.StrOpt('project_network_v6_cidr',
+ deprecated_name='tenant_network_v6_cidr',
default="2003::/48",
- help="The cidr block to allocate tenant ipv6 subnets from"),
- cfg.IntOpt('tenant_network_v6_mask_bits',
+ help="The cidr block to allocate project ipv6 subnets from"),
+ cfg.IntOpt('project_network_v6_mask_bits',
+ deprecated_name='tenant_network_v6_mask_bits',
default=64,
- help="The mask bits for tenant ipv6 subnets"),
- cfg.BoolOpt('tenant_networks_reachable',
+ help="The mask bits for project ipv6 subnets"),
+ cfg.BoolOpt('project_networks_reachable',
+ deprecated_name='tenant_networks_reachable',
default=False,
- help="Whether tenant networks can be reached directly from "
+ help="Whether project networks can be reached directly from "
"the test client. This must be set to True when the "
"'fixed' ssh_connect_method is selected."),
cfg.StrOpt('public_network_id',
@@ -574,41 +544,6 @@
" port admin state"),
]
-messaging_group = cfg.OptGroup(name='messaging',
- title='Messaging Service')
-
-MessagingGroup = [
- cfg.StrOpt('catalog_type',
- default='messaging',
- help='Catalog type of the Messaging service.'),
- cfg.IntOpt('max_queues_per_page',
- default=20,
- help='The maximum number of queue records per page when '
- 'listing queues'),
- cfg.IntOpt('max_queue_metadata',
- default=65536,
- help='The maximum metadata size for a queue'),
- cfg.IntOpt('max_messages_per_page',
- default=20,
- help='The maximum number of queue message per page when '
- 'listing (or) posting messages'),
- cfg.IntOpt('max_message_size',
- default=262144,
- help='The maximum size of a message body'),
- cfg.IntOpt('max_messages_per_claim',
- default=20,
- help='The maximum number of messages per claim'),
- cfg.IntOpt('max_message_ttl',
- default=1209600,
- help='The maximum ttl for a message'),
- cfg.IntOpt('max_claim_ttl',
- default=43200,
- help='The maximum ttl for a claim'),
- cfg.IntOpt('max_claim_grace',
- default=43200,
- help='The maximum grace period for a claim'),
-]
-
validation_group = cfg.OptGroup(name='validation',
title='SSH Validation options')
@@ -697,7 +632,7 @@
cfg.StrOpt('network_for_ssh',
default='public',
help="Network used for SSH connections. Ignored if "
- "use_floatingip_for_ssh=true or run_validation=false.",
+ "connect_method=floating or run_validation=false.",
deprecated_opts=[cfg.DeprecatedOpt('network_for_ssh',
group='compute')]),
]
@@ -727,22 +662,10 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the volume service."),
- cfg.StrOpt('backend1_name',
- default='',
- help='Name of the backend1 (must be declared in cinder.conf)',
- deprecated_for_removal=True),
- cfg.StrOpt('backend2_name',
- default='',
- help='Name of the backend2 (must be declared in cinder.conf)',
- deprecated_for_removal=True),
cfg.ListOpt('backend_names',
default=['BACKEND_1', 'BACKEND_2'],
help='A list of backend names separated by comma. '
- 'The backend name must be declared in cinder.conf',
- deprecated_opts=[cfg.DeprecatedOpt('BACKEND_1',
- group='volume'),
- cfg.DeprecatedOpt('BACKEND_2',
- group='volume')]),
+ 'The backend name must be declared in cinder.conf'),
cfg.StrOpt('storage_protocol',
default='iSCSI',
help='Backend protocol to target when creating volume types'),
@@ -788,7 +711,11 @@
default=True,
help='Update bootable status of a volume '
'Not implemented on icehouse ',
- deprecated_for_removal=True)
+ deprecated_for_removal=True),
+ # TODO(ynesenenko): Remove volume_services once liberty-eol happens.
+ cfg.BoolOpt('volume_services',
+ default=False,
+ help='Extract correct host info from host@backend')
]
@@ -915,50 +842,6 @@
]
-telemetry_group = cfg.OptGroup(name='telemetry',
- title='Telemetry Service Options')
-
-TelemetryGroup = [
- cfg.StrOpt('catalog_type',
- default='metering',
- help="Catalog type of the Telemetry service."),
- cfg.StrOpt('endpoint_type',
- default='publicURL',
- choices=['public', 'admin', 'internal',
- 'publicURL', 'adminURL', 'internalURL'],
- help="The endpoint type to use for the telemetry service."),
- cfg.BoolOpt('too_slow_to_test',
- default=True,
- deprecated_for_removal=True,
- help="This variable is used as flag to enable "
- "notification tests")
-]
-
-alarming_group = cfg.OptGroup(name='alarming',
- title='Alarming Service Options')
-
-AlarmingGroup = [
- cfg.StrOpt('catalog_type',
- default='alarming',
- help="Catalog type of the Alarming service."),
- cfg.StrOpt('endpoint_type',
- default='publicURL',
- choices=['public', 'admin', 'internal',
- 'publicURL', 'adminURL', 'internalURL'],
- help="The endpoint type to use for the alarming service."),
-]
-
-
-telemetry_feature_group = cfg.OptGroup(name='telemetry-feature-enabled',
- title='Enabled Ceilometer Features')
-
-TelemetryFeaturesGroup = [
- cfg.BoolOpt('events',
- default=False,
- help="Runs Ceilometer event-related tests"),
-]
-
-
dashboard_group = cfg.OptGroup(name="dashboard",
title="Dashboard options")
@@ -997,7 +880,7 @@
DataProcessingFeaturesGroup = [
cfg.ListOpt('plugins',
- default=["vanilla", "hdp"],
+ default=["vanilla", "cdh"],
deprecated_group="data_processing-feature-enabled",
help="List of enabled data processing plugins")
]
@@ -1036,7 +919,7 @@
default=False,
help='Allows a full cleaning process after a stress test.'
' Caution : this cleanup will remove every objects of'
- ' every tenant.')
+ ' every project.')
]
@@ -1071,11 +954,6 @@
default='cirros-0.3.1-x86_64-vmlinuz',
help='AKI image file name',
deprecated_for_removal=True),
- cfg.IntOpt(
- 'large_ops_number',
- default=0,
- help="specifies how many resources to request at once. Used "
- "for large operations testing."),
# TODO(yfried): add support for dhcpcd
cfg.StrOpt('dhcp_client',
default='udhcpc',
@@ -1108,12 +986,6 @@
cfg.BoolOpt('heat',
default=False,
help="Whether or not Heat is expected to be available"),
- cfg.BoolOpt('ceilometer',
- default=True,
- help="Whether or not Ceilometer is expected to be available"),
- cfg.BoolOpt('aodh',
- default=False,
- help="Whether or not Aodh is expected to be available"),
cfg.BoolOpt('horizon',
default=True,
help="Whether or not Horizon is expected to be available"),
@@ -1126,9 +998,6 @@
cfg.BoolOpt('trove',
default=False,
help="Whether or not Trove is expected to be available"),
- cfg.BoolOpt('zaqar',
- default=False,
- help="Whether or not Zaqar is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1189,6 +1058,11 @@
'live_migration, pause, rescue, resize '
'shelve, snapshot, and suspend')
+
+# NOTE(deva): Ironic tests have been ported to tempest.lib. New config options
+# should be added to ironic/ironic_tempest_plugin/config.py.
+# However, these options need to remain here for testing stable
+# branches until Liberty release reaches EOL.
BaremetalGroup = [
cfg.StrOpt('catalog_type',
default='baremetal',
@@ -1250,7 +1124,6 @@
(image_feature_group, ImageFeaturesGroup),
(network_group, NetworkGroup),
(network_feature_group, NetworkFeaturesGroup),
- (messaging_group, MessagingGroup),
(validation_group, ValidationGroup),
(volume_group, VolumeGroup),
(volume_feature_group, VolumeFeaturesGroup),
@@ -1258,9 +1131,6 @@
(object_storage_feature_group, ObjectStoreFeaturesGroup),
(database_group, DatabaseGroup),
(orchestration_group, OrchestrationGroup),
- (telemetry_group, TelemetryGroup),
- (telemetry_feature_group, TelemetryFeaturesGroup),
- (alarming_group, AlarmingGroup),
(dashboard_group, DashboardGroup),
(data_processing_group, DataProcessingGroup),
(data_processing_feature_group, DataProcessingFeaturesGroup),
@@ -1291,7 +1161,10 @@
generator to discover the options exposed to users.
"""
ext_plugins = plugins.TempestTestPluginManager()
- opt_list = [(getattr(g, 'name', None), o) for g, o in _opts]
+ # Make a shallow copy of the options list that can be
+ # extended by plugins. Send back the group object
+ # to allow group help text to be generated.
+ opt_list = [(g, o) for g, o in _opts]
opt_list.extend(ext_plugins.get_plugin_options_list())
return opt_list
@@ -1326,9 +1199,6 @@
'object-storage-feature-enabled']
self.database = _CONF.database
self.orchestration = _CONF.orchestration
- self.messaging = _CONF.messaging
- self.telemetry = _CONF.telemetry
- self.telemetry_feature_enabled = _CONF['telemetry-feature-enabled']
self.dashboard = _CONF.dashboard
self.data_processing = _CONF['data-processing']
self.data_processing_feature_enabled = _CONF[
@@ -1340,12 +1210,6 @@
self.baremetal = _CONF.baremetal
self.input_scenario = _CONF['input-scenario']
self.negative = _CONF.negative
- _CONF.set_default('domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
- _CONF.set_default('alt_domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
logging.tempest_set_log_file('tempest.log')
def __init__(self, parse_conf=True, config_path=None):
@@ -1379,7 +1243,8 @@
_CONF([], project='tempest')
logging_cfg_path = "%s/logging.conf" % os.path.dirname(path)
- if (not hasattr(_CONF, 'log_config_append') and
+ if ((not hasattr(_CONF, 'log_config_append') or
+ _CONF.log_config_append is None) and
os.path.isfile(logging_cfg_path)):
# if logging conf is in place we need to set log_config_append
_CONF.log_config_append = logging_cfg_path
@@ -1421,3 +1286,72 @@
CONF = TempestConfigProxy()
+
+
+def skip_unless_config(*args):
+ """Decorator to raise a skip if a config opt doesn't exist or is False
+
+ :param str group: The first arg, the option group to check
+ :param str name: The second arg, the option name to check
+ :param str msg: Optional third arg, the skip msg to use if a skip is raised
+ :raises testtools.TestCaseskipException: If the specified config option
+ doesn't exist or it exists and evaluates to False
+ """
+ def decorator(f):
+ group = args[0]
+ name = args[1]
+
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if not hasattr(CONF, group):
+ msg = "Config group %s doesn't exist" % group
+ raise testtools.TestCase.skipException(msg)
+
+ conf_group = getattr(CONF, group)
+ if not hasattr(conf_group, name):
+ msg = "Config option %s.%s doesn't exist" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+
+ value = getattr(conf_group, name)
+ if not value:
+ if len(args) == 3:
+ msg = args[2]
+ else:
+ msg = "Config option %s.%s is false" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def skip_if_config(*args):
+ """Raise a skipException if a config exists and is True
+
+ :param str group: The first arg, the option group to check
+ :param str name: The second arg, the option name to check
+ :param str msg: Optional third arg, the skip msg to use if a skip is raised
+ :raises testtools.TestCase.skipException: If the specified config option
+ exists and evaluates to True
+ """
+ def decorator(f):
+ group = args[0]
+ name = args[1]
+
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if hasattr(CONF, group):
+ conf_group = getattr(CONF, group)
+ if hasattr(conf_group, name):
+ value = getattr(conf_group, name)
+ if value:
+ if len(args) == 3:
+ msg = args[2]
+ else:
+ msg = "Config option %s.%s is false" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 86e8460..92f335f 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -82,11 +82,6 @@
message = "Image %(image_id)s failed to become ACTIVE in the allotted time"
-class EC2RegisterImageException(TempestException):
- message = ("Image %(image_id)s failed to become 'available' "
- "in the allotted time")
-
-
class VolumeBuildErrorException(TempestException):
message = "Volume %(volume_id)s failed to build and is in ERROR status"
@@ -108,25 +103,10 @@
"due to '%(stack_status_reason)s'")
-class StackResourceBuildErrorException(TempestException):
- message = ("Resource %(resource_name)s in stack %(stack_identifier)s is "
- "in %(resource_status)s status due to "
- "'%(resource_status_reason)s'")
-
-
-class AuthenticationFailure(TempestException):
- message = ("Authentication with user %(user)s and password "
- "%(password)s failed auth using tenant %(tenant)s.")
-
-
class EndpointNotFound(TempestException):
message = "Endpoint not found"
-class ImageFault(TempestException):
- message = "Got image fault"
-
-
class IdentityError(TempestException):
message = "Got identity error"
@@ -135,12 +115,8 @@
message = "The server is not reachable via the configured network"
-class TearDownException(TempestException):
- message = "%(num)d cleanUp operation failed"
-
-
# 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
+# of get_network_from_name and preprov_creds to tempest.lib, and it should
# be migrated along with them
class InvalidTestResource(TempestException):
message = "%(name) is not a valid %(type), or the name is ambiguous"
@@ -176,20 +152,6 @@
message = "Invalid structure of table with details"
-class InvalidAPIVersionString(TempestException):
- message = ("API Version String %(version)s is of invalid format. Must "
- "be of format MajorNum.MinorNum or string 'latest'.")
-
-
-class JSONSchemaNotFound(TempestException):
- message = ("JSON Schema for %(version)s is not found in \n"
- " %(schema_versions_info)s")
-
-
-class InvalidAPIVersionRange(TempestException):
- message = ("API Min Version is greater than Max version")
-
-
class CommandFailed(Exception):
def __init__(self, returncode, cmd, output, stderr):
super(CommandFailed, self).__init__()
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 88598de..aff9dee 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -19,8 +19,7 @@
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
- 'trove', 'ironic', 'savanna', 'heat', 'ceilometer',
- 'zaqar', 'sahara']
+ 'trove', 'ironic', 'savanna', 'heat', 'sahara']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
@@ -69,10 +68,12 @@
if pep8.noqa(physical_line):
return
- if 'tempest/test.py' not in filename:
- if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
- return (physical_line.find('def'),
- "T105: (setUp|tearDown)Class can not be used in tests")
+ if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
+ return
+
+ if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
+ return (physical_line.find('def'),
+ "T105: (setUp|tearDown)Class can not be used in tests")
def no_vi_headers(physical_line, line_number, lines):
@@ -117,11 +118,6 @@
T108
"""
- if './tempest/api/network/' in filename:
- # Network API tests are migrating from Tempest to Neutron repo now.
- # So here should avoid network API tests checks.
- return
-
msg = "T108: hyphen should not be specified at the end of rand_name()"
if RAND_NAME_HYPHEN_RE.match(logical_line):
return 0, msg
@@ -144,12 +140,12 @@
"""
if TESTTOOLS_SKIP_DECORATOR.match(logical_line):
yield (0, "T109: Cannot use testtools.skip decorator; instead use "
- "decorators.skip_because from tempest-lib")
+ "decorators.skip_because from tempest.lib")
def _common_service_clients_check(logical_line, physical_line, filename,
ignored_list_file=None):
- if 'tempest/services/' not in filename:
+ if not re.match('tempest/(lib/)?services/.*', filename):
return False
if ignored_list_file is not None:
@@ -223,6 +219,43 @@
yield (0, msg)
+def dont_import_local_tempest_into_lib(logical_line, filename):
+ """Check that tempest.lib should not import local tempest code
+
+ T112
+ """
+ if 'tempest/lib/' not in filename:
+ return
+
+ if not ('from tempest' in logical_line
+ or 'import tempest' in logical_line):
+ return
+
+ if ('from tempest.lib' in logical_line
+ or 'import tempest.lib' in logical_line):
+ return
+
+ msg = ("T112: tempest.lib should not import local tempest code to avoid "
+ "circular dependency")
+ yield (0, msg)
+
+
+def use_rand_uuid_instead_of_uuid4(logical_line, filename):
+ """Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
+
+ T113
+ """
+ if 'tempest/lib/' in filename:
+ return
+
+ if 'uuid.uuid4()' not in logical_line:
+ return
+
+ msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() "
+ "instead of uuid.uuid4()/uuid.uuid4().hex")
+ yield (0, msg)
+
+
def factory(register):
register(import_no_clients_in_api_and_scenario_tests)
register(scenario_tests_need_service_tags)
@@ -234,3 +267,5 @@
register(no_testtools_skip_decorator)
register(get_resources_on_service_clients)
register(delete_resources_on_service_clients)
+ register(dont_import_local_tempest_into_lib)
+ register(use_rand_uuid_instead_of_uuid4)
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
index ce69931..4ef9012 100644
--- a/tempest/hacking/ignored_list_T110.txt
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -1,9 +1,5 @@
-./tempest/services/identity/v3/json/identity_client.py
-./tempest/services/messaging/json/messaging_client.py
./tempest/services/object_storage/object_client.py
-./tempest/services/telemetry/json/alarming_client.py
-./tempest/services/telemetry/json/telemetry_client.py
./tempest/services/volume/base/base_qos_client.py
./tempest/services/volume/base/base_backups_client.py
./tempest/services/baremetal/base.py
-./tempest/services/network/json/network_client.py
+./tempest/services/network/json/routers_client.py
diff --git a/tempest/hacking/ignored_list_T111.txt b/tempest/hacking/ignored_list_T111.txt
index 20d58d2..8017e76 100644
--- a/tempest/hacking/ignored_list_T111.txt
+++ b/tempest/hacking/ignored_list_T111.txt
@@ -1,2 +1 @@
./tempest/services/baremetal/base.py
-./tempest/services/network/json/quotas_client.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/__init__.py
similarity index 100%
rename from tempest/api/messaging/__init__.py
rename to tempest/lib/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/__init__.py
diff --git a/tempest/api_schema/response/__init__.py b/tempest/lib/api_schema/response/__init__.py
similarity index 100%
rename from tempest/api_schema/response/__init__.py
rename to tempest/lib/api_schema/response/__init__.py
diff --git a/tempest/api_schema/response/compute/__init__.py b/tempest/lib/api_schema/response/compute/__init__.py
similarity index 100%
rename from tempest/api_schema/response/compute/__init__.py
rename to tempest/lib/api_schema/response/compute/__init__.py
diff --git a/tempest/api_schema/response/compute/v2_1/__init__.py b/tempest/lib/api_schema/response/compute/v2_1/__init__.py
similarity index 100%
rename from tempest/api_schema/response/compute/v2_1/__init__.py
rename to tempest/lib/api_schema/response/compute/v2_1/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/agents.py b/tempest/lib/api_schema/response/compute/v2_1/agents.py
new file mode 100644
index 0000000..6f712b4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/agents.py
@@ -0,0 +1,82 @@
+# 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.
+
+common_agent_info = {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'hypervisor': {'type': 'string'},
+ 'os': {'type': 'string'},
+ 'architecture': {'type': 'string'},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['agent_id', 'hypervisor', 'os', 'architecture',
+ 'version', 'url', 'md5hash']
+}
+
+list_agents = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agents': {
+ 'type': 'array',
+ 'items': common_agent_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['agents']
+ }
+}
+
+create_agent = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agent': common_agent_info
+ },
+ 'additionalProperties': False,
+ 'required': ['agent']
+ }
+}
+
+update_agent = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agent': {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['agent_id', 'version', 'url', 'md5hash']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['agent']
+ }
+}
+
+delete_agent = {
+ 'status_code': [200]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/aggregates.py b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
new file mode 100644
index 0000000..1a9fe41
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
@@ -0,0 +1,92 @@
+# 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.
+
+import copy
+
+# create-aggregate api doesn't have 'hosts' and 'metadata' attributes.
+aggregate_for_create = {
+ 'type': 'object',
+ 'properties': {
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': {'type': 'string'},
+ 'deleted': {'type': 'boolean'},
+ 'deleted_at': {'type': ['string', 'null']},
+ 'id': {'type': 'integer'},
+ 'name': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['availability_zone', 'created_at', 'deleted',
+ 'deleted_at', 'id', 'name', 'updated_at'],
+}
+
+common_aggregate_info = copy.deepcopy(aggregate_for_create)
+common_aggregate_info['properties'].update({
+ 'hosts': {'type': 'array'},
+ 'metadata': {'type': 'object'}
+})
+common_aggregate_info['required'].extend(['hosts', 'metadata'])
+
+list_aggregates = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregates': {
+ 'type': 'array',
+ 'items': common_aggregate_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregates'],
+ }
+}
+
+get_aggregate = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregate': common_aggregate_info
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregate'],
+ }
+}
+
+aggregate_set_metadata = get_aggregate
+# The 'updated_at' attribute of 'update_aggregate' can't be null.
+update_aggregate = copy.deepcopy(get_aggregate)
+update_aggregate['response_body']['properties']['aggregate']['properties'][
+ 'updated_at'] = {
+ 'type': 'string'
+ }
+
+delete_aggregate = {
+ 'status_code': [200]
+}
+
+create_aggregate = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregate': aggregate_for_create
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregate'],
+ }
+}
+
+aggregate_add_remove_host = get_aggregate
diff --git a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
new file mode 100644
index 0000000..d9aebce
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
@@ -0,0 +1,78 @@
+# 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.
+
+import copy
+
+
+base = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'availabilityZoneInfo': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'zoneName': {'type': 'string'},
+ 'zoneState': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['available']
+ },
+ # NOTE: Here is the difference between detail and
+ # non-detail.
+ 'hosts': {'type': 'null'}
+ },
+ 'additionalProperties': False,
+ 'required': ['zoneName', 'zoneState', 'hosts']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['availabilityZoneInfo']
+ }
+}
+
+detail = {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a hostname
+ '^[a-zA-Z0-9-_.]+$': {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a service name
+ '^.*$': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'},
+ 'active': {'type': 'boolean'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['available', 'active', 'updated_at']
+ }
+ }
+ }
+ }
+}
+
+list_availability_zone_list = copy.deepcopy(base)
+
+list_availability_zone_list_detail = copy.deepcopy(base)
+list_availability_zone_list_detail['response_body']['properties'][
+ 'availabilityZoneInfo']['items']['properties']['hosts'] = detail
diff --git a/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py b/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py
new file mode 100644
index 0000000..d1ee877
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py
@@ -0,0 +1,63 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+node = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'interfaces': {'type': 'array'},
+ 'host': {'type': 'string'},
+ 'task_state': {'type': ['string', 'null']},
+ 'cpus': {'type': ['integer', 'string']},
+ 'memory_mb': {'type': ['integer', 'string']},
+ 'disk_gb': {'type': ['integer', 'string']},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'interfaces', 'host', 'task_state', 'cpus', 'memory_mb',
+ 'disk_gb']
+}
+
+list_baremetal_nodes = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'nodes': {
+ 'type': 'array',
+ 'items': node
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['nodes']
+ }
+}
+
+baremetal_node = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'node': node
+ },
+ 'additionalProperties': False,
+ 'required': ['node']
+ }
+}
+get_baremetal_node = copy.deepcopy(baremetal_node)
+get_baremetal_node['response_body']['properties']['node'][
+ 'properties'].update({'instance_uuid': {'type': ['string', 'null']}})
+get_baremetal_node['response_body']['properties']['node'][
+ 'required'].append('instance_uuid')
diff --git a/tempest/lib/api_schema/response/compute/v2_1/certificates.py b/tempest/lib/api_schema/response/compute/v2_1/certificates.py
new file mode 100644
index 0000000..4e7cbe4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/certificates.py
@@ -0,0 +1,41 @@
+# 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.
+
+import copy
+
+_common_schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'certificate': {
+ 'type': 'object',
+ 'properties': {
+ 'data': {'type': 'string'},
+ 'private_key': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['data', 'private_key']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['certificate']
+ }
+}
+
+get_certificate = copy.deepcopy(_common_schema)
+get_certificate['response_body']['properties']['certificate'][
+ 'properties']['private_key'].update({'type': 'null'})
+
+create_certificate = copy.deepcopy(_common_schema)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/extensions.py b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
new file mode 100644
index 0000000..a6a455c
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
@@ -0,0 +1,47 @@
+# 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.
+
+list_extensions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'extensions': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'updated': {
+ 'type': 'string',
+ 'format': 'data-time'
+ },
+ 'name': {'type': 'string'},
+ 'links': {'type': 'array'},
+ 'namespace': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'alias': {'type': 'string'},
+ 'description': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['updated', 'name', 'links', 'namespace',
+ 'alias', 'description']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['extensions']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py b/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
new file mode 100644
index 0000000..a653213
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
@@ -0,0 +1,41 @@
+# 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.lib.api_schema.response.compute.v2_1 import parameter_types
+
+get_fixed_ip = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'fixed_ip': {
+ 'type': 'object',
+ 'properties': {
+ 'address': parameter_types.ip_address,
+ 'cidr': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'hostname': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['address', 'cidr', 'host', 'hostname']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['fixed_ip']
+ }
+}
+
+reserve_unreserve_fixed_ip = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
new file mode 100644
index 0000000..547d94d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -0,0 +1,103 @@
+# 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.lib.api_schema.response.compute.v2_1 import parameter_types
+
+list_flavors = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['name', 'links', 'id']
+ }
+ },
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'required': ['flavors']
+ }
+}
+
+common_flavor_info = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'ram': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ # 'swap' attributes comes as integer value but if it is empty
+ # it comes as "". So defining type of as string and integer.
+ 'swap': {'type': ['integer', 'string']},
+ 'disk': {'type': 'integer'},
+ 'id': {'type': 'string'},
+ 'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+ 'os-flavor-access:is_public': {'type': 'boolean'},
+ 'rxtx_factor': {'type': 'number'},
+ 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+ # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+ 'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id']
+}
+
+list_flavors_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': common_flavor_info
+ },
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['flavors']
+ }
+}
+
+unset_flavor_extra_specs = {
+ 'status_code': [200]
+}
+
+create_get_flavor_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor': common_flavor_info
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor']
+ }
+}
+
+delete_flavor = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py
new file mode 100644
index 0000000..a4d6af0
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py
@@ -0,0 +1,36 @@
+# 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.
+
+add_remove_list_flavor_access = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor_access': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor_id', 'tenant_id'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor_access']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
new file mode 100644
index 0000000..a438d48
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -0,0 +1,40 @@
+# 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.
+
+set_get_flavor_extra_specs = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'extra_specs': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['extra_specs']
+ }
+}
+
+set_get_flavor_extra_specs_key = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
new file mode 100644
index 0000000..0c66590
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
@@ -0,0 +1,148 @@
+# 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.lib.api_schema.response.compute.v2_1 import parameter_types
+
+common_floating_ip_info = {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but
+ # here allows 'string' also because we will be
+ # able to change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'pool': {'type': ['string', 'null']},
+ 'instance_id': {'type': ['string', 'null']},
+ 'ip': parameter_types.ip_address,
+ 'fixed_ip': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'pool', 'instance_id',
+ 'ip', 'fixed_ip'],
+
+}
+list_floating_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips': {
+ 'type': 'array',
+ 'items': common_floating_ip_info
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips'],
+ }
+}
+
+create_get_floating_ip = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip': common_floating_ip_info
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip'],
+ }
+}
+
+list_floating_ip_pools = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip_pools': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip_pools'],
+ }
+}
+
+add_remove_floating_ip = {
+ 'status_code': [202]
+}
+
+create_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_create': {
+ 'type': 'object',
+ 'properties': {
+ 'interface': {'type': ['string', 'null']},
+ 'ip_range': {'type': 'string'},
+ 'pool': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['interface', 'ip_range', 'pool'],
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips_bulk_create'],
+ }
+}
+
+delete_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_delete': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips_bulk_delete'],
+ }
+}
+
+list_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip_info': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'address': parameter_types.ip_address,
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'interface': {'type': ['string', 'null']},
+ 'pool': {'type': ['string', 'null']},
+ 'project_id': {'type': ['string', 'null']},
+ 'fixed_ip': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ # NOTE: fixed_ip is introduced after JUNO release,
+ # So it is not defined as 'required'.
+ 'required': ['address', 'instance_uuid', 'interface',
+ 'pool', 'project_id'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip_info'],
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/hosts.py b/tempest/lib/api_schema/response/compute/v2_1/hosts.py
new file mode 100644
index 0000000..ae70ff1
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/hosts.py
@@ -0,0 +1,116 @@
+# 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.
+
+import copy
+
+
+list_hosts = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hosts': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'host_name': {'type': 'string'},
+ 'service': {'type': 'string'},
+ 'zone': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['host_name', 'service', 'zone']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hosts']
+ }
+}
+
+get_host_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {
+ 'type': 'array',
+ 'item': {
+ 'type': 'object',
+ 'properties': {
+ 'resource': {
+ 'type': 'object',
+ 'properties': {
+ 'cpu': {'type': 'integer'},
+ 'disk_gb': {'type': 'integer'},
+ 'host': {'type': 'string'},
+ 'memory_mb': {'type': 'integer'},
+ 'project': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['cpu', 'disk_gb', 'host',
+ 'memory_mb', 'project']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['resource']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['host']
+ }
+}
+
+startup_host = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'power_action': {'enum': ['startup']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'power_action']
+ }
+}
+
+# The 'power_action' attribute of 'shutdown_host' API is 'shutdown'
+shutdown_host = copy.deepcopy(startup_host)
+
+shutdown_host['response_body']['properties']['power_action'] = {
+ 'enum': ['shutdown']
+}
+
+# The 'power_action' attribute of 'reboot_host' API is 'reboot'
+reboot_host = copy.deepcopy(startup_host)
+
+reboot_host['response_body']['properties']['power_action'] = {
+ 'enum': ['reboot']
+}
+
+update_host = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'maintenance_mode': {'enum': ['on_maintenance',
+ 'off_maintenance']},
+ 'status': {'enum': ['enabled', 'disabled']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'maintenance_mode', 'status']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py
new file mode 100644
index 0000000..d15b4f6
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py
@@ -0,0 +1,195 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+get_hypervisor_statistics = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor_statistics': {
+ 'type': 'object',
+ 'properties': {
+ 'count': {'type': 'integer'},
+ 'current_workload': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
+ 'free_disk_gb': {'type': 'integer'},
+ 'free_ram_mb': {'type': 'integer'},
+ 'local_gb': {'type': 'integer'},
+ 'local_gb_used': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'memory_mb_used': {'type': 'integer'},
+ 'running_vms': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ 'vcpus_used': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['count', 'current_workload',
+ 'disk_available_least', 'free_disk_gb',
+ 'free_ram_mb', 'local_gb', 'local_gb_used',
+ 'memory_mb', 'memory_mb_used', 'running_vms',
+ 'vcpus', 'vcpus_used']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor_statistics']
+ }
+}
+
+
+hypervisor_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'cpu_info': {'type': 'string'},
+ 'current_workload': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
+ 'host_ip': parameter_types.ip_address,
+ 'free_disk_gb': {'type': 'integer'},
+ 'free_ram_mb': {'type': 'integer'},
+ 'hypervisor_hostname': {'type': 'string'},
+ 'hypervisor_type': {'type': 'string'},
+ 'hypervisor_version': {'type': 'integer'},
+ 'id': {'type': ['integer', 'string']},
+ 'local_gb': {'type': 'integer'},
+ 'local_gb_used': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'memory_mb_used': {'type': 'integer'},
+ 'running_vms': {'type': 'integer'},
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'disabled_reason': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'id']
+ },
+ 'vcpus': {'type': 'integer'},
+ 'vcpus_used': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['cpu_info', 'current_workload',
+ 'disk_available_least', 'host_ip',
+ 'free_disk_gb', 'free_ram_mb',
+ 'hypervisor_hostname', 'hypervisor_type',
+ 'hypervisor_version', 'id', 'local_gb',
+ 'local_gb_used', 'memory_mb', 'memory_mb_used',
+ 'running_vms', 'service', 'vcpus', 'vcpus_used']
+}
+
+list_hypervisors_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisors': {
+ 'type': 'array',
+ 'items': hypervisor_detail
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisors']
+ }
+}
+
+get_hypervisor = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor': hypervisor_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor']
+ }
+}
+
+list_search_hypervisors = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisors': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'hypervisor_hostname': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['id', 'hypervisor_hostname']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisors']
+ }
+}
+
+get_hypervisor_uptime = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'hypervisor_hostname': {'type': 'string'},
+ 'uptime': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['id', 'hypervisor_hostname', 'uptime']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor']
+ }
+}
+
+get_hypervisors_servers = copy.deepcopy(list_search_hypervisors)
+get_hypervisors_servers['response_body']['properties']['hypervisors']['items'][
+ 'properties']['servers'] = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'uuid': {'type': 'string'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ }
+# In V2 API, if there is no servers (VM) on the Hypervisor host then 'servers'
+# attribute will not be present in response body So it is not 'required'.
diff --git a/tempest/lib/api_schema/response/compute/v2_1/images.py b/tempest/lib/api_schema/response/compute/v2_1/images.py
new file mode 100644
index 0000000..b0f1934
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/images.py
@@ -0,0 +1,154 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+image_links = copy.deepcopy(parameter_types.links)
+image_links['items']['properties'].update({'type': {'type': 'string'}})
+
+common_image_schema = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'links': image_links,
+ 'name': {'type': ['string', 'null']},
+ 'created': {'type': 'string'},
+ 'minDisk': {'type': 'integer'},
+ 'minRam': {'type': 'integer'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'OS-EXT-IMG-SIZE:size': {'type': ['integer', 'null']},
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # 'server' attributes only comes in response body if image is
+ # associated with any server. 'OS-EXT-IMG-SIZE:size' & 'OS-DCF:diskConfig'
+ # are API extension, So those are not defined as 'required'.
+ 'required': ['id', 'status', 'updated', 'links', 'name',
+ 'created', 'minDisk', 'minRam', 'progress',
+ 'metadata']
+}
+
+get_image = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'image': common_image_schema
+ },
+ 'additionalProperties': False,
+ 'required': ['image']
+ }
+}
+
+list_images = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'images': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': image_links,
+ 'name': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'images_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): images_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['images']
+ }
+}
+
+create_image = {
+ 'status_code': [202],
+ 'response_header': {
+ 'type': 'object',
+ 'properties': parameter_types.response_header
+ }
+}
+create_image['response_header']['properties'].update(
+ {'location': {
+ 'type': 'string',
+ 'format': 'uri'}
+ }
+)
+create_image['response_header']['required'] = ['location']
+
+delete = {
+ 'status_code': [204]
+}
+
+image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+image_meta_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+
+list_images_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'images': {
+ 'type': 'array',
+ 'items': common_image_schema
+ },
+ 'images_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): images_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['images']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py b/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py
new file mode 100644
index 0000000..c6c4deb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py
@@ -0,0 +1,62 @@
+# 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.
+
+common_instance_usage_audit_log = {
+ 'type': 'object',
+ 'properties': {
+ 'hosts_not_run': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'log': {'type': 'object'},
+ 'num_hosts': {'type': 'integer'},
+ 'num_hosts_done': {'type': 'integer'},
+ 'num_hosts_not_run': {'type': 'integer'},
+ 'num_hosts_running': {'type': 'integer'},
+ 'overall_status': {'type': 'string'},
+ 'period_beginning': {'type': 'string'},
+ 'period_ending': {'type': 'string'},
+ 'total_errors': {'type': 'integer'},
+ 'total_instances': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['hosts_not_run', 'log', 'num_hosts', 'num_hosts_done',
+ 'num_hosts_not_run', 'num_hosts_running', 'overall_status',
+ 'period_beginning', 'period_ending', 'total_errors',
+ 'total_instances']
+}
+
+get_instance_usage_audit_log = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instance_usage_audit_log': common_instance_usage_audit_log
+ },
+ 'additionalProperties': False,
+ 'required': ['instance_usage_audit_log']
+ }
+}
+
+list_instance_usage_audit_log = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instance_usage_audit_logs': common_instance_usage_audit_log
+ },
+ 'additionalProperties': False,
+ 'required': ['instance_usage_audit_logs']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/interfaces.py b/tempest/lib/api_schema/response/compute/v2_1/interfaces.py
new file mode 100644
index 0000000..9984750
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/interfaces.py
@@ -0,0 +1,73 @@
+# 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.lib.api_schema.response.compute.v2_1 import parameter_types
+
+interface_common_info = {
+ 'type': 'object',
+ 'properties': {
+ 'port_state': {'type': 'string'},
+ 'fixed_ips': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'subnet_id': {
+ 'type': 'string',
+ 'format': 'uuid'
+ },
+ 'ip_address': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ 'required': ['subnet_id', 'ip_address']
+ }
+ },
+ 'port_id': {'type': 'string', 'format': 'uuid'},
+ 'net_id': {'type': 'string', 'format': 'uuid'},
+ 'mac_addr': parameter_types.mac_address
+ },
+ 'additionalProperties': False,
+ 'required': ['port_state', 'fixed_ips', 'port_id', 'net_id', 'mac_addr']
+}
+
+get_create_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'interfaceAttachment': interface_common_info
+ },
+ 'additionalProperties': False,
+ 'required': ['interfaceAttachment']
+ }
+}
+
+list_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'interfaceAttachments': {
+ 'type': 'array',
+ 'items': interface_common_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['interfaceAttachments']
+ }
+}
+
+delete_interface = {
+ 'status_code': [202]
+}
diff --git a/tempest/api_schema/response/compute/v2_1/keypairs.py b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
similarity index 100%
rename from tempest/api_schema/response/compute/v2_1/keypairs.py
rename to tempest/lib/api_schema/response/compute/v2_1/keypairs.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/limits.py b/tempest/lib/api_schema/response/compute/v2_1/limits.py
new file mode 100644
index 0000000..81f175f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/limits.py
@@ -0,0 +1,106 @@
+# 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.
+
+get_limit = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'limits': {
+ 'type': 'object',
+ 'properties': {
+ 'absolute': {
+ 'type': 'object',
+ 'properties': {
+ 'maxTotalRAMSize': {'type': 'integer'},
+ 'totalCoresUsed': {'type': 'integer'},
+ 'maxTotalInstances': {'type': 'integer'},
+ 'maxTotalFloatingIps': {'type': 'integer'},
+ 'totalSecurityGroupsUsed': {'type': 'integer'},
+ 'maxTotalCores': {'type': 'integer'},
+ 'totalFloatingIpsUsed': {'type': 'integer'},
+ 'maxSecurityGroups': {'type': 'integer'},
+ 'maxServerMeta': {'type': 'integer'},
+ 'maxPersonality': {'type': 'integer'},
+ 'maxImageMeta': {'type': 'integer'},
+ 'maxPersonalitySize': {'type': 'integer'},
+ 'maxSecurityGroupRules': {'type': 'integer'},
+ 'maxTotalKeypairs': {'type': 'integer'},
+ 'totalRAMUsed': {'type': 'integer'},
+ 'totalInstancesUsed': {'type': 'integer'},
+ 'maxServerGroupMembers': {'type': 'integer'},
+ 'maxServerGroups': {'type': 'integer'},
+ 'totalServerGroupsUsed': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): maxServerGroupMembers, maxServerGroups
+ # and totalServerGroupsUsed are API extension,
+ # and some environments return a response without these
+ # attributes.So they are not 'required'.
+ 'required': ['maxImageMeta',
+ 'maxPersonality',
+ 'maxPersonalitySize',
+ 'maxSecurityGroupRules',
+ 'maxSecurityGroups',
+ 'maxServerMeta',
+ 'maxTotalCores',
+ 'maxTotalFloatingIps',
+ 'maxTotalInstances',
+ 'maxTotalKeypairs',
+ 'maxTotalRAMSize',
+ 'totalCoresUsed',
+ 'totalFloatingIpsUsed',
+ 'totalInstancesUsed',
+ 'totalRAMUsed',
+ 'totalSecurityGroupsUsed']
+ },
+ 'rate': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'limit': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'next-available':
+ {'type': 'string'},
+ 'remaining':
+ {'type': 'integer'},
+ 'unit':
+ {'type': 'string'},
+ 'value':
+ {'type': 'integer'},
+ 'verb':
+ {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ },
+ 'regex': {'type': 'string'},
+ 'uri': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['absolute', 'rate']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['limits']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/migrations.py b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
new file mode 100644
index 0000000..b7d66ea
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
@@ -0,0 +1,51 @@
+# 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.
+
+list_migrations = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'migrations': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'integer'},
+ 'status': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'source_node': {'type': ['string', 'null']},
+ 'source_compute': {'type': ['string', 'null']},
+ 'dest_node': {'type': ['string', 'null']},
+ 'dest_compute': {'type': ['string', 'null']},
+ 'dest_host': {'type': ['string', 'null']},
+ 'old_instance_type_id': {'type': ['integer', 'null']},
+ 'new_instance_type_id': {'type': ['integer', 'null']},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'id', 'status', 'instance_uuid', 'source_node',
+ 'source_compute', 'dest_node', 'dest_compute',
+ 'dest_host', 'old_instance_type_id',
+ 'new_instance_type_id', 'created_at', 'updated_at'
+ ]
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['migrations']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
new file mode 100644
index 0000000..07cc890
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -0,0 +1,96 @@
+# 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.
+
+links = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['href', 'rel']
+ }
+}
+
+mac_address = {
+ 'type': 'string',
+ 'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
+}
+
+ip_address = {
+ 'oneOf': [
+ {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ },
+ {'type': 'null'}
+ ]
+}
+
+access_ip_v4 = {
+ 'type': 'string',
+ 'oneOf': [{'format': 'ipv4'}, {'enum': ['']}]
+}
+
+access_ip_v6 = {
+ 'type': 'string',
+ 'oneOf': [{'format': 'ipv6'}, {'enum': ['']}]
+}
+
+addresses = {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for 'private' or something.
+ '^[a-zA-Z0-9-_.]+$': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'version': {'type': 'integer'},
+ 'addr': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['version', 'addr']
+ }
+ }
+ }
+}
+
+response_header = {
+ 'connection': {'type': 'string'},
+ 'content-length': {'type': 'string'},
+ 'content-type': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'x-compute-request-id': {'type': 'string'},
+ 'vary': {'type': 'string'},
+ 'x-openstack-nova-api-version': {'type': 'string'},
+ 'date': {
+ 'type': 'string',
+ 'format': 'data-time'
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py b/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py
new file mode 100644
index 0000000..03d7f12
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py
@@ -0,0 +1,31 @@
+# Copyright 2014 IBM 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 quotas
+
+# NOTE(mriedem): os-quota-class-sets responses are the same as os-quota-sets
+# except for the key in the response body is quota_class_set instead of
+# quota_set, so update this copy of the schema from os-quota-sets.
+get_quota_class_set = copy.deepcopy(quotas.get_quota_set)
+get_quota_class_set['response_body']['properties']['quota_class_set'] = (
+ get_quota_class_set['response_body']['properties'].pop('quota_set'))
+get_quota_class_set['response_body']['required'] = ['quota_class_set']
+
+update_quota_class_set = copy.deepcopy(quotas.update_quota_set)
+update_quota_class_set['response_body']['properties']['quota_class_set'] = (
+ update_quota_class_set['response_body']['properties'].pop('quota_set'))
+update_quota_class_set['response_body']['required'] = ['quota_class_set']
diff --git a/tempest/lib/api_schema/response/compute/v2_1/quotas.py b/tempest/lib/api_schema/response/compute/v2_1/quotas.py
new file mode 100644
index 0000000..7953983
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/quotas.py
@@ -0,0 +1,65 @@
+# 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.
+
+import copy
+
+update_quota_set = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'quota_set': {
+ 'type': 'object',
+ 'properties': {
+ 'instances': {'type': 'integer'},
+ 'cores': {'type': 'integer'},
+ 'ram': {'type': 'integer'},
+ 'floating_ips': {'type': 'integer'},
+ 'fixed_ips': {'type': 'integer'},
+ 'metadata_items': {'type': 'integer'},
+ 'key_pairs': {'type': 'integer'},
+ 'security_groups': {'type': 'integer'},
+ 'security_group_rules': {'type': 'integer'},
+ 'server_group_members': {'type': 'integer'},
+ 'server_groups': {'type': 'integer'},
+ 'injected_files': {'type': 'integer'},
+ 'injected_file_content_bytes': {'type': 'integer'},
+ 'injected_file_path_bytes': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE: server_group_members and server_groups are represented
+ # when enabling quota_server_group extension. So they should
+ # not be required.
+ 'required': ['instances', 'cores', 'ram',
+ 'floating_ips', 'fixed_ips',
+ 'metadata_items', 'key_pairs',
+ 'security_groups', 'security_group_rules',
+ 'injected_files', 'injected_file_content_bytes',
+ 'injected_file_path_bytes']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['quota_set']
+ }
+}
+
+get_quota_set = copy.deepcopy(update_quota_set)
+get_quota_set['response_body']['properties']['quota_set']['properties'][
+ 'id'] = {'type': 'string'}
+get_quota_set['response_body']['properties']['quota_set']['required'].extend([
+ 'id'])
+
+delete_quota = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py b/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py
new file mode 100644
index 0000000..2ec2826
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py
@@ -0,0 +1,65 @@
+# 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.
+
+common_security_group_default_rule_info = {
+ 'type': 'object',
+ 'properties': {
+ 'from_port': {'type': 'integer'},
+ 'id': {'type': 'integer'},
+ 'ip_protocol': {'type': 'string'},
+ 'ip_range': {
+ 'type': 'object',
+ 'properties': {
+ 'cidr': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['cidr'],
+ },
+ 'to_port': {'type': 'integer'},
+ },
+ 'additionalProperties': False,
+ 'required': ['from_port', 'id', 'ip_protocol', 'ip_range', 'to_port'],
+}
+
+create_get_security_group_default_rule = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_default_rule':
+ common_security_group_default_rule_info
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_default_rule']
+ }
+}
+
+delete_security_group_default_rule = {
+ 'status_code': [204]
+}
+
+list_security_group_default_rules = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_default_rules': {
+ 'type': 'array',
+ 'items': common_security_group_default_rule_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_default_rules']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/security_groups.py b/tempest/lib/api_schema/response/compute/v2_1/security_groups.py
new file mode 100644
index 0000000..5ed5a5c
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/security_groups.py
@@ -0,0 +1,113 @@
+# 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.
+
+common_security_group_rule = {
+ 'from_port': {'type': ['integer', 'null']},
+ 'to_port': {'type': ['integer', 'null']},
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_id': {'type': 'string'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ },
+ 'ip_protocol': {'type': ['string', 'null']},
+ # 'parent_group_id' can be UUID so defining it as 'string' also.
+ 'parent_group_id': {'type': ['string', 'integer', 'null']},
+ 'ip_range': {
+ 'type': 'object',
+ 'properties': {
+ 'cidr': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # When optional argument is provided in request body
+ # like 'group_id' then, attribute 'cidr' does not
+ # comes in response body. So it is not 'required'.
+ },
+ 'id': {'type': ['string', 'integer']}
+}
+
+common_security_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string']},
+ 'name': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'rules': {
+ 'type': 'array',
+ 'items': {
+ 'type': ['object', 'null'],
+ 'properties': common_security_group_rule,
+ 'additionalProperties': False,
+ }
+ },
+ 'description': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'tenant_id', 'rules', 'description'],
+}
+
+list_security_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_groups': {
+ 'type': 'array',
+ 'items': common_security_group
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_groups']
+ }
+}
+
+get_security_group = create_security_group = update_security_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group': common_security_group
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group']
+ }
+}
+
+delete_security_group = {
+ 'status_code': [202]
+}
+
+create_security_group_rule = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_rule': {
+ 'type': 'object',
+ 'properties': common_security_group_rule,
+ 'additionalProperties': False,
+ 'required': ['from_port', 'to_port', 'group', 'ip_protocol',
+ 'parent_group_id', 'id', 'ip_range']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_rule']
+ }
+}
+
+delete_security_group_rule = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
new file mode 100644
index 0000000..3289f04
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -0,0 +1,554 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'security_groups': {'type': 'array'},
+ 'links': parameter_types.links,
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: OS-DCF:diskConfig & security_groups are API extension,
+ # and some environments return a response without these
+ # attributes.So they are not 'required'.
+ 'required': ['id', 'links']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+list_servers = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+delete_server = {
+ 'status_code': [204],
+}
+
+common_show_server = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6
+ },
+ 'additionalProperties': False,
+ # NOTE(GMann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+update_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': common_show_server
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+server_detail = copy.deepcopy(common_show_server)
+server_detail['properties'].update({
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+
+ # NOTE: Non-admin users also can see "OS-SRV-USG" and "OS-EXT-AZ"
+ # attributes.
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+
+ # NOTE: Admin users only can see "OS-EXT-STS" and "OS-EXT-SRV-ATTR"
+ # attributes.
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'config_drive': {'type': 'string'}
+})
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann): Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+rescue_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'adminPass': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['adminPass']
+ }
+}
+
+list_virtual_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'virtual_interfaces': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'mac_address': parameter_types.mac_address,
+ 'OS-EXT-VIF-NET:net_id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # 'OS-EXT-VIF-NET:net_id' is API extension So it is
+ # not defined as 'required'
+ 'required': ['id', 'mac_address']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['virtual_interfaces']
+ }
+}
+
+common_attach_volume_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': ['string', 'null']},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': ['integer', 'string']}
+ },
+ 'additionalProperties': False,
+ # 'device' is optional in response.
+ 'required': ['id', 'volumeId', 'serverId']
+}
+
+attach_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachment': common_attach_volume_info
+ },
+ 'additionalProperties': False,
+ 'required': ['volumeAttachment']
+ }
+}
+
+detach_volume = {
+ 'status_code': [202]
+}
+
+show_volume_attachment = copy.deepcopy(attach_volume)
+show_volume_attachment['response_body']['properties'][
+ 'volumeAttachment']['properties'].update({'serverId': {'type': 'string'}})
+
+list_volume_attachments = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachments': {
+ 'type': 'array',
+ 'items': common_attach_volume_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volumeAttachments']
+ }
+}
+list_volume_attachments['response_body']['properties'][
+ 'volumeAttachments']['items']['properties'].update(
+ {'serverId': {'type': 'string'}})
+
+list_addresses_by_network = {
+ 'status_code': [200],
+ 'response_body': parameter_types.addresses
+}
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': parameter_types.addresses
+ },
+ 'additionalProperties': False,
+ 'required': ['addresses']
+ }
+}
+
+common_server_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'policies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ # 'members' attribute contains the array of instance's UUID of
+ # instances present in server group
+ 'members': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_show_server_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_group': common_server_group
+ },
+ 'additionalProperties': False,
+ 'required': ['server_group']
+ }
+}
+
+delete_server_group = {
+ 'status_code': [204]
+}
+
+list_server_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_groups': {
+ 'type': 'array',
+ 'items': common_server_group
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['server_groups']
+ }
+}
+
+instance_actions = {
+ 'type': 'object',
+ 'properties': {
+ 'action': {'type': 'string'},
+ 'request_id': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'project_id': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'message': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['action', 'request_id', 'user_id', 'project_id',
+ 'start_time', 'message', 'instance_uuid']
+}
+
+instance_action_events = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'event': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'finish_time': {'type': 'string'},
+ 'result': {'type': 'string'},
+ 'traceback': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['event', 'start_time', 'finish_time', 'result',
+ 'traceback']
+ }
+}
+
+list_instance_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceActions': {
+ 'type': 'array',
+ 'items': instance_actions
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['instanceActions']
+ }
+}
+
+instance_actions_with_events = copy.deepcopy(instance_actions)
+instance_actions_with_events['properties'].update({
+ 'events': instance_action_events})
+# 'events' does not come in response body always so it is not
+# defined as 'required'
+
+show_instance_action = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceAction': instance_actions_with_events
+ },
+ 'additionalProperties': False,
+ 'required': ['instanceAction']
+ }
+}
+
+show_password = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'password': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['password']
+ }
+}
+
+get_vnc_console = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'console': {
+ 'type': 'object',
+ 'properties': {
+ 'type': {'type': 'string'},
+ 'url': {
+ 'type': 'string',
+ 'format': 'uri'
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['type', 'url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['console']
+ }
+}
+
+get_console_output = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'output': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['output']
+ }
+}
+
+set_server_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+list_server_metadata = copy.deepcopy(set_server_metadata)
+
+update_server_metadata = copy.deepcopy(set_server_metadata)
+
+delete_server_metadata_item = {
+ 'status_code': [204]
+}
+
+set_show_server_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+
+server_actions_common_schema = {
+ 'status_code': [202]
+}
+
+server_actions_delete_password = {
+ 'status_code': [204]
+}
+
+server_actions_confirm_resize = copy.deepcopy(
+ server_actions_delete_password)
+
+update_attached_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/services.py b/tempest/lib/api_schema/response/compute/v2_1/services.py
new file mode 100644
index 0000000..ddef7b2
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/services.py
@@ -0,0 +1,65 @@
+# 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.
+
+list_services = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'services': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string'],
+ 'pattern': '^[a-zA-Z!]*@[0-9]+$'},
+ 'zone': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ 'disabled_reason': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'zone', 'host', 'state', 'binary',
+ 'status', 'updated_at', 'disabled_reason']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['services']
+ }
+}
+
+enable_disable_service = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'host': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'binary', 'host']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['service']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/snapshots.py b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
new file mode 100644
index 0000000..01a524b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
@@ -0,0 +1,61 @@
+# Copyright 2015 Fujitsu(fnst) 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.
+
+common_snapshot_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'size': {'type': 'integer'},
+ 'createdAt': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'displayDescription': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'volumeId', 'status', 'size',
+ 'createdAt', 'displayName', 'displayDescription']
+}
+
+create_get_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_info
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot']
+ }
+}
+
+list_snapshots = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshots': {
+ 'type': 'array',
+ 'items': common_snapshot_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshots']
+ }
+}
+
+delete_snapshot = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py b/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py
new file mode 100644
index 0000000..ddfab96
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py
@@ -0,0 +1,53 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+param_network = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'cidr': {'type': ['string', 'null']},
+ 'label': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'cidr', 'label']
+}
+
+
+list_tenant_networks = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'networks': {
+ 'type': 'array',
+ 'items': param_network
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['networks']
+ }
+}
+
+
+get_tenant_network = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'network': param_network
+ },
+ 'additionalProperties': False,
+ 'required': ['network']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
new file mode 100644
index 0000000..d51ef12
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
@@ -0,0 +1,92 @@
+# 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.
+
+import copy
+
+_server_usages = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'ended_at': {
+ 'oneOf': [
+ {'type': 'string'},
+ {'type': 'null'}
+ ]
+ },
+ 'flavor': {'type': 'string'},
+ 'hours': {'type': 'number'},
+ 'instance_id': {'type': 'string'},
+ 'local_gb': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'name': {'type': 'string'},
+ 'started_at': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'uptime': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ },
+ 'required': ['ended_at', 'flavor', 'hours', 'instance_id', 'local_gb',
+ 'memory_mb', 'name', 'started_at', 'state', 'tenant_id',
+ 'uptime', 'vcpus']
+ }
+}
+
+_tenant_usage_list = {
+ 'type': 'object',
+ 'properties': {
+ 'server_usages': _server_usages,
+ 'start': {'type': 'string'},
+ 'stop': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'total_hours': {'type': 'number'},
+ 'total_local_gb_usage': {'type': 'number'},
+ 'total_memory_mb_usage': {'type': 'number'},
+ 'total_vcpus_usage': {'type': 'number'},
+ },
+ 'required': ['start', 'stop', 'tenant_id',
+ 'total_hours', 'total_local_gb_usage',
+ 'total_memory_mb_usage', 'total_vcpus_usage']
+}
+
+# 'required' of get_tenant is different from list_tenant's.
+_tenant_usage_get = copy.deepcopy(_tenant_usage_list)
+_tenant_usage_get['required'] = ['server_usages', 'start', 'stop', 'tenant_id',
+ 'total_hours', 'total_local_gb_usage',
+ 'total_memory_mb_usage', 'total_vcpus_usage']
+
+list_tenant_usage = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_usages': {
+ 'type': 'array',
+ 'items': _tenant_usage_list
+ }
+ },
+ 'required': ['tenant_usages']
+ }
+}
+
+get_tenant_usage = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_usage': _tenant_usage_get
+ },
+ 'required': ['tenant_usage']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/versions.py b/tempest/lib/api_schema/response/compute/v2_1/versions.py
new file mode 100644
index 0000000..08a9fab
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/versions.py
@@ -0,0 +1,110 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+
+_version = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string', 'format': 'uri'},
+ 'rel': {'type': 'string'},
+ 'type': {'type': 'string'},
+ },
+ 'required': ['href', 'rel'],
+ 'additionalProperties': False
+ }
+ },
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string', 'format': 'date-time'},
+ 'version': {'type': 'string'},
+ 'min_version': {'type': 'string'},
+ 'media-types': {
+ 'type': 'array',
+ 'properties': {
+ 'base': {'type': 'string'},
+ 'type': {'type': 'string'},
+ }
+ },
+ },
+ # NOTE: version and min_version have been added since Kilo,
+ # so they should not be required.
+ # NOTE(sdague): media-types only shows up in single version requests.
+ 'required': ['id', 'links', 'status', 'updated'],
+ 'additionalProperties': False
+}
+
+list_versions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'versions': {
+ 'type': 'array',
+ 'items': _version
+ }
+ },
+ 'required': ['versions'],
+ 'additionalProperties': False
+ }
+}
+
+
+_detail_get_version = copy.deepcopy(_version)
+_detail_get_version['properties'].pop('min_version')
+_detail_get_version['properties'].pop('version')
+_detail_get_version['properties'].pop('updated')
+_detail_get_version['properties']['media-types'] = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'base': {'type': 'string'},
+ 'type': {'type': 'string'}
+ }
+ }
+}
+_detail_get_version['required'] = ['id', 'links', 'status', 'media-types']
+
+get_version = {
+ 'status_code': [300],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'choices': {
+ 'type': 'array',
+ 'items': _detail_get_version
+ }
+ },
+ 'required': ['choices'],
+ 'additionalProperties': False
+ }
+}
+
+get_one_version = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'version': _version
+ },
+ 'additionalProperties': False
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/volumes.py b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
new file mode 100644
index 0000000..bb34acb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
@@ -0,0 +1,120 @@
+# 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.
+
+create_get_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'availabilityZone': {'type': 'string'},
+ 'createdAt': {'type': 'string'},
+ 'displayDescription': {'type': ['string', 'null']},
+ 'volumeType': {'type': ['string', 'null']},
+ 'snapshotId': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'size': {'type': 'integer'},
+ 'attachments': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE- If volume is not attached to any server
+ # then, 'attachments' attributes comes as array
+ # with empty objects "[{}]" due to that elements
+ # of 'attachments' cannot defined as 'required'.
+ # If it would come as empty array "[]" then,
+ # those elements can be defined as 'required'.
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'status', 'displayName', 'availabilityZone',
+ 'createdAt', 'displayDescription', 'volumeType',
+ 'snapshotId', 'metadata', 'size', 'attachments']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+list_volumes = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'availabilityZone': {'type': 'string'},
+ 'createdAt': {'type': 'string'},
+ 'displayDescription': {'type': ['string', 'null']},
+ 'volumeType': {'type': ['string', 'null']},
+ 'snapshotId': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'size': {'type': 'integer'},
+ 'attachments': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE- If volume is not attached to any server
+ # then, 'attachments' attributes comes as array
+ # with empty object "[{}]" due to that elements
+ # of 'attachments' cannot defined as 'required'
+ # If it would come as empty array "[]" then,
+ # those elements can be defined as 'required'.
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'status', 'displayName',
+ 'availabilityZone', 'createdAt',
+ 'displayDescription', 'volumeType',
+ 'snapshotId', 'metadata', 'size',
+ 'attachments']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+delete_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/response/compute/v2_19/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_19/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
new file mode 100644
index 0000000..883839e
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -0,0 +1,49 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21
+from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
+
+get_server = copy.deepcopy(serversv29.get_server)
+get_server['response_body']['properties']['server'][
+ 'properties'].update({'description': {'type': ['string', 'null']}})
+get_server['response_body']['properties']['server'][
+ 'required'].append('description')
+
+list_servers_detail = copy.deepcopy(serversv29.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({'description': {'type': ['string', 'null']}})
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'required'].append('description')
+
+update_server = copy.deepcopy(serversv21.update_server)
+update_server['response_body']['properties']['server'][
+ 'properties'].update({'description': {'type': ['string', 'null']}})
+update_server['response_body']['properties']['server'][
+ 'required'].append('description')
+
+rebuild_server = copy.deepcopy(serversv21.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'description': {'type': ['string', 'null']}})
+rebuild_server['response_body']['properties']['server'][
+ 'required'].append('description')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+ serversv21.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'description': {'type': ['string', 'null']}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('description')
diff --git a/tempest/api_schema/response/compute/v2_2/__init__.py b/tempest/lib/api_schema/response/compute/v2_2/__init__.py
similarity index 100%
rename from tempest/api_schema/response/compute/v2_2/__init__.py
rename to tempest/lib/api_schema/response/compute/v2_2/__init__.py
diff --git a/tempest/api_schema/response/compute/v2_2/keypairs.py b/tempest/lib/api_schema/response/compute/v2_2/keypairs.py
similarity index 95%
rename from tempest/api_schema/response/compute/v2_2/keypairs.py
rename to tempest/lib/api_schema/response/compute/v2_2/keypairs.py
index 5d8d24d..0bb7771 100644
--- a/tempest/api_schema/response/compute/v2_2/keypairs.py
+++ b/tempest/lib/api_schema/response/compute/v2_2/keypairs.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.response.compute.v2_1 import keypairs
+from tempest.lib.api_schema.response.compute.v2_1 import keypairs
get_keypair = copy.deepcopy(keypairs.get_keypair)
get_keypair['response_body']['properties']['keypair'][
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/response/compute/v2_9/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_9/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
new file mode 100644
index 0000000..e9b7249
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -0,0 +1,29 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers
+
+get_server = copy.deepcopy(servers.get_server)
+get_server['response_body']['properties']['server'][
+ 'properties'].update({'locked': {'type': 'boolean'}})
+get_server['response_body']['properties']['server'][
+ 'required'].append('locked')
+
+list_servers_detail = copy.deepcopy(servers.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({'locked': {'type': 'boolean'}})
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'required'].append('locked')
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
new file mode 100644
index 0000000..974ba82
--- /dev/null
+++ b/tempest/lib/auth.py
@@ -0,0 +1,799 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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 abc
+import copy
+import datetime
+import re
+
+from oslo_log import log as logging
+import six
+from six.moves.urllib import parse as urlparse
+
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as json_v2id
+from tempest.lib.services.identity.v3 import token_client as json_v3id
+
+ISO8601_FLOAT_SECONDS = '%Y-%m-%dT%H:%M:%S.%fZ'
+ISO8601_INT_SECONDS = '%Y-%m-%dT%H:%M:%SZ'
+LOG = logging.getLogger(__name__)
+
+
+def replace_version(url, new_version):
+ parts = urlparse.urlparse(url)
+ version_path = '/%s' % new_version
+ path, subs = re.subn(r'(^|/)+v\d+(?:\.\d+)?',
+ version_path,
+ parts.path,
+ count=1)
+ if not subs:
+ path = '%s%s' % (parts.path.rstrip('/'), version_path)
+ url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ path,
+ parts.params,
+ parts.query,
+ parts.fragment))
+ return url
+
+
+def apply_url_filters(url, filters):
+ if filters.get('api_version', None) is not None:
+ url = replace_version(url, filters['api_version'])
+ parts = urlparse.urlparse(url)
+ if filters.get('skip_path', None) is not None and parts.path != '':
+ url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ '/',
+ parts.params,
+ parts.query,
+ parts.fragment))
+
+ return url
+
+
+@six.add_metaclass(abc.ABCMeta)
+class AuthProvider(object):
+ """Provide authentication"""
+
+ SCOPES = set(['project'])
+
+ def __init__(self, credentials, scope='project'):
+ """Auth provider __init__
+
+ :param credentials: credentials for authentication
+ :param scope: the default scope to be used by the credential providers
+ when requesting a token. Valid values depend on the
+ AuthProvider class implementation, and are defined in
+ the set SCOPES. Default value is 'project'.
+ """
+ if self.check_credentials(credentials):
+ self.credentials = credentials
+ else:
+ if isinstance(credentials, Credentials):
+ password = credentials.get('password')
+ message = "Credentials are: " + str(credentials)
+ if password is None:
+ message += " Password is not defined."
+ else:
+ message += " Password is defined."
+ raise exceptions.InvalidCredentials(message)
+ else:
+ raise TypeError("credentials object is of type %s, which is"
+ " not a valid Credentials object type." %
+ credentials.__class__.__name__)
+ self._scope = None
+ self.scope = scope
+ self.cache = None
+ self.alt_auth_data = None
+ self.alt_part = None
+
+ def __str__(self):
+ return "Creds :{creds}, cached auth data: {cache}".format(
+ creds=self.credentials, cache=self.cache)
+
+ @abc.abstractmethod
+ def _decorate_request(self, filters, method, url, headers=None, body=None,
+ auth_data=None):
+ """Decorate request with authentication data"""
+ return
+
+ @abc.abstractmethod
+ def _get_auth(self):
+ return
+
+ @abc.abstractmethod
+ def _fill_credentials(self, auth_data_body):
+ return
+
+ def fill_credentials(self):
+ """Fill credentials object with data from auth"""
+ auth_data = self.get_auth()
+ self._fill_credentials(auth_data[1])
+ return self.credentials
+
+ @classmethod
+ def check_credentials(cls, credentials):
+ """Verify credentials are valid."""
+ return isinstance(credentials, Credentials) and credentials.is_valid()
+
+ @property
+ def auth_data(self):
+ """Auth data for set scope"""
+ return self.get_auth()
+
+ @property
+ def scope(self):
+ """Scope used in auth requests"""
+ return self._scope
+
+ @auth_data.deleter
+ def auth_data(self):
+ self.clear_auth()
+
+ def get_auth(self):
+ """Returns auth from cache if available, else auth first"""
+ if self.cache is None or self.is_expired(self.cache):
+ self.set_auth()
+ return self.cache
+
+ def set_auth(self):
+ """Forces setting auth.
+
+ Forces setting auth, ignores cache if it exists.
+ Refills credentials.
+ """
+ self.cache = self._get_auth()
+ self._fill_credentials(self.cache[1])
+
+ def clear_auth(self):
+ """Clear access cache
+
+ Can be called to clear the access cache so that next request
+ will fetch a new token and base_url.
+ """
+ self.cache = None
+ self.credentials.reset()
+
+ @abc.abstractmethod
+ def is_expired(self, auth_data):
+ return
+
+ def auth_request(self, method, url, headers=None, body=None, filters=None):
+ """Obtains auth data and decorates a request with that.
+
+ :param method: HTTP method of the request
+ :param url: relative URL of the request (path)
+ :param headers: HTTP headers of the request
+ :param body: HTTP body in case of POST / PUT
+ :param filters: select a base URL out of the catalog
+ :returns a Tuple (url, headers, body)
+ """
+ orig_req = dict(url=url, headers=headers, body=body)
+
+ auth_url, auth_headers, auth_body = self._decorate_request(
+ filters, method, url, headers, body)
+ auth_req = dict(url=auth_url, headers=auth_headers, body=auth_body)
+
+ # Overwrite part if the request if it has been requested
+ if self.alt_part is not None:
+ if self.alt_auth_data is not None:
+ alt_url, alt_headers, alt_body = self._decorate_request(
+ filters, method, url, headers, body,
+ auth_data=self.alt_auth_data)
+ alt_auth_req = dict(url=alt_url, headers=alt_headers,
+ body=alt_body)
+ if auth_req[self.alt_part] == alt_auth_req[self.alt_part]:
+ raise exceptions.BadAltAuth(part=self.alt_part)
+ auth_req[self.alt_part] = alt_auth_req[self.alt_part]
+
+ else:
+ # If the requested part is not affected by auth, we are
+ # not altering auth as expected, raise an exception
+ if auth_req[self.alt_part] == orig_req[self.alt_part]:
+ raise exceptions.BadAltAuth(part=self.alt_part)
+ # If alt auth data is None, skip auth in the requested part
+ auth_req[self.alt_part] = orig_req[self.alt_part]
+
+ # Next auth request will be normal, unless otherwise requested
+ self.reset_alt_auth_data()
+
+ return auth_req['url'], auth_req['headers'], auth_req['body']
+
+ def reset_alt_auth_data(self):
+ """Configure auth provider to provide valid authentication data"""
+ self.alt_part = None
+ self.alt_auth_data = None
+
+ def set_alt_auth_data(self, request_part, auth_data):
+ """Alternate auth data on next request
+
+ Configure auth provider to provide alt authentication data
+ on a part of the *next* auth_request. If credentials are None,
+ set invalid data.
+ :param request_part: request part to contain invalid auth: url,
+ headers, body
+ :param auth_data: alternative auth_data from which to get the
+ invalid data to be injected
+ """
+ self.alt_part = request_part
+ self.alt_auth_data = auth_data
+
+ @abc.abstractmethod
+ def base_url(self, filters, auth_data=None):
+ """Extracts the base_url based on provided filters"""
+ return
+
+ @scope.setter
+ def scope(self, value):
+ """Set the scope to be used in token requests
+
+ :param scope: scope to be used. If the scope is different, clear caches
+ """
+ if value not in self.SCOPES:
+ raise exceptions.InvalidScope(
+ scope=value, auth_provider=self.__class__.__name__)
+ if value != self.scope:
+ self.clear_auth()
+ self._scope = value
+
+
+class KeystoneAuthProvider(AuthProvider):
+
+ EXPIRY_DATE_FORMATS = (ISO8601_FLOAT_SECONDS, ISO8601_INT_SECONDS)
+
+ token_expiry_threshold = datetime.timedelta(seconds=60)
+
+ def __init__(self, credentials, auth_url,
+ disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None, scope='project'):
+ super(KeystoneAuthProvider, self).__init__(credentials, scope)
+ self.dsvm = disable_ssl_certificate_validation
+ self.ca_certs = ca_certs
+ self.trace_requests = trace_requests
+ self.auth_url = auth_url
+ self.auth_client = self._auth_client(auth_url)
+
+ def _decorate_request(self, filters, method, url, headers=None, body=None,
+ auth_data=None):
+ if auth_data is None:
+ auth_data = self.get_auth()
+ token, _ = auth_data
+ base_url = self.base_url(filters=filters, auth_data=auth_data)
+ # build authenticated request
+ # returns new request, it does not touch the original values
+ _headers = copy.deepcopy(headers) if headers is not None else {}
+ _headers['X-Auth-Token'] = str(token)
+ if url is None or url == "":
+ _url = base_url
+ else:
+ # Join base URL and url, and remove multiple contiguous slashes
+ _url = "/".join([base_url, url])
+ parts = [x for x in urlparse.urlparse(_url)]
+ parts[2] = re.sub("/{2,}", "/", parts[2])
+ _url = urlparse.urlunparse(parts)
+ # no change to method or body
+ return str(_url), _headers, body
+
+ @abc.abstractmethod
+ def _auth_client(self):
+ return
+
+ @abc.abstractmethod
+ def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ By default all fields available in Credentials are passed to the
+ token request. Scope may affect this.
+ """
+ return
+
+ def _get_auth(self):
+ # Bypasses the cache
+ auth_func = getattr(self.auth_client, 'get_token')
+ auth_params = self._auth_params()
+
+ # returns token, auth_data
+ token, auth_data = auth_func(**auth_params)
+ return token, auth_data
+
+ def _parse_expiry_time(self, expiry_string):
+ expiry = None
+ for date_format in self.EXPIRY_DATE_FORMATS:
+ try:
+ expiry = datetime.datetime.strptime(
+ expiry_string, date_format)
+ except ValueError:
+ pass
+ if expiry is None:
+ raise ValueError(
+ "time data '{data}' does not match any of the"
+ "expected formats: {formats}".format(
+ data=expiry_string, formats=self.EXPIRY_DATE_FORMATS))
+ return expiry
+
+ def get_token(self):
+ return self.get_auth()[0]
+
+
+class KeystoneV2AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V2 API
+
+ The Keystone Identity V2 API defines both unscoped and project scoped
+ tokens. This auth provider only implements 'project'.
+ """
+
+ SCOPES = set(['project'])
+
+ def _auth_client(self, auth_url):
+ return json_v2id.TokenClient(
+ auth_url, disable_ssl_certificate_validation=self.dsvm,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+
+ def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ All fields available in Credentials are passed to the token request.
+ """
+ return dict(
+ user=self.credentials.username,
+ password=self.credentials.password,
+ tenant=self.credentials.tenant_name,
+ auth_data=True)
+
+ def _fill_credentials(self, auth_data_body):
+ tenant = auth_data_body['token']['tenant']
+ user = auth_data_body['user']
+ if self.credentials.tenant_name is None:
+ self.credentials.tenant_name = tenant['name']
+ if self.credentials.tenant_id is None:
+ self.credentials.tenant_id = tenant['id']
+ if self.credentials.username is None:
+ self.credentials.username = user['name']
+ if self.credentials.user_id is None:
+ self.credentials.user_id = user['id']
+
+ def base_url(self, filters, auth_data=None):
+ """Base URL from catalog
+
+ Filters can be:
+ - service: compute, image, etc
+ - region: the service region
+ - endpoint_type: adminURL, publicURL, internalURL
+ - api_version: replace catalog version with this
+ - skip_path: take just the base URL
+ """
+ if auth_data is None:
+ auth_data = self.get_auth()
+ token, _auth_data = auth_data
+ service = filters.get('service')
+ region = filters.get('region')
+ endpoint_type = filters.get('endpoint_type', 'publicURL')
+
+ if service is None:
+ raise exceptions.EndpointNotFound("No service provided")
+
+ _base_url = None
+ for ep in _auth_data['serviceCatalog']:
+ if ep["type"] == service:
+ for _ep in ep['endpoints']:
+ if region is not None and _ep['region'] == region:
+ _base_url = _ep.get(endpoint_type)
+ if not _base_url:
+ # No region matching, use the first
+ _base_url = ep['endpoints'][0].get(endpoint_type)
+ break
+ if _base_url is None:
+ raise exceptions.EndpointNotFound(
+ "service: %s, region: %s, endpoint_type: %s" %
+ (service, region, endpoint_type))
+ return apply_url_filters(_base_url, filters)
+
+ def is_expired(self, auth_data):
+ _, access = auth_data
+ expiry = self._parse_expiry_time(access['token']['expires'])
+ return (expiry - self.token_expiry_threshold <=
+ datetime.datetime.utcnow())
+
+
+class KeystoneV3AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V3 API"""
+
+ SCOPES = set(['project', 'domain', 'unscoped', None])
+
+ def _auth_client(self, auth_url):
+ return json_v3id.V3TokenClient(
+ auth_url, disable_ssl_certificate_validation=self.dsvm,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+
+ def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ Fields available in Credentials are passed to the token request,
+ depending on the value of scope. Valid values for scope are: "project",
+ "domain". Any other string (e.g. "unscoped") or None will lead to an
+ unscoped token request.
+ """
+
+ auth_params = dict(
+ user_id=self.credentials.user_id,
+ username=self.credentials.username,
+ user_domain_id=self.credentials.user_domain_id,
+ user_domain_name=self.credentials.user_domain_name,
+ password=self.credentials.password,
+ auth_data=True)
+
+ if self.scope == 'project':
+ auth_params.update(
+ project_domain_id=self.credentials.project_domain_id,
+ project_domain_name=self.credentials.project_domain_name,
+ project_id=self.credentials.project_id,
+ project_name=self.credentials.project_name)
+
+ if self.scope == 'domain':
+ auth_params.update(
+ domain_id=self.credentials.domain_id,
+ domain_name=self.credentials.domain_name)
+
+ return auth_params
+
+ def _fill_credentials(self, auth_data_body):
+ # project or domain, depending on the scope
+ project = auth_data_body.get('project', None)
+ domain = auth_data_body.get('domain', None)
+ # user is always there
+ user = auth_data_body['user']
+ # Set project fields
+ if project is not None:
+ if self.credentials.project_name is None:
+ self.credentials.project_name = project['name']
+ if self.credentials.project_id is None:
+ self.credentials.project_id = project['id']
+ if self.credentials.project_domain_id is None:
+ self.credentials.project_domain_id = project['domain']['id']
+ if self.credentials.project_domain_name is None:
+ self.credentials.project_domain_name = (
+ project['domain']['name'])
+ # Set domain fields
+ if domain is not None:
+ if self.credentials.domain_id is None:
+ self.credentials.domain_id = domain['id']
+ if self.credentials.domain_name is None:
+ self.credentials.domain_name = domain['name']
+ # Set user fields
+ if self.credentials.username is None:
+ self.credentials.username = user['name']
+ if self.credentials.user_id is None:
+ self.credentials.user_id = user['id']
+ if self.credentials.user_domain_id is None:
+ self.credentials.user_domain_id = user['domain']['id']
+ if self.credentials.user_domain_name is None:
+ self.credentials.user_domain_name = user['domain']['name']
+
+ def base_url(self, filters, auth_data=None):
+ """Base URL from catalog
+
+ If scope is not 'project', it may be that there is not catalog in
+ the auth_data. In such case, as long as the requested service is
+ 'identity', we can use the original auth URL to build the base_url.
+
+ Filters can be:
+ - service: compute, image, etc
+ - region: the service region
+ - endpoint_type: adminURL, publicURL, internalURL
+ - api_version: replace catalog version with this
+ - skip_path: take just the base URL
+ """
+ if auth_data is None:
+ auth_data = self.get_auth()
+ token, _auth_data = auth_data
+ service = filters.get('service')
+ region = filters.get('region')
+ endpoint_type = filters.get('endpoint_type', 'public')
+
+ if service is None:
+ raise exceptions.EndpointNotFound("No service provided")
+
+ if 'URL' in endpoint_type:
+ endpoint_type = endpoint_type.replace('URL', '')
+ _base_url = None
+ catalog = _auth_data.get('catalog', [])
+ # Select entries with matching service type
+ service_catalog = [ep for ep in catalog if ep['type'] == service]
+ if len(service_catalog) > 0:
+ service_catalog = service_catalog[0]['endpoints']
+ else:
+ if len(catalog) == 0 and service == 'identity':
+ # NOTE(andreaf) If there's no catalog at all and the service
+ # is identity, it's a valid use case. Having a non-empty
+ # catalog with no identity in it is not valid instead.
+ return apply_url_filters(self.auth_url, filters)
+ else:
+ # No matching service
+ raise exceptions.EndpointNotFound(service)
+ # Filter by endpoint type (interface)
+ filtered_catalog = [ep for ep in service_catalog if
+ ep['interface'] == endpoint_type]
+ if len(filtered_catalog) == 0:
+ # No matching type, keep all and try matching by region at least
+ filtered_catalog = service_catalog
+ # Filter by region
+ filtered_catalog = [ep for ep in filtered_catalog if
+ ep['region'] == region]
+ if len(filtered_catalog) == 0:
+ # No matching region, take the first endpoint
+ filtered_catalog = [service_catalog[0]]
+ # There should be only one match. If not take the first.
+ _base_url = filtered_catalog[0].get('url', None)
+ if _base_url is None:
+ raise exceptions.EndpointNotFound(service)
+ return apply_url_filters(_base_url, filters)
+
+ def is_expired(self, auth_data):
+ _, access = auth_data
+ expiry = self._parse_expiry_time(access['expires_at'])
+ return (expiry - self.token_expiry_threshold <=
+ datetime.datetime.utcnow())
+
+
+def is_identity_version_supported(identity_version):
+ return identity_version in IDENTITY_VERSION
+
+
+def get_credentials(auth_url, fill_in=True, identity_version='v2',
+ disable_ssl_certificate_validation=None, ca_certs=None,
+ trace_requests=None, **kwargs):
+ """Builds a credentials object based on the configured auth_version
+
+ :param auth_url (string): Full URI of the OpenStack Identity API(Keystone)
+ which is used to fetch the token from Identity service.
+ :param fill_in (boolean): obtain a token and fill in all credential
+ details provided by the identity service. When fill_in is not
+ specified, credentials are not validated. Validation can be invoked
+ by invoking ``is_valid()``
+ :param identity_version (string): identity API version is used to
+ select the matching auth provider and credentials class
+ :param disable_ssl_certificate_validation: whether to enforce SSL
+ certificate validation in SSL API requests to the auth system
+ :param ca_certs: CA certificate bundle for validation of certificates
+ in SSL API requests to the auth system
+ :param trace_requests: trace in log API requests to the auth system
+ :param kwargs (dict): Dict of credential key/value pairs
+
+ Examples:
+
+ Returns credentials from the provided parameters:
+ >>> get_credentials(username='foo', password='bar')
+
+ Returns credentials including IDs:
+ >>> get_credentials(username='foo', password='bar', fill_in=True)
+ """
+ if not is_identity_version_supported(identity_version):
+ raise exceptions.InvalidIdentityVersion(
+ identity_version=identity_version)
+
+ credential_class, auth_provider_class = IDENTITY_VERSION.get(
+ identity_version)
+
+ creds = credential_class(**kwargs)
+ # Fill in the credentials fields that were not specified
+ if fill_in:
+ dsvm = disable_ssl_certificate_validation
+ auth_provider = auth_provider_class(
+ creds, auth_url, disable_ssl_certificate_validation=dsvm,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+ creds = auth_provider.fill_credentials()
+ return creds
+
+
+class Credentials(object):
+ """Set of credentials for accessing OpenStack services
+
+ ATTRIBUTES: list of valid class attributes representing credentials.
+ """
+
+ ATTRIBUTES = []
+ COLLISIONS = []
+
+ def __init__(self, **kwargs):
+ """Enforce the available attributes at init time (only).
+
+ Additional attributes can still be set afterwards if tests need
+ to do so.
+ """
+ self._initial = kwargs
+ self._apply_credentials(kwargs)
+
+ def _apply_credentials(self, attr):
+ for (key1, key2) in self.COLLISIONS:
+ val1 = attr.get(key1)
+ val2 = attr.get(key2)
+ if val1 and val2 and val1 != val2:
+ msg = ('Cannot have conflicting values for %s and %s' %
+ (key1, key2))
+ raise exceptions.InvalidCredentials(msg)
+ for key in attr.keys():
+ if key in self.ATTRIBUTES:
+ setattr(self, key, attr[key])
+ else:
+ msg = '%s is not a valid attr for %s' % (key, self.__class__)
+ raise exceptions.InvalidCredentials(msg)
+
+ def __str__(self):
+ """Represent only attributes included in self.ATTRIBUTES"""
+ attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ _repr = dict((k, getattr(self, k)) for k in attrs)
+ return str(_repr)
+
+ def __eq__(self, other):
+ """Credentials are equal if attributes in self.ATTRIBUTES are equal"""
+ return str(self) == str(other)
+
+ def __getattr__(self, key):
+ # If an attribute is set, __getattr__ is not invoked
+ # If an attribute is not set, and it is a known one, return None
+ if key in self.ATTRIBUTES:
+ return None
+ else:
+ raise AttributeError
+
+ def __delitem__(self, key):
+ # For backwards compatibility, support dict behaviour
+ if key in self.ATTRIBUTES:
+ delattr(self, key)
+ else:
+ raise AttributeError
+
+ def get(self, item, default=None):
+ # In this patch act as dict for backward compatibility
+ try:
+ return getattr(self, item)
+ except AttributeError:
+ return default
+
+ def get_init_attributes(self):
+ return self._initial.keys()
+
+ def is_valid(self):
+ raise NotImplementedError
+
+ def reset(self):
+ # First delete all known attributes
+ for key in self.ATTRIBUTES:
+ if getattr(self, key) is not None:
+ delattr(self, key)
+ # Then re-apply initial setup
+ self._apply_credentials(self._initial)
+
+
+class KeystoneV2Credentials(Credentials):
+
+ ATTRIBUTES = ['username', 'password', 'tenant_name', 'user_id',
+ 'tenant_id', 'project_id', 'project_name']
+ COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
+
+ def __str__(self):
+ """Represent only attributes included in self.ATTRIBUTES"""
+ attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ _repr = dict((k, getattr(self, k)) for k in attrs)
+ return str(_repr)
+
+ def __setattr__(self, key, value):
+ # NOTE(andreaf) In order to ease the migration towards 'project' we
+ # support v2 credentials configured with 'project' and translate it
+ # to tenant on the fly. The original kwargs are stored for clients
+ # that may rely on them. We also set project when tenant is defined
+ # so clients can rely on project being part of credentials.
+ parent = super(KeystoneV2Credentials, self)
+ # for project_* set tenant only
+ if key == 'project_id':
+ parent.__setattr__('tenant_id', value)
+ elif key == 'project_name':
+ parent.__setattr__('tenant_name', value)
+ if key == 'tenant_id':
+ parent.__setattr__('project_id', value)
+ elif key == 'tenant_name':
+ parent.__setattr__('project_name', value)
+ # trigger default behaviour for all attributes
+ parent.__setattr__(key, value)
+
+ def is_valid(self):
+ """Check of credentials (no API call)
+
+ Minimum set of valid credentials, are username and password.
+ Tenant is optional.
+ """
+ return None not in (self.username, self.password)
+
+
+class KeystoneV3Credentials(Credentials):
+ """Credentials suitable for the Keystone Identity V3 API"""
+
+ ATTRIBUTES = ['domain_id', 'domain_name', 'password', 'username',
+ 'project_domain_id', 'project_domain_name', 'project_id',
+ 'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
+ 'user_domain_name', 'user_id']
+ COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
+
+ def __setattr__(self, key, value):
+ parent = super(KeystoneV3Credentials, self)
+ # for tenant_* set both project and tenant
+ if key == 'tenant_id':
+ parent.__setattr__('project_id', value)
+ elif key == 'tenant_name':
+ parent.__setattr__('project_name', value)
+ # for project_* set both project and tenant
+ if key == 'project_id':
+ parent.__setattr__('tenant_id', value)
+ elif key == 'project_name':
+ parent.__setattr__('tenant_name', value)
+ # for *_domain_* set both user and project if not set yet
+ if key == 'user_domain_id':
+ if self.project_domain_id is None:
+ parent.__setattr__('project_domain_id', value)
+ if key == 'project_domain_id':
+ if self.user_domain_id is None:
+ parent.__setattr__('user_domain_id', value)
+ if key == 'user_domain_name':
+ if self.project_domain_name is None:
+ parent.__setattr__('project_domain_name', value)
+ if key == 'project_domain_name':
+ if self.user_domain_name is None:
+ parent.__setattr__('user_domain_name', value)
+ # support domain_name coming from config
+ if key == 'domain_name':
+ if self.user_domain_name is None:
+ parent.__setattr__('user_domain_name', value)
+ if self.project_domain_name is None:
+ parent.__setattr__('project_domain_name', value)
+ # finally trigger default behaviour for all attributes
+ parent.__setattr__(key, value)
+
+ def is_valid(self):
+ """Check of credentials (no API call)
+
+ Valid combinations of v3 credentials (excluding token)
+ - User id, password (optional domain)
+ - User name, password and its domain id/name
+ For the scope, valid combinations are:
+ - None
+ - Project id (optional domain)
+ - Project name and its domain id/name
+ - Domain id
+ - Domain name
+ """
+ valid_user_domain = any(
+ [self.user_domain_id is not None,
+ self.user_domain_name is not None])
+ valid_project_domain = any(
+ [self.project_domain_id is not None,
+ self.project_domain_name is not None])
+ valid_user = any(
+ [self.user_id is not None,
+ self.username is not None and valid_user_domain])
+ valid_project_scope = any(
+ [self.project_name is None and self.project_id is None,
+ self.project_id is not None,
+ self.project_name is not None and valid_project_domain])
+ valid_domain_scope = any(
+ [self.domain_id is None and self.domain_name is None,
+ self.domain_id or self.domain_name])
+ return all([self.password is not None,
+ valid_user,
+ valid_project_scope and valid_domain_scope])
+
+
+IDENTITY_VERSION = {'v2': (KeystoneV2Credentials, KeystoneV2AuthProvider),
+ 'v3': (KeystoneV3Credentials, KeystoneV3AuthProvider)}
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
new file mode 100644
index 0000000..227ac37
--- /dev/null
+++ b/tempest/lib/base.py
@@ -0,0 +1,71 @@
+# 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 logging
+import os
+
+import fixtures
+import testtools
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase):
+ setUpClassCalled = False
+
+ # NOTE(sdague): log_format is defined inline here instead of using the oslo
+ # default because going through the config path recouples config to the
+ # stress tests too early, and depending on testr order will fail unit tests
+ log_format = ('%(asctime)s %(process)d %(levelname)-8s '
+ '[%(name)s] %(message)s')
+
+ @classmethod
+ def setUpClass(cls):
+ if hasattr(super(BaseTestCase, cls), 'setUpClass'):
+ super(BaseTestCase, cls).setUpClass()
+ cls.setUpClassCalled = True
+
+ @classmethod
+ def tearDownClass(cls):
+ if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
+ super(BaseTestCase, cls).tearDownClass()
+
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ if not self.setUpClassCalled:
+ raise RuntimeError("setUpClass does not calls the super's"
+ "setUpClass in the "
+ + self.__class__.__name__)
+ test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
+ try:
+ test_timeout = int(test_timeout)
+ except ValueError:
+ test_timeout = 0
+ if test_timeout > 0:
+ self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
+
+ if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
+ os.environ.get('OS_STDOUT_CAPTURE') == '1'):
+ stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+ if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
+ os.environ.get('OS_STDERR_CAPTURE') == '1'):
+ stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+ if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
+ os.environ.get('OS_LOG_CAPTURE') != '0'):
+ self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
+ format=self.log_format,
+ level=None))
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/cli/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/cli/__init__.py
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
new file mode 100644
index 0000000..a43d002
--- /dev/null
+++ b/tempest/lib/cli/base.py
@@ -0,0 +1,410 @@
+# 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 logging
+import os
+import shlex
+import subprocess
+
+import six
+
+from tempest.lib import base
+import tempest.lib.cli.output_parser
+from tempest.lib import exceptions
+
+
+LOG = logging.getLogger(__name__)
+
+
+def execute(cmd, action, flags='', params='', fail_ok=False,
+ merge_stderr=False, cli_dir='/usr/bin'):
+ """Executes specified command for the given action.
+
+ :param cmd: command to be executed
+ :type cmd: string
+ :param action: string of the cli command to run
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: string of any optional positional args to use
+ :type params: string
+ :param fail_ok: boolean if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: boolean if True the stderr buffer is merged into
+ stdout
+ :type merge_stderr: boolean
+ :param cli_dir: The path where the cmd can be executed
+ :type cli_dir: string
+ """
+ cmd = ' '.join([os.path.join(cli_dir, cmd),
+ flags, action, params])
+ LOG.info("running: '%s'" % cmd)
+ if six.PY2:
+ cmd = cmd.encode('utf-8')
+ cmd = shlex.split(cmd)
+ result = ''
+ result_err = ''
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
+ proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if not fail_ok and proc.returncode != 0:
+ raise exceptions.CommandFailed(proc.returncode,
+ cmd,
+ result,
+ result_err)
+ if six.PY2:
+ return result
+ else:
+ return os.fsdecode(result)
+
+
+class CLIClient(object):
+ """Class to use OpenStack official python client CLI's with auth
+
+ :param username: The username to authenticate with
+ :type username: string
+ :param password: The password to authenticate with
+ :type password: string
+ :param tenant_name: The name of the tenant to use with the client calls
+ :type tenant_name: string
+ :param uri: The auth uri for the OpenStack Deployment
+ :type uri: string
+ :param cli_dir: The path where the python client binaries are installed.
+ defaults to /usr/bin
+ :type cli_dir: string
+ :param insecure: if True, --insecure is passed to python client binaries.
+ :type insecure: boolean
+ """
+
+ def __init__(self, username='', password='', tenant_name='', uri='',
+ cli_dir='', insecure=False, *args, **kwargs):
+ """Initialize a new CLIClient object."""
+ super(CLIClient, self).__init__()
+ self.cli_dir = cli_dir if cli_dir else '/usr/bin'
+ self.username = username
+ self.tenant_name = tenant_name
+ self.password = password
+ self.uri = uri
+ self.insecure = insecure
+
+ def nova(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes nova command for the given action.
+
+ :param action: the cli command to run using nova
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'nova', action, flags, params, fail_ok, merge_stderr)
+
+ def nova_manage(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes nova-manage command for the given action.
+
+ :param action: the cli command to run using nova-manage
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return execute(
+ 'nova-manage', action, flags, params, fail_ok, merge_stderr,
+ self.cli_dir)
+
+ def keystone(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes keystone command for the given action.
+
+ :param action: the cli command to run using keystone
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return self.cmd_with_auth(
+ 'keystone', action, flags, params, fail_ok, merge_stderr)
+
+ def glance(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes glance command for the given action.
+
+ :param action: the cli command to run using glance
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'glance', action, flags, params, fail_ok, merge_stderr)
+
+ def ceilometer(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL',
+ merge_stderr=False):
+ """Executes ceilometer command for the given action.
+
+ :param action: the cli command to run using ceilometer
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'ceilometer', action, flags, params, fail_ok, merge_stderr)
+
+ def heat(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL', merge_stderr=False):
+ """Executes heat command for the given action.
+
+ :param action: the cli command to run using heat
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'heat', action, flags, params, fail_ok, merge_stderr)
+
+ def cinder(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes cinder command for the given action.
+
+ :param action: the cli command to run using cinder
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'cinder', action, flags, params, fail_ok, merge_stderr)
+
+ def swift(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes swift command for the given action.
+
+ :param action: the cli command to run using swift
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'swift', action, flags, params, fail_ok, merge_stderr)
+
+ def neutron(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes neutron command for the given action.
+
+ :param action: the cli command to run using neutron
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'neutron', action, flags, params, fail_ok, merge_stderr)
+
+ def sahara(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL', merge_stderr=True):
+ """Executes sahara command for the given action.
+
+ :param action: the cli command to run using sahara
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'sahara', action, flags, params, fail_ok, merge_stderr)
+
+ def openstack(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes openstack command for the given action.
+
+ :param action: the cli command to run using openstack
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return self.cmd_with_auth(
+ 'openstack', action, flags, params, fail_ok, merge_stderr)
+
+ def cmd_with_auth(self, cmd, action, flags='', params='',
+ fail_ok=False, merge_stderr=False):
+ """Executes given command with auth attributes appended.
+
+ :param cmd: command to be executed
+ :type cmd: string
+ :param action: command on cli to run
+ :type action: string
+ :param flags: optional cli flags to use
+ :type flags: string
+ :param params: optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the cli return
+ code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+ '--os-auth-url %s' %
+ (self.username,
+ self.tenant_name,
+ self.password,
+ self.uri))
+ if self.insecure:
+ flags = creds + ' --insecure ' + flags
+ else:
+ flags = creds + ' ' + flags
+ return execute(cmd, action, flags, params, fail_ok, merge_stderr,
+ self.cli_dir)
+
+
+class ClientTestBase(base.BaseTestCase):
+ """Base test class for testing the OpenStack client CLI interfaces."""
+
+ def setUp(self):
+ super(ClientTestBase, self).setUp()
+ self.clients = self._get_clients()
+ self.parser = tempest.lib.cli.output_parser
+
+ def _get_clients(self):
+ """Abstract method to initialize CLIClient object.
+
+ This method must be overloaded in child test classes. It should be
+ used to initialize the CLIClient object with the appropriate
+ credentials during the setUp() phase of tests.
+ """
+ raise NotImplementedError
+
+ def assertTableStruct(self, items, field_names):
+ """Verify that all items has keys listed in field_names.
+
+ :param items: items to assert are field names in the output table
+ :type items: list
+ :param field_names: field names from the output table of the cmd
+ :type field_names: list
+ """
+ for item in items:
+ for field in field_names:
+ self.assertIn(field, item)
+
+ def assertFirstLineStartsWith(self, lines, beginning):
+ """Verify that the first line starts with a string
+
+ :param lines: strings for each line of output
+ :type lines: list
+ :param beginning: verify this is at the beginning of the first line
+ :type beginning: string
+ """
+ self.assertTrue(lines[0].startswith(beginning),
+ msg=('Beginning of first line has invalid content: %s'
+ % lines[:3]))
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
new file mode 100644
index 0000000..0313505
--- /dev/null
+++ b/tempest/lib/cli/output_parser.py
@@ -0,0 +1,170 @@
+# 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.
+
+"""Collection of utilities for parsing CLI clients output."""
+
+import logging
+import re
+
+from tempest.lib import exceptions
+
+
+LOG = logging.getLogger(__name__)
+
+
+delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+
+
+def details_multiple(output_lines, with_label=False):
+ """Return list of dicts with item details from cli output tables.
+
+ If with_label is True, key '__label' is added to each items dict.
+ For more about 'label' see OutputParser.tables().
+ """
+ items = []
+ tables_ = tables(output_lines)
+ for table_ in tables_:
+ if ('Property' not in table_['headers']
+ or 'Value' not in table_['headers']):
+ raise exceptions.InvalidStructure()
+ item = {}
+ for value in table_['values']:
+ item[value[0]] = value[1]
+ if with_label:
+ item['__label'] = table_['label']
+ items.append(item)
+ return items
+
+
+def details(output_lines, with_label=False):
+ """Return dict with details of first item (table) found in output."""
+ items = details_multiple(output_lines, with_label)
+ return items[0]
+
+
+def listing(output_lines):
+ """Return list of dicts with basic item info parsed from cli output."""
+
+ items = []
+ table_ = table(output_lines)
+ for row in table_['values']:
+ item = {}
+ for col_idx, col_key in enumerate(table_['headers']):
+ item[col_key] = row[col_idx]
+ items.append(item)
+ return items
+
+
+def tables(output_lines):
+ """Find all ascii-tables in output and parse them.
+
+ Return list of tables parsed from cli output as dicts.
+ (see OutputParser.table())
+
+ And, if found, label key (separated line preceding the table)
+ is added to each tables dict.
+ """
+ tables_ = []
+
+ table_ = []
+ label = None
+
+ start = False
+ header = False
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ if not start:
+ start = True
+ elif not header:
+ # we are after head area
+ header = True
+ else:
+ # table ends here
+ start = header = None
+ table_.append(line)
+
+ parsed = table(table_)
+ parsed['label'] = label
+ tables_.append(parsed)
+
+ table_ = []
+ label = None
+ continue
+ if start:
+ table_.append(line)
+ else:
+ if label is None:
+ label = line
+ else:
+ LOG.warning('Invalid line between tables: %s' % line)
+ if len(table_) > 0:
+ LOG.warning('Missing end of table')
+
+ return tables_
+
+
+def table(output_lines):
+ """Parse single table from cli output.
+
+ Return dict with list of column names in 'headers' key and
+ rows in 'values' key.
+ """
+ table_ = {'headers': [], 'values': []}
+ columns = None
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ if not output_lines[-1]:
+ # skip last line if empty (just newline at the end)
+ output_lines = output_lines[:-1]
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ columns = _table_columns(line)
+ continue
+ if '|' not in line:
+ LOG.warning('skipping invalid table line: %s' % line)
+ continue
+ row = []
+ for col in columns:
+ row.append(line[col[0]:col[1]].strip())
+ if table_['headers']:
+ table_['values'].append(row)
+ else:
+ table_['headers'] = row
+
+ return table_
+
+
+def _table_columns(first_table_row):
+ """Find column ranges in output line.
+
+ Return list of tuples (start,end) for each column
+ detected by plus (+) characters in delimiter line.
+ """
+ positions = []
+ start = 1 # there is '+' at 0
+ while start < len(first_table_row):
+ end = first_table_row.find('+', start)
+ if end == -1:
+ break
+ positions.append((start, end))
+ start = end + 1
+ return positions
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/cmd/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/cmd/__init__.py
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
new file mode 100755
index 0000000..be3aa49
--- /dev/null
+++ b/tempest/lib/cmd/check_uuid.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+
+# Copyright 2014 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 argparse
+import ast
+import importlib
+import inspect
+import os
+import sys
+import unittest
+import uuid
+
+import six.moves.urllib.parse as urlparse
+
+DECORATOR_MODULE = 'test'
+DECORATOR_NAME = 'idempotent_id'
+DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
+IMPORT_LINE = 'from tempest import %s' % DECORATOR_MODULE
+DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
+ DECORATOR_NAME)
+UNIT_TESTS_EXCLUDE = 'tempest.tests'
+
+
+class SourcePatcher(object):
+
+ """"Lazy patcher for python source files"""
+
+ def __init__(self):
+ self.source_files = None
+ self.patches = None
+ self.clear()
+
+ def clear(self):
+ """Clear inner state"""
+ self.source_files = {}
+ self.patches = {}
+
+ @staticmethod
+ def _quote(s):
+ return urlparse.quote(s)
+
+ @staticmethod
+ def _unquote(s):
+ return urlparse.unquote(s)
+
+ def add_patch(self, filename, patch, line_no):
+ """Add lazy patch"""
+ if filename not in self.source_files:
+ with open(filename) as f:
+ self.source_files[filename] = self._quote(f.read())
+ patch_id = str(uuid.uuid4())
+ if not patch.endswith('\n'):
+ patch += '\n'
+ self.patches[patch_id] = self._quote(patch)
+ lines = self.source_files[filename].split(self._quote('\n'))
+ lines[line_no - 1] = ''.join(('{%s:s}' % patch_id, lines[line_no - 1]))
+ self.source_files[filename] = self._quote('\n').join(lines)
+
+ def _save_changes(self, filename, source):
+ print('%s fixed' % filename)
+ with open(filename, 'w') as f:
+ f.write(source)
+
+ def apply_patches(self):
+ """Apply all patches"""
+ for filename in self.source_files:
+ patched_source = self._unquote(
+ self.source_files[filename].format(**self.patches)
+ )
+ self._save_changes(filename, patched_source)
+ self.clear()
+
+
+class TestChecker(object):
+
+ def __init__(self, package):
+ self.package = package
+ self.base_path = os.path.abspath(os.path.dirname(package.__file__))
+
+ def _path_to_package(self, path):
+ relative_path = path[len(self.base_path) + 1:]
+ if relative_path:
+ return '.'.join((self.package.__name__,) +
+ tuple(relative_path.split('/')))
+ else:
+ return self.package.__name__
+
+ def _modules_search(self):
+ """Recursive search for python modules in base package"""
+ modules = []
+ for root, dirs, files in os.walk(self.base_path):
+ if not os.path.exists(os.path.join(root, '__init__.py')):
+ continue
+ root_package = self._path_to_package(root)
+ for item in files:
+ if item.endswith('.py'):
+ module_name = '.'.join((root_package,
+ os.path.splitext(item)[0]))
+ if not module_name.startswith(UNIT_TESTS_EXCLUDE):
+ modules.append(module_name)
+ return modules
+
+ @staticmethod
+ def _get_idempotent_id(test_node):
+ """Return key-value dict with all metadata from @test.idempotent_id"""
+ idempotent_id = None
+ for decorator in test_node.decorator_list:
+ if (hasattr(decorator, 'func') and
+ hasattr(decorator.func, 'attr') and
+ decorator.func.attr == DECORATOR_NAME and
+ hasattr(decorator.func, 'value') and
+ decorator.func.value.id == DECORATOR_MODULE):
+ for arg in decorator.args:
+ idempotent_id = ast.literal_eval(arg)
+ return idempotent_id
+
+ @staticmethod
+ def _is_decorator(line):
+ return line.strip().startswith('@')
+
+ @staticmethod
+ def _is_def(line):
+ return line.strip().startswith('def ')
+
+ def _add_uuid_to_test(self, patcher, test_node, source_path):
+ with open(source_path) as src:
+ src_lines = src.read().split('\n')
+ lineno = test_node.lineno
+ insert_position = lineno
+ while True:
+ if (self._is_def(src_lines[lineno - 1]) or
+ (self._is_decorator(src_lines[lineno - 1]) and
+ (DECORATOR_TEMPLATE.split('(')[0] <=
+ src_lines[lineno - 1].strip().split('(')[0]))):
+ insert_position = lineno
+ break
+ lineno += 1
+ patcher.add_patch(
+ source_path,
+ ' ' * test_node.col_offset + DECORATOR_TEMPLATE % uuid.uuid4(),
+ insert_position
+ )
+
+ @staticmethod
+ def _is_test_case(module, node):
+ if (node.__class__ is ast.ClassDef and
+ hasattr(module, node.name) and
+ inspect.isclass(getattr(module, node.name))):
+ return issubclass(getattr(module, node.name), unittest.TestCase)
+
+ @staticmethod
+ def _is_test_method(node):
+ return (node.__class__ is ast.FunctionDef
+ and node.name.startswith('test_'))
+
+ @staticmethod
+ def _next_node(body, node):
+ if body.index(node) < len(body):
+ return body[body.index(node) + 1]
+
+ @staticmethod
+ def _import_name(node):
+ if isinstance(node, ast.Import):
+ return node.names[0].name
+ elif isinstance(node, ast.ImportFrom):
+ return '%s.%s' % (node.module, node.names[0].name)
+
+ def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
+ with open(source_path) as f:
+ src_lines = f.read().split('\n')
+ line_no = 0
+ tempest_imports = [node for node in src_parsed.body
+ if self._import_name(node) and
+ 'tempest.' in self._import_name(node)]
+ if not tempest_imports:
+ import_snippet = '\n'.join(('', IMPORT_LINE, ''))
+ else:
+ for node in tempest_imports:
+ if self._import_name(node) < DECORATOR_IMPORT:
+ continue
+ else:
+ line_no = node.lineno
+ import_snippet = IMPORT_LINE
+ break
+ else:
+ line_no = tempest_imports[-1].lineno
+ while True:
+ if (not src_lines[line_no - 1] or
+ getattr(self._next_node(src_parsed.body,
+ tempest_imports[-1]),
+ 'lineno') == line_no or
+ line_no == len(src_lines)):
+ break
+ line_no += 1
+ import_snippet = '\n'.join((IMPORT_LINE, ''))
+ patcher.add_patch(source_path, import_snippet, line_no)
+
+ def get_tests(self):
+ """Get test methods with sources from base package with metadata"""
+ tests = {}
+ for module_name in self._modules_search():
+ tests[module_name] = {}
+ module = importlib.import_module(module_name)
+ source_path = '.'.join(
+ (os.path.splitext(module.__file__)[0], 'py')
+ )
+ with open(source_path, 'r') as f:
+ source = f.read()
+ tests[module_name]['source_path'] = source_path
+ tests[module_name]['tests'] = {}
+ source_parsed = ast.parse(source)
+ tests[module_name]['ast'] = source_parsed
+ tests[module_name]['import_valid'] = (
+ hasattr(module, DECORATOR_MODULE) and
+ inspect.ismodule(getattr(module, DECORATOR_MODULE))
+ )
+ test_cases = (node for node in source_parsed.body
+ if self._is_test_case(module, node))
+ for node in test_cases:
+ for subnode in filter(self._is_test_method, node.body):
+ test_name = '%s.%s' % (node.name, subnode.name)
+ tests[module_name]['tests'][test_name] = subnode
+ return tests
+
+ @staticmethod
+ def _filter_tests(function, tests):
+ """Filter tests with condition 'function(test_node) == True'"""
+ result = {}
+ for module_name in tests:
+ for test_name in tests[module_name]['tests']:
+ if function(module_name, test_name, tests):
+ if module_name not in result:
+ result[module_name] = {
+ 'ast': tests[module_name]['ast'],
+ 'source_path': tests[module_name]['source_path'],
+ 'import_valid': tests[module_name]['import_valid'],
+ 'tests': {}
+ }
+ result[module_name]['tests'][test_name] = \
+ tests[module_name]['tests'][test_name]
+ return result
+
+ def find_untagged(self, tests):
+ """Filter all tests without uuid in metadata"""
+ def check_uuid_in_meta(module_name, test_name, tests):
+ idempotent_id = self._get_idempotent_id(
+ tests[module_name]['tests'][test_name])
+ return not idempotent_id
+ return self._filter_tests(check_uuid_in_meta, tests)
+
+ def report_collisions(self, tests):
+ """Reports collisions if there are any
+
+ Returns true if collisions exist.
+ """
+ uuids = {}
+
+ def report(module_name, test_name, tests):
+ test_uuid = self._get_idempotent_id(
+ tests[module_name]['tests'][test_name])
+ if not test_uuid:
+ return
+ if test_uuid in uuids:
+ error_str = "%s:%s\n uuid %s collision: %s<->%s\n%s:%s" % (
+ tests[module_name]['source_path'],
+ tests[module_name]['tests'][test_name].lineno,
+ test_uuid,
+ test_name,
+ uuids[test_uuid]['test_name'],
+ uuids[test_uuid]['source_path'],
+ uuids[test_uuid]['test_node'].lineno,
+ )
+ print(error_str)
+ print("cannot automatically resolve the collision, please "
+ "manually remove the duplicate value on the new test.")
+ return True
+ else:
+ uuids[test_uuid] = {
+ 'module': module_name,
+ 'test_name': test_name,
+ 'test_node': tests[module_name]['tests'][test_name],
+ 'source_path': tests[module_name]['source_path']
+ }
+ return bool(self._filter_tests(report, tests))
+
+ def report_untagged(self, tests):
+ """Reports untagged tests if there are any
+
+ Returns true if untagged tests exist.
+ """
+ def report(module_name, test_name, tests):
+ error_str = "%s:%s\nmissing @test.idempotent_id('...')\n%s\n" % (
+ tests[module_name]['source_path'],
+ tests[module_name]['tests'][test_name].lineno,
+ test_name
+ )
+ print(error_str)
+ return True
+ return bool(self._filter_tests(report, tests))
+
+ def fix_tests(self, tests):
+ """Add uuids to all specified in tests and fix it in source files"""
+ patcher = SourcePatcher()
+ for module_name in tests:
+ add_import_once = True
+ for test_name in tests[module_name]['tests']:
+ if not tests[module_name]['import_valid'] and add_import_once:
+ self._add_import_for_test_uuid(
+ patcher,
+ tests[module_name]['ast'],
+ tests[module_name]['source_path']
+ )
+ add_import_once = False
+ self._add_uuid_to_test(
+ patcher, tests[module_name]['tests'][test_name],
+ tests[module_name]['source_path'])
+ patcher.apply_patches()
+
+
+def run():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--package', action='store', dest='package',
+ default='tempest', type=str,
+ help='Package with tests')
+ parser.add_argument('--fix', action='store_true', dest='fix_tests',
+ help='Attempt to fix tests without UUIDs')
+ args = parser.parse_args()
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+ pkg = importlib.import_module(args.package)
+ checker = TestChecker(pkg)
+ errors = False
+ tests = checker.get_tests()
+ untagged = checker.find_untagged(tests)
+ errors = checker.report_collisions(tests) or errors
+ if args.fix_tests and untagged:
+ checker.fix_tests(untagged)
+ else:
+ errors = checker.report_untagged(untagged) or errors
+ if errors:
+ sys.exit("@test.idempotent_id existence and uniqueness checks failed\n"
+ "Run 'tox -v -euuidgen' to automatically fix tests with\n"
+ "missing @test.idempotent_id decorators.")
+
+if __name__ == '__main__':
+ run()
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
new file mode 100755
index 0000000..f35b14c
--- /dev/null
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python2
+
+# 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.
+
+"""
+Track test skips via launchpadlib API and raise alerts if a bug
+is fixed but a skip is still in the Tempest test code
+"""
+
+import argparse
+import logging
+import os
+import re
+
+try:
+ from launchpadlib import launchpad
+except ImportError:
+ launchpad = None
+
+LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('test_path', help='Path of test dir')
+ return parser.parse_args()
+
+
+def info(msg, *args, **kwargs):
+ logging.info(msg, *args, **kwargs)
+
+
+def debug(msg, *args, **kwargs):
+ logging.debug(msg, *args, **kwargs)
+
+
+def find_skips(start):
+ """Find the entire list of skipped tests.
+
+ Returns a list of tuples (method, bug) that represent
+ test methods that have been decorated to skip because of
+ a particular bug.
+ """
+ results = {}
+ debug("Searching in %s", start)
+ for root, _dirs, files in os.walk(start):
+ for name in files:
+ if name.startswith('test_') and name.endswith('py'):
+ path = os.path.join(root, name)
+ debug("Searching in %s", path)
+ temp_result = find_skips_in_file(path)
+ for method_name, bug_no in temp_result:
+ if results.get(bug_no):
+ result_dict = results.get(bug_no)
+ if result_dict.get(name):
+ result_dict[name].append(method_name)
+ else:
+ result_dict[name] = [method_name]
+ results[bug_no] = result_dict
+ else:
+ results[bug_no] = {name: [method_name]}
+ return results
+
+
+def find_skips_in_file(path):
+ """Return the skip tuples in a test file."""
+ BUG_RE = re.compile(r'\s*@.*skip_because\(bug=[\'"](\d+)[\'"]')
+ DEF_RE = re.compile(r'\s*def (\w+)\(')
+ bug_found = False
+ results = []
+ with open(path, 'rb') as content:
+ lines = content.readlines()
+ for x, line in enumerate(lines):
+ if not bug_found:
+ res = BUG_RE.match(line)
+ if res:
+ bug_no = int(res.group(1))
+ debug("Found bug skip %s on line %d", bug_no, x + 1)
+ bug_found = True
+ else:
+ res = DEF_RE.match(line)
+ if res:
+ method = res.group(1)
+ debug("Found test method %s skips for bug %d",
+ method, bug_no)
+ results.append((method, bug_no))
+ bug_found = False
+ return results
+
+
+def get_results(result_dict):
+ results = []
+ for bug_no in result_dict.keys():
+ for method in result_dict[bug_no]:
+ results.append((method, bug_no))
+ return results
+
+
+def main():
+ logging.basicConfig(format='%(levelname)s: %(message)s',
+ level=logging.INFO)
+ parser = parse_args()
+ results = find_skips(parser.test_path)
+ unique_bugs = sorted(set([bug for (method, bug) in get_results(results)]))
+ unskips = []
+ duplicates = []
+ info("Total bug skips found: %d", len(results))
+ info("Total unique bugs causing skips: %d", len(unique_bugs))
+ if launchpad is not None:
+ lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
+ 'production',
+ LPCACHEDIR)
+ else:
+ print("To check the bug status launchpadlib should be installed")
+ exit(1)
+
+ for bug_no in unique_bugs:
+ bug = lp.bugs[bug_no]
+ duplicate = bug.duplicate_of_link
+ if duplicate is not None:
+ dup_id = duplicate.split('/')[-1]
+ duplicates.append((bug_no, dup_id))
+ for task in bug.bug_tasks:
+ info("Bug #%7s (%12s - %12s)", bug_no,
+ task.importance, task.status)
+ if task.status in ('Fix Released', 'Fix Committed'):
+ unskips.append(bug_no)
+
+ for bug_id, dup_id in duplicates:
+ if bug_id not in unskips:
+ dup_bug = lp.bugs[dup_id]
+ for task in dup_bug.bug_tasks:
+ info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)",
+ bug_id, dup_id, task.importance, task.status)
+ if task.status in ('Fix Released', 'Fix Committed'):
+ unskips.append(bug_id)
+
+ unskips = sorted(set(unskips))
+ if unskips:
+ print("The following bugs have been fixed and the corresponding skips")
+ print("should be removed from the test cases:")
+ print()
+ for bug in unskips:
+ message = " %7s in " % bug
+ locations = ["%s" % x for x in results[bug].keys()]
+ message += " and ".join(locations)
+ print(message)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/common/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/common/__init__.py
diff --git a/tempest/common/api_version_request.py b/tempest/lib/common/api_version_request.py
similarity index 92%
rename from tempest/common/api_version_request.py
rename to tempest/lib/common/api_version_request.py
index d8a5b56..b2b68a6 100644
--- a/tempest/common/api_version_request.py
+++ b/tempest/lib/common/api_version_request.py
@@ -14,7 +14,7 @@
import re
-from tempest import exceptions
+from tempest.lib import exceptions
# Define the minimum and maximum version of the API across all of the
@@ -39,6 +39,11 @@
This class provides convenience methods for manipulation
and comparison of version numbers that we need to do to
implement microversions.
+
+ :param version_string: String representation of APIVersionRequest.
+ Correct format is 'X.Y', where 'X' and 'Y' are int values.
+ None value should be used to create Null APIVersionRequest,
+ which is equal to 0.0
"""
# NOTE: This 'latest' version is a magic number, we assume any
@@ -47,13 +52,7 @@
latest_ver_minor = 99999
def __init__(self, version_string=None):
- """Create an API version request object.
-
- :param version_string: String representation of APIVersionRequest.
- Correct format is 'X.Y', where 'X' and 'Y' are int values.
- None value should be used to create Null APIVersionRequest,
- which is equal to 0.0
- """
+ """Create an API version request object."""
# NOTE(gmann): 'version_string' as String "None" will be considered as
# invalid version string.
self.ver_major = 0
@@ -77,6 +76,12 @@
return ("API Version Request: %s" % self.get_string())
def is_null(self):
+ """Checks whether version is null.
+
+ Return True if version object is null otherwise False.
+
+ :returns: boolean
+ """
return self.ver_major == 0 and self.ver_minor == 0
def _format_type_error(self, other):
@@ -120,9 +125,9 @@
greater than or equal to the minimum version and less than
or equal to the maximum version.
- @param min_version: Minimum acceptable version.
- @param max_version: Maximum acceptable version.
- @returns: boolean
+ :param min_version: Minimum acceptable version.
+ :param max_version: Maximum acceptable version.
+ :returns: boolean
If min_version is null then there is no minimum limit.
If max_version is null then there is no maximum limit.
diff --git a/tempest/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
similarity index 75%
rename from tempest/common/api_version_utils.py
rename to tempest/lib/common/api_version_utils.py
index c3d977f..1371b3c 100644
--- a/tempest/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -14,8 +14,11 @@
import testtools
-from tempest.common import api_version_request
-from tempest import exceptions
+from tempest.lib.common import api_version_request
+from tempest.lib import exceptions
+
+
+LATEST_MICROVERSION = 'latest'
class BaseMicroversionTest(object):
@@ -27,11 +30,25 @@
# for all microversions. We need to define microversion range
# (min_microversion, max_microversion) on each test class if necessary.
min_microversion = None
- max_microversion = 'latest'
+ max_microversion = LATEST_MICROVERSION
def check_skip_with_microversion(test_min_version, test_max_version,
cfg_min_version, cfg_max_version):
+ """Checks API microversions range and returns whether test needs to be skip
+
+ Compare the test and configured microversion range and returns
+ whether test microversion range is out of configured one.
+ This method can be used to skip the test based on configured and test
+ microversion range.
+
+ :param test_min_version: Test Minimum Microversion
+ :param test_max_version: Test Maximum Microversion
+ :param cfg_min_version: Configured Minimum Microversion
+ :param cfg_max_version: Configured Maximum Microversion
+ :returns: boolean
+ """
+
min_version = api_version_request.APIVersionRequest(test_min_version)
max_version = api_version_request.APIVersionRequest(test_max_version)
config_min_version = api_version_request.APIVersionRequest(cfg_min_version)
@@ -65,6 +82,16 @@
def select_request_microversion(test_min_version, cfg_min_version):
+ """Select microversion from test and configuration min version.
+
+ Compare requested microversion and return the maximum
+ microversion out of those.
+
+ :param test_min_version: Test Minimum Microversion
+ :param cfg_min_version: Configured Minimum Microversion
+ :returns: Selected microversion string
+ """
+
test_version = api_version_request.APIVersionRequest(test_min_version)
cfg_version = api_version_request.APIVersionRequest(cfg_min_version)
max_version = cfg_version if cfg_version >= test_version else test_version
@@ -74,15 +101,15 @@
def assert_version_header_matches_request(api_microversion_header_name,
api_microversion,
response_header):
- """Checks API microversion in resposne header
+ """Checks API microversion in response header
Verify whether microversion is present in response header
and with specified 'api_microversion' value.
- @param: api_microversion_header_name: Microversion header name
+ :param api_microversion_header_name: Microversion header name
Example- "X-OpenStack-Nova-API-Version"
- @param: api_microversion: Microversion number like "2.10"
- @param: response_header: Response header where microversion is
+ :param api_microversion: Microversion number like "2.10"
+ :param response_header: Response header where microversion is
expected to be present.
"""
api_microversion_header_name = api_microversion_header_name.lower()
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
new file mode 100644
index 0000000..dffc5f9
--- /dev/null
+++ b/tempest/lib/common/http.py
@@ -0,0 +1,55 @@
+# Copyright 2016 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 urllib3
+
+
+class ClosingHttp(urllib3.poolmanager.PoolManager):
+ def __init__(self, disable_ssl_certificate_validation=False,
+ ca_certs=None):
+ kwargs = {}
+
+ if disable_ssl_certificate_validation:
+ urllib3.disable_warnings()
+ kwargs['cert_reqs'] = 'CERT_NONE'
+
+ if ca_certs:
+ kwargs['cert_reqs'] = 'CERT_REQUIRED'
+ kwargs['ca_certs'] = ca_certs
+
+ super(ClosingHttp, self).__init__(**kwargs)
+
+ def request(self, url, method, *args, **kwargs):
+
+ class Response(dict):
+ def __init__(self, info):
+ for key, value in info.getheaders().items():
+ self[key.lower()] = value
+ self.status = info.status
+ self['status'] = str(self.status)
+ self.reason = info.reason
+ self.version = info.version
+ self['content-location'] = url
+
+ original_headers = kwargs.get('headers', {})
+ new_headers = dict(original_headers, connection='close')
+ new_kwargs = dict(kwargs, headers=new_headers)
+
+ # Follow up to 5 redirections. Don't raise an exception if
+ # it's exceeded but return the HTTP 3XX response instead.
+ retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+ r = super(ClosingHttp, self).request(method, url, retries=retry,
+ *args, **new_kwargs)
+ return Response(r), r.data
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
new file mode 100644
index 0000000..179db17
--- /dev/null
+++ b/tempest/lib/common/rest_client.py
@@ -0,0 +1,950 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import collections
+import email.utils
+import logging as real_logging
+import re
+import time
+
+import jsonschema
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+import six
+
+from tempest.lib.common import http
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions
+
+# redrive rate limited calls at most twice
+MAX_RECURSION_DEPTH = 2
+
+# All the successful HTTP status codes from RFC 7231 & 4918
+HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
+
+# All the redirection HTTP status codes from RFC 7231 & 4918
+HTTP_REDIRECTION = (300, 301, 302, 303, 304, 305, 306, 307)
+
+# JSON Schema validator and format checker used for JSON Schema validation
+JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
+FORMAT_CHECKER = jsonschema.draft4_format_checker
+
+
+class RestClient(object):
+ """Unified OpenStack RestClient class
+
+ This class is used for building openstack api clients on top of. It is
+ intended to provide a base layer for wrapping outgoing http requests in
+ keystone auth as well as providing response code checking and error
+ handling.
+
+ :param auth_provider: an auth provider object used to wrap requests in auth
+ :param str service: The service name to use for the catalog lookup
+ :param str region: The region to use for the catalog lookup
+ :param str endpoint_type: The endpoint type to use for the catalog lookup
+ :param int build_interval: Time in seconds between to status checks in
+ wait loops
+ :param int build_timeout: Timeout in seconds to wait for a wait operation.
+ :param bool disable_ssl_certificate_validation: Set to true to disable ssl
+ certificate validation
+ :param str ca_certs: File containing the CA Bundle to use in verifying a
+ TLS server cert
+ :param str trace_request: Regex to use for specifying logging the entirety
+ of the request and response payload
+ """
+ TYPE = "json"
+
+ # The version of the API this client implements
+ api_version = None
+
+ LOG = logging.getLogger(__name__)
+
+ def __init__(self, auth_provider, service, region,
+ endpoint_type='publicURL',
+ build_interval=1, build_timeout=60,
+ disable_ssl_certificate_validation=False, ca_certs=None,
+ trace_requests=''):
+ self.auth_provider = auth_provider
+ self.service = service
+ self.region = region
+ self.endpoint_type = endpoint_type
+ self.build_interval = build_interval
+ self.build_timeout = build_timeout
+ self.trace_requests = trace_requests
+
+ self._skip_path = False
+ self.general_header_lc = set(('cache-control', 'connection',
+ 'date', 'pragma', 'trailer',
+ 'transfer-encoding', 'via',
+ 'warning'))
+ self.response_header_lc = set(('accept-ranges', 'age', 'etag',
+ 'location', 'proxy-authenticate',
+ 'retry-after', 'server',
+ 'vary', 'www-authenticate'))
+ dscv = disable_ssl_certificate_validation
+ self.http_obj = http.ClosingHttp(
+ disable_ssl_certificate_validation=dscv, ca_certs=ca_certs)
+
+ def _get_type(self):
+ return self.TYPE
+
+ def get_headers(self, accept_type=None, send_type=None):
+ """Return the default headers which will be used with outgoing requests
+
+ :param str accept_type: The media type to use for the Accept header, if
+ one isn't provided the object var TYPE will be
+ used
+ :param str send_type: The media-type to use for the Content-Type
+ header, if one isn't provided the object var
+ TYPE will be used
+ :rtype: dict
+ :return: The dictionary of headers which can be used in the headers
+ dict for outgoing request
+ """
+ if accept_type is None:
+ accept_type = self._get_type()
+ if send_type is None:
+ send_type = self._get_type()
+ return {'Content-Type': 'application/%s' % send_type,
+ 'Accept': 'application/%s' % accept_type}
+
+ def __str__(self):
+ STRING_LIMIT = 80
+ str_format = ("service:%s, base_url:%s, "
+ "filters: %s, build_interval:%s, build_timeout:%s"
+ "\ntoken:%s..., \nheaders:%s...")
+ return str_format % (self.service, self.base_url,
+ self.filters, self.build_interval,
+ self.build_timeout,
+ str(self.token)[0:STRING_LIMIT],
+ str(self.get_headers())[0:STRING_LIMIT])
+
+ @property
+ def user(self):
+ """The username used for requests
+
+ :rtype: string
+ :return: The username being used for requests
+ """
+
+ return self.auth_provider.credentials.username
+
+ @property
+ def user_id(self):
+ """The user_id used for requests
+
+ :rtype: string
+ :return: The user id being used for requests
+ """
+ return self.auth_provider.credentials.user_id
+
+ @property
+ def tenant_name(self):
+ """The tenant/project being used for requests
+
+ :rtype: string
+ :return: The tenant/project name being used for requests
+ """
+ return self.auth_provider.credentials.tenant_name
+
+ @property
+ def tenant_id(self):
+ """The tenant/project id being used for requests
+
+ :rtype: string
+ :return: The tenant/project id being used for requests
+ """
+ return self.auth_provider.credentials.tenant_id
+
+ @property
+ def password(self):
+ """The password being used for requests
+
+ :rtype: string
+ :return: The password being used for requests
+ """
+ return self.auth_provider.credentials.password
+
+ @property
+ def base_url(self):
+ return self.auth_provider.base_url(filters=self.filters)
+
+ @property
+ def token(self):
+ return self.auth_provider.get_token()
+
+ @property
+ def filters(self):
+ _filters = dict(
+ service=self.service,
+ endpoint_type=self.endpoint_type,
+ region=self.region
+ )
+ if self.api_version is not None:
+ _filters['api_version'] = self.api_version
+ if self._skip_path:
+ _filters['skip_path'] = self._skip_path
+ return _filters
+
+ def skip_path(self):
+ """When set, ignore the path part of the base URL from the catalog"""
+ self._skip_path = True
+
+ def reset_path(self):
+ """When reset, use the base URL from the catalog as-is"""
+ self._skip_path = False
+
+ @classmethod
+ def expected_success(cls, expected_code, read_code):
+ """Check expected success response code against the http response
+
+ :param int expected_code: The response code that is expected.
+ Optionally a list of integers can be used
+ to specify multiple valid success codes
+ :param int read_code: The response code which was returned in the
+ response
+ :raises AssertionError: if the expected_code isn't a valid http success
+ response code
+ :raises exceptions.InvalidHttpSuccessCode: if the read code isn't an
+ expected http success code
+ """
+ assert_msg = ("This function only allowed to use for HTTP status"
+ "codes which explicitly defined in the RFC 7231 & 4918."
+ "{0} is not a defined Success Code!"
+ ).format(expected_code)
+ if isinstance(expected_code, list):
+ for code in expected_code:
+ assert code in HTTP_SUCCESS + HTTP_REDIRECTION, assert_msg
+ else:
+ assert expected_code in HTTP_SUCCESS + HTTP_REDIRECTION, assert_msg
+
+ # NOTE(afazekas): the http status code above 400 is processed by
+ # the _error_checker method
+ if read_code < 400:
+ pattern = """Unexpected http success status code {0},
+ The expected status code is {1}"""
+ if ((not isinstance(expected_code, list) and
+ (read_code != expected_code)) or
+ (isinstance(expected_code, list) and
+ (read_code not in expected_code))):
+ details = pattern.format(read_code, expected_code)
+ raise exceptions.InvalidHttpSuccessCode(details)
+
+ def post(self, url, body, headers=None, extra_headers=False,
+ chunked=False):
+ """Send a HTTP POST request using keystone auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :param bool chunked: sends the body with chunked encoding
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('POST', url, extra_headers, headers, body, chunked)
+
+ def get(self, url, headers=None, extra_headers=False):
+ """Send a HTTP GET request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('GET', url, extra_headers, headers)
+
+ def delete(self, url, headers=None, body=None, extra_headers=False):
+ """Send a HTTP DELETE request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param dict body: the request body
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('DELETE', url, extra_headers, headers, body)
+
+ def patch(self, url, body, headers=None, extra_headers=False):
+ """Send a HTTP PATCH request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('PATCH', url, extra_headers, headers, body)
+
+ def put(self, url, body, headers=None, extra_headers=False, chunked=False):
+ """Send a HTTP PUT request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :param bool chunked: sends the body with chunked encoding
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('PUT', url, extra_headers, headers, body, chunked)
+
+ def head(self, url, headers=None, extra_headers=False):
+ """Send a HTTP HEAD request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('HEAD', url, extra_headers, headers)
+
+ def copy(self, url, headers=None, extra_headers=False):
+ """Send a HTTP COPY request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('COPY', url, extra_headers, headers)
+
+ def get_versions(self):
+ """Get the versions on a endpoint from the keystone catalog
+
+ This method will make a GET request on the baseurl from the keystone
+ catalog to return a list of API versions. It is expected that a GET
+ on the endpoint in the catalog will return a list of supported API
+ versions.
+
+ :return tuple with response headers and list of version numbers
+ :rtype: tuple
+ """
+ resp, body = self.get('')
+ body = self._parse_resp(body)
+ versions = map(lambda x: x['id'], body)
+ return resp, versions
+
+ def _get_request_id(self, resp):
+ for i in ('x-openstack-request-id', 'x-compute-request-id'):
+ if i in resp:
+ return resp[i]
+ return ""
+
+ def _safe_body(self, body, maxlen=4096):
+ # convert a structure into a string safely
+ try:
+ text = six.text_type(body)
+ except UnicodeDecodeError:
+ # if this isn't actually text, return marker that
+ return "<BinaryData: removed>"
+ if len(text) > maxlen:
+ return text[:maxlen]
+ else:
+ return text
+
+ def _log_request_start(self, method, req_url, req_headers=None,
+ req_body=None):
+ if req_headers is None:
+ req_headers = {}
+ caller_name = misc_utils.find_test_caller()
+ if self.trace_requests and re.search(self.trace_requests, caller_name):
+ self.LOG.debug('Starting Request (%s): %s %s' %
+ (caller_name, method, req_url))
+
+ def _log_request_full(self, method, req_url, resp,
+ secs="", req_headers=None,
+ req_body=None, resp_body=None,
+ caller_name=None, extra=None):
+ if 'X-Auth-Token' in req_headers:
+ req_headers['X-Auth-Token'] = '<omitted>'
+ # A shallow copy is sufficient
+ resp_log = resp.copy()
+ if 'x-subject-token' in resp_log:
+ resp_log['x-subject-token'] = '<omitted>'
+ log_fmt = """Request - Headers: %s
+ Body: %s
+ Response - Headers: %s
+ Body: %s"""
+
+ self.LOG.debug(
+ log_fmt % (
+ str(req_headers),
+ self._safe_body(req_body),
+ str(resp_log),
+ self._safe_body(resp_body)),
+ extra=extra)
+
+ def _log_request(self, method, req_url, resp,
+ secs="", req_headers=None,
+ req_body=None, resp_body=None):
+ if req_headers is None:
+ req_headers = {}
+ # if we have the request id, put it in the right part of the log
+ extra = dict(request_id=self._get_request_id(resp))
+ # NOTE(sdague): while we still have 6 callers to this function
+ # we're going to just provide work around on who is actually
+ # providing timings by gracefully adding no content if they don't.
+ # Once we're down to 1 caller, clean this up.
+ caller_name = misc_utils.find_test_caller()
+ if secs:
+ secs = " %.3fs" % secs
+ self.LOG.info(
+ 'Request (%s): %s %s %s%s' % (
+ caller_name,
+ resp['status'],
+ method,
+ req_url,
+ secs),
+ extra=extra)
+
+ # Also look everything at DEBUG if you want to filter this
+ # out, don't run at debug.
+ if self.LOG.isEnabledFor(real_logging.DEBUG):
+ self._log_request_full(method, req_url, resp, secs, req_headers,
+ req_body, resp_body, caller_name, extra)
+
+ def _parse_resp(self, body):
+ try:
+ body = json.loads(body)
+ except ValueError:
+ return body
+
+ # We assume, that if the first value of the deserialized body's
+ # item set is a dict or a list, that we just return the first value
+ # of deserialized body.
+ # Essentially "cutting out" the first placeholder element in a body
+ # that looks like this:
+ #
+ # {
+ # "users": [
+ # ...
+ # ]
+ # }
+ try:
+ # 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:
+ return body
+ # Just return the "wrapped" element
+ first_key, first_item = six.next(six.iteritems(body))
+ if isinstance(first_item, (dict, list)):
+ return first_item
+ except (ValueError, IndexError):
+ pass
+ return body
+
+ def response_checker(self, method, resp, resp_body):
+ """A sanity check on the response from a HTTP request
+
+ This method does a sanity check on whether the response from an HTTP
+ request conforms the HTTP RFC.
+
+ :param str method: The HTTP verb of the request associated with the
+ response being passed in.
+ :param resp: The response headers
+ :param resp_body: The body of the response
+ :raises ResponseWithNonEmptyBody: If the response with the status code
+ is not supposed to have a body
+ :raises ResponseWithEntity: If the response code is 205 but has an
+ entity
+ """
+ if (resp.status in set((204, 205, 304)) or resp.status < 200 or
+ method.upper() == 'HEAD') and resp_body:
+ raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
+ # NOTE(afazekas):
+ # If the HTTP Status Code is 205
+ # 'The response MUST NOT include an entity.'
+ # A HTTP entity has an entity-body and an 'entity-header'.
+ # In the HTTP response specification (Section 6) the 'entity-header'
+ # 'generic-header' and 'response-header' are in OR relation.
+ # All headers not in the above two group are considered as entity
+ # header in every interpretation.
+
+ if (resp.status == 205 and
+ 0 != len(set(resp.keys()) - set(('status',)) -
+ self.response_header_lc - self.general_header_lc)):
+ raise exceptions.ResponseWithEntity()
+ # NOTE(afazekas)
+ # Now the swift sometimes (delete not empty container)
+ # returns with non json error response, we can create new rest class
+ # for swift.
+ # Usually RFC2616 says error responses SHOULD contain an explanation.
+ # The warning is normal for SHOULD/SHOULD NOT case
+
+ # Likely it will cause an error
+ if method != 'HEAD' and not resp_body and resp.status >= 400:
+ self.LOG.warning("status >= 400 response with empty body")
+
+ def _request(self, method, url, headers=None, body=None, chunked=False):
+ """A simple HTTP request interface."""
+ # Authenticate the request with the auth provider
+ req_url, req_headers, req_body = self.auth_provider.auth_request(
+ method, url, headers, body, self.filters)
+
+ # Do the actual request, and time it
+ start = time.time()
+ self._log_request_start(method, req_url)
+ resp, resp_body = self.raw_request(
+ req_url, method, headers=req_headers, body=req_body,
+ chunked=chunked
+ )
+ end = time.time()
+ self._log_request(method, req_url, resp, secs=(end - start),
+ req_headers=req_headers, req_body=req_body,
+ resp_body=resp_body)
+
+ # Verify HTTP response codes
+ self.response_checker(method, resp, resp_body)
+
+ return resp, resp_body
+
+ def raw_request(self, url, method, headers=None, body=None, chunked=False):
+ """Send a raw HTTP request without the keystone catalog or auth
+
+ This method sends a HTTP request in the same manner as the request()
+ method, however it does so without using keystone auth or the catalog
+ to determine the base url. Additionally no response handling is done
+ the results from the request are just returned.
+
+ :param str url: Full url to send the request
+ :param str method: The HTTP verb to use for the request
+ :param str headers: Headers to use for the request if none are specifed
+ the headers
+ :param str body: Body to send with the request
+ :param bool chunked: sends the body with chunked encoding
+ :rtype: tuple
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ """
+ if headers is None:
+ headers = self.get_headers()
+ return self.http_obj.request(url, method, headers=headers,
+ body=body, chunked=chunked)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+ """Send a HTTP request with keystone auth and using the catalog
+
+ This method will send an HTTP request using keystone auth in the
+ headers and the catalog to determine the endpoint to use for the
+ baseurl to send the request to. Additionally
+
+ When a response is received it will check it to see if an error
+ response was received. If it was an exception will be raised to enable
+ it to be handled quickly.
+
+ This method will also handle rate-limiting, if a 413 response code is
+ received it will retry the request after waiting the 'retry-after'
+ duration from the header.
+
+ :param str method: The HTTP verb to use for the request
+ :param str url: Relative url to send the request to
+ :param bool extra_headers: Boolean value than indicates if the headers
+ returned by the get_headers() method are to
+ be used but additional headers are needed in
+ the request pass them in as a dict.
+ :param dict headers: Headers to use for the request if none are
+ specifed the headers returned from the
+ get_headers() method are used. If the request
+ explicitly requires no headers use an empty dict.
+ :param str body: Body to send with the request
+ :param bool chunked: sends the body with chunked encoding
+ :rtype: tuple
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :raises UnexpectedContentType: If the content-type of the response
+ isn't an expect type
+ :raises Unauthorized: If a 401 response code is received
+ :raises Forbidden: If a 403 response code is received
+ :raises NotFound: If a 404 response code is received
+ :raises BadRequest: If a 400 response code is received
+ :raises Gone: If a 410 response code is received
+ :raises Conflict: If a 409 response code is received
+ :raises OverLimit: If a 413 response code is received and over_limit is
+ not in the response body
+ :raises RateLimitExceeded: If a 413 response code is received and
+ over_limit is in the response body
+ :raises InvalidContentType: If a 415 response code is received
+ :raises UnprocessableEntity: If a 422 response code is received
+ :raises InvalidHTTPResponseBody: The response body wasn't valid JSON
+ and couldn't be parsed
+ :raises NotImplemented: If a 501 response code is received
+ :raises ServerFault: If a 500 response code is received
+ :raises UnexpectedResponseCode: If a response code above 400 is
+ received and it doesn't fall into any
+ of the handled checks
+ """
+ # if extra_headers is True
+ # default headers would be added to headers
+ retry = 0
+
+ if headers is None:
+ # NOTE(vponomaryov): if some client do not need headers,
+ # it should explicitly pass empty dict
+ headers = self.get_headers()
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers())
+ except (ValueError, TypeError):
+ headers = self.get_headers()
+
+ resp, resp_body = self._request(method, url, headers=headers,
+ body=body, chunked=chunked)
+
+ while (resp.status == 413 and
+ 'retry-after' in resp and
+ not self.is_absolute_limit(
+ resp, self._parse_resp(resp_body)) and
+ retry < MAX_RECURSION_DEPTH):
+ retry += 1
+ delay = self._get_retry_after_delay(resp)
+ self.LOG.debug(
+ "Sleeping %s seconds based on retry-after header", delay
+ )
+ time.sleep(delay)
+ resp, resp_body = self._request(method, url,
+ headers=headers, body=body)
+ self._error_checker(method, url, headers, body,
+ resp, resp_body)
+ return resp, resp_body
+
+ def _get_retry_after_delay(self, resp):
+ """Extract the delay from the retry-after header.
+
+ This supports both integer and HTTP date formatted retry-after headers
+ per RFC 2616.
+
+ :param resp: The response containing the retry-after headers
+ :rtype: int
+ :return: The delay in seconds, clamped to be at least 1 second
+ :raises ValueError: On failing to parse the delay
+ """
+ delay = None
+ try:
+ delay = int(resp['retry-after'])
+ except (ValueError, KeyError):
+ pass
+
+ try:
+ retry_timestamp = self._parse_http_date(resp['retry-after'])
+ date_timestamp = self._parse_http_date(resp['date'])
+ delay = int(retry_timestamp - date_timestamp)
+ except (ValueError, OverflowError, KeyError):
+ pass
+
+ if delay is None:
+ raise ValueError(
+ "Failed to parse retry-after header %r as either int or "
+ "HTTP-date." % resp.get('retry-after')
+ )
+
+ # Retry-after headers do not have sub-second precision. Clients may
+ # receive a delay of 0. After sleeping 0 seconds, we would (likely) hit
+ # another 413. To avoid this, always sleep at least 1 second.
+ return max(1, delay)
+
+ def _parse_http_date(self, val):
+ """Parse an HTTP date, like 'Fri, 31 Dec 1999 23:59:59 GMT'.
+
+ Return an epoch timestamp (float), as returned by time.mktime().
+ """
+ parts = email.utils.parsedate(val)
+ if not parts:
+ raise ValueError("Failed to parse date %s" % val)
+ return time.mktime(parts)
+
+ def _error_checker(self, method, url,
+ headers, body, resp, resp_body):
+
+ # NOTE(mtreinish): Check for httplib response from glance_http. The
+ # object can't be used here because importing httplib breaks httplib2.
+ # If another object from a class not imported were passed here as
+ # resp this could possibly fail
+ if str(type(resp)) == "<type 'instance'>":
+ ctype = resp.getheader('content-type')
+ else:
+ try:
+ ctype = resp['content-type']
+ # NOTE(mtreinish): Keystone delete user responses doesn't have a
+ # content-type header. (They don't have a body) So just pretend it
+ # is set.
+ except KeyError:
+ ctype = 'application/json'
+
+ # It is not an error response
+ if resp.status < 400:
+ return
+
+ JSON_ENC = ['application/json', 'application/json; charset=utf-8']
+ # NOTE(mtreinish): This is for compatibility with Glance and swift
+ # APIs. These are the return content types that Glance api v1
+ # (and occasionally swift) are using.
+ TXT_ENC = ['text/plain', 'text/html', 'text/html; charset=utf-8',
+ 'text/plain; charset=utf-8']
+
+ if ctype.lower() in JSON_ENC:
+ parse_resp = True
+ elif ctype.lower() in TXT_ENC:
+ parse_resp = False
+ else:
+ raise exceptions.UnexpectedContentType(str(resp.status),
+ resp=resp)
+
+ if resp.status == 401:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Unauthorized(resp_body, resp=resp)
+
+ if resp.status == 403:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Forbidden(resp_body, resp=resp)
+
+ if resp.status == 404:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.NotFound(resp_body, resp=resp)
+
+ if resp.status == 400:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.BadRequest(resp_body, resp=resp)
+
+ if resp.status == 410:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Gone(resp_body, resp=resp)
+
+ if resp.status == 409:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Conflict(resp_body, resp=resp)
+
+ if resp.status == 413:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ if self.is_absolute_limit(resp, resp_body):
+ raise exceptions.OverLimit(resp_body, resp=resp)
+ else:
+ raise exceptions.RateLimitExceeded(resp_body, resp=resp)
+
+ if resp.status == 415:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.InvalidContentType(resp_body, resp=resp)
+
+ if resp.status == 422:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.UnprocessableEntity(resp_body, resp=resp)
+
+ if resp.status in (500, 501):
+ message = resp_body
+ if parse_resp:
+ try:
+ resp_body = self._parse_resp(resp_body)
+ except ValueError:
+ # If response body is a non-json string message.
+ # Use resp_body as is and raise InvalidResponseBody
+ # exception.
+ raise exceptions.InvalidHTTPResponseBody(message)
+ else:
+ if isinstance(resp_body, dict):
+ # I'm seeing both computeFault
+ # and cloudServersFault come back.
+ # Will file a bug to fix, but leave as is for now.
+ if 'cloudServersFault' in resp_body:
+ message = resp_body['cloudServersFault']['message']
+ elif 'computeFault' in resp_body:
+ message = resp_body['computeFault']['message']
+ elif 'error' in resp_body:
+ message = resp_body['error']['message']
+ elif 'message' in resp_body:
+ message = resp_body['message']
+ else:
+ message = resp_body
+
+ if resp.status == 501:
+ raise exceptions.NotImplemented(resp_body, resp=resp,
+ message=message)
+ else:
+ raise exceptions.ServerFault(resp_body, resp=resp,
+ message=message)
+
+ if resp.status >= 400:
+ raise exceptions.UnexpectedResponseCode(str(resp.status),
+ resp=resp)
+
+ def is_absolute_limit(self, resp, resp_body):
+ if (not isinstance(resp_body, collections.Mapping) or
+ 'retry-after' not in resp):
+ return True
+ return 'exceed' in resp_body.get('message', 'blabla')
+
+ def wait_for_resource_deletion(self, id):
+ """Waits for a resource to be deleted
+
+ This method will loop over is_resource_deleted until either
+ is_resource_deleted returns True or the build timeout is reached. This
+ depends on is_resource_deleted being implemented
+
+ :param str id: The id of the resource to check
+ :raises TimeoutException: If the build_timeout has elapsed and the
+ resource still hasn't been deleted
+ """
+ start_time = int(time.time())
+ while True:
+ if self.is_resource_deleted(id):
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ message = ('Failed to delete %(resource_type)s %(id)s within '
+ 'the required time (%(timeout)s s).' %
+ {'resource_type': self.resource_type, 'id': id,
+ 'timeout': self.build_timeout})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
+ time.sleep(self.build_interval)
+
+ def is_resource_deleted(self, id):
+ """Subclasses override with specific deletion detection."""
+ message = ('"%s" does not implement is_resource_deleted'
+ % self.__class__.__name__)
+ raise NotImplementedError(message)
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'resource'
+
+ @classmethod
+ def validate_response(cls, schema, resp, body):
+ # Only check the response if the status code is a success code
+ # TODO(cyeoh): Eventually we should be able to verify that a failure
+ # code if it exists is something that we expect. This is explicitly
+ # declared in the V3 API and so we should be able to export this in
+ # the response schema. For now we'll ignore it.
+ if resp.status in HTTP_SUCCESS + HTTP_REDIRECTION:
+ cls.expected_success(schema['status_code'], resp.status)
+
+ # Check the body of a response
+ body_schema = schema.get('response_body')
+ if body_schema:
+ try:
+ jsonschema.validate(body, body_schema,
+ cls=JSONSCHEMA_VALIDATOR,
+ format_checker=FORMAT_CHECKER)
+ except jsonschema.ValidationError as ex:
+ msg = ("HTTP response body is invalid (%s)") % ex
+ raise exceptions.InvalidHTTPResponseBody(msg)
+ else:
+ if body:
+ msg = ("HTTP response body should not exist (%s)") % body
+ raise exceptions.InvalidHTTPResponseBody(msg)
+
+ # Check the header of a response
+ header_schema = schema.get('response_header')
+ if header_schema:
+ try:
+ jsonschema.validate(resp, header_schema,
+ cls=JSONSCHEMA_VALIDATOR,
+ format_checker=FORMAT_CHECKER)
+ except jsonschema.ValidationError as ex:
+ msg = ("HTTP response header is invalid (%s)") % ex
+ raise exceptions.InvalidHTTPResponseHeader(msg)
+
+
+class ResponseBody(dict):
+ """Class that wraps an http response and dict body into a single value.
+
+ Callers that receive this object will normally use it as a dict but
+ can extract the response if needed.
+ """
+
+ def __init__(self, response, body=None):
+ body_data = body or {}
+ self.update(body_data)
+ self.response = response
+
+ def __str__(self):
+ body = super(ResponseBody, self).__str__()
+ return "response: %s\nBody: %s" % (self.response, body)
+
+
+class ResponseBodyData(object):
+ """Class that wraps an http response and string data into a single value.
+
+ """
+
+ def __init__(self, response, data):
+ self.response = response
+ self.data = data
+
+ def __str__(self):
+ return "response: %s\nBody: %s" % (self.response, self.data)
+
+
+class ResponseBodyList(list):
+ """Class that wraps an http response and list body into a single value.
+
+ Callers that receive this object will normally use it as a list but
+ can extract the response if needed.
+ """
+
+ def __init__(self, response, body=None):
+ body_data = body or []
+ self.extend(body_data)
+ self.response = response
+
+ def __str__(self):
+ body = super(ResponseBodyList, self).__str__()
+ return "response: %s\nBody: %s" % (self.response, body)
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
new file mode 100644
index 0000000..a831dbd
--- /dev/null
+++ b/tempest/lib/common/ssh.py
@@ -0,0 +1,174 @@
+# 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 select
+import socket
+import time
+import warnings
+
+from oslo_log import log as logging
+import six
+
+from tempest.lib import exceptions
+
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ import paramiko
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Client(object):
+
+ def __init__(self, host, username, password=None, timeout=300, pkey=None,
+ channel_timeout=10, look_for_keys=False, key_filename=None):
+ self.host = host
+ self.username = username
+ self.password = password
+ if isinstance(pkey, six.string_types):
+ pkey = paramiko.RSAKey.from_private_key(
+ six.StringIO(str(pkey)))
+ self.pkey = pkey
+ self.look_for_keys = look_for_keys
+ self.key_filename = key_filename
+ self.timeout = int(timeout)
+ self.channel_timeout = float(channel_timeout)
+ self.buf_size = 1024
+
+ def _get_ssh_connection(self, sleep=1.5, backoff=1):
+ """Returns an ssh connection to the specified host."""
+ bsleep = sleep
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(
+ paramiko.AutoAddPolicy())
+ _start_time = time.time()
+ if self.pkey is not None:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with public key authentication",
+ self.host, self.username)
+ else:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with password %s",
+ self.host, self.username, str(self.password))
+ attempts = 0
+ while True:
+ try:
+ ssh.connect(self.host, username=self.username,
+ password=self.password,
+ look_for_keys=self.look_for_keys,
+ key_filename=self.key_filename,
+ timeout=self.channel_timeout, pkey=self.pkey)
+ LOG.info("ssh connection to %s@%s successfully created",
+ self.username, self.host)
+ return ssh
+ except (EOFError,
+ socket.error,
+ paramiko.SSHException) as e:
+ if self._is_timed_out(_start_time):
+ LOG.exception("Failed to establish authenticated ssh"
+ " connection to %s@%s after %d attempts",
+ self.username, self.host, attempts)
+ raise exceptions.SSHTimeout(host=self.host,
+ user=self.username,
+ password=self.password)
+ bsleep += backoff
+ attempts += 1
+ LOG.warning("Failed to establish authenticated ssh"
+ " connection to %s@%s (%s). Number attempts: %s."
+ " Retry after %d seconds.",
+ self.username, self.host, e, attempts, bsleep)
+ time.sleep(bsleep)
+
+ def _is_timed_out(self, start_time):
+ return (time.time() - self.timeout) > start_time
+
+ @staticmethod
+ def _can_system_poll():
+ return hasattr(select, 'poll')
+
+ def exec_command(self, cmd, encoding="utf-8"):
+ """Execute the specified command on the server
+
+ Note that this method is reading whole command outputs to memory, thus
+ shouldn't be used for large outputs.
+
+ :param str cmd: Command to run at remote server.
+ :param str encoding: Encoding for result from paramiko.
+ Result will not be decoded if None.
+ :returns: data read from standard output of the command.
+ :raises: SSHExecCommandFailed if command returns nonzero
+ status. The exception contains command status stderr content.
+ :raises: TimeoutException if cmd doesn't end when timeout expires.
+ """
+ ssh = self._get_ssh_connection()
+ transport = ssh.get_transport()
+ with transport.open_session() as channel:
+ channel.fileno() # Register event pipe
+ channel.exec_command(cmd)
+ channel.shutdown_write()
+ exit_status = channel.recv_exit_status()
+
+ # If the executing host is linux-based, poll the channel
+ if self._can_system_poll():
+ out_data_chunks = []
+ err_data_chunks = []
+ poll = select.poll()
+ poll.register(channel, select.POLLIN)
+ start_time = time.time()
+
+ while True:
+ ready = poll.poll(self.channel_timeout)
+ if not any(ready):
+ if not self._is_timed_out(start_time):
+ continue
+ raise exceptions.TimeoutException(
+ "Command: '{0}' executed on host '{1}'.".format(
+ cmd, self.host))
+ if not ready[0]: # If there is nothing to read.
+ continue
+ out_chunk = err_chunk = None
+ if channel.recv_ready():
+ out_chunk = channel.recv(self.buf_size)
+ out_data_chunks += out_chunk,
+ if channel.recv_stderr_ready():
+ err_chunk = channel.recv_stderr(self.buf_size)
+ err_data_chunks += err_chunk,
+ if not err_chunk and not out_chunk:
+ break
+ out_data = b''.join(out_data_chunks)
+ err_data = b''.join(err_data_chunks)
+ # Just read from the channels
+ else:
+ out_file = channel.makefile('rb', self.buf_size)
+ err_file = channel.makefile_stderr('rb', self.buf_size)
+ out_data = out_file.read()
+ err_data = err_file.read()
+ if encoding:
+ out_data = out_data.decode(encoding)
+ err_data = err_data.decode(encoding)
+
+ if 0 != exit_status:
+ raise exceptions.SSHExecCommandFailed(
+ command=cmd, exit_status=exit_status,
+ stderr=err_data, stdout=out_data)
+ return out_data
+
+ def test_connection_auth(self):
+ """Raises an exception when we can not connect to server via ssh."""
+ connection = self._get_ssh_connection()
+ connection.close()
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/common/utils/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/common/utils/__init__.py
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
new file mode 100644
index 0000000..45e5067
--- /dev/null
+++ b/tempest/lib/common/utils/data_utils.py
@@ -0,0 +1,207 @@
+# 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 itertools
+import netaddr
+import random
+import string
+import uuid
+
+import six.moves
+
+
+def rand_uuid():
+ """Generate a random UUID string
+
+ :return: a random UUID (e.g. '1dc12c7d-60eb-4b61-a7a2-17cf210155b6')
+ :rtype: string
+ """
+ return str(uuid.uuid4())
+
+
+def rand_uuid_hex():
+ """Generate a random UUID hex string
+
+ :return: a random UUID (e.g. '0b98cf96d90447bda4b46f31aeb1508c')
+ :rtype: string
+ """
+ return uuid.uuid4().hex
+
+
+def rand_name(name='', prefix=None):
+ """Generate a random name that includes a random number
+
+ :param str name: The name that you want to include
+ :param str prefix: The prefix that you want to include
+ :return: a random name. The format is
+ '<prefix>-<random number>-<name>-<random number>'.
+ (e.g. 'prefixfoo-1308607012-namebar-154876201')
+ :rtype: string
+ """
+ randbits = str(random.randint(1, 0x7fffffff))
+ rand_name = randbits
+ if name:
+ rand_name = name + '-' + rand_name
+ if prefix:
+ rand_name = prefix + '-' + rand_name
+ return rand_name
+
+
+def rand_password(length=15):
+ """Generate a random password
+
+ :param int length: The length of password that you expect to set
+ (If it's smaller than 3, it's same as 3.)
+ :return: a random password. The format is
+ '<random upper letter>-<random number>-<random special character>
+ -<random ascii letters or digit characters or special symbols>'
+ (e.g. 'G2*ac8&lKFFgh%2')
+ :rtype: string
+ """
+ upper = random.choice(string.ascii_uppercase)
+ ascii_char = string.ascii_letters
+ digits = string.digits
+ digit = random.choice(string.digits)
+ puncs = '~!@#$%^&*_=+'
+ punc = random.choice(puncs)
+ seed = ascii_char + digits + puncs
+ pre = upper + digit + punc
+ password = pre + ''.join(random.choice(seed) for x in range(length - 3))
+ return password
+
+
+def rand_url():
+ """Generate a random url that includes a random number
+
+ :return: a random url. The format is 'https://url-<random number>.com'.
+ (e.g. 'https://url-154876201.com')
+ :rtype: string
+ """
+ randbits = str(random.randint(1, 0x7fffffff))
+ return 'https://url-' + randbits + '.com'
+
+
+def rand_int_id(start=0, end=0x7fffffff):
+ """Generate a random integer value
+
+ :param int start: The value that you expect to start here
+ :param int end: The value that you expect to end here
+ :return: a random integer value
+ :rtype: int
+ """
+ return random.randint(start, end)
+
+
+def rand_mac_address():
+ """Generate an Ethernet MAC address
+
+ :return: an random Ethernet MAC address
+ :rtype: string
+ """
+ # NOTE(vish): We would prefer to use 0xfe here to ensure that linux
+ # bridge mac addresses don't change, but it appears to
+ # conflict with libvirt, so we use the next highest octet
+ # that has the unicast and locally administered bits set
+ # properly: 0xfa.
+ # Discussion: https://bugs.launchpad.net/nova/+bug/921838
+ mac = [0xfa, 0x16, 0x3e,
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)]
+ return ':'.join(["%02x" % x for x in mac])
+
+
+def rand_infiniband_guid_address():
+ """Generate an Infiniband GUID address
+
+ :return: an random Infiniband GUID address
+ :rtype: string
+ """
+ guid = []
+ for i in range(8):
+ guid.append("%02x" % random.randint(0x00, 0xff))
+ return ':'.join(guid)
+
+
+def parse_image_id(image_ref):
+ """Return the image id from a given image ref
+
+ This function just returns the last word of the given image ref string
+ splitting with '/'.
+ :param str image_ref: a string that includes the image id
+ :return: the image id string
+ :rtype: string
+ """
+ return image_ref.rsplit('/')[-1]
+
+
+def arbitrary_string(size=4, base_text=None):
+ """Return size characters from base_text
+
+ This generates a string with an arbitrary number of characters, generated
+ by looping the base_text string. If the size is smaller than the size of
+ base_text, returning string is shrinked to the size.
+ :param int size: a returning charactors size
+ :param str base_text: a string you want to repeat
+ :return: size string
+ :rtype: string
+ """
+ if not base_text:
+ base_text = 'test'
+ return ''.join(itertools.islice(itertools.cycle(base_text), size))
+
+
+def random_bytes(size=1024):
+ """Return size randomly selected bytes as a string
+
+ :param int size: a returning bytes size
+ :return: size randomly bytes
+ :rtype: string
+ """
+ return ''.join([chr(random.randint(0, 255))
+ for i in range(size)])
+
+
+def get_ipv6_addr_by_EUI64(cidr, mac):
+ """Generate a IPv6 addr by EUI-64 with CIDR and MAC
+
+ :param str cidr: a IPv6 CIDR
+ :param str mac: a MAC address
+ :return: an IPv6 Address
+ :rtype: netaddr.IPAddress
+ """
+ # Check if the prefix is IPv4 address
+ is_ipv4 = netaddr.valid_ipv4(cidr)
+ if is_ipv4:
+ msg = "Unable to generate IP address by EUI64 for IPv4 prefix"
+ raise TypeError(msg)
+ try:
+ eui64 = int(netaddr.EUI(mac).eui64())
+ prefix = netaddr.IPNetwork(cidr)
+ return netaddr.IPAddress(prefix.first + eui64 ^ (1 << 57))
+ except (ValueError, netaddr.AddrFormatError):
+ raise TypeError('Bad prefix or mac format for generating IPv6 '
+ 'address by EUI-64: %(prefix)s, %(mac)s:'
+ % {'prefix': cidr, 'mac': mac})
+ except TypeError:
+ raise TypeError('Bad prefix type for generate IPv6 address by '
+ 'EUI-64: %s' % cidr)
+
+
+# Courtesy of http://stackoverflow.com/a/312464
+def chunkify(sequence, chunksize):
+ """Yield successive chunks from `sequence`."""
+ for i in six.moves.xrange(0, len(sequence), chunksize):
+ yield sequence[i:i + chunksize]
diff --git a/tempest/lib/common/utils/misc.py b/tempest/lib/common/utils/misc.py
new file mode 100644
index 0000000..b97dd86
--- /dev/null
+++ b/tempest/lib/common/utils/misc.py
@@ -0,0 +1,87 @@
+# 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 inspect
+import re
+
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+def singleton(cls):
+ """Simple wrapper for classes that should only have a single instance."""
+ instances = {}
+
+ def getinstance():
+ if cls not in instances:
+ instances[cls] = cls()
+ return instances[cls]
+ return getinstance
+
+
+def find_test_caller():
+ """Find the caller class and test name.
+
+ Because we know that the interesting things that call us are
+ test_* methods, and various kinds of setUp / tearDown, we
+ can look through the call stack to find appropriate methods,
+ and the class we were in when those were called.
+ """
+ caller_name = None
+ names = []
+ frame = inspect.currentframe()
+ is_cleanup = False
+ # Start climbing the ladder until we hit a good method
+ while True:
+ try:
+ frame = frame.f_back
+ name = frame.f_code.co_name
+ names.append(name)
+ if re.search("^(test_|setUp|tearDown)", name):
+ cname = ""
+ if 'self' in frame.f_locals:
+ cname = frame.f_locals['self'].__class__.__name__
+ if 'cls' in frame.f_locals:
+ cname = frame.f_locals['cls'].__name__
+ caller_name = cname + ":" + name
+ break
+ elif re.search("^_run_cleanup", name):
+ is_cleanup = True
+ elif name == 'main':
+ caller_name = 'main'
+ break
+ else:
+ cname = ""
+ if 'self' in frame.f_locals:
+ cname = frame.f_locals['self'].__class__.__name__
+ if 'cls' in frame.f_locals:
+ cname = frame.f_locals['cls'].__name__
+
+ # the fact that we are running cleanups is indicated pretty
+ # deep in the stack, so if we see that we want to just
+ # start looking for a real class name, and declare victory
+ # once we do.
+ if is_cleanup and cname:
+ if not re.search("^RunTest", cname):
+ caller_name = cname + ":_run_cleanups"
+ break
+ except Exception:
+ break
+ # prevents frame leaks
+ del frame
+ if caller_name is None:
+ LOG.debug("Sane call name not found in %s" % names)
+ return caller_name
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
new file mode 100644
index 0000000..e78e624
--- /dev/null
+++ b/tempest/lib/decorators.py
@@ -0,0 +1,80 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 functools
+import uuid
+
+import six
+import testtools
+
+
+def skip_because(*args, **kwargs):
+ """A decorator useful to skip tests hitting known bugs
+
+ @param bug: bug number causing the test to skip
+ @param condition: optional condition to be True for the skip to have place
+ """
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ skip = False
+ if "condition" in kwargs:
+ if kwargs["condition"] is True:
+ skip = True
+ else:
+ skip = True
+ if "bug" in kwargs and skip is True:
+ if not kwargs['bug'].isdigit():
+ raise ValueError('bug must be a valid bug number')
+ msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def idempotent_id(id):
+ """Stub for metadata decorator"""
+ if not isinstance(id, six.string_types):
+ raise TypeError('Test idempotent_id must be string not %s'
+ '' % type(id).__name__)
+ uuid.UUID(id)
+
+ def decorator(f):
+ f = testtools.testcase.attr('id-%s' % id)(f)
+ if f.__doc__:
+ f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
+ else:
+ f.__doc__ = 'Test idempotent id: %s' % id
+ return f
+ return decorator
+
+
+class skip_unless_attr(object):
+ """Decorator to skip tests if a specified attr does not exists or False"""
+ def __init__(self, attr, msg=None):
+ self.attr = attr
+ self.message = msg or ("Test case attribute %s not found "
+ "or False") % attr
+
+ def __call__(self, func):
+ def _skipper(*args, **kw):
+ """Wrapped skipper function."""
+ testobj = args[0]
+ if not getattr(testobj, self.attr, False):
+ raise testtools.TestCase.skipException(self.message)
+ func(*args, **kw)
+ _skipper.__name__ = func.__name__
+ _skipper.__doc__ = func.__doc__
+ return _skipper
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
new file mode 100644
index 0000000..259bbbb
--- /dev/null
+++ b/tempest/lib/exceptions.py
@@ -0,0 +1,223 @@
+# 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 testtools
+
+
+class TempestException(Exception):
+ """Base Tempest Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = "An unknown exception occurred"
+
+ def __init__(self, *args, **kwargs):
+ super(TempestException, self).__init__()
+ try:
+ self._error_string = self.message % kwargs
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+ if len(args) > 0:
+ # If there is a non-kwarg parameter, assume it's the error
+ # message or reason description and tack it on to the end
+ # of the exception message
+ # Convert all arguments into their string representations...
+ args = ["%s" % arg for arg in args]
+ self._error_string = (self._error_string +
+ "\nDetails: %s" % '\n'.join(args))
+
+ def __str__(self):
+ return self._error_string
+
+
+class RestClientException(TempestException,
+ testtools.TestCase.failureException):
+ def __init__(self, resp_body=None, *args, **kwargs):
+ if 'resp' in kwargs:
+ self.resp = kwargs.get('resp')
+ self.resp_body = resp_body
+ message = kwargs.get("message", resp_body)
+ super(RestClientException, self).__init__(message, *args, **kwargs)
+
+
+class OtherRestClientException(RestClientException):
+ pass
+
+
+class ServerRestClientException(RestClientException):
+ pass
+
+
+class ClientRestClientException(RestClientException):
+ pass
+
+
+class InvalidHttpSuccessCode(OtherRestClientException):
+ message = "The success code is different than the expected one"
+
+
+class NotFound(ClientRestClientException):
+ message = "Object not found"
+
+
+class Unauthorized(ClientRestClientException):
+ message = 'Unauthorized'
+
+
+class Forbidden(ClientRestClientException):
+ message = "Forbidden"
+
+
+class TimeoutException(OtherRestClientException):
+ message = "Request timed out"
+
+
+class BadRequest(ClientRestClientException):
+ message = "Bad request"
+
+
+class UnprocessableEntity(ClientRestClientException):
+ message = "Unprocessable entity"
+
+
+class RateLimitExceeded(ClientRestClientException):
+ message = "Rate limit exceeded"
+
+
+class OverLimit(ClientRestClientException):
+ message = "Quota exceeded"
+
+
+class ServerFault(ServerRestClientException):
+ message = "Got server fault"
+
+
+class NotImplemented(ServerRestClientException):
+ message = "Got NotImplemented error"
+
+
+class Conflict(ClientRestClientException):
+ message = "An object with that identifier already exists"
+
+
+class Gone(ClientRestClientException):
+ message = "The requested resource is no longer available"
+
+
+class ResponseWithNonEmptyBody(OtherRestClientException):
+ message = ("RFC Violation! Response with %(status)d HTTP Status Code "
+ "MUST NOT have a body")
+
+
+class ResponseWithEntity(OtherRestClientException):
+ message = ("RFC Violation! Response with 205 HTTP Status Code "
+ "MUST NOT have an entity")
+
+
+class InvalidHTTPResponseBody(OtherRestClientException):
+ message = "HTTP response body is invalid json or xml"
+
+
+class InvalidHTTPResponseHeader(OtherRestClientException):
+ message = "HTTP response header is invalid"
+
+
+class InvalidContentType(ClientRestClientException):
+ message = "Invalid content type provided"
+
+
+class UnexpectedContentType(OtherRestClientException):
+ message = "Unexpected content type provided"
+
+
+class UnexpectedResponseCode(OtherRestClientException):
+ message = "Unexpected response code received"
+
+
+class InvalidStructure(TempestException):
+ message = "Invalid structure of table with details"
+
+
+class InvalidAPIVersionString(TempestException):
+ message = ("API Version String %(version)s is of invalid format. Must "
+ "be of format MajorNum.MinorNum or string 'latest'.")
+
+
+class JSONSchemaNotFound(TempestException):
+ message = ("JSON Schema for %(version)s is not found in\n"
+ " %(schema_versions_info)s")
+
+
+class InvalidAPIVersionRange(TempestException):
+ message = ("The API version range is invalid.")
+
+
+class BadAltAuth(TempestException):
+ """Used when trying and failing to change to alt creds.
+
+ If alt creds end up the same as primary creds, use this
+ exception. This is often going to be the case when you assume
+ project_id is in the url, but it's not.
+
+ """
+ message = "The alt auth looks the same as primary auth for %(part)s"
+
+
+class CommandFailed(Exception):
+ def __init__(self, returncode, cmd, output, stderr):
+ super(CommandFailed, self).__init__()
+ self.returncode = returncode
+ self.cmd = cmd
+ self.stdout = output
+ self.stderr = stderr
+
+ def __str__(self):
+ return ("Command '%s' returned non-zero exit status %d.\n"
+ "stdout:\n%s\n"
+ "stderr:\n%s" % (self.cmd,
+ self.returncode,
+ self.stdout,
+ self.stderr))
+
+
+class IdentityError(TempestException):
+ message = "Got identity error"
+
+
+class EndpointNotFound(TempestException):
+ message = "Endpoint not found"
+
+
+class InvalidCredentials(TempestException):
+ message = "Invalid Credentials"
+
+
+class InvalidScope(TempestException):
+ message = "Invalid Scope %(scope)s for %(auth_provider)s"
+
+
+class SSHTimeout(TempestException):
+ message = ("Connection to the %(host)s via SSH timed out.\n"
+ "User: %(user)s, Password: %(password)s")
+
+
+class SSHExecCommandFailed(TempestException):
+ """Raised when remotely executed command returns nonzero status."""
+ message = ("Command '%(command)s', exit status: %(exit_status)d, "
+ "stderr:\n%(stderr)s\n"
+ "stdout:\n%(stdout)s")
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/__init__.py
diff --git a/tempest/services/compute/__init__.py b/tempest/lib/services/compute/__init__.py
similarity index 100%
rename from tempest/services/compute/__init__.py
rename to tempest/lib/services/compute/__init__.py
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
new file mode 100644
index 0000000..6d3a817
--- /dev/null
+++ b/tempest/lib/services/compute/agents_client.py
@@ -0,0 +1,64 @@
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import agents as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class AgentsClient(base_compute_client.BaseComputeClient):
+ """Tests Agents API"""
+
+ def list_agents(self, **params):
+ """List all agent builds."""
+ url = 'os-agents'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_agents, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_agent(self, **kwargs):
+ """Create an agent build.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#agentbuild
+ """
+ post_body = json.dumps({'agent': kwargs})
+ resp, body = self.post('os-agents', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_agent(self, agent_id):
+ """Delete an existing agent build."""
+ resp, body = self.delete("os-agents/%s" % agent_id)
+ self.validate_response(schema.delete_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_agent(self, agent_id, **kwargs):
+ """Update an agent build.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatebuild
+ """
+ put_body = json.dumps({'para': kwargs})
+ resp, body = self.put('os-agents/%s' % agent_id, put_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
new file mode 100644
index 0000000..168126c
--- /dev/null
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -0,0 +1,117 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import aggregates as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class AggregatesClient(base_compute_client.BaseComputeClient):
+
+ def list_aggregates(self):
+ """Get aggregate list."""
+ resp, body = self.get("os-aggregates")
+ body = json.loads(body)
+ self.validate_response(schema.list_aggregates, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_aggregate(self, aggregate_id):
+ """Get details of the given aggregate."""
+ resp, body = self.get("os-aggregates/%s" % aggregate_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_aggregate(self, **kwargs):
+ """Create a new aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createaggregate
+ """
+ post_body = json.dumps({'aggregate': kwargs})
+ resp, body = self.post('os-aggregates', post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.create_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_aggregate(self, aggregate_id, **kwargs):
+ """Update an aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateaggregate
+ """
+ put_body = json.dumps({'aggregate': kwargs})
+ resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.update_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_aggregate(self, aggregate_id):
+ """Delete the given aggregate."""
+ resp, body = self.delete("os-aggregates/%s" % aggregate_id)
+ self.validate_response(schema.delete_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_aggregate(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'aggregate'
+
+ def add_host(self, aggregate_id, **kwargs):
+ """Add a host to the given aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addhost
+ """
+ post_body = json.dumps({'add_host': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_add_remove_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_host(self, aggregate_id, **kwargs):
+ """Remove a host from the given aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removehost
+ """
+ post_body = json.dumps({'remove_host': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_add_remove_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_metadata(self, aggregate_id, **kwargs):
+ """Replace the aggregate's existing metadata with new metadata."""
+ post_body = json.dumps({'set_metadata': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_set_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/availability_zone_client.py b/tempest/lib/services/compute/availability_zone_client.py
new file mode 100644
index 0000000..a911191
--- /dev/null
+++ b/tempest/lib/services/compute/availability_zone_client.py
@@ -0,0 +1,36 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import availability_zone \
+ as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class AvailabilityZoneClient(base_compute_client.BaseComputeClient):
+
+ def list_availability_zones(self, detail=False):
+ url = 'os-availability-zone'
+ schema_list = schema.list_availability_zone_list
+ if detail:
+ url += '/detail'
+ schema_list = schema.list_availability_zone_list_detail
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema_list, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/baremetal_nodes_client.py b/tempest/lib/services/compute/baremetal_nodes_client.py
new file mode 100644
index 0000000..06dc369
--- /dev/null
+++ b/tempest/lib/services/compute/baremetal_nodes_client.py
@@ -0,0 +1,43 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import baremetal_nodes \
+ as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class BaremetalNodesClient(base_compute_client.BaseComputeClient):
+ """Tests Baremetal API"""
+
+ def list_baremetal_nodes(self, **params):
+ """List all baremetal nodes."""
+ url = 'os-baremetal-nodes'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_baremetal_nodes, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_baremetal_node(self, baremetal_node_id):
+ """Return the details of a single baremetal node."""
+ url = 'os-baremetal-nodes/%s' % baremetal_node_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_baremetal_node, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/base_compute_client.py b/tempest/lib/services/compute/base_compute_client.py
new file mode 100644
index 0000000..433c94c
--- /dev/null
+++ b/tempest/lib/services/compute/base_compute_client.py
@@ -0,0 +1,96 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.common import api_version_request
+from tempest.lib.common import api_version_utils
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+
+COMPUTE_MICROVERSION = None
+
+
+class BaseComputeClient(rest_client.RestClient):
+ """Base compute service clients class to support microversion.
+
+ This class adds microversion to API request header if that is set
+ and provides interface to select appropriate JSON schema file for
+ response validation.
+
+ :param auth_provider: An auth provider object used to wrap requests in
+ auth
+ :param str service: The service name to use for the catalog lookup
+ :param str region: The region to use for the catalog lookup
+ :param kwargs: kwargs required by rest_client.RestClient
+ """
+
+ api_microversion_header_name = 'X-OpenStack-Nova-API-Version'
+
+ def __init__(self, auth_provider, service, region,
+ **kwargs):
+ super(BaseComputeClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+
+ def get_headers(self):
+ headers = super(BaseComputeClient, self).get_headers()
+ if COMPUTE_MICROVERSION:
+ headers[self.api_microversion_header_name] = COMPUTE_MICROVERSION
+ return headers
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+ resp, resp_body = super(BaseComputeClient, self).request(
+ method, url, extra_headers, headers, body, chunked)
+ if (COMPUTE_MICROVERSION and
+ COMPUTE_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+ api_version_utils.assert_version_header_matches_request(
+ self.api_microversion_header_name,
+ COMPUTE_MICROVERSION,
+ resp)
+ return resp, resp_body
+
+ def get_schema(self, schema_versions_info):
+ """Get JSON schema
+
+ This method provides the matching schema for requested
+ microversion.
+
+ :param schema_versions_info: List of dict which provides schema
+ information with range of valid versions.
+
+ Example::
+
+ schema_versions_info = [
+ {'min': None, 'max': '2.1', 'schema': schemav21},
+ {'min': '2.2', 'max': '2.9', 'schema': schemav22},
+ {'min': '2.10', 'max': None, 'schema': schemav210}]
+ """
+ schema = None
+ version = api_version_request.APIVersionRequest(COMPUTE_MICROVERSION)
+ for items in schema_versions_info:
+ min_version = api_version_request.APIVersionRequest(items['min'])
+ max_version = api_version_request.APIVersionRequest(items['max'])
+ # This is case where COMPUTE_MICROVERSION is None, which means
+ # request without microversion So select base v2.1 schema.
+ if version.is_null() and items['min'] is None:
+ schema = items['schema']
+ break
+ # else select appropriate schema as per COMPUTE_MICROVERSION
+ elif version.matches(min_version, max_version):
+ schema = items['schema']
+ break
+ if schema is None:
+ raise exceptions.JSONSchemaNotFound(
+ version=version.get_string(),
+ schema_versions_info=schema_versions_info)
+ return schema
diff --git a/tempest/lib/services/compute/certificates_client.py b/tempest/lib/services/compute/certificates_client.py
new file mode 100644
index 0000000..822756c
--- /dev/null
+++ b/tempest/lib/services/compute/certificates_client.py
@@ -0,0 +1,38 @@
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import certificates as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class CertificatesClient(base_compute_client.BaseComputeClient):
+
+ def show_certificate(self, certificate_id):
+ url = "os-certificates/%s" % certificate_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_certificate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_certificate(self):
+ """Create a certificate."""
+ url = "os-certificates"
+ resp, body = self.post(url, None)
+ body = json.loads(body)
+ self.validate_response(schema.create_certificate, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/extensions_client.py b/tempest/lib/services/compute/extensions_client.py
new file mode 100644
index 0000000..afaf282
--- /dev/null
+++ b/tempest/lib/services/compute/extensions_client.py
@@ -0,0 +1,35 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import extensions as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class ExtensionsClient(base_compute_client.BaseComputeClient):
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_extensions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/fixed_ips_client.py b/tempest/lib/services/compute/fixed_ips_client.py
new file mode 100644
index 0000000..c25ac2c
--- /dev/null
+++ b/tempest/lib/services/compute/fixed_ips_client.py
@@ -0,0 +1,41 @@
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import fixed_ips as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class FixedIPsClient(base_compute_client.BaseComputeClient):
+
+ def show_fixed_ip(self, fixed_ip):
+ url = "os-fixed-ips/%s" % fixed_ip
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_fixed_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reserve_fixed_ip(self, fixed_ip, **kwargs):
+ """Reserve/Unreserve a fixed IP.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#reserveIP
+ """
+ url = "os-fixed-ips/%s/action" % fixed_ip
+ resp, body = self.post(url, json.dumps(kwargs))
+ self.validate_response(schema.reserve_unreserve_fixed_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
new file mode 100644
index 0000000..e377c84
--- /dev/null
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -0,0 +1,179 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import flavors as schema
+from tempest.lib.api_schema.response.compute.v2_1 import flavors_access \
+ as schema_access
+from tempest.lib.api_schema.response.compute.v2_1 import flavors_extra_specs \
+ as schema_extra_specs
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class FlavorsClient(base_compute_client.BaseComputeClient):
+
+ def list_flavors(self, detail=False, **params):
+ url = 'flavors'
+ _schema = schema.list_flavors
+
+ if detail:
+ url += '/detail'
+ _schema = schema.list_flavors_details
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_flavor(self, flavor_id):
+ resp, body = self.get("flavors/%s" % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_flavor_details, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_flavor(self, **kwargs):
+ """Create a new flavor or instance type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#create-flavors
+ """
+ if kwargs.get('ephemeral'):
+ kwargs['OS-FLV-EXT-DATA:ephemeral'] = kwargs.pop('ephemeral')
+ if kwargs.get('is_public'):
+ kwargs['os-flavor-access:is_public'] = kwargs.pop('is_public')
+
+ post_body = json.dumps({'flavor': kwargs})
+ resp, body = self.post('flavors', post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.create_get_flavor_details, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_flavor(self, flavor_id):
+ """Delete the given flavor."""
+ resp, body = self.delete("flavors/{0}".format(flavor_id))
+ self.validate_response(schema.delete_flavor, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ # Did not use show_flavor(id) for verification as it gives
+ # 200 ok even for deleted id. LP #981263
+ # we can remove the loop here and use get by ID when bug gets sortedout
+ flavors = self.list_flavors(detail=True)['flavors']
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'flavor'
+
+ def set_flavor_extra_spec(self, flavor_id, **kwargs):
+ """Set extra Specs to the mentioned flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateFlavorExtraSpec
+ """
+ post_body = json.dumps({'extra_specs': kwargs})
+ resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_flavor_extra_specs(self, flavor_id):
+ """Get extra Specs details of the mentioned flavor."""
+ resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_flavor_extra_spec(self, flavor_id, key):
+ """Get extra Specs key-value of the mentioned flavor and key."""
+ resp, body = self.get('flavors/%s/os-extra_specs/%s' % (flavor_id,
+ key))
+ body = json.loads(body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update specified extra Specs of the mentioned flavor and key.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateflavorspec
+ """
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key), json.dumps(kwargs))
+ body = json.loads(body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def unset_flavor_extra_spec(self, flavor_id, key): # noqa
+ # NOTE: This noqa is for passing T111 check and we cannot rename
+ # to keep backwards compatibility.
+ """Unset extra Specs from the mentioned flavor."""
+ resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key))
+ self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_flavor_access(self, flavor_id):
+ """Get flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ post_body = {
+ 'addTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ post_body = {
+ 'removeTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ip_pools_client.py b/tempest/lib/services/compute/floating_ip_pools_client.py
new file mode 100644
index 0000000..d3af050
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ip_pools_client.py
@@ -0,0 +1,35 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class FloatingIPPoolsClient(base_compute_client.BaseComputeClient):
+
+ def list_floating_ip_pools(self, params=None):
+ """Gets all floating IP Pools list."""
+ url = 'os-floating-ip-pools'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ip_pools, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ips_bulk_client.py b/tempest/lib/services/compute/floating_ips_bulk_client.py
new file mode 100644
index 0000000..5f06009
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ips_bulk_client.py
@@ -0,0 +1,51 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class FloatingIPsBulkClient(base_compute_client.BaseComputeClient):
+
+ def create_floating_ips_bulk(self, ip_range, pool, interface):
+ """Allocate floating IPs in bulk."""
+ post_body = {
+ 'ip_range': ip_range,
+ 'pool': pool,
+ 'interface': interface
+ }
+ post_body = json.dumps({'floating_ips_bulk_create': post_body})
+ resp, body = self.post('os-floating-ips-bulk', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_floating_ips_bulk(self):
+ """Gets all floating IPs in bulk."""
+ resp, body = self.get('os-floating-ips-bulk')
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_floating_ips_bulk(self, ip_range):
+ """Deletes the provided floating IPs in bulk."""
+ post_body = json.dumps({'ip_range': ip_range})
+ resp, body = self.put('os-floating-ips-bulk/delete', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.delete_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
new file mode 100644
index 0000000..03e4894
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ips_client.py
@@ -0,0 +1,104 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class FloatingIPsClient(base_compute_client.BaseComputeClient):
+
+ def list_floating_ips(self, **params):
+ """Returns a list of all floating IPs filtered by any parameters."""
+ url = 'os-floating-ips'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ips, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_floating_ip(self, floating_ip_id):
+ """Get the details of a floating IP."""
+ url = "os-floating-ips/%s" % floating_ip_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_floating_ip(self, **kwargs):
+ """Allocate a floating IP to the project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createFloatingIP
+ """
+ url = 'os-floating-ips'
+ post_body = json.dumps(kwargs)
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_floating_ip(self, floating_ip_id):
+ """Deletes the provided floating IP from the project."""
+ url = "os-floating-ips/%s" % floating_ip_id
+ resp, body = self.delete(url)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def associate_floating_ip_to_server(self, floating_ip, server_id):
+ """Associate the provided floating IP to a specific server."""
+ url = "servers/%s/action" % server_id
+ post_body = {
+ 'addFloatingIp': {
+ 'address': floating_ip,
+ }
+ }
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def disassociate_floating_ip_from_server(self, floating_ip, server_id):
+ """Disassociate the provided floating IP from a specific server."""
+ url = "servers/%s/action" % server_id
+ post_body = {
+ 'removeFloatingIp': {
+ 'address': floating_ip,
+ }
+ }
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_floating_ip(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 'floating_ip'
diff --git a/tempest/lib/services/compute/hosts_client.py b/tempest/lib/services/compute/hosts_client.py
new file mode 100644
index 0000000..16b5edd
--- /dev/null
+++ b/tempest/lib/services/compute/hosts_client.py
@@ -0,0 +1,98 @@
+# Copyright 2013 IBM Corp.
+#
+# 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.api_schema.response.compute.v2_1 import hosts as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class HostsClient(base_compute_client.BaseComputeClient):
+
+ def list_hosts(self, **params):
+ """List all hosts."""
+
+ url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_host(self, hostname):
+ """Show detail information for the host."""
+
+ resp, body = self.get("os-hosts/%s" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.get_host_detail, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_host(self, hostname, **kwargs):
+ """Update a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#enablehost
+ """
+
+ request_body = {
+ 'status': None,
+ 'maintenance_mode': None,
+ }
+ request_body.update(**kwargs)
+ request_body = json.dumps(request_body)
+
+ resp, body = self.put("os-hosts/%s" % hostname, request_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def startup_host(self, hostname): # noqa
+ # NOTE: This noqa is for passing T110 check and we cannot rename
+ # to keep backwards compatibility. Actually, the root problem
+ # of this is a wrong API design. GET operation should not change
+ # resource status, but current API does that.
+ """Startup a host."""
+
+ resp, body = self.get("os-hosts/%s/startup" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.startup_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def shutdown_host(self, hostname): # noqa
+ # NOTE: This noqa is for passing T110 check and we cannot rename
+ # to keep backwards compatibility. Actually, the root problem
+ # of this is a wrong API design. GET operation should not change
+ # resource status, but current API does that.
+ """Shutdown a host."""
+
+ resp, body = self.get("os-hosts/%s/shutdown" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.shutdown_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reboot_host(self, hostname): # noqa
+ # NOTE: This noqa is for passing T110 check and we cannot rename
+ # to keep backwards compatibility. Actually, the root problem
+ # of this is a wrong API design. GET operation should not change
+ # resource status, but current API does that.
+ """Reboot a host."""
+
+ resp, body = self.get("os-hosts/%s/reboot" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.reboot_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
new file mode 100644
index 0000000..23c304e
--- /dev/null
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -0,0 +1,73 @@
+# Copyright 2013 IBM 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.api_schema.response.compute.v2_1 import hypervisors as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class HypervisorClient(base_compute_client.BaseComputeClient):
+
+ def list_hypervisors(self, detail=False):
+ """List hypervisors information."""
+ url = 'os-hypervisors'
+ _schema = schema.list_search_hypervisors
+ if detail:
+ url += '/detail'
+ _schema = schema.list_hypervisors_detail
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor(self, hypervisor_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hypervisor_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_servers_on_hypervisor(self, hypervisor_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hypervisor_name)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisors_servers, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor_statistics(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics')
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor_statistics, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor_uptime(self, hypervisor_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hypervisor_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor_uptime, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def search_hypervisor(self, hypervisor_name): # noqa
+ # NOTE: This noqa is for passing T110 check and we cannot rename
+ # to keep backwards compatibility.
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/search' % hypervisor_name)
+ body = json.loads(body)
+ self.validate_response(schema.list_search_hypervisors, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
new file mode 100644
index 0000000..da8a61e
--- /dev/null
+++ b/tempest/lib/services/compute/images_client.py
@@ -0,0 +1,145 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import images as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class ImagesClient(base_compute_client.BaseComputeClient):
+
+ def create_image(self, server_id, **kwargs):
+ """Create an image of the original server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createImage
+ """
+
+ post_body = {'createImage': kwargs}
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/action' % server_id,
+ post_body)
+ self.validate_response(schema.create_image, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_images(self, detail=False, **params):
+ """Return a list of all images filtered by any parameter.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listImages
+ """
+ url = 'images'
+ _schema = schema.list_images
+ if detail:
+ url += '/detail'
+ _schema = schema.list_images_details
+
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_image(self, image_id):
+ """Return the details of a single image."""
+ resp, body = self.get("images/%s" % image_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ self.validate_response(schema.get_image, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_image(self, image_id):
+ """Delete the provided image."""
+ resp, body = self.delete("images/%s" % image_id)
+ self.validate_response(schema.delete, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_image_metadata(self, image_id):
+ """List all metadata items for an image."""
+ resp, body = self.get("images/%s/metadata" % image_id)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_image_metadata(self, image_id, meta):
+ """Set the metadata for an image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createImageMetadata
+ """
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('images/%s/metadata' % image_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_image_metadata(self, image_id, meta):
+ """Update the metadata for an image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateImageMetadata
+ """
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('images/%s/metadata' % image_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_image_metadata_item(self, image_id, key):
+ """Return the value for a specific image metadata key."""
+ resp, body = self.get("images/%s/metadata/%s" % (image_id, key))
+ body = json.loads(body)
+ self.validate_response(schema.image_meta_item, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_image_metadata_item(self, image_id, key, meta):
+ """Set the value for a specific image metadata key.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#setImageMetadataItem
+ """
+ post_body = json.dumps({'meta': meta})
+ resp, body = self.put('images/%s/metadata/%s' % (image_id, key),
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_meta_item, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_image_metadata_item(self, image_id, key):
+ """Delete a single image metadata key/value pair."""
+ resp, body = self.delete("images/%s/metadata/%s" %
+ (image_id, key))
+ self.validate_response(schema.delete, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ # Added status check for user with admin role
+ try:
+ if self.show_image(id)['image']['status'] == 'DELETED':
+ return True
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'image'
diff --git a/tempest/lib/services/compute/instance_usage_audit_log_client.py b/tempest/lib/services/compute/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..1b94306
--- /dev/null
+++ b/tempest/lib/services/compute/instance_usage_audit_log_client.py
@@ -0,0 +1,39 @@
+# Copyright 2013 IBM 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.api_schema.response.compute.v2_1 import \
+ instance_usage_audit_logs as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class InstanceUsagesAuditLogClient(base_compute_client.BaseComputeClient):
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_instance_usage_audit_log,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_instance_usage_audit_log, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/interfaces_client.py b/tempest/lib/services/compute/interfaces_client.py
new file mode 100644
index 0000000..80192a1
--- /dev/null
+++ b/tempest/lib/services/compute/interfaces_client.py
@@ -0,0 +1,56 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import interfaces as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class InterfacesClient(base_compute_client.BaseComputeClient):
+
+ def list_interfaces(self, server_id):
+ resp, body = self.get('servers/%s/os-interface' % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_interface(self, server_id, **kwargs):
+ """Create an interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createAttachInterface
+ """
+ post_body = {'interfaceAttachment': kwargs}
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/os-interface' % server_id,
+ body=post_body)
+ body = json.loads(body)
+ self.validate_response(schema.get_create_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_interface(self, server_id, port_id):
+ resp, body = self.get('servers/%s/os-interface/%s' % (server_id,
+ port_id))
+ body = json.loads(body)
+ self.validate_response(schema.get_create_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_interface(self, server_id, port_id):
+ resp, body = self.delete('servers/%s/os-interface/%s' % (server_id,
+ port_id))
+ self.validate_response(schema.delete_interface, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
new file mode 100644
index 0000000..7b8e6b2
--- /dev/null
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -0,0 +1,70 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import keypairs as schemav21
+from tempest.lib.api_schema.response.compute.v2_2 import keypairs as schemav22
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class KeyPairsClient(base_compute_client.BaseComputeClient):
+
+ schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21},
+ {'min': '2.2', 'max': None, 'schema': schemav22}]
+
+ def list_keypairs(self, **params):
+ url = 'os-keypairs'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.list_keypairs, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_keypair(self, keypair_name, **params):
+ url = "os-keypairs/%s" % keypair_name
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.get_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_keypair(self, **kwargs):
+ """Create a keypair.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createKeypair
+ """
+ post_body = json.dumps({'keypair': kwargs})
+ resp, body = self.post("os-keypairs", body=post_body)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.create_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_keypair(self, keypair_name, **params):
+ url = "os-keypairs/%s" % keypair_name
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.delete(url)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.delete_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/limits_client.py b/tempest/lib/services/compute/limits_client.py
new file mode 100644
index 0000000..efe9889
--- /dev/null
+++ b/tempest/lib/services/compute/limits_client.py
@@ -0,0 +1,29 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import limits as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class LimitsClient(base_compute_client.BaseComputeClient):
+
+ def show_limits(self):
+ resp, body = self.get("limits")
+ body = json.loads(body)
+ self.validate_response(schema.get_limit, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
new file mode 100644
index 0000000..5eae8aa
--- /dev/null
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -0,0 +1,39 @@
+# Copyright 2014 NEC Corporation.
+#
+# 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.api_schema.response.compute.v2_1 import migrations as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class MigrationsClient(base_compute_client.BaseComputeClient):
+
+ def list_migrations(self, **params):
+ """List all migrations.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#returnmigrations
+ """
+
+ url = 'os-migrations'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_migrations, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/networks_client.py b/tempest/lib/services/compute/networks_client.py
new file mode 100644
index 0000000..6c8c943
--- /dev/null
+++ b/tempest/lib/services/compute/networks_client.py
@@ -0,0 +1,34 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class NetworksClient(base_compute_client.BaseComputeClient):
+
+ def list_networks(self):
+ resp, body = self.get("os-networks")
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_network(self, network_id):
+ resp, body = self.get("os-networks/%s" % network_id)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
new file mode 100644
index 0000000..9dc04ad
--- /dev/null
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -0,0 +1,49 @@
+# Copyright 2012 NTT Data
+# 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.api_schema.response.compute.v2_1\
+ import quota_classes as classes_schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class QuotaClassesClient(base_compute_client.BaseComputeClient):
+
+ def show_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(classes_schema.get_quota_class_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """Update the quota class's limits for one or more resources.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatequota
+ """
+ post_body = json.dumps({'quota_class_set': kwargs})
+
+ resp, body = self.put('os-quota-class-sets/%s' % quota_class_id,
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(classes_schema.update_quota_class_set,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
new file mode 100644
index 0000000..184a3d7
--- /dev/null
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -0,0 +1,69 @@
+# Copyright 2012 NTT Data
+# 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.api_schema.response.compute.v2_1 import quotas as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class QuotasClient(base_compute_client.BaseComputeClient):
+
+ def show_quota_set(self, tenant_id, user_id=None):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % tenant_id
+ if user_id:
+ url += '?user_id=%s' % user_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % tenant_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_set(self, tenant_id, user_id=None, **kwargs):
+ """Updates the tenant's quota limits for one or more resources.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatesquotatenant
+ """
+
+ post_body = json.dumps({'quota_set': kwargs})
+
+ if user_id:
+ resp, body = self.put('os-quota-sets/%s?user_id=%s' %
+ (tenant_id, user_id), post_body)
+ else:
+ resp, body = self.put('os-quota-sets/%s' % tenant_id,
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.update_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+ self.validate_response(schema.delete_quota, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_group_default_rules_client.py b/tempest/lib/services/compute/security_group_default_rules_client.py
new file mode 100644
index 0000000..d57c8e0
--- /dev/null
+++ b/tempest/lib/services/compute/security_group_default_rules_client.py
@@ -0,0 +1,65 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_group_default_rule as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class SecurityGroupDefaultRulesClient(base_compute_client.BaseComputeClient):
+
+ def create_security_default_group_rule(self, **kwargs):
+ """Create security group default rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html
+ #createSecGroupDefaultRule
+ """
+ post_body = json.dumps({'security_group_default_rule': kwargs})
+ url = 'os-security-group-default-rules'
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group_default_rule(self,
+ security_group_default_rule_id):
+ """Delete the provided Security Group default rule."""
+ resp, body = self.delete('os-security-group-default-rules/%s' % (
+ security_group_default_rule_id))
+ self.validate_response(schema.delete_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_security_group_default_rules(self):
+ """List all Security Group default rules."""
+ resp, body = self.get('os-security-group-default-rules')
+ body = json.loads(body)
+ self.validate_response(schema.list_security_group_default_rules,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_security_group_default_rule(self, security_group_default_rule_id):
+ """Return the details of provided Security Group default rule."""
+ resp, body = self.get('os-security-group-default-rules/%s' %
+ security_group_default_rule_id)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_group_rules_client.py b/tempest/lib/services/compute/security_group_rules_client.py
new file mode 100644
index 0000000..c969b81
--- /dev/null
+++ b/tempest/lib/services/compute/security_group_rules_client.py
@@ -0,0 +1,44 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_groups as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class SecurityGroupRulesClient(base_compute_client.BaseComputeClient):
+
+ def create_security_group_rule(self, **kwargs):
+ """Create a new security group rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSecGroupRule
+ """
+ post_body = json.dumps({'security_group_rule': kwargs})
+ url = 'os-security-group-rules'
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_security_group_rule, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group_rule(self, group_rule_id):
+ """Deletes the provided Security Group rule."""
+ resp, body = self.delete('os-security-group-rules/%s' %
+ group_rule_id)
+ self.validate_response(schema.delete_security_group_rule, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
new file mode 100644
index 0000000..6b9c7e1
--- /dev/null
+++ b/tempest/lib/services/compute/security_groups_client.py
@@ -0,0 +1,90 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_groups as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class SecurityGroupsClient(base_compute_client.BaseComputeClient):
+
+ def list_security_groups(self, **params):
+ """List all security groups for a user."""
+
+ url = 'os-security-groups'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_security_groups, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_security_group(self, security_group_id):
+ """Get the details of a Security Group."""
+ url = "os-security-groups/%s" % security_group_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_security_group(self, **kwargs):
+ """Create a new security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSecGroup
+ """
+ post_body = json.dumps({'security_group': kwargs})
+ resp, body = self.post('os-security-groups', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.get_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_security_group(self, security_group_id, **kwargs):
+ """Update a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateSecGroup
+ """
+ post_body = json.dumps({'security_group': kwargs})
+ resp, body = self.put('os-security-groups/%s' % security_group_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group(self, security_group_id):
+ """Delete the provided Security Group."""
+ resp, body = self.delete(
+ 'os-security-groups/%s' % security_group_id)
+ self.validate_response(schema.delete_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_security_group(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'security_group'
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
new file mode 100644
index 0000000..e370457
--- /dev/null
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -0,0 +1,57 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class ServerGroupsClient(base_compute_client.BaseComputeClient):
+
+ def create_server_group(self, **kwargs):
+ """Create the server group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createServerGroup
+ """
+ post_body = json.dumps({'server_group': kwargs})
+ resp, body = self.post('os-server-groups', post_body)
+
+ body = json.loads(body)
+ 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)
+ self.validate_response(schema.delete_server_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_server_groups(self):
+ """List the server-groups."""
+ resp, body = self.get("os-server-groups")
+ body = json.loads(body)
+ self.validate_response(schema.list_server_groups, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server_group(self, server_group_id):
+ """Get the details of given server_group."""
+ resp, body = self.get("os-server-groups/%s" % server_group_id)
+ body = json.loads(body)
+ 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
new file mode 100644
index 0000000..8e4eca1
--- /dev/null
+++ b/tempest/lib/services/compute/servers_client.py
@@ -0,0 +1,581 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
+from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class ServersClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.8', 'schema': schema},
+ {'min': '2.9', 'max': '2.18', 'schema': schemav29},
+ {'min': '2.19', 'max': None, 'schema': schemav219}]
+
+ def __init__(self, auth_provider, service, region,
+ enable_instance_password=True, **kwargs):
+ super(ServersClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+ self.enable_instance_password = enable_instance_password
+
+ def create_server(self, **kwargs):
+ """Create server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createServer
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ :param scheduler_hints: The name is changed to os:scheduler_hints and
+ the parameter is set in the same level as the parameter 'server'.
+ """
+ body = copy.deepcopy(kwargs)
+ if body.get('disk_config'):
+ body['OS-DCF:diskConfig'] = body.pop('disk_config')
+
+ hints = None
+ if body.get('scheduler_hints'):
+ hints = {'os:scheduler_hints': body.pop('scheduler_hints')}
+
+ post_body = {'server': body}
+
+ if hints:
+ post_body.update(hints)
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers', post_body)
+
+ body = json.loads(body)
+ # NOTE(maurosr): this deals with the case of multiple server create
+ # with return reservation id set True
+ if 'reservation_id' in body:
+ return rest_client.ResponseBody(resp, body)
+ if self.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_server(self, server_id, **kwargs):
+ """Update server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateServer
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ if kwargs.get('disk_config'):
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+
+ post_body = json.dumps({'server': kwargs})
+ resp, body = self.put("servers/%s" % server_id, post_body)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.update_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server(self, server_id):
+ """Get server details."""
+ resp, body = self.get("servers/%s" % server_id)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.get_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_server(self, server_id):
+ """Delete server."""
+ resp, body = self.delete("servers/%s" % server_id)
+ self.validate_response(schema.delete_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_servers(self, detail=False, **params):
+ """List servers.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listServers
+ and http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listDetailServers
+ """
+
+ url = 'servers'
+ schema = self.get_schema(self.schema_versions_info)
+ _schema = schema.list_servers
+
+ if detail:
+ url += '/detail'
+ _schema = schema.list_servers_detail
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_addresses(self, server_id):
+ """Lists all addresses for a server."""
+ resp, body = self.get("servers/%s/ips" % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_addresses_by_network(self, server_id, network_id):
+ """Lists all addresses of a specific network type for a server."""
+ resp, body = self.get("servers/%s/ips/%s" %
+ (server_id, network_id))
+ body = json.loads(body)
+ self.validate_response(schema.list_addresses_by_network, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def action(self, server_id, action_name,
+ schema=schema.server_actions_common_schema,
+ **kwargs):
+ post_body = json.dumps({action_name: kwargs})
+ resp, body = self.post('servers/%s/action' % server_id,
+ post_body)
+ if body:
+ body = json.loads(body)
+ self.validate_response(schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_backup(self, server_id, **kwargs):
+ """Backup a server instance.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createBackup
+ """
+ return self.action(server_id, "createBackup", **kwargs)
+
+ def change_password(self, server_id, **kwargs):
+ """Change the root password for the server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#changePassword
+ """
+ return self.action(server_id, 'changePassword', **kwargs)
+
+ def show_password(self, server_id):
+ resp, body = self.get("servers/%s/os-server-password" %
+ server_id)
+ body = json.loads(body)
+ self.validate_response(schema.show_password, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_password(self, server_id):
+ """Removes the encrypted server password from the metadata server
+
+ Note that this does not actually change the instance server
+ password.
+ """
+ resp, body = self.delete("servers/%s/os-server-password" %
+ server_id)
+ self.validate_response(schema.server_actions_delete_password,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reboot_server(self, server_id, **kwargs):
+ """Reboot a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#reboot
+ """
+ return self.action(server_id, 'reboot', **kwargs)
+
+ def rebuild_server(self, server_id, image_ref, **kwargs):
+ """Rebuild a server with a new image.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#rebuild
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ kwargs['imageRef'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+ schema = self.get_schema(self.schema_versions_info)
+ if self.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild',
+ rebuild_schema, **kwargs)
+
+ def resize_server(self, server_id, flavor_ref, **kwargs):
+ """Change the flavor of a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resize
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ kwargs['flavorRef'] = flavor_ref
+ if 'disk_config' in kwargs:
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+ return self.action(server_id, 'resize', **kwargs)
+
+ def confirm_resize_server(self, server_id, **kwargs):
+ """Confirm the flavor change for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#confirmResize
+ """
+ return self.action(server_id, 'confirmResize',
+ schema.server_actions_confirm_resize,
+ **kwargs)
+
+ def revert_resize_server(self, server_id, **kwargs):
+ """Revert a server back to its original flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#revertResize
+ """
+ return self.action(server_id, 'revertResize', **kwargs)
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_server_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ if no_metadata_field:
+ post_body = ""
+ else:
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.set_server_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_server_metadata(self, server_id, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('servers/%s/metadata' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_server_metadata,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (server_id, key))
+ body = json.loads(body)
+ self.validate_response(schema.set_show_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ post_body = json.dumps({'meta': meta})
+ resp, body = self.put('servers/%s/metadata/%s' % (server_id, key),
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.set_show_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (server_id, key))
+ self.validate_response(schema.delete_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def stop_server(self, server_id, **kwargs):
+ return self.action(server_id, 'os-stop', **kwargs)
+
+ def start_server(self, server_id, **kwargs):
+ return self.action(server_id, 'os-start', **kwargs)
+
+ def attach_volume(self, server_id, **kwargs):
+ """Attaches a volume to a server instance."""
+ post_body = json.dumps({'volumeAttachment': kwargs})
+ resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.attach_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_attached_volume(self, server_id, attachment_id, **kwargs):
+ """Swaps a volume attached to an instance for another volume"""
+ post_body = json.dumps({'volumeAttachment': kwargs})
+ resp, body = self.put('servers/%s/os-volume_attachments/%s' %
+ (server_id, attachment_id),
+ post_body)
+ self.validate_response(schema.update_attached_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def detach_volume(self, server_id, volume_id): # noqa
+ """Detaches a volume from a server instance."""
+ resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
+ (server_id, volume_id))
+ self.validate_response(schema.detach_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_attachment(self, server_id, volume_id):
+ """Return details about the given volume attachment."""
+ resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
+ server_id, volume_id))
+ body = json.loads(body)
+ self.validate_response(schema.show_volume_attachment, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_attachments(self, server_id):
+ """Returns the list of volume attachments for a given instance."""
+ resp, body = self.get('servers/%s/os-volume_attachments' % (
+ server_id))
+ body = json.loads(body)
+ self.validate_response(schema.list_volume_attachments, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_security_group(self, server_id, **kwargs):
+ """Add a security group to the server.
+
+ Available params: TODO
+ """
+ # TODO(oomichi): The api-site doesn't contain this API description.
+ # So the above should be changed to the api-site link after
+ # adding the description on the api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524199
+ return self.action(server_id, 'addSecurityGroup', **kwargs)
+
+ def remove_security_group(self, server_id, **kwargs):
+ """Remove a security group from the server.
+
+ Available params: TODO
+ """
+ # TODO(oomichi): The api-site doesn't contain this API description.
+ # So the above should be changed to the api-site link after
+ # adding the description on the api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524199
+ return self.action(server_id, 'removeSecurityGroup', **kwargs)
+
+ def live_migrate_server(self, server_id, **kwargs):
+ """This should be called with administrator privileges.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#migrateLive
+ """
+ return self.action(server_id, 'os-migrateLive', **kwargs)
+
+ def migrate_server(self, server_id, **kwargs):
+ """Migrate a server to a new host.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#migrate
+ """
+ return self.action(server_id, 'migrate', **kwargs)
+
+ def lock_server(self, server_id, **kwargs):
+ """Lock the given server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#lock
+ """
+ return self.action(server_id, 'lock', **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """UNlock the given server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unlock
+ """
+ return self.action(server_id, 'unlock', **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspend the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#suspend
+ """
+ return self.action(server_id, 'suspend', **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspend the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resume
+ """
+ return self.action(server_id, 'resume', **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pause the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#pause
+ """
+ return self.action(server_id, 'pause', **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pause the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unpause
+ """
+ return self.action(server_id, 'unpause', **kwargs)
+
+ def reset_state(self, server_id, **kwargs):
+ """Reset the state of a server to active/error.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resetState
+ """
+ return self.action(server_id, 'os-resetState', **kwargs)
+
+ def shelve_server(self, server_id, **kwargs):
+ """Shelve the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#shelve
+ """
+ return self.action(server_id, 'shelve', **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelve the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unshelve
+ """
+ return self.action(server_id, 'unshelve', **kwargs)
+
+ def shelve_offload_server(self, server_id, **kwargs):
+ """Shelve-offload the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#shelveOffload
+ """
+ return self.action(server_id, 'shelveOffload', **kwargs)
+
+ def get_console_output(self, server_id, **kwargs):
+ """Get console output.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#getConsoleOutput
+ """
+ return self.action(server_id, 'os-getConsoleOutput',
+ schema.get_console_output, **kwargs)
+
+ def list_virtual_interfaces(self, server_id):
+ """List the virtual interfaces used in an instance."""
+ resp, body = self.get('/'.join(['servers', server_id,
+ 'os-virtual-interfaces']))
+ body = json.loads(body)
+ self.validate_response(schema.list_virtual_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def rescue_server(self, server_id, **kwargs):
+ """Rescue the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#rescue
+ """
+ return self.action(server_id, 'rescue', schema.rescue_server, **kwargs)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue')
+
+ 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))
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" %
+ server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_instance_actions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (server_id, request_id))
+ body = json.loads(body)
+ self.validate_response(schema.show_instance_action, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#forceDelete
+ """
+ return self.action(server_id, 'forceDelete', **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#restore
+ """
+ return self.action(server_id, 'restore', **kwargs)
+
+ def reset_network(self, server_id, **kwargs):
+ """Reset the Network of a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resetNetwork
+ """
+ return self.action(server_id, 'resetNetwork', **kwargs)
+
+ def inject_network_info(self, server_id, **kwargs):
+ """Inject the Network Info into server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#injectNetworkInfo
+ """
+ return self.action(server_id, 'injectNetworkInfo', **kwargs)
+
+ def get_vnc_console(self, server_id, **kwargs):
+ """Get URL of VNC console.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#getVNCConsole
+ """
+ return self.action(server_id, "os-getVNCConsole",
+ schema.get_vnc_console, **kwargs)
+
+ def add_fixed_ip(self, server_id, **kwargs):
+ """Add a fixed IP to server instance.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addFixedIp
+ """
+ return self.action(server_id, 'addFixedIp', **kwargs)
+
+ def remove_fixed_ip(self, server_id, **kwargs):
+ """Remove input fixed IP from input server instance.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removeFixedIp
+ """
+ return self.action(server_id, 'removeFixedIp', **kwargs)
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
new file mode 100644
index 0000000..a190e5f
--- /dev/null
+++ b/tempest/lib/services/compute/services_client.py
@@ -0,0 +1,59 @@
+# Copyright 2013 NEC Corporation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import services as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class ServicesClient(base_compute_client.BaseComputeClient):
+
+ def list_services(self, **params):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_services, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def enable_service(self, **kwargs):
+ """Enable service on a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#enableScheduling
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/enable', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.enable_disable_service, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def disable_service(self, **kwargs):
+ """Disable service on a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#disableScheduling
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/disable', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.enable_disable_service, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/snapshots_client.py b/tempest/lib/services/compute/snapshots_client.py
new file mode 100644
index 0000000..be41957
--- /dev/null
+++ b/tempest/lib/services/compute/snapshots_client.py
@@ -0,0 +1,77 @@
+# Copyright 2015 Fujitsu(fnst) 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 six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import snapshots as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class SnapshotsClient(base_compute_client.BaseComputeClient):
+
+ def create_snapshot(self, volume_id, **kwargs):
+ """Create a snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSnapshot
+ """
+ post_body = {
+ 'volume_id': volume_id
+ }
+ post_body.update(kwargs)
+ post_body = json.dumps({'snapshot': post_body})
+ resp, body = self.post('os-snapshots', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_snapshot(self, snapshot_id):
+ url = "os-snapshots/%s" % snapshot_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_snapshots(self, detail=False, params=None):
+ url = 'os-snapshots'
+
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_snapshots, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_snapshot(self, snapshot_id):
+ resp, body = self.delete("os-snapshots/%s" % snapshot_id)
+ self.validate_response(schema.delete_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_snapshot(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'snapshot'
diff --git a/tempest/lib/services/compute/tenant_networks_client.py b/tempest/lib/services/compute/tenant_networks_client.py
new file mode 100644
index 0000000..04d8bab
--- /dev/null
+++ b/tempest/lib/services/compute/tenant_networks_client.py
@@ -0,0 +1,35 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import tenant_networks
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class TenantNetworksClient(base_compute_client.BaseComputeClient):
+
+ def list_tenant_networks(self):
+ resp, body = self.get("os-tenant-networks")
+ body = json.loads(body)
+ self.validate_response(tenant_networks.list_tenant_networks, resp,
+ body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_tenant_network(self, network_id):
+ resp, body = self.get("os-tenant-networks/%s" % network_id)
+ body = json.loads(body)
+ self.validate_response(tenant_networks.get_tenant_network, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/tenant_usages_client.py b/tempest/lib/services/compute/tenant_usages_client.py
new file mode 100644
index 0000000..5a748c7
--- /dev/null
+++ b/tempest/lib/services/compute/tenant_usages_client.py
@@ -0,0 +1,44 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import tenant_usages
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class TenantUsagesClient(base_compute_client.BaseComputeClient):
+
+ def list_tenant_usages(self, **params):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(tenant_usages.list_tenant_usage, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_tenant_usage(self, tenant_id, **params):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(tenant_usages.get_tenant_usage, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/versions_client.py b/tempest/lib/services/compute/versions_client.py
new file mode 100644
index 0000000..eb4e7e9
--- /dev/null
+++ b/tempest/lib/services/compute/versions_client.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 re
+
+from oslo_serialization import jsonutils as json
+from six.moves import urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import versions as schema
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class VersionsClient(base_compute_client.BaseComputeClient):
+
+ def _get_base_version_url(self):
+ # NOTE: The URL which is got from keystone's catalog contains
+ # API version and project-id like "/app-name/v2/{project-id}" or
+ # "/v2/{project-id}", but we need to access the URL which doesn't
+ # contain API version for getting API versions. For that, here
+ # should use raw_request() instead of get().
+ endpoint = self.base_url
+ url = urllib.parse.urlsplit(endpoint)
+ new_path = re.split(r'(^|/)+v\d+(\.\d+)?', url.path)[0]
+ url = list(url)
+ url[2] = new_path + '/'
+ return urllib.parse.urlunsplit(url)
+
+ def list_versions(self):
+ version_url = self._get_base_version_url()
+ resp, body = self.raw_request(version_url, 'GET')
+ body = json.loads(body)
+ self.validate_response(schema.list_versions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_version_by_url(self, version_url):
+ """Get the version document by url.
+
+ This gets the version document for a url, useful in testing
+ the contents of things like /v2/ or /v2.1/ in Nova. That
+ controller needs authenticated access, so we have to get
+ ourselves a token before making the request.
+
+ """
+ # we need a token for this request
+ resp, body = self.raw_request(version_url, 'GET',
+ {'X-Auth-Token': self.token})
+ body = json.loads(body)
+ self.validate_response(schema.get_one_version, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
new file mode 100644
index 0000000..41d9af2
--- /dev/null
+++ b/tempest/lib/services/compute/volumes_client.py
@@ -0,0 +1,77 @@
+# 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.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import volumes as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import base_compute_client
+
+
+class VolumesClient(base_compute_client.BaseComputeClient):
+
+ def list_volumes(self, detail=False, **params):
+ """List all the volumes created."""
+ url = 'os-volumes'
+
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_volumes, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume(self, volume_id):
+ """Return the details of a single volume."""
+ url = "os-volumes/%s" % volume_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume(self, **kwargs):
+ """Create a new Volume.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createVolume
+ """
+ post_body = json.dumps({'volume': kwargs})
+ resp, body = self.post('os-volumes', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume(self, volume_id):
+ """Delete the Specified Volume."""
+ resp, body = self.delete("os-volumes/%s" % volume_id)
+ self.validate_response(schema.delete_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_volume(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'volume'
diff --git a/tempest/api/telemetry/__init__.py b/tempest/lib/services/identity/__init__.py
similarity index 100%
rename from tempest/api/telemetry/__init__.py
rename to tempest/lib/services/identity/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/identity/v2/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/identity/v2/__init__.py
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
new file mode 100644
index 0000000..5716027
--- /dev/null
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -0,0 +1,125 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+
+
+class TokenClient(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(TokenClient, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+
+ if auth_url is None:
+ raise exceptions.IdentityError("Couldn't determine auth_url")
+
+ # Normalize URI to ensure /tokens is in it.
+ if 'tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/tokens'
+
+ self.auth_url = auth_url
+
+ def auth(self, user, password, tenant=None):
+ creds = {
+ 'auth': {
+ 'passwordCredentials': {
+ 'username': user,
+ 'password': password,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds, sort_keys=True)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return rest_client.ResponseBody(resp, body['access'])
+
+ def auth_token(self, token_id, tenant=None):
+ creds = {
+ 'auth': {
+ 'token': {
+ 'id': token_id,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return rest_client.ResponseBody(resp, body['access'])
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+ """A simple HTTP request interface.
+
+ Note: this overloads the `request` method from the parent class and
+ thus must implement the same method signature.
+ """
+ if headers is None:
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp, req_headers=headers,
+ req_body='<omitted>', resp_body=resp_body)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise exceptions.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ return resp, json.loads(resp_body)
+
+ def get_token(self, user, password, tenant, auth_data=False):
+ """Returns (token id, token data) for supplied credentials."""
+ body = self.auth(user, password, tenant)
+
+ if auth_data:
+ return body['token']['id'], body
+ else:
+ return body['token']['id']
+
+
+class TokenClientJSON(TokenClient):
+ LOG = logging.getLogger(__name__)
+
+ def _warn(self):
+ self.LOG.warning("%s class was deprecated and renamed to %s" %
+ (self.__class__.__name__, 'TokenClient'))
+
+ def __init__(self, *args, **kwargs):
+ self._warn()
+ super(TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/identity/v3/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/identity/v3/__init__.py
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
new file mode 100644
index 0000000..964d43f
--- /dev/null
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -0,0 +1,187 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+
+
+class V3TokenClient(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(V3TokenClient, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+
+ if auth_url is None:
+ raise exceptions.IdentityError("Couldn't determine auth_url")
+
+ if 'auth/tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/auth/tokens'
+
+ self.auth_url = auth_url
+
+ def auth(self, user_id=None, username=None, password=None, project_id=None,
+ project_name=None, user_domain_id=None, user_domain_name=None,
+ project_domain_id=None, project_domain_name=None, domain_id=None,
+ domain_name=None, token=None):
+ """Obtains a token from the authentication service
+
+ :param user_id: user id
+ :param username: user name
+ :param user_domain_id: the user domain id
+ :param user_domain_name: the user domain name
+ :param project_domain_id: the project domain id
+ :param project_domain_name: the project domain name
+ :param domain_id: a domain id to scope to
+ :param domain_name: a domain name to scope to
+ :param project_id: a project id to scope to
+ :param project_name: a project name to scope to
+ :param token: a token to re-scope.
+
+ Accepts different combinations of credentials.
+ Sample sample valid combinations:
+ - token
+ - token, project_name, project_domain_id
+ - user_id, password
+ - username, password, user_domain_id
+ - username, password, project_name, user_domain_id, project_domain_id
+ Validation is left to the server side.
+ """
+ creds = {
+ 'auth': {
+ 'identity': {
+ 'methods': [],
+ }
+ }
+ }
+ id_obj = creds['auth']['identity']
+ if token:
+ id_obj['methods'].append('token')
+ id_obj['token'] = {
+ 'id': token
+ }
+
+ if (user_id or username) and password:
+ id_obj['methods'].append('password')
+ id_obj['password'] = {
+ 'user': {
+ 'password': password,
+ }
+ }
+ if user_id:
+ id_obj['password']['user']['id'] = user_id
+ else:
+ id_obj['password']['user']['name'] = username
+
+ _domain = None
+ if user_domain_id is not None:
+ _domain = dict(id=user_domain_id)
+ elif user_domain_name is not None:
+ _domain = dict(name=user_domain_name)
+ if _domain:
+ id_obj['password']['user']['domain'] = _domain
+
+ if (project_id or project_name):
+ _project = dict()
+
+ if project_id:
+ _project['id'] = project_id
+ elif project_name:
+ _project['name'] = project_name
+
+ if project_domain_id is not None:
+ _project['domain'] = {'id': project_domain_id}
+ elif project_domain_name is not None:
+ _project['domain'] = {'name': project_domain_name}
+
+ creds['auth']['scope'] = dict(project=_project)
+ elif domain_id:
+ creds['auth']['scope'] = dict(domain={'id': domain_id})
+ elif domain_name:
+ creds['auth']['scope'] = dict(domain={'name': domain_name})
+
+ body = json.dumps(creds, sort_keys=True)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(201, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+ """A simple HTTP request interface.
+
+ Note: this overloads the `request` method from the parent class and
+ thus must implement the same method signature.
+ """
+ if headers is None:
+ # Always accept 'json', for xml token client too.
+ # Because XML response is not easily
+ # converted to the corresponding JSON one
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp, req_headers=headers,
+ req_body='<omitted>', resp_body=resp_body)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise exceptions.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201, 204]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ return resp, json.loads(resp_body)
+
+ def get_token(self, **kwargs):
+ """Returns (token id, token data) for supplied credentials"""
+
+ auth_data = kwargs.pop('auth_data', False)
+
+ if not (kwargs.get('user_domain_id') or
+ kwargs.get('user_domain_name')):
+ kwargs['user_domain_name'] = 'Default'
+
+ if not (kwargs.get('project_domain_id') or
+ kwargs.get('project_domain_name')):
+ kwargs['project_domain_name'] = 'Default'
+
+ body = self.auth(**kwargs)
+
+ token = body.response.get('x-subject-token')
+ if auth_data:
+ return token, body['token']
+ else:
+ return token
+
+
+class V3TokenClientJSON(V3TokenClient):
+ LOG = logging.getLogger(__name__)
+
+ def _warn(self):
+ self.LOG.warning("%s class was deprecated and renamed to %s" %
+ (self.__class__.__name__, 'V3TokenClient'))
+
+ def __init__(self, *args, **kwargs):
+ self._warn()
+ super(V3TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/network/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/network/__init__.py
diff --git a/tempest/services/network/json/agents_client.py b/tempest/lib/services/network/agents_client.py
similarity index 97%
rename from tempest/services/network/json/agents_client.py
rename to tempest/lib/services/network/agents_client.py
index 8bec847..c5d4c66 100644
--- a/tempest/services/network/json/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class AgentsClient(base.BaseNetworkClient):
diff --git a/tempest/services/network/json/base.py b/tempest/lib/services/network/base.py
similarity index 85%
rename from tempest/services/network/json/base.py
rename to tempest/lib/services/network/base.py
index 6ebc245..a6ada04 100644
--- a/tempest/services/network/json/base.py
+++ b/tempest/lib/services/network/base.py
@@ -13,10 +13,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseNetworkClient(service_client.ServiceClient):
+class BaseNetworkClient(rest_client.RestClient):
"""Base class for Tempest REST clients for Neutron.
@@ -34,13 +34,13 @@
resp, body = self.get(req_uri)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_resource(self, uri):
req_uri = self.uri_prefix + uri
resp, body = self.delete(req_uri)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource(self, uri, **fields):
# fields is a dict which key is 'fields' and value is a
@@ -52,7 +52,7 @@
resp, body = self.get(req_uri)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_resource(self, uri, post_data):
req_uri = self.uri_prefix + uri
@@ -60,7 +60,7 @@
resp, body = self.post(req_uri, req_post_data)
body = json.loads(body)
self.expected_success(201, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_resource(self, uri, post_data):
req_uri = self.uri_prefix + uri
@@ -68,4 +68,4 @@
resp, body = self.put(req_uri, req_post_data)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/network/json/extensions_client.py b/tempest/lib/services/network/extensions_client.py
similarity index 94%
rename from tempest/services/network/json/extensions_client.py
rename to tempest/lib/services/network/extensions_client.py
index 64d3a4f..3910c84 100644
--- a/tempest/services/network/json/extensions_client.py
+++ b/tempest/lib/services/network/extensions_client.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class ExtensionsClient(base.BaseNetworkClient):
diff --git a/tempest/lib/services/network/floating_ips_client.py b/tempest/lib/services/network/floating_ips_client.py
new file mode 100644
index 0000000..1968e05
--- /dev/null
+++ b/tempest/lib/services/network/floating_ips_client.py
@@ -0,0 +1,38 @@
+# 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 base
+
+
+class FloatingIPsClient(base.BaseNetworkClient):
+
+ def create_floatingip(self, **kwargs):
+ uri = '/floatingips'
+ post_data = {'floatingip': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_floatingip(self, floatingip_id, **kwargs):
+ uri = '/floatingips/%s' % floatingip_id
+ post_data = {'floatingip': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_floatingip(self, floatingip_id, **fields):
+ uri = '/floatingips/%s' % floatingip_id
+ return self.show_resource(uri, **fields)
+
+ def delete_floatingip(self, floatingip_id):
+ uri = '/floatingips/%s' % floatingip_id
+ return self.delete_resource(uri)
+
+ def list_floatingips(self, **filters):
+ uri = '/floatingips'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
new file mode 100644
index 0000000..36cf8e3
--- /dev/null
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -0,0 +1,33 @@
+# 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 base
+
+
+class MeteringLabelRulesClient(base.BaseNetworkClient):
+
+ def create_metering_label_rule(self, **kwargs):
+ uri = '/metering/metering-label-rules'
+ post_data = {'metering_label_rule': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_metering_label_rule(self, metering_label_rule_id, **fields):
+ uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
+ return self.show_resource(uri, **fields)
+
+ def delete_metering_label_rule(self, metering_label_rule_id):
+ uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
+ return self.delete_resource(uri)
+
+ def list_metering_label_rules(self, **filters):
+ uri = '/metering/metering-label-rules'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/metering_labels_client.py b/tempest/lib/services/network/metering_labels_client.py
new file mode 100644
index 0000000..2350ecd
--- /dev/null
+++ b/tempest/lib/services/network/metering_labels_client.py
@@ -0,0 +1,33 @@
+# 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 base
+
+
+class MeteringLabelsClient(base.BaseNetworkClient):
+
+ def create_metering_label(self, **kwargs):
+ uri = '/metering/metering-labels'
+ post_data = {'metering_label': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_metering_label(self, metering_label_id, **fields):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.show_resource(uri, **fields)
+
+ def delete_metering_label(self, metering_label_id):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.delete_resource(uri)
+
+ def list_metering_labels(self, **filters):
+ uri = '/metering/metering-labels'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
new file mode 100644
index 0000000..24c2ec5
--- /dev/null
+++ b/tempest/lib/services/network/networks_client.py
@@ -0,0 +1,51 @@
+# 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 base
+
+
+class NetworksClient(base.BaseNetworkClient):
+
+ def create_network(self, **kwargs):
+ uri = '/networks'
+ post_data = {'network': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_network(self, network_id, **kwargs):
+ uri = '/networks/%s' % network_id
+ post_data = {'network': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_network(self, network_id, **fields):
+ uri = '/networks/%s' % network_id
+ return self.show_resource(uri, **fields)
+
+ def delete_network(self, network_id):
+ uri = '/networks/%s' % network_id
+ return self.delete_resource(uri)
+
+ def list_networks(self, **filters):
+ uri = '/networks'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_networks(self, **kwargs):
+ """Create multiple networks in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreateNetwork
+ """
+ uri = '/networks'
+ return self.create_resource(uri, kwargs)
+
+ def list_dhcp_agents_on_hosting_network(self, network_id):
+ uri = '/networks/%s/dhcp-agents' % network_id
+ return self.list_resources(uri)
diff --git a/tempest/lib/services/network/ports_client.py b/tempest/lib/services/network/ports_client.py
new file mode 100644
index 0000000..eba11d3
--- /dev/null
+++ b/tempest/lib/services/network/ports_client.py
@@ -0,0 +1,55 @@
+# 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 import exceptions as lib_exc
+from tempest.lib.services.network import base
+
+
+class PortsClient(base.BaseNetworkClient):
+
+ def create_port(self, **kwargs):
+ uri = '/ports'
+ post_data = {'port': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_port(self, port_id, **kwargs):
+ uri = '/ports/%s' % port_id
+ post_data = {'port': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_port(self, port_id, **fields):
+ uri = '/ports/%s' % port_id
+ return self.show_resource(uri, **fields)
+
+ def delete_port(self, port_id):
+ uri = '/ports/%s' % port_id
+ return self.delete_resource(uri)
+
+ def list_ports(self, **filters):
+ uri = '/ports'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_ports(self, **kwargs):
+ """Create multiple ports in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreatePorts
+ """
+ uri = '/ports'
+ return self.create_resource(uri, kwargs)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_port(id)
+ except lib_exc.NotFound:
+ return True
+ return False
diff --git a/tempest/services/network/json/quotas_client.py b/tempest/lib/services/network/quotas_client.py
similarity index 84%
rename from tempest/services/network/json/quotas_client.py
rename to tempest/lib/services/network/quotas_client.py
index 9b65a54..752b253 100644
--- a/tempest/services/network/json/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class QuotasClient(base.BaseNetworkClient):
@@ -22,7 +22,9 @@
uri = '/quotas/%s' % tenant_id
return self.update_resource(uri, put_body)
- def reset_quotas(self, tenant_id):
+ def reset_quotas(self, tenant_id): # noqa
+ # NOTE: This noqa is for passing T111 check and we cannot rename
+ # to keep backwards compatibility.
uri = '/quotas/%s' % tenant_id
return self.delete_resource(uri)
diff --git a/tempest/services/network/json/security_group_rules_client.py b/tempest/lib/services/network/security_group_rules_client.py
similarity index 96%
rename from tempest/services/network/json/security_group_rules_client.py
rename to tempest/lib/services/network/security_group_rules_client.py
index b2ba5b2..944eba6 100644
--- a/tempest/services/network/json/security_group_rules_client.py
+++ b/tempest/lib/services/network/security_group_rules_client.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class SecurityGroupRulesClient(base.BaseNetworkClient):
diff --git a/tempest/services/network/json/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
similarity index 96%
rename from tempest/services/network/json/security_groups_client.py
rename to tempest/lib/services/network/security_groups_client.py
index a60d2a6..0e25339 100644
--- a/tempest/services/network/json/security_groups_client.py
+++ b/tempest/lib/services/network/security_groups_client.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class SecurityGroupsClient(base.BaseNetworkClient):
diff --git a/tempest/services/network/json/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
similarity index 96%
rename from tempest/services/network/json/subnetpools_client.py
rename to tempest/lib/services/network/subnetpools_client.py
index f921bb0..12349b1 100644
--- a/tempest/services/network/json/subnetpools_client.py
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class SubnetpoolsClient(base.BaseNetworkClient):
diff --git a/tempest/lib/services/network/subnets_client.py b/tempest/lib/services/network/subnets_client.py
new file mode 100644
index 0000000..63ed13e
--- /dev/null
+++ b/tempest/lib/services/network/subnets_client.py
@@ -0,0 +1,47 @@
+# 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 base
+
+
+class SubnetsClient(base.BaseNetworkClient):
+
+ def create_subnet(self, **kwargs):
+ uri = '/subnets'
+ post_data = {'subnet': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_subnet(self, subnet_id, **kwargs):
+ uri = '/subnets/%s' % subnet_id
+ post_data = {'subnet': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_subnet(self, subnet_id, **fields):
+ uri = '/subnets/%s' % subnet_id
+ return self.show_resource(uri, **fields)
+
+ def delete_subnet(self, subnet_id):
+ uri = '/subnets/%s' % subnet_id
+ return self.delete_resource(uri)
+
+ def list_subnets(self, **filters):
+ uri = '/subnets'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_subnets(self, **kwargs):
+ """Create multiple subnets in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreateSubnet
+ """
+ uri = '/subnets'
+ return self.create_resource(uri, kwargs)
diff --git a/tempest/manager.py b/tempest/manager.py
index 9904aa6..cafa5b9 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import auth
-
from tempest.common import cred_provider
from tempest import config
from tempest import exceptions
+from tempest.lib import auth
CONF = config.CONF
@@ -29,13 +28,14 @@
and a client object for a test case to use in performing actions.
"""
- def __init__(self, credentials):
+ def __init__(self, credentials, scope='project'):
"""Initialization of base manager class
Credentials to be used within the various client classes managed by the
Manager object must be defined.
:param credentials: type Credentials or TestResources
+ :param scope: default scope for tokens produced by the auth provider
"""
self.credentials = credentials
# Check if passed or default credentials are valid
@@ -49,9 +49,8 @@
else:
creds = self.credentials
# Creates an auth provider for the credentials
- self.auth_provider = get_auth_provider(creds, pre_auth=True)
- # FIXME(andreaf) unused
- self.client_attr_names = []
+ self.auth_provider = get_auth_provider(creds, pre_auth=True,
+ scope=scope)
def get_auth_provider_class(credentials):
@@ -61,7 +60,7 @@
return auth.KeystoneV2AuthProvider, CONF.identity.uri
-def get_auth_provider(credentials, pre_auth=False):
+def get_auth_provider(credentials, pre_auth=False, scope='project'):
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
@@ -74,6 +73,7 @@
auth_provider_class, auth_url = get_auth_provider_class(
credentials)
_auth_provider = auth_provider_class(credentials, auth_url,
+ scope=scope,
**default_params)
if pre_auth:
_auth_provider.set_auth()
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index cd36f38..7389722 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -20,8 +20,6 @@
from oslo_log import log
from oslo_serialization import jsonutils as json
import six
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest.common import compute
from tempest.common.utils import data_utils
@@ -29,7 +27,9 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
-from tempest.services.network import resources as net_resources
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
+from tempest.scenario import network_resources
import tempest.test
CONF = config.CONF
@@ -63,9 +63,9 @@
cls.servers_client = cls.manager.servers_client
cls.interface_client = cls.manager.interfaces_client
# Neutron network client
- cls.network_client = cls.manager.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
@@ -216,7 +216,7 @@
networks = kwargs.pop('networks')
# If there are no networks passed to us we look up
- # for the tenant's private networks and create a port
+ # for the project's private networks and create a port
# if there is only one private network. The same behaviour
# as we would expect when passing the call to the clients
# with no networks
@@ -261,7 +261,7 @@
return server
def create_volume(self, size=None, name=None, snapshot_id=None,
- imageRef=None, volume_type=None, wait_on_delete=True):
+ imageRef=None, volume_type=None):
if name is None:
name = data_utils.rand_name(self.__class__.__name__)
kwargs = {'display_name': name,
@@ -272,24 +272,18 @@
kwargs.update({'size': size})
volume = self.volumes_client.create_volume(**kwargs)['volume']
- if wait_on_delete:
- self.addCleanup(self.volumes_client.wait_for_resource_deletion,
- volume['id'])
- self.addCleanup(self.delete_wrapper,
- self.volumes_client.delete_volume, volume['id'])
- else:
- self.addCleanup_with_wait(
- waiter_callable=self.volumes_client.wait_for_resource_deletion,
- thing_id=volume['id'], thing_id_param='id',
- cleanup_callable=self.delete_wrapper,
- cleanup_args=[self.volumes_client.delete_volume, volume['id']])
+ self.addCleanup(self.volumes_client.wait_for_resource_deletion,
+ volume['id'])
+ self.addCleanup(self.delete_wrapper,
+ self.volumes_client.delete_volume, volume['id'])
# NOTE(e0ne): Cinder API v2 uses name instead of display_name
if 'display_name' in volume:
self.assertEqual(name, volume['display_name'])
else:
self.assertEqual(name, volume['name'])
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
# The volume retrieved on creation has a non-up-to-date status.
# Retrieval after it becomes active ensures correct details.
volume = self.volumes_client.show_volume(volume['id'])['volume']
@@ -328,9 +322,6 @@
for ruleset in rulesets:
sg_rule = _client_rules.create_security_group_rule(
parent_group_id=secgroup_id, **ruleset)['security_group_rule']
- self.addCleanup(self.delete_wrapper,
- _client_rules.delete_security_group_rule,
- sg_rule['id'])
rules.append(sg_rule)
return rules
@@ -380,7 +371,7 @@
linux_client.validate_authentication()
except Exception as e:
message = ('Initializing SSH connection to %(ip)s failed. '
- 'Error: %(error)s' % {'ip_address': ip_address,
+ 'Error: %(error)s' % {'ip': ip_address,
'error': e})
caller = misc_utils.find_test_caller()
if caller:
@@ -396,8 +387,6 @@
if properties is None:
properties = {}
name = data_utils.rand_name('%s-' % name)
- image_file = open(path, 'rb')
- self.addCleanup(image_file.close)
params = {
'name': name,
'container_format': fmt,
@@ -408,7 +397,8 @@
image = self.image_client.create_image(**params)['image']
self.addCleanup(self.image_client.delete_image, image['id'])
self.assertEqual("queued", image['status'])
- self.image_client.update_image(image['id'], data=image_file)
+ with open(path, 'rb') as image_file:
+ self.image_client.update_image(image['id'], data=image_file)
return image['id']
def glance_image_create(self):
@@ -469,13 +459,13 @@
LOG.debug("Creating a snapshot image for server: %s", server['name'])
image = _images_client.create_image(server['id'], name=name)
image_id = image.response['location'].split('images/')[1]
- _image_client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(_image_client, image_id, 'active')
self.addCleanup_with_wait(
waiter_callable=_image_client.wait_for_resource_deletion,
thing_id=image_id, thing_id_param='id',
cleanup_callable=self.delete_wrapper,
cleanup_args=[_image_client.delete_image, image_id])
- snapshot_image = _image_client.get_image_meta(image_id)
+ snapshot_image = _image_client.check_image(image_id)
bdm = snapshot_image.get('properties', {}).get('block_device_mapping')
if bdm:
@@ -488,8 +478,8 @@
self.addCleanup(
self.delete_wrapper, self.snapshots_client.delete_snapshot,
snapshot_id)
- self.snapshots_client.wait_for_snapshot_status(snapshot_id,
- 'available')
+ waiters.wait_for_snapshot_status(self.snapshots_client,
+ snapshot_id, 'available')
image_name = snapshot_image['name']
self.assertEqual(name, image_name)
@@ -502,14 +492,16 @@
server['id'], volumeId=volume_to_attach['id'], device='/dev/%s'
% CONF.compute.volume_device_name)['volumeAttachment']
self.assertEqual(volume_to_attach['id'], volume['id'])
- self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'in-use')
# Return the updated volume after the attachment
return self.volumes_client.show_volume(volume['id'])['volume']
def nova_volume_detach(self, server, volume):
self.servers_client.detach_volume(server['id'], volume['id'])
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
volume = self.volumes_client.show_volume(volume['id'])['volume']
self.assertEqual('available', volume['status'])
@@ -610,6 +602,8 @@
def create_floating_ip(self, thing, pool_name=None):
"""Create a floating IP and associates to a server on Nova"""
+ if not pool_name:
+ pool_name = CONF.network.floating_network_name
floating_ip = (self.compute_floating_ips_client.
create_floating_ip(pool=pool_name)['floating_ip'])
self.addCleanup(self.delete_wrapper,
@@ -654,7 +648,7 @@
"""
if CONF.validation.connect_method == 'floating':
# The tests calling this method don't have a floating IP
- # and can't make use of the validattion resources. So the
+ # and can't make use of the validation resources. So the
# method is creating the floating IP there.
return self.create_floating_ip(server)['ip']
elif CONF.validation.connect_method == 'fixed':
@@ -692,18 +686,20 @@
super(NetworkScenarioTest, cls).resource_setup()
cls.tenant_id = cls.manager.identity_client.tenant_id
- def _create_network(self, client=None, networks_client=None,
- tenant_id=None, namestart='network-smoke-'):
- if not client:
- client = self.network_client
+ def _create_network(self, networks_client=None,
+ routers_client=None, tenant_id=None,
+ namestart='network-smoke-'):
if not networks_client:
networks_client = self.networks_client
+ if not routers_client:
+ routers_client = self.routers_client
if not tenant_id:
- tenant_id = client.tenant_id
+ tenant_id = networks_client.tenant_id
name = data_utils.rand_name(namestart)
result = networks_client.create_network(name=name, tenant_id=tenant_id)
- network = net_resources.DeletableNetwork(
- networks_client=networks_client, **result['network'])
+ network = network_resources.DeletableNetwork(
+ networks_client=networks_client, routers_client=routers_client,
+ **result['network'])
self.assertEqual(network.name, name)
self.addCleanup(self.delete_wrapper, network.delete)
return network
@@ -722,7 +718,7 @@
def _list_routers(self, *args, **kwargs):
"""List routers using admin creds """
- routers_list = self.admin_manager.network_client.list_routers(
+ routers_list = self.admin_manager.routers_client.list_routers(
*args, **kwargs)
return routers_list['routers']
@@ -738,16 +734,17 @@
*args, **kwargs)
return agents_list['agents']
- def _create_subnet(self, network, client=None, subnets_client=None,
- namestart='subnet-smoke', **kwargs):
+ def _create_subnet(self, network, subnets_client=None,
+ routers_client=None, namestart='subnet-smoke',
+ **kwargs):
"""Create a subnet for the given network
within the cidr block configured for tenant networks.
"""
- if not client:
- client = self.network_client
if not subnets_client:
subnets_client = self.subnets_client
+ if not routers_client:
+ routers_client = self.routers_client
def cidr_in_use(cidr, tenant_id):
"""Check cidr existence
@@ -762,11 +759,11 @@
if ip_version == 6:
tenant_cidr = netaddr.IPNetwork(
- CONF.network.tenant_network_v6_cidr)
- num_bits = CONF.network.tenant_network_v6_mask_bits
+ CONF.network.project_network_v6_cidr)
+ num_bits = CONF.network.project_network_v6_mask_bits
else:
- tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- num_bits = CONF.network.tenant_network_mask_bits
+ tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
+ num_bits = CONF.network.project_network_mask_bits
result = None
str_cidr = None
@@ -793,9 +790,9 @@
if not is_overlapping_cidr:
raise
self.assertIsNotNone(result, 'Unable to allocate tenant network')
- subnet = net_resources.DeletableSubnet(
- network_client=client, subnets_client=subnets_client,
- **result['subnet'])
+ subnet = network_resources.DeletableSubnet(
+ subnets_client=subnets_client,
+ routers_client=routers_client, **result['subnet'])
self.assertEqual(subnet.cidr, str_cidr)
self.addCleanup(self.delete_wrapper, subnet.delete)
return subnet
@@ -810,21 +807,24 @@
network_id=network_id,
**kwargs)
self.assertIsNotNone(result, 'Unable to allocate port')
- port = net_resources.DeletablePort(ports_client=client,
- **result['port'])
+ port = network_resources.DeletablePort(ports_client=client,
+ **result['port'])
self.addCleanup(self.delete_wrapper, port.delete)
return port
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
- ports = self._list_ports(device_id=server['id'], status='ACTIVE',
- fixed_ip=ip_addr)
+ ports = self._list_ports(device_id=server['id'], fixed_ip=ip_addr)
# A port can have more then one IP address in some cases.
# If the network is dual-stack (IPv4 + IPv6), this port is associated
# with 2 subnets
port_map = [(p["id"], fxip["ip_address"])
for p in ports
for fxip in p["fixed_ips"]
- if netaddr.valid_ipv4(fxip["ip_address"])]
+ if netaddr.valid_ipv4(fxip["ip_address"])
+ and p['status'] == 'ACTIVE']
+ inactive = [p for p in ports if p['status'] != 'ACTIVE']
+ if inactive:
+ LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
self.assertNotEqual(0, len(port_map),
"No IPv4 addresses found in: %s" % ports)
@@ -838,7 +838,7 @@
net = self._list_networks(name=network_name)
self.assertNotEqual(len(net), 0,
"Unable to get network by name: %s" % network_name)
- return net_resources.AttributeDict(net[0])
+ return network_resources.AttributeDict(net[0])
def create_floating_ip(self, thing, external_network_id=None,
port_id=None, client=None):
@@ -857,7 +857,7 @@
tenant_id=thing['tenant_id'],
fixed_ip_address=ip4
)
- floating_ip = net_resources.DeletableFloatingIp(
+ floating_ip = network_resources.DeletableFloatingIp(
client=client,
**result['floatingip'])
self.addCleanup(self.delete_wrapper, floating_ip.delete)
@@ -878,8 +878,8 @@
def check_floating_ip_status(self, floating_ip, status):
"""Verifies floatingip reaches the given status
- :param floating_ip: net_resources.DeletableFloatingIp floating IP to
- to check status
+ :param floating_ip: network_resources.DeletableFloatingIp floating
+ IP to check status
:param status: target status
:raises: AssertionError if status doesn't match
"""
@@ -903,7 +903,7 @@
private_key,
should_connect=True,
servers_for_debug=None):
- if not CONF.network.tenant_networks_reachable:
+ if not CONF.network.project_networks_reachable:
msg = 'Tenant networks not configured to be reachable.'
LOG.info(msg)
return
@@ -991,8 +991,8 @@
description=sg_desc)
sg_dict['tenant_id'] = tenant_id
result = client.create_security_group(**sg_dict)
- secgroup = net_resources.DeletableSecurityGroup(
- client=client,
+ secgroup = network_resources.DeletableSecurityGroup(
+ client=client, routers_client=self.routers_client,
**result['security_group']
)
self.assertEqual(secgroup.name, sg_name)
@@ -1016,8 +1016,8 @@
]
msg = "No default security group for tenant %s." % (tenant_id)
self.assertTrue(len(sgs) > 0, msg)
- return net_resources.DeletableSecurityGroup(client=client,
- **sgs[0])
+ return network_resources.DeletableSecurityGroup(client=client,
+ **sgs[0])
def _create_security_group_rule(self, secgroup=None,
sec_group_rules_client=None,
@@ -1055,11 +1055,10 @@
ruleset.update(kwargs)
sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
- sg_rule = net_resources.DeletableSecurityGroupRule(
+ sg_rule = network_resources.DeletableSecurityGroupRule(
client=sec_group_rules_client,
**sg_rule['security_group_rule']
)
- self.addCleanup(self.delete_wrapper, sg_rule.delete)
self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
self.assertEqual(secgroup.id, sg_rule.security_group_id)
@@ -1070,10 +1069,11 @@
security_groups_client=None):
"""Create loginable security group rule
- These rules are intended to permit inbound ssh and icmp
- traffic from all sources, so no group_id is provided.
- Setting a group_id would only permit traffic from ports
- belonging to the same security group.
+ This function will create:
+ 1. egress and ingress tcp port 22 allow rule in order to allow ssh
+ access for ipv4.
+ 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
+ 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
"""
if security_group_rules_client is None:
@@ -1129,14 +1129,14 @@
routes traffic to the public network.
"""
if not client:
- client = self.network_client
+ client = self.routers_client
if not tenant_id:
tenant_id = client.tenant_id
router_id = CONF.network.public_router_id
network_id = CONF.network.public_network_id
if router_id:
body = client.show_router(router_id)
- return net_resources.AttributeDict(**body['router'])
+ return network_resources.AttributeDict(**body['router'])
elif network_id:
router = self._create_router(client, tenant_id)
router.set_gateway(network_id)
@@ -1148,15 +1148,15 @@
def _create_router(self, client=None, tenant_id=None,
namestart='router-smoke'):
if not client:
- client = self.network_client
+ client = self.routers_client
if not tenant_id:
tenant_id = client.tenant_id
name = data_utils.rand_name(namestart)
result = client.create_router(name=name,
admin_state_up=True,
tenant_id=tenant_id)
- router = net_resources.DeletableRouter(client=client,
- **result['router'])
+ router = network_resources.DeletableRouter(routers_client=client,
+ **result['router'])
self.assertEqual(router.name, name)
self.addCleanup(self.delete_wrapper, router.delete)
return router
@@ -1165,15 +1165,14 @@
router.update(admin_state_up=admin_state_up)
self.assertEqual(admin_state_up, router.admin_state_up)
- def create_networks(self, client=None, networks_client=None,
- subnets_client=None, tenant_id=None,
- dns_nameservers=None):
+ def create_networks(self, networks_client=None,
+ routers_client=None, subnets_client=None,
+ tenant_id=None, dns_nameservers=None):
"""Create a network with a subnet connected to a router.
The baremetal driver is a special case since all nodes are
on the same shared network.
- :param client: network client to create resources with.
:param tenant_id: id of tenant to create resources in.
:param dns_nameservers: list of dns servers to send to subnet.
:returns: network, subnet, router
@@ -1193,12 +1192,14 @@
subnet = None
else:
network = self._create_network(
- client=client, networks_client=networks_client,
+ networks_client=networks_client,
tenant_id=tenant_id)
- router = self._get_router(client=client, tenant_id=tenant_id)
+ router = self._get_router(client=routers_client,
+ tenant_id=tenant_id)
- subnet_kwargs = dict(network=network, client=client,
- subnets_client=subnets_client)
+ subnet_kwargs = dict(network=network,
+ subnets_client=subnets_client,
+ routers_client=routers_client)
# use explicit check because empty list is a valid option
if dns_nameservers is not None:
subnet_kwargs['dns_nameservers'] = dns_nameservers
diff --git a/tempest/services/network/resources.py b/tempest/scenario/network_resources.py
similarity index 73%
rename from tempest/services/network/resources.py
rename to tempest/scenario/network_resources.py
index 10911f7..667476f 100644
--- a/tempest/services/network/resources.py
+++ b/tempest/scenario/network_resources.py
@@ -14,9 +14,13 @@
# under the License.
import abc
+import time
import six
+from tempest import exceptions
+from tempest.lib.common.utils import misc
+
class AttributeDict(dict):
"""Provide attribute access (dict.key) to dictionary values."""
@@ -37,8 +41,8 @@
def __init__(self, *args, **kwargs):
self.client = kwargs.pop('client', None)
- self.network_client = kwargs.pop('network_client', None)
self.networks_client = kwargs.pop('networks_client', None)
+ self.routers_client = kwargs.pop('routers_client', None)
self.subnets_client = kwargs.pop('subnets_client', None)
self.ports_client = kwargs.pop('ports_client', None)
super(DeletableResource, self).__init__(*args, **kwargs)
@@ -66,7 +70,35 @@
self.refresh()
return self
- return self.client.wait_for_resource_status(helper_get, status)
+ return self.wait_for_resource_status(helper_get, status)
+
+ def wait_for_resource_status(self, fetch, status):
+ """Waits for a network resource to reach a status
+
+ @param fetch: the callable to be used to query the resource status
+ @type fetch: callable that takes no parameters and returns the resource
+ @param status: the status that the resource has to reach
+ @type status: String
+ """
+ interval = self.build_interval
+ timeout = self.build_timeout
+ start_time = time.time()
+
+ while time.time() - start_time <= timeout:
+ resource = fetch()
+ if resource['status'] == status:
+ return
+ time.sleep(interval)
+
+ # At this point, the wait has timed out
+ message = 'Resource %s' % (str(resource))
+ message += ' failed to reach status %s' % status
+ message += ' (current: %s)' % resource['status']
+ message += ' within the required time %s' % timeout
+ caller = misc.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
class DeletableNetwork(DeletableResource):
@@ -89,14 +121,13 @@
def add_to_router(self, router_id):
self._router_ids.add(router_id)
- self.network_client.add_router_interface_with_subnet_id(
- router_id, subnet_id=self.id)
+ self.routers_client.add_router_interface(router_id,
+ subnet_id=self.id)
def delete(self):
for router_id in self._router_ids.copy():
- self.network_client.remove_router_interface_with_subnet_id(
- router_id,
- subnet_id=self.id)
+ self.routers_client.remove_router_interface(router_id,
+ subnet_id=self.id)
self._router_ids.remove(router_id)
self.subnets_client.delete_subnet(self.id)
@@ -110,14 +141,14 @@
return self.update(external_gateway_info=dict())
def update(self, *args, **kwargs):
- result = self.client.update_router(self.id,
- *args,
- **kwargs)
+ result = self.routers_client.update_router(self.id,
+ *args,
+ **kwargs)
return super(DeletableRouter, self).update(**result['router'])
def delete(self):
self.unset_gateway()
- self.client.delete_router(self.id)
+ self.routers_client.delete_router(self.id)
class DeletableFloatingIp(DeletableResource):
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 15d9b66..655d19d 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -15,7 +15,6 @@
from oslo_log import log as logging
-from tempest.common import waiters
from tempest import config
from tempest.scenario import manager
from tempest import test
@@ -37,32 +36,10 @@
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
* Verifies SSH connectivity using created keypair via floating IP
- * Verifies instance rebuild with ephemeral partition preservation
* Deletes instance
* Monitors the associated Ironic node for power and
expected state transitions
"""
- def rebuild_instance(self, preserve_ephemeral=False):
- self.rebuild_server(server_id=self.instance['id'],
- preserve_ephemeral=preserve_ephemeral,
- wait=False)
-
- node = self.get_node(instance_id=self.instance['id'])
-
- # We should remain on the same node
- self.assertEqual(self.node['uuid'], node['uuid'])
- self.node = node
-
- waiters.wait_for_server_status(
- self.servers_client,
- server_id=self.instance['id'],
- status='REBUILD',
- ready_wait=False)
- waiters.wait_for_server_status(
- self.servers_client,
- server_id=self.instance['id'],
- status='ACTIVE')
-
def verify_partition(self, client, label, mount, gib_size):
"""Verify a labeled partition's mount point and size."""
LOG.info("Looking for partition %s mounted on %s" % (label, mount))
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
deleted file mode 100644
index 402077f..0000000
--- a/tempest/scenario/test_large_ops.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# Copyright 2013 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 import exceptions as lib_exc
-
-from tempest.common import fixed_network
-from tempest.common.utils import data_utils
-from tempest.common import waiters
-from tempest import config
-from tempest.scenario import manager
-from tempest import test
-
-CONF = config.CONF
-
-
-class TestLargeOpsScenario(manager.ScenarioTest):
-
- """Test large operations.
-
- This test below:
- * Spin up multiple instances in one nova call, and repeat three times
- * as a regular user
- * TODO: same thing for cinder
-
- """
-
- @classmethod
- def skip_checks(cls):
- super(TestLargeOpsScenario, cls).skip_checks()
- if CONF.scenario.large_ops_number < 1:
- raise cls.skipException("large_ops_number not set to multiple "
- "instances")
-
- @classmethod
- def setup_credentials(cls):
- cls.set_network_resources()
- super(TestLargeOpsScenario, cls).setup_credentials()
-
- @classmethod
- def resource_setup(cls):
- super(TestLargeOpsScenario, cls).resource_setup()
- # list of cleanup calls to be executed in reverse order
- cls._cleanup_resources = []
-
- @classmethod
- def resource_cleanup(cls):
- while cls._cleanup_resources:
- function, args, kwargs = cls._cleanup_resources.pop(-1)
- try:
- function(*args, **kwargs)
- except lib_exc.NotFound:
- pass
- super(TestLargeOpsScenario, cls).resource_cleanup()
-
- @classmethod
- def addCleanupClass(cls, function, *arguments, **keywordArguments):
- cls._cleanup_resources.append((function, arguments, keywordArguments))
-
- def _wait_for_server_status(self, status):
- for server in self.servers:
- # Make sure nova list keeps working throughout the build process
- self.servers_client.list_servers()
- waiters.wait_for_server_status(self.servers_client,
- server['id'], status)
-
- def nova_boot(self, image):
- name = data_utils.rand_name('scenario-server')
- flavor_id = CONF.compute.flavor_ref
- # Explicitly create secgroup to avoid cleanup at the end of testcases.
- # Since no traffic is tested, we don't need to actually add rules to
- # secgroup
- secgroup = self.compute_security_groups_client.create_security_group(
- name='secgroup-%s' % name,
- description='secgroup-desc-%s' % name)['security_group']
- self.addCleanupClass(
- self.compute_security_groups_client.delete_security_group,
- secgroup['id'])
- create_kwargs = {
- 'min_count': CONF.scenario.large_ops_number,
- 'security_groups': [{'name': secgroup['name']}]
- }
- network = self.get_tenant_network()
- create_kwargs = fixed_network.set_networks_kwarg(network,
- create_kwargs)
- self.servers_client.create_server(
- name=name,
- imageRef=image,
- flavorRef=flavor_id,
- **create_kwargs)
- # needed because of bug 1199788
- params = {'name': name}
- server_list = self.servers_client.list_servers(**params)
- self.servers = server_list['servers']
- for server in self.servers:
- # after deleting all servers - wait for all servers to clear
- # before cleanup continues
- self.addCleanupClass(waiters.wait_for_server_termination,
- self.servers_client,
- server['id'])
- for server in self.servers:
- self.addCleanupClass(self.servers_client.delete_server,
- server['id'])
- self._wait_for_server_status('ACTIVE')
-
- def _large_ops_scenario(self):
- image = self.glance_image_create()
- self.nova_boot(image)
-
- @test.idempotent_id('14ba0e78-2ed9-4d17-9659-a48f4756ecb3')
- @test.services('compute', 'image')
- def test_large_ops_scenario_1(self):
- self._large_ops_scenario()
-
- @test.idempotent_id('b9b79b88-32aa-42db-8f8f-dcc8f4b4ccfe')
- @test.services('compute', 'image')
- def test_large_ops_scenario_2(self):
- self._large_ops_scenario()
-
- @test.idempotent_id('3aab7e82-2de3-419a-9da1-9f3a070668fb')
- @test.services('compute', 'image')
- def test_large_ops_scenario_3(self):
- self._large_ops_scenario()
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 2cbe6dc..4c2d31b 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -38,9 +38,9 @@
@classmethod
def skip_checks(cls):
super(TestNetworkAdvancedServerOps, cls).skip_checks()
- if not (CONF.network.tenant_networks_reachable
+ if not (CONF.network.project_networks_reachable
or CONF.network.public_network_id):
- msg = ('Either tenant_networks_reachable must be "true", or '
+ msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 79a5099..f0ae223 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -24,7 +24,7 @@
from tempest import config
from tempest import exceptions
from tempest.scenario import manager
-from tempest.services.network import resources as net_resources
+from tempest.scenario import network_resources
from tempest import test
CONF = config.CONF
@@ -59,7 +59,7 @@
Determine which types of networks to test as follows:
* Configure tenant network checks (via the
- 'tenant_networks_reachable' key) if the Tempest host should
+ 'project_networks_reachable' key) if the Tempest host should
have direct connectivity to tenant networks. This is likely to
be the case if Tempest is running on the same host as a
single-node devstack installation with IP namespaces disabled.
@@ -81,9 +81,9 @@
@classmethod
def skip_checks(cls):
super(TestNetworkBasicOps, cls).skip_checks()
- if not (CONF.network.tenant_networks_reachable
+ if not (CONF.network.project_networks_reachable
or CONF.network.public_network_id):
- msg = ('Either tenant_networks_reachable must be "true", or '
+ msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
for ext in ['router', 'security-group']:
@@ -250,9 +250,8 @@
interface = self.interface_client.create_interface(
server_id=server['id'],
net_id=self.new_net.id)['interfaceAttachment']
- self.addCleanup(self.network_client.wait_for_resource_deletion,
- 'port',
- interface['port_id'], client=self.ports_client)
+ self.addCleanup(self.ports_client.wait_for_resource_deletion,
+ interface['port_id'])
self.addCleanup(self.delete_wrapper,
self.interface_client.delete_interface,
server['id'], interface['port_id'])
@@ -270,8 +269,9 @@
"Old port: %s. Number of new ports: %d" % (
CONF.network.build_timeout, old_port,
len(self.new_port_list)))
- new_port = net_resources.DeletablePort(ports_client=self.ports_client,
- **self.new_port_list[0])
+ new_port = network_resources.DeletablePort(
+ ports_client=self.ports_client,
+ **self.new_port_list[0])
def check_new_nic():
new_nic_list = self._get_server_nics(ssh_client)
@@ -626,9 +626,6 @@
"admin_state_up of instance port to True")
@test.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
- @testtools.skipUnless(CONF.compute_feature_enabled.preserve_ports,
- 'Preserving ports on instance delete may not be '
- 'supported in the version of Nova being tested.')
@test.services('compute', 'network')
def test_preserve_preexisting_port(self):
"""Test preserve pre-existing port
@@ -683,20 +680,21 @@
# TODO(yfried): refactor this test to be used for other agents (dhcp)
# as well
- list_hosts = (self.admin_manager.network_client.
+ list_hosts = (self.admin_manager.routers_client.
list_l3_agents_hosting_router)
schedule_router = (self.admin_manager.network_agents_client.
create_router_on_l3_agent)
unschedule_router = (self.admin_manager.network_agents_client.
delete_router_from_l3_agent)
- agent_list = set(a["id"] for a in
- self._list_agents(agent_type="L3 agent"))
+ agent_list_alive = set(a["id"] for a in
+ self._list_agents(agent_type="L3 agent") 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 tenant view.
- admin = self.admin_manager.network_client.show_router(self.router.id)
+ # for the distributed flag because self.router only has a project view.
+ admin = self.admin_manager.routers_client.show_router(self.router.id)
if admin['router'].get('distributed', False):
msg = "Rescheduling test does not apply to distributed routers."
raise self.skipException(msg)
@@ -706,7 +704,7 @@
# remove resource from agents
hosting_agents = set(a["id"] for a in
list_hosts(self.router.id)['agents'])
- no_migration = agent_list == hosting_agents
+ no_migration = agent_list_alive == hosting_agents
LOG.info("Router will be assigned to {mig} hosting agent".
format(mig="the same" if no_migration else "a new"))
@@ -726,7 +724,7 @@
# schedule resource to new agent
target_agent = list(hosting_agents if no_migration else
- agent_list - hosting_agents)[0]
+ agent_list_alive - hosting_agents)[0]
schedule_router(target_agent,
router_id=self.router['id'])
self.assertEqual(
@@ -741,6 +739,8 @@
msg='After router rescheduling')
@test.requires_ext(service='network', extension='port-security')
+ @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+ 'NIC hotplug not available')
@test.idempotent_id('7c0bb1a2-d053-49a4-98f9-ca1a1d849f63')
@test.services('compute', 'network')
def test_port_security_macspoofing_port(self):
@@ -754,10 +754,10 @@
The test steps are :
1. Create a new network.
2. Connect (hotplug) the VM to a new network.
- 3. Check the VM can ping the DHCP interface of this network.
+ 3. Check the VM can ping a server on the new network ("peer")
4. Spoof the mac address of the new VM interface.
5. Check the Security Group enforces mac spoofing and blocks pings via
- spoofed interface (VM cannot ping the DHCP interface).
+ spoofed interface (VM cannot ping the peer).
6. Disable port-security of the spoofed port- set the flag to false.
7. Retest 3rd step and check that the Security Group allows pings via
the spoofed interface.
@@ -777,19 +777,19 @@
private_key = self._get_server_key(server)
ssh_client = self.get_remote_client(fip.floating_ip_address,
private_key=private_key)
- spoof_nic = ssh_client.get_nic_name(spoof_port["mac_address"])
- dhcp_ports = self._list_ports(device_owner="network:dhcp",
- network_id=self.new_net["id"])
- new_net_dhcp = dhcp_ports[0]["fixed_ips"][0]["ip_address"]
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
+ name = data_utils.rand_name('peer')
+ peer = self._create_server(name, self.new_net)
+ peer_address = peer['addresses'][self.new_net.name][0]['addr']
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
ssh_client.set_mac_address(spoof_nic, spoof_mac)
new_mac = ssh_client.get_mac_address(nic=spoof_nic)
self.assertEqual(spoof_mac, new_mac)
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=False)
self.ports_client.update_port(spoof_port["id"],
port_security_enabled=False,
security_groups=[])
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index fc33dd9..a52d8f9 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -44,9 +44,9 @@
if not (CONF.network_feature_enabled.ipv6
and CONF.network_feature_enabled.ipv6_subnet_attributes):
raise cls.skipException('IPv6 or its attributes not supported')
- if not (CONF.network.tenant_networks_reachable
+ if not (CONF.network.project_networks_reachable
or CONF.network.public_network_id):
- msg = ('Either tenant_networks_reachable must be "true", or '
+ msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
if CONF.baremetal.driver_enabled:
@@ -145,7 +145,7 @@
"ports: %s")
% (self.network_v6, ports))
mac6 = ports[0]
- ssh.set_nic_state(ssh.get_nic_name(mac6))
+ ssh.set_nic_state(ssh.get_nic_name_by_mac(mac6))
def _prepare_and_test(self, address6_mode, n_subnets6=1, dualnet=False):
net_list = self.prepare_network(address6_mode=address6_mode,
diff --git a/tempest/scenario/test_object_storage_telemetry_middleware.py b/tempest/scenario/test_object_storage_telemetry_middleware.py
deleted file mode 100644
index eee4d3d..0000000
--- a/tempest/scenario/test_object_storage_telemetry_middleware.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright 2014 Red Hat
-#
-# 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_log import log as logging
-
-from tempest import config
-from tempest.scenario import manager
-from tempest import test
-
-CONF = config.CONF
-
-LOG = logging.getLogger(__name__)
-
-# Loop for up to 120 seconds waiting on notifications
-# NOTE(chdent): The choice of 120 seconds is fairly
-# arbitrary: Long enough to give the notifications the
-# chance to travel across a highly latent bus but not
-# so long as to allow excessive latency to never be visible.
-# TODO(chdent): Ideally this value would come from configuration.
-NOTIFICATIONS_WAIT = 120
-NOTIFICATIONS_SLEEP = 1
-
-
-class TestObjectStorageTelemetry(manager.ObjectStorageScenarioTest):
- """Test that swift uses the ceilometer middleware.
-
- * create container.
- * upload a file to the created container.
- * retrieve the file from the created container.
- * wait for notifications from ceilometer.
- """
-
- @classmethod
- def skip_checks(cls):
- super(TestObjectStorageTelemetry, cls).skip_checks()
- if not CONF.service_available.ceilometer:
- skip_msg = ("%s skipped as ceilometer is not available" %
- cls.__name__)
- raise cls.skipException(skip_msg)
-
- @classmethod
- def setup_clients(cls):
- super(TestObjectStorageTelemetry, cls).setup_clients()
- cls.telemetry_client = cls.os_operator.telemetry_client
-
- def _confirm_notifications(self, container_name, obj_name):
- # NOTE: Loop seeking for appropriate notifications about the containers
- # and objects sent to swift.
-
- def _check_samples():
- # NOTE: Return True only if we have notifications about some
- # containers and some objects and the notifications are about
- # the expected containers and objects.
- # Otherwise returning False will case _check_samples to be
- # called again.
- results = self.telemetry_client.list_samples(
- 'storage.objects.incoming.bytes')
- LOG.debug('got samples %s', results)
-
- # Extract container info from samples.
- containers, objects = [], []
- for sample in results:
- meta = sample['resource_metadata']
- if meta.get('container') and meta['container'] != 'None':
- containers.append(meta['container'])
- elif (meta.get('target.metadata:container') and
- meta['target.metadata:container'] != 'None'):
- containers.append(meta['target.metadata:container'])
-
- if meta.get('object') and meta['object'] != 'None':
- objects.append(meta['object'])
- elif (meta.get('target.metadata:object') and
- meta['target.metadata:object'] != 'None'):
- objects.append(meta['target.metadata:object'])
-
- return (container_name in containers and obj_name in objects)
-
- self.assertTrue(test.call_until_true(_check_samples,
- NOTIFICATIONS_WAIT,
- NOTIFICATIONS_SLEEP),
- 'Correct notifications were not received after '
- '%s seconds.' % NOTIFICATIONS_WAIT)
-
- @test.idempotent_id('6d6b88e5-3e38-41bc-b34a-79f713a6cb84')
- @test.services('object_storage', 'telemetry')
- def test_swift_middleware_notifies(self):
- container_name = self.create_container()
- obj_name, _ = self.upload_object_to_container(container_name)
- self._confirm_notifications(container_name, obj_name)
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 18bd764..adc9008 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -12,6 +12,7 @@
# 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_log import log
from tempest import clients
from tempest.common.utils import data_utils
@@ -20,6 +21,7 @@
from tempest import test
CONF = config.CONF
+LOG = log.getLogger(__name__)
class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest):
@@ -43,6 +45,12 @@
success - ping returns
failure - ping_timeout reached
+ multi-node:
+ Multi-Node mode is enabled when CONF.compute.min_compute_nodes > 1.
+ Tests connectivity between servers on different compute nodes.
+ When enabled, test will boot each new server to different
+ compute nodes.
+
setup:
for primary tenant:
1. create a network&subnet
@@ -128,9 +136,9 @@
msg = ('Not currently supported when using vnic_type'
' direct or macvtap')
raise cls.skipException(msg)
- if not (CONF.network.tenant_networks_reachable or
+ if not (CONF.network.project_networks_reachable or
CONF.network.public_network_id):
- msg = ('Either tenant_networks_reachable must be "true", or '
+ msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
if not test.is_extension_enabled('security-group', 'network'):
@@ -151,6 +159,14 @@
@classmethod
def resource_setup(cls):
super(TestSecurityGroupsBasicOps, cls).resource_setup()
+
+ cls.multi_node = CONF.compute.min_compute_nodes > 1 and \
+ test.is_scheduler_filter_enabled("DifferentHostFilter")
+ if cls.multi_node:
+ LOG.info("Working in Multi Node mode")
+ else:
+ LOG.info("Working in Single Node mode")
+
cls.floating_ips = {}
cls.tenants = {}
creds = cls.manager.credentials
@@ -162,6 +178,12 @@
cls.floating_ip_access = not CONF.network.public_router_id
def setUp(self):
+ """Set up a single tenant with an accessible server.
+
+ If multi-host is enabled, save created server uuids.
+ """
+ self.servers = []
+
super(TestSecurityGroupsBasicOps, self).setUp()
self._deploy_tenant(self.primary_tenant)
self._verify_network_details(self.primary_tenant)
@@ -178,7 +200,7 @@
client=tenant.manager.security_groups_client
)
- # don't use default secgroup since it allows in-tenant traffic
+ # don't use default secgroup since it allows in-project traffic
def_sg = self._create_empty_security_group(
namestart='secgroup_general-',
tenant_id=tenant.creds.tenant_id,
@@ -233,21 +255,44 @@
# and distributed routers; 'device_owner' is "" by default.
return port['device_owner'].startswith('network:router_interface')
- def _create_server(self, name, tenant, security_groups=None):
- """creates a server and assigns to security group"""
+ def _create_server(self, name, tenant, security_groups=None, **kwargs):
+ """Creates a server and assigns it to security group.
+
+ If multi-host is enabled, Ensures servers are created on different
+ compute nodes, by storing created servers' ids and uses different_host
+ as scheduler_hints on creation.
+ Validates servers are created as requested, using admin client.
+ """
if security_groups is None:
security_groups = [tenant.security_groups['default']]
security_groups_names = [{'name': s['name']} for s in security_groups]
+ if self.multi_node:
+ kwargs["scheduler_hints"] = {'different_host': self.servers}
server = self.create_server(
name=name,
networks=[{'uuid': tenant.network.id}],
key_name=tenant.keypair['name'],
security_groups=security_groups_names,
wait_until='ACTIVE',
- clients=tenant.manager)
+ clients=tenant.manager,
+ **kwargs)
self.assertEqual(
sorted([s['name'] for s in security_groups]),
sorted([s['name'] for s in server['security_groups']]))
+
+ # Verify servers are on different compute nodes
+ if self.multi_node:
+ adm_get_server = self.admin_manager.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"]
+ for s in self.servers]
+ self.assertNotIn(new_host, host_list,
+ message="Failed to boot servers on different "
+ "Compute nodes.")
+
+ self.servers.append(server["id"])
+
return server
def _create_tenant_servers(self, tenant, num=1):
@@ -262,7 +307,7 @@
def _set_access_point(self, tenant):
# creates a server in a secgroup with rule allowing external ssh
- # in order to access tenant internal network
+ # in order to access project internal network
# workaround ip namespace
secgroups = tenant.security_groups.values()
name = 'server-{tenant}-access_point'.format(
@@ -282,8 +327,8 @@
def _create_tenant_network(self, tenant):
network, subnet, router = self.create_networks(
- client=tenant.manager.network_client,
networks_client=tenant.manager.networks_client,
+ routers_client=tenant.manager.routers_client,
subnets_client=tenant.manager.subnets_client)
tenant.set_network(network, subnet, router)
@@ -348,6 +393,7 @@
)
self._create_security_group_rule(
secgroup=tenant.security_groups['default'],
+ security_groups_client=tenant.manager.security_groups_client,
**ruleset
)
access_point_ssh = self._connect_to_access_point(tenant)
@@ -427,7 +473,7 @@
if not self.credentials_provider.is_multi_tenant():
raise self.skipException("No secondary tenant defined")
try:
- # deploy new tenant
+ # deploy new project
self._deploy_tenant(self.alt_tenant)
self._verify_network_details(self.alt_tenant)
self._verify_mac_addr(self.alt_tenant)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 4b932ce..504d72b 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -31,7 +31,7 @@
"""The test suite for server advanced operations
This test case stresses some advanced server instance operations:
- * Resizing an instance
+ * Resizing a volume-backed instance
* Sequence suspend resume
"""
@@ -50,10 +50,10 @@
@test.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
- @test.services('compute')
- def test_resize_server_confirm(self):
+ @test.services('compute', 'volume')
+ def test_resize_volume_backed_server_confirm(self):
# We create an instance for use in this test
- instance = self.create_server(wait_until='ACTIVE')
+ instance = self.create_server(wait_until='ACTIVE', volume_backed=True)
instance_id = instance['id']
resize_flavor = CONF.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index dcb095b..a9f2dff 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -119,7 +119,7 @@
@test.idempotent_id('7fff3fb3-91d8-4fd0-bd7d-0204f1f180ba')
@test.attr(type='smoke')
@test.services('compute', 'network')
- def test_server_basicops(self):
+ def test_server_basic_ops(self):
keypair = self.create_keypair()
self.security_group = self._create_security_group()
security_groups = [{'name': self.security_group['name']}]
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index a91f9c5..6d3ecd4 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log
import testtools
+from tempest.common import compute
from tempest.common import waiters
from tempest import config
from tempest.scenario import manager
@@ -23,8 +23,6 @@
CONF = config.CONF
-LOG = log.getLogger(__name__)
-
class TestShelveInstance(manager.ScenarioTest):
"""This test shelves then unshelves a Nova instance
@@ -38,18 +36,9 @@
"""
def _shelve_then_unshelve_server(self, server):
- self.servers_client.shelve_server(server['id'])
- offload_time = CONF.compute.shelved_offload_time
- if offload_time >= 0:
- waiters.wait_for_server_status(self.servers_client, server['id'],
- 'SHELVED_OFFLOADED',
- extra_timeout=offload_time)
- else:
- waiters.wait_for_server_status(self.servers_client,
- server['id'], 'SHELVED')
- self.servers_client.shelve_offload_server(server['id'])
- waiters.wait_for_server_status(self.servers_client, server['id'],
- 'SHELVED_OFFLOADED')
+ compute.shelve_server(self.servers_client, server['id'],
+ force_shelve_offload=True)
+
self.servers_client.unshelve_server(server['id'])
waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index f4cc4e3..d6528a3 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log
import testtools
from tempest import config
@@ -22,8 +21,6 @@
CONF = config.CONF
-LOG = log.getLogger(__name__)
-
class TestSnapshotPattern(manager.ScenarioTest):
"""This test is for snapshotting an instance and booting with it.
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 799e049..e7223c7 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,19 +16,18 @@
import time
from oslo_log import log as logging
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
from tempest import test
-import tempest.test
CONF = config.CONF
-
LOG = logging.getLogger(__name__)
@@ -69,14 +68,15 @@
self.snapshots_client.delete_snapshot(snapshot['id'])
try:
while self.snapshots_client.show_snapshot(
- snapshot['id'])['snapshot']:
+ snapshot['id'])['snapshot']:
time.sleep(1)
except lib_exc.NotFound:
pass
self.addCleanup(cleaner)
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
- self.snapshots_client.wait_for_snapshot_status(snapshot['id'],
- 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
+ waiters.wait_for_snapshot_status(self.snapshots_client,
+ snapshot['id'], 'available')
self.assertEqual(snapshot_name, snapshot['display_name'])
return snapshot
@@ -89,16 +89,16 @@
LOG.debug("Partitions:%s" % part)
return CONF.compute.volume_device_name in part
- if not tempest.test.call_until_true(_func,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if not test.call_until_true(_func,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
raise exceptions.TimeoutException
@decorators.skip_because(bug="1205344")
@test.idempotent_id('10fd234a-515c-41e5-b092-8323060598c5')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting is not available.')
- @tempest.test.services('compute', 'network', 'volume', 'image')
+ @test.services('compute', 'network', 'volume', 'image')
def test_stamp_pattern(self):
# prepare for booting an instance
keypair = self.create_keypair()
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 39351f8..25d825a 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log
+from oslo_log import log as logging
from tempest.common.utils import data_utils
from tempest.common import waiters
@@ -19,8 +19,7 @@
from tempest import test
CONF = config.CONF
-
-LOG = log.getLogger(__name__)
+LOG = logging.getLogger(__name__)
class TestVolumeBootPattern(manager.ScenarioTest):
@@ -36,6 +35,11 @@
* Boot an additional instance from the new snapshot based volume
* Check written content in the instance booted from snapshot
"""
+
+ # Boot from volume scenario is quite slow, and needs extra
+ # breathing room to get through deletes in the time allotted.
+ TIMEOUT_SCALING_FACTOR = 2
+
@classmethod
def skip_checks(cls):
super(TestVolumeBootPattern, cls).skip_checks()
@@ -83,7 +87,8 @@
self.addCleanup(
self.snapshots_client.wait_for_resource_deletion, snap['id'])
self.addCleanup(self.snapshots_client.delete_snapshot, snap['id'])
- self.snapshots_client.wait_for_snapshot_status(snap['id'], 'available')
+ waiters.wait_for_snapshot_status(self.snapshots_client,
+ snap['id'], 'available')
# NOTE(e0ne): Cinder API v2 uses name instead of display_name
if 'display_name' in snap:
@@ -105,42 +110,53 @@
@test.attr(type='smoke')
@test.services('compute', 'volume', 'image')
def test_volume_boot_pattern(self):
+ LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
security_group = self._create_security_group()
# create an instance from volume
+ LOG.info("Booting instance 1 from volume")
volume_origin = self._create_volume_from_image()
instance_1st = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
+ LOG.info("Booted first instance: %s" % instance_1st)
# write content to volume on instance
+ LOG.info("Setting timestamp in instance %s" % instance_1st)
ip_instance_1st = self.get_server_ip(instance_1st)
timestamp = self.create_timestamp(ip_instance_1st,
private_key=keypair['private_key'])
# delete instance
+ LOG.info("Deleting first instance: %s" % instance_1st)
self._delete_server(instance_1st)
# create a 2nd instance from volume
instance_2nd = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
+ LOG.info("Booted second instance %s" % instance_2nd)
# check the content of written file
+ LOG.info("Getting timestamp in instance %s" % instance_2nd)
ip_instance_2nd = self.get_server_ip(instance_2nd)
timestamp2 = self.get_timestamp(ip_instance_2nd,
private_key=keypair['private_key'])
self.assertEqual(timestamp, timestamp2)
# snapshot a volume
+ LOG.info("Creating snapshot from volume: %s" % volume_origin['id'])
snapshot = self._create_snapshot_from_volume(volume_origin['id'])
# create a 3rd instance from snapshot
+ LOG.info("Creating third instance from snapshot: %s" % snapshot['id'])
volume = self._create_volume_from_snapshot(snapshot['id'])
server_from_snapshot = (
self._boot_instance_from_volume(volume['id'],
keypair, security_group))
# check the content of written file
+ LOG.info("Logging into third instance to get timestamp: %s" %
+ server_from_snapshot)
server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
timestamp3 = self.get_timestamp(server_from_snapshot_ip,
private_key=keypair['private_key'])
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index 3cbb3bc..75fd000 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -18,14 +18,14 @@
import unicodedata
from oslo_serialization import jsonutils as json
-from tempest_lib.common.utils import misc
-from tempest_lib import exceptions as exc_lib
import testscenarios
import testtools
from tempest import clients
from tempest.common import credentials_factory as credentials
from tempest import config
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions as exc_lib
CONF = config.CONF
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
index d8cb99d..2bdd092 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -16,7 +16,7 @@
import six
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
def handle_errors(f):
@@ -39,7 +39,7 @@
return wrapper
-class BaremetalClient(service_client.ServiceClient):
+class BaremetalClient(rest_client.RestClient):
"""Base Tempest REST client for Ironic API."""
uri_prefix = ''
@@ -111,7 +111,7 @@
uri += "?%s" % urllib.urlencode(kwargs)
resp, body = self.get(uri)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@@ -127,7 +127,7 @@
else:
uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
resp, body = self.get(uri)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@@ -145,7 +145,7 @@
uri = self._get_uri(resource)
resp, body = self.post(uri, body=body)
- self.expected_success(201, resp['status'])
+ self.expected_success(201, resp.status)
return resp, self.deserialize(body)
@@ -160,7 +160,7 @@
uri = self._get_uri(resource, uuid)
resp, body = self.delete(uri)
- self.expected_success(204, resp['status'])
+ self.expected_success(204, resp.status)
return resp, body
def _patch_request(self, resource, uuid, patch_object):
@@ -176,7 +176,7 @@
patch_body = json.dumps(patch_object)
resp, body = self.patch(uri, body=patch_body)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@handle_errors
@@ -201,5 +201,5 @@
put_body = json.dumps(put_object)
resp, body = self.put(uri, body=put_body)
- self.expected_success(202, resp['status'])
+ self.expected_success([202, 204], resp.status)
return resp, body
diff --git a/tempest/services/base_microversion_client.py b/tempest/services/base_microversion_client.py
deleted file mode 100644
index 4c750f5..0000000
--- a/tempest/services/base_microversion_client.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2016 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.common import rest_client
-
-
-class BaseMicroversionClient(rest_client.RestClient):
- """Base class to support microversion in service clients
-
- This class is used to support microversion in service clients.
- This provides feature to make API request with microversion.
- Service clients derived from this class will be able to send API
- request to server with or without microversion.
- If api_microversion is not set on service client then API request will be
- normal request without microversion.
-
- """
- def __init__(self, auth_provider, service, region,
- api_microversion_header_name, **kwargs):
- """Base Microversion Client __init__
-
- :param auth_provider: an auth provider object used to wrap requests in
- auth
- :param str service: The service name to use for the catalog lookup
- :param str region: The region to use for the catalog lookup
- :param str api_microversion_header_name: The microversion header name
- to use for sending API
- request with microversion
- :param kwargs: kwargs required by rest_client.RestClient
- """
- super(BaseMicroversionClient, self).__init__(
- auth_provider, service, region, **kwargs)
- self.api_microversion_header_name = api_microversion_header_name
- self.api_microversion = None
-
- def get_headers(self):
- headers = super(BaseMicroversionClient, self).get_headers()
- if self.api_microversion:
- headers[self.api_microversion_header_name] = self.api_microversion
- return headers
-
- def set_api_microversion(self, microversion):
- self.api_microversion = microversion
diff --git a/tempest/services/compute/json/__init__.py b/tempest/services/compute/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/compute/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/compute/json/base_compute_client.py b/tempest/services/compute/json/base_compute_client.py
deleted file mode 100644
index 5349af6..0000000
--- a/tempest/services/compute/json/base_compute_client.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.common import api_version_request
-from tempest.common import api_version_utils
-from tempest import exceptions
-from tempest.services import base_microversion_client
-
-
-class BaseComputeClient(base_microversion_client.BaseMicroversionClient):
-
- def __init__(self, auth_provider, service, region,
- api_microversion_header_name='X-OpenStack-Nova-API-Version',
- **kwargs):
- super(BaseComputeClient, self).__init__(
- auth_provider, service, region,
- api_microversion_header_name, **kwargs)
-
- def request(self, method, url, extra_headers=False, headers=None,
- body=None):
- resp, resp_body = super(BaseComputeClient, self).request(
- method, url, extra_headers, headers, body)
- if self.api_microversion and self.api_microversion != 'latest':
- api_version_utils.assert_version_header_matches_request(
- self.api_microversion_header_name,
- self.api_microversion,
- resp)
- return resp, resp_body
-
- def get_schema(self, schema_versions_info):
- """Get JSON schema
-
- This method provides the matching schema for requested
- microversion (self.api_microversion).
- :param schema_versions_info: List of dict which provides schema
- information with range of valid versions.
- Example -
- schema_versions_info = [
- {'min': None, 'max': '2.1', 'schema': schemav21},
- {'min': '2.2', 'max': '2.9', 'schema': schemav22},
- {'min': '2.10', 'max': None, 'schema': schemav210}]
- """
- schema = None
- version = api_version_request.APIVersionRequest(self.api_microversion)
- for items in schema_versions_info:
- min_version = api_version_request.APIVersionRequest(items['min'])
- max_version = api_version_request.APIVersionRequest(items['max'])
- # This is case where self.api_microversion is None, which means
- # request without microversion So select base v2.1 schema.
- if version.is_null() and items['min'] is None:
- schema = items['schema']
- break
- # else select appropriate schema as per self.api_microversion
- elif version.matches(min_version, max_version):
- schema = items['schema']
- break
- if schema is None:
- raise exceptions.JSONSchemaNotFound(
- version=version.get_string(),
- schema_versions_info=schema_versions_info)
- return schema
diff --git a/tempest/services/compute/json/keypairs_client.py b/tempest/services/compute/json/keypairs_client.py
deleted file mode 100644
index ec9b1e0..0000000
--- a/tempest/services/compute/json/keypairs_client.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# 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.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.api_schema.response.compute.v2_1 import keypairs as schemav21
-from tempest.api_schema.response.compute.v2_2 import keypairs as schemav22
-from tempest.common import service_client
-from tempest.services.compute.json import base_compute_client
-
-
-class KeyPairsClient(base_compute_client.BaseComputeClient):
-
- schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21},
- {'min': '2.2', 'max': None, 'schema': schemav22}]
-
- def list_keypairs(self):
- resp, body = self.get("os-keypairs")
- body = json.loads(body)
- schema = self.get_schema(self.schema_versions_info)
- self.validate_response(schema.list_keypairs, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def show_keypair(self, keypair_name):
- resp, body = self.get("os-keypairs/%s" % keypair_name)
- body = json.loads(body)
- schema = self.get_schema(self.schema_versions_info)
- self.validate_response(schema.get_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def create_keypair(self, **kwargs):
- post_body = json.dumps({'keypair': kwargs})
- resp, body = self.post("os-keypairs", body=post_body)
- body = json.loads(body)
- schema = self.get_schema(self.schema_versions_info)
- self.validate_response(schema.create_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def delete_keypair(self, keypair_name):
- resp, body = self.delete("os-keypairs/%s" % keypair_name)
- schema = self.get_schema(self.schema_versions_info)
- self.validate_response(schema.delete_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/data_processing/v1_1/data_processing_client.py b/tempest/services/data_processing/v1_1/data_processing_client.py
index 5aa2622..c74672f 100644
--- a/tempest/services/data_processing/v1_1/data_processing_client.py
+++ b/tempest/services/data_processing/v1_1/data_processing_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DataProcessingClient(service_client.ServiceClient):
+class DataProcessingClient(rest_client.RestClient):
def _request_and_check_resp(self, request_func, uri, resp_status):
"""Make a request and check response status code.
@@ -26,7 +26,7 @@
"""
resp, body = request_func(uri)
self.expected_success(resp_status, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _request_and_check_resp_data(self, request_func, uri, resp_status):
"""Make a request and check response status code.
@@ -47,7 +47,7 @@
resp, body = request_func(uri, headers=headers, *args, **kwargs)
self.expected_success(resp_status, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_node_group_templates(self):
"""List all node group templates for a user."""
diff --git a/tempest/services/database/json/flavors_client.py b/tempest/services/database/json/flavors_client.py
index dbb5172..bd8ffb0 100644
--- a/tempest/services/database/json/flavors_client.py
+++ b/tempest/services/database/json/flavors_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves import urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseFlavorsClient(service_client.ServiceClient):
+class DatabaseFlavorsClient(rest_client.RestClient):
def list_db_flavors(self, params=None):
url = 'flavors'
@@ -29,10 +29,10 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_db_flavor(self, db_flavor_id):
resp, body = self.get("flavors/%s" % db_flavor_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/database/json/limits_client.py b/tempest/services/database/json/limits_client.py
index da495d7..a1c58c2 100644
--- a/tempest/services/database/json/limits_client.py
+++ b/tempest/services/database/json/limits_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseLimitsClient(service_client.ServiceClient):
+class DatabaseLimitsClient(rest_client.RestClient):
def list_db_limits(self, params=None):
"""List all limits."""
@@ -29,4 +29,4 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/database/json/versions_client.py b/tempest/services/database/json/versions_client.py
index 7a560d9..2f28203 100644
--- a/tempest/services/database/json/versions_client.py
+++ b/tempest/services/database/json/versions_client.py
@@ -16,24 +16,14 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseVersionsClient(service_client.ServiceClient):
+class DatabaseVersionsClient(rest_client.RestClient):
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
- dscv = disable_ssl_certificate_validation
+ def __init__(self, auth_provider, service, region, **kwargs):
super(DatabaseVersionsClient, self).__init__(
- auth_provider, service, region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, service, region, **kwargs)
self.skip_path()
def list_db_versions(self, params=None):
@@ -45,4 +35,4 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/endpoints_client.py b/tempest/services/identity/v2/json/endpoints_client.py
index ff9907d..ba9f867 100644
--- a/tempest/services/identity/v2/json/endpoints_client.py
+++ b/tempest/services/identity/v2/json/endpoints_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class EndpointsClient(service_client.ServiceClient):
+class EndpointsClient(rest_client.RestClient):
api_version = "v2.0"
def create_endpoint(self, service_id, region_id, **kwargs):
@@ -33,18 +33,18 @@
resp, body = self.post('/endpoints', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_endpoints(self):
"""List Endpoints - Returns Endpoints."""
resp, body = self.get('/endpoints')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_endpoint(self, endpoint_id):
"""Delete an endpoint."""
url = '/endpoints/%s' % endpoint_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/identity_client.py b/tempest/services/identity/v2/json/identity_client.py
index f045bb7..6caff0e 100644
--- a/tempest/services/identity/v2/json/identity_client.py
+++ b/tempest/services/identity/v2/json/identity_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class IdentityClient(service_client.ServiceClient):
+class IdentityClient(rest_client.RestClient):
api_version = "v2.0"
def show_api_description(self):
@@ -24,24 +24,24 @@
resp, body = self.get(url)
self.expected_success([200, 203], resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_token(self, token_id):
"""Get token details."""
resp, body = self.get("tokens/%s" % token_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_token(self, token_id):
"""Delete a token."""
resp, body = self.delete("tokens/%s" % token_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_extensions(self):
"""List all the extensions."""
resp, body = self.get('/extensions')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/roles_client.py b/tempest/services/identity/v2/json/roles_client.py
index ef6dfe9..acd97c6 100644
--- a/tempest/services/identity/v2/json/roles_client.py
+++ b/tempest/services/identity/v2/json/roles_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class RolesClient(service_client.ServiceClient):
+class RolesClient(rest_client.RestClient):
api_version = "v2.0"
def create_role(self, **kwargs):
@@ -28,14 +28,14 @@
resp, body = self.post('OS-KSADM/roles', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_role(self, role_id):
"""Get a role by its id."""
resp, body = self.get('OS-KSADM/roles/%s' % role_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_role(self, role_id):
"""Delete a role."""
@@ -49,7 +49,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def assign_user_role(self, tenant_id, user_id, role_id):
"""Add roles to a user on a tenant."""
@@ -57,18 +57,18 @@
(tenant_id, user_id, role_id), "")
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user_role(self, tenant_id, user_id, role_id):
"""Removes a role assignment for a user on a tenant."""
resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_roles(self):
"""Returns roles."""
resp, body = self.get('OS-KSADM/roles')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/services/identity/v2/json/services_client.py
index 436d00d..d8be6c6 100644
--- a/tempest/services/identity/v2/json/services_client.py
+++ b/tempest/services/identity/v2/json/services_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ServicesClient(service_client.ServiceClient):
+class ServicesClient(rest_client.RestClient):
api_version = "v2.0"
def create_service(self, name, type, **kwargs):
@@ -31,7 +31,7 @@
resp, body = self.post('/OS-KSADM/services', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_service(self, service_id):
"""Get Service."""
@@ -39,18 +39,18 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_services(self):
"""List Service - Returns Services."""
resp, body = self.get('/OS-KSADM/services')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_service(self, service_id):
"""Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/tenants_client.py b/tempest/services/identity/v2/json/tenants_client.py
index 937ae6f..034938e 100644
--- a/tempest/services/identity/v2/json/tenants_client.py
+++ b/tempest/services/identity/v2/json/tenants_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class TenantsClient(service_client.ServiceClient):
+class TenantsClient(rest_client.RestClient):
api_version = "v2.0"
def create_tenant(self, name, **kwargs):
@@ -36,27 +36,27 @@
resp, body = self.post('tenants', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_tenant(self, tenant_id):
"""Delete a tenant."""
resp, body = self.delete('tenants/%s' % str(tenant_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_tenant(self, tenant_id):
"""Get tenant details."""
resp, body = self.get('tenants/%s' % str(tenant_id))
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_tenants(self):
"""Returns tenants."""
resp, body = self.get('tenants')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant."""
@@ -74,11 +74,11 @@
resp, body = self.post('tenants/%s' % tenant_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_tenant_users(self, tenant_id):
"""List users for a Tenant."""
resp, body = self.get('/tenants/%s/users' % tenant_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/users_client.py b/tempest/services/identity/v2/json/users_client.py
index 5327638..5f8127f 100644
--- a/tempest/services/identity/v2/json/users_client.py
+++ b/tempest/services/identity/v2/json/users_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class UsersClient(service_client.ServiceClient):
+class UsersClient(rest_client.RestClient):
api_version = "v2.0"
def create_user(self, name, password, tenant_id, email, **kwargs):
@@ -33,7 +33,7 @@
resp, body = self.post('users', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user(self, user_id, **kwargs):
"""Updates a user."""
@@ -41,27 +41,27 @@
resp, body = self.put('users/%s' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user(self, user_id):
"""Delete a user."""
resp, body = self.delete("users/%s" % user_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_users(self):
"""Get the list of users."""
resp, body = self.get("users")
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def enable_disable_user(self, user_id, **kwargs):
"""Enables or disables a user.
@@ -77,7 +77,7 @@
resp, body = self.put('users/%s/enabled' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user_password(self, user_id, **kwargs):
"""Update User Password."""
@@ -89,7 +89,7 @@
resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user_own_password(self, user_id, **kwargs):
"""User updates own password"""
@@ -104,7 +104,7 @@
resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_user_ec2_credentials(self, user_id, **kwargs):
# TODO(piyush): Current api-site doesn't contain this API description.
@@ -115,23 +115,23 @@
post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user_ec2_credentials(self, user_id, access):
resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
(user_id, access))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_user_ec2_credentials(self, user_id):
resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_user_ec2_credentials(self, user_id, access):
resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
(user_id, access))
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/credentials_client.py b/tempest/services/identity/v3/json/credentials_client.py
index 753e960..6ab94d0 100644
--- a/tempest/services/identity/v3/json/credentials_client.py
+++ b/tempest/services/identity/v3/json/credentials_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class CredentialsClient(service_client.ServiceClient):
+class CredentialsClient(rest_client.RestClient):
api_version = "v3"
def create_credential(self, **kwargs):
@@ -36,7 +36,7 @@
self.expected_success(201, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_credential(self, credential_id, **kwargs):
"""Updates a credential.
@@ -49,7 +49,7 @@
self.expected_success(200, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_credential(self, credential_id):
"""To GET Details of a credential."""
@@ -57,17 +57,17 @@
self.expected_success(200, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_credentials(self):
"""Lists out all the available credentials."""
resp, body = self.get('credentials')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_credential(self, credential_id):
"""Deletes a credential."""
resp, body = self.delete('credentials/%s' % credential_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/domains_client.py b/tempest/services/identity/v3/json/domains_client.py
new file mode 100644
index 0000000..d129a0a
--- /dev/null
+++ b/tempest/services/identity/v3/json/domains_client.py
@@ -0,0 +1,77 @@
+# Copyright 2016 Red Hat, 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class DomainsClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.post('domains', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_domain(self, domain_id):
+ """Deletes a domain."""
+ resp, body = self.delete('domains/%s' % str(domain_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_domains(self, params=None):
+ """List Domains."""
+ url = 'domains'
+ 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)
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ body = self.show_domain(domain_id)['domain']
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.patch('domains/%s' % domain_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
index ede5edb..db30508 100644
--- a/tempest/services/identity/v3/json/endpoints_client.py
+++ b/tempest/services/identity/v3/json/endpoints_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class EndPointClient(service_client.ServiceClient):
+class EndPointsClient(rest_client.RestClient):
api_version = "v3"
def list_endpoints(self):
@@ -30,7 +30,7 @@
resp, body = self.get('endpoints')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_endpoint(self, **kwargs):
"""Create endpoint.
@@ -42,7 +42,7 @@
resp, body = self.post('endpoints', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_endpoint(self, endpoint_id, **kwargs):
"""Updates an endpoint with given parameters.
@@ -54,10 +54,17 @@
resp, body = self.patch('endpoints/%s' % endpoint_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_endpoint(self, endpoint_id):
"""Delete endpoint."""
resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
self.expected_success(204, resp_header.status)
- return service_client.ResponseBody(resp_header, resp_body)
+ return rest_client.ResponseBody(resp_header, resp_body)
+
+ def show_endpoint(self, endpoint_id):
+ """Get endpoint."""
+ resp_header, resp_body = self.get('endpoints/%s' % endpoint_id)
+ 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/services/identity/v3/json/groups_client.py b/tempest/services/identity/v3/json/groups_client.py
index 6ed85cf..1a495f8 100644
--- a/tempest/services/identity/v3/json/groups_client.py
+++ b/tempest/services/identity/v3/json/groups_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class GroupsClient(service_client.ServiceClient):
+class GroupsClient(rest_client.RestClient):
api_version = "v3"
def create_group(self, **kwargs):
@@ -35,21 +35,21 @@
resp, body = self.post('groups', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
"""Get group details."""
resp, body = self.get('groups/%s' % group_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_groups(self):
"""Lists the groups."""
resp, body = self.get('groups')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_group(self, group_id, **kwargs):
"""Updates a group.
@@ -61,36 +61,36 @@
resp, body = self.patch('groups/%s' % group_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id):
"""Delete a group."""
resp, body = self.delete('groups/%s' % str(group_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def add_group_user(self, group_id, user_id):
"""Add user into group."""
resp, body = self.put('groups/%s/users/%s' % (group_id, user_id),
None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_group_users(self, group_id):
"""List users in group."""
resp, body = self.get('groups/%s/users' % group_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def check_group_user_existence(self, group_id, user_id):
"""Check user in group."""
resp, body = self.head('groups/%s/users/%s' % (group_id, user_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index fba26d4..8177e35 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -14,12 +14,11 @@
# under the License.
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class IdentityV3Client(service_client.ServiceClient):
+class IdentityClient(rest_client.RestClient):
api_version = "v3"
def show_api_description(self):
@@ -28,263 +27,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def create_user(self, user_name, password=None, project_id=None,
- email=None, domain_id='default', **kwargs):
- """Creates a user."""
- en = kwargs.get('enabled', True)
- description = kwargs.get('description', None)
- default_project_id = kwargs.get('default_project_id')
- post_body = {
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'description': description,
- 'domain_id': domain_id,
- 'email': email,
- 'enabled': en,
- 'name': user_name,
- 'password': password
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.post('users', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_user(self, user_id, name, **kwargs):
- """Updates a user."""
- body = self.show_user(user_id)['user']
- email = kwargs.get('email', body['email'])
- en = kwargs.get('enabled', body['enabled'])
- project_id = kwargs.get('project_id', body['project_id'])
- if 'default_project_id' in body.keys():
- default_project_id = kwargs.get('default_project_id',
- body['default_project_id'])
- else:
- default_project_id = kwargs.get('default_project_id')
- description = kwargs.get('description', body['description'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'name': name,
- 'email': email,
- 'enabled': en,
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'id': user_id,
- 'domain_id': domain_id,
- 'description': description
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.patch('users/%s' % user_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_user_password(self, user_id, **kwargs):
- """Update a user password
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#changeUserPassword
- """
- update_user = json.dumps({'user': kwargs})
- resp, _ = self.post('users/%s/password' % user_id, update_user)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def list_user_projects(self, user_id):
- """Lists the projects on which a user has roles assigned."""
- resp, body = self.get('users/%s/projects' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_users(self, params=None):
- """Get the list of users."""
- url = 'users'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_user(self, user_id):
- """GET a user."""
- resp, body = self.get("users/%s" % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_user(self, user_id):
- """Deletes a User."""
- resp, body = self.delete("users/%s" % user_id)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def create_project(self, name, **kwargs):
- """Creates a project."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- domain_id = kwargs.get('domain_id', 'default')
- post_body = {
- 'description': description,
- 'domain_id': domain_id,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'project': post_body})
- resp, body = self.post('projects', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_projects(self, params=None):
- url = "projects"
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_project(self, project_id, **kwargs):
- body = self.get_project(project_id)['project']
- name = kwargs.get('name', body['name'])
- desc = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'id': project_id,
- 'name': name,
- 'description': desc,
- 'enabled': en,
- 'domain_id': domain_id,
- }
- post_body = json.dumps({'project': post_body})
- resp, body = self.patch('projects/%s' % project_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def get_project(self, project_id):
- """GET a Project."""
- resp, body = self.get("projects/%s" % project_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_project(self, project_id):
- """Delete a project."""
- resp, body = self.delete('projects/%s' % str(project_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def create_role(self, **kwargs):
- """Create a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.post('roles', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_role(self, role_id):
- """GET a Role."""
- resp, body = self.get('roles/%s' % str(role_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_roles(self):
- """Get the list of Roles."""
- resp, body = self.get("roles")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_role(self, role_id, **kwargs):
- """Update a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.patch('roles/%s' % str(role_id), post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role(self, role_id):
- """Delete a role."""
- resp, body = self.delete('roles/%s' % str(role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role(self, project_id, user_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def create_domain(self, name, **kwargs):
- """Creates a domain."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
- resp, body = self.post('domains', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_domain(self, domain_id):
- """Delete a domain."""
- resp, body = self.delete('domains/%s' % str(domain_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_domains(self, params=None):
- """List Domains."""
- url = 'domains'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_domain(self, domain_id, **kwargs):
- """Updates a domain."""
- body = self.get_domain(domain_id)['domain']
- description = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- name = kwargs.get('name', body['name'])
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
- resp, body = self.patch('domains/%s' % domain_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def get_domain(self, domain_id):
- """Get Domain details."""
- resp, body = self.get('domains/%s' % domain_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_token(self, resp_token):
"""Get token details."""
@@ -292,199 +35,11 @@
resp, body = self.get("auth/tokens", headers=headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_token(self, resp_token):
"""Deletes token."""
headers = {'X-Subject-Token': resp_token}
resp, body = self.delete("auth/tokens", headers=headers)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_user_groups(self, user_id):
- """Lists groups which a user belongs to."""
- resp, body = self.get('users/%s/groups' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role_on_project(self, project_id, user_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role_on_domain(self, domain_id, user_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_user_roles_on_project(self, project_id, user_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/users/%s/roles' %
- (project_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_user_roles_on_domain(self, domain_id, user_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/users/%s/roles' %
- (domain_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_project(self, project_id, user_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def check_user_role_existence_on_project(self, project_id,
- user_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def check_user_role_existence_on_domain(self, domain_id,
- user_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def assign_group_role_on_project(self, project_id, group_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_group_role_on_domain(self, domain_id, group_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_group_roles_on_project(self, project_id, group_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/groups/%s/roles' %
- (project_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_group_roles_on_domain(self, domain_id, group_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/groups/%s/roles' %
- (domain_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_project(self, project_id, group_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def check_role_from_group_on_project_existence(self, project_id,
- group_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def check_role_from_group_on_domain_existence(self, domain_id,
- group_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def create_trust(self, **kwargs):
- """Creates a trust.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3-ext.html#createTrust
- """
- post_body = json.dumps({'trust': kwargs})
- resp, body = self.post('OS-TRUST/trusts', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_trust(self, trust_id):
- """Deletes a trust."""
- resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def get_trusts(self, trustor_user_id=None, trustee_user_id=None):
- """GET trusts."""
- if trustor_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
- % trustor_user_id)
- elif trustee_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
- % trustee_user_id)
- else:
- resp, body = self.get("OS-TRUST/trusts")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def get_trust(self, trust_id):
- """GET trust."""
- resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def get_trust_roles(self, trust_id):
- """GET roles delegated by a trust."""
- resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def get_trust_role(self, trust_id, role_id):
- """GET role delegated by a trust."""
- resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
- % (trust_id, role_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def check_trust_role(self, trust_id, role_id):
- """HEAD Check if role is delegated by a trust."""
- resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
- % (trust_id, role_id))
- self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/policies_client.py b/tempest/services/identity/v3/json/policies_client.py
index 639ed6d..f28db9a 100644
--- a/tempest/services/identity/v3/json/policies_client.py
+++ b/tempest/services/identity/v3/json/policies_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class PoliciesClient(service_client.ServiceClient):
+class PoliciesClient(rest_client.RestClient):
api_version = "v3"
def create_policy(self, **kwargs):
@@ -35,14 +35,14 @@
resp, body = self.post('policies', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_policies(self):
"""Lists the policies."""
resp, body = self.get('policies')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_policy(self, policy_id):
"""Lists out the given policy."""
@@ -50,7 +50,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_policy(self, policy_id, **kwargs):
"""Updates a policy.
@@ -63,11 +63,11 @@
resp, body = self.patch(url, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_policy(self, policy_id):
"""Deletes the policy."""
url = "policies/%s" % policy_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/projects_client.py b/tempest/services/identity/v3/json/projects_client.py
new file mode 100644
index 0000000..97e43df
--- /dev/null
+++ b/tempest/services/identity/v3/json/projects_client.py
@@ -0,0 +1,73 @@
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class ProjectsClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_project(self, name, **kwargs):
+ """Create a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#createProject
+
+ """
+ # Include the project name to the kwargs parameters
+ kwargs['name'] = name
+ post_body = json.dumps({'project': kwargs})
+ resp, body = self.post('projects', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_projects(self, params=None):
+ url = "projects"
+ 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)
+
+ def update_project(self, project_id, **kwargs):
+ """Update a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#updateProject
+
+ """
+ post_body = json.dumps({'project': kwargs})
+ resp, body = self.patch('projects/%s' % project_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_project(self, project_id):
+ """GET a Project."""
+ resp, body = self.get("projects/%s" % project_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_project(self, project_id):
+ """Delete a project."""
+ resp, body = self.delete('projects/%s' % str(project_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/regions_client.py b/tempest/services/identity/v3/json/regions_client.py
index bc4b7a1..90dd9d7 100644
--- a/tempest/services/identity/v3/json/regions_client.py
+++ b/tempest/services/identity/v3/json/regions_client.py
@@ -20,10 +20,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class RegionsClient(service_client.ServiceClient):
+class RegionsClient(rest_client.RestClient):
api_version = "v3"
def create_region(self, region_id=None, **kwargs):
@@ -45,7 +45,7 @@
resp, body = method(url, req_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_region(self, region_id, **kwargs):
"""Updates a region.
@@ -57,7 +57,7 @@
resp, body = self.patch('regions/%s' % region_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_region(self, region_id):
"""Get region."""
@@ -65,7 +65,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_regions(self, params=None):
"""List regions."""
@@ -75,10 +75,10 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_region(self, region_id):
"""Delete region."""
resp, body = self.delete('regions/%s' % region_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/roles_client.py b/tempest/services/identity/v3/json/roles_client.py
new file mode 100644
index 0000000..bdb0490
--- /dev/null
+++ b/tempest/services/identity/v3/json/roles_client.py
@@ -0,0 +1,315 @@
+# Copyright 2016 Red Hat, 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class RolesClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_role(self, **kwargs):
+ """Create a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#createRole
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.post('roles', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % str(role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_roles(self):
+ """Get the list of Roles."""
+ resp, body = self.get("roles")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_role(self, role_id, **kwargs):
+ """Update a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#updateRole
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.patch('roles/%s' % str(role_id), post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_project(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_domain(self, domain_id, user_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_project(self, project_id, user_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/users/%s/roles' %
+ (project_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_domain(self, domain_id, user_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/users/%s/roles' %
+ (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_project(self, project_id, user_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_role_existence_on_project(self, project_id,
+ user_id, role_id):
+ """Check role of a user on a project."""
+ resp, body = self.head('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_user_role_existence_on_domain(self, domain_id,
+ user_id, role_id):
+ """Check role of a user on a domain."""
+ resp, body = self.head('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_group_role_on_project(self, project_id, group_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_group_role_on_domain(self, domain_id, group_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_project(self, project_id, group_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/groups/%s/roles' %
+ (project_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_domain(self, domain_id, group_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/groups/%s/roles' %
+ (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_project(self, project_id, group_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_role_from_group_on_project_existence(self, project_id,
+ group_id, role_id):
+ """Check role of a user on a project."""
+ resp, body = self.head('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_role_from_group_on_domain_existence(self, domain_id,
+ group_id, role_id):
+ """Check role of a user on a domain."""
+ resp, body = self.head('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_domains_user(
+ self, domain_id, user_id, role_id):
+ """Assigns a role to a user on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_user_on_domain(
+ self, domain_id, user_id, role_id):
+ """Revokes an inherited project role from a user on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_user_on_domain(
+ self, domain_id, user_id):
+ """Lists the inherited project roles on a domain for a user."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/users/%s/roles/inherited_to_projects"
+ % (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_inherited_project_role_on_domain(
+ self, domain_id, user_id, role_id):
+ """Checks whether a user has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_domains_group(
+ self, domain_id, group_id, role_id):
+ """Assigns a role to a group on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_group_on_domain(
+ self, domain_id, group_id, role_id):
+ """Revokes an inherited project role from a group on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_group_on_domain(
+ self, domain_id, group_id):
+ """Lists the inherited project roles on a domain for a group."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/groups/%s/roles/inherited_to_projects"
+ % (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_inherited_project_role_on_domain(
+ self, domain_id, group_id, role_id):
+ """Checks whether a group has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_projects_user(
+ self, project_id, user_id, role_id):
+ """Assigns a role to a user on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_user_on_project(
+ self, project_id, user_id, role_id):
+ """Revokes an inherited role from a user on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_has_flag_on_inherited_to_project(
+ self, project_id, user_id, role_id):
+ """Checks whether a user has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_projects_group(
+ self, project_id, group_id, role_id):
+ """Assigns a role to a group on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_group_on_project(
+ self, project_id, group_id, role_id):
+ """Revokes an inherited role from a group on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_has_flag_on_inherited_to_project(
+ self, project_id, group_id, role_id):
+ """Checks whether a group has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/services_client.py b/tempest/services/identity/v3/json/services_client.py
index dd65f1d..e863016 100644
--- a/tempest/services/identity/v3/json/services_client.py
+++ b/tempest/services/identity/v3/json/services_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ServicesClient(service_client.ServiceClient):
+class ServicesClient(rest_client.RestClient):
api_version = "v3"
def update_service(self, service_id, **kwargs):
@@ -35,7 +35,7 @@
resp, body = self.patch('services/%s' % service_id, patch_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_service(self, service_id):
"""Get Service."""
@@ -43,7 +43,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_service(self, **kwargs):
"""Creates a service.
@@ -55,16 +55,16 @@
resp, body = self.post("services", body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_service(self, serv_id):
url = "services/" + serv_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_services(self):
resp, body = self.get('services')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/trusts_client.py b/tempest/services/identity/v3/json/trusts_client.py
new file mode 100644
index 0000000..dedee05
--- /dev/null
+++ b/tempest/services/identity/v3/json/trusts_client.py
@@ -0,0 +1,82 @@
+# Copyright 2016 Red Hat, 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class TrustsClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_trust(self, **kwargs):
+ """Creates a trust.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3-ext.html#createTrust
+ """
+ post_body = json.dumps({'trust': kwargs})
+ resp, body = self.post('OS-TRUST/trusts', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_trust(self, trust_id):
+ """Deletes a trust."""
+ resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_trusts(self, trustor_user_id=None, trustee_user_id=None):
+ """GET trusts."""
+ if trustor_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
+ % trustor_user_id)
+ elif trustee_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
+ % trustee_user_id)
+ else:
+ resp, body = self.get("OS-TRUST/trusts")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_trust(self, trust_id):
+ """GET trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_trust_roles(self, trust_id):
+ """GET roles delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_trust_role(self, trust_id, role_id):
+ """GET role delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_trust_role(self, trust_id, role_id):
+ """HEAD Check if role is delegated by a trust."""
+ resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/services/identity/v3/json/users_clients.py
new file mode 100644
index 0000000..3ab8eab
--- /dev/null
+++ b/tempest/services/identity/v3/json/users_clients.py
@@ -0,0 +1,121 @@
+# Copyright 2016 Red Hat, 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class UsersClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_user(self, user_name, password=None, project_id=None,
+ email=None, domain_id='default', **kwargs):
+ """Creates a user."""
+ en = kwargs.get('enabled', True)
+ description = kwargs.get('description', None)
+ default_project_id = kwargs.get('default_project_id')
+ post_body = {
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'description': description,
+ 'domain_id': domain_id,
+ 'email': email,
+ 'enabled': en,
+ 'name': user_name,
+ 'password': password
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.post('users', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user(self, user_id, name, **kwargs):
+ """Updates a user."""
+ body = self.show_user(user_id)['user']
+ email = kwargs.get('email', body['email'])
+ en = kwargs.get('enabled', body['enabled'])
+ project_id = kwargs.get('project_id', body['project_id'])
+ if 'default_project_id' in body.keys():
+ default_project_id = kwargs.get('default_project_id',
+ body['default_project_id'])
+ else:
+ default_project_id = kwargs.get('default_project_id')
+ description = kwargs.get('description', body['description'])
+ domain_id = kwargs.get('domain_id', body['domain_id'])
+ post_body = {
+ 'name': name,
+ 'email': email,
+ 'enabled': en,
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'id': user_id,
+ 'domain_id': domain_id,
+ 'description': description
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.patch('users/%s' % user_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user_password(self, user_id, **kwargs):
+ """Update a user password
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#changeUserPassword
+ """
+ update_user = json.dumps({'user': kwargs})
+ resp, _ = self.post('users/%s/password' % user_id, update_user)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def list_user_projects(self, user_id):
+ """Lists the projects on which a user has roles assigned."""
+ resp, body = self.get('users/%s/projects' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_users(self, params=None):
+ """Get the list of users."""
+ url = 'users'
+ 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)
+
+ def show_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_groups(self, user_id):
+ """Lists groups which a user belongs to."""
+ resp, body = self.get('users/%s/groups' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index af2e68c..a36075f 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -16,42 +16,27 @@
import copy
import errno
import os
-import time
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest.common import glance_http
-from tempest.common import service_client
-from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
-class ImagesClient(service_client.ServiceClient):
+class ImagesClient(rest_client.RestClient):
- def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ def __init__(self, auth_provider, catalog_type, region, **kwargs):
super(ImagesClient, self).__init__(
- auth_provider,
- catalog_type,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, catalog_type, region, **kwargs)
self._http = None
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
+ self.dscv = kwargs.get("disable_ssl_certificate_validation")
+ self.ca_certs = kwargs.get("ca_certs")
def _image_meta_from_headers(self, headers):
meta = {'properties': {}}
@@ -130,7 +115,7 @@
self._error_checker('POST', '/v1/images', headers, data, resp,
body_iter)
body = json.loads(''.join([c for c in body_iter]))
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _update_with_data(self, image_id, headers, data):
url = '/v1/images/%s' % image_id
@@ -139,7 +124,7 @@
self._error_checker('PUT', url, headers, data,
resp, body_iter)
body = json.loads(''.join([c for c in body_iter]))
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
@property
def http(self):
@@ -158,7 +143,7 @@
resp, body = self.post('v1/images', None, headers)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_image(self, image_id, **kwargs):
headers = {}
@@ -172,13 +157,13 @@
resp, body = self.put(url, None, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
url = 'v1/images/%s' % image_id
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_images(self, detail=False, **kwargs):
"""Return a list of all images filtered by input parameters.
@@ -208,24 +193,27 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
- def get_image_meta(self, image_id):
+ def check_image(self, image_id):
+ """Check image metadata."""
url = 'v1/images/%s' % image_id
resp, __ = self.head(url)
self.expected_success(200, resp.status)
body = self._image_meta_from_headers(resp)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
+ """Get image details plus the image itself."""
url = 'v1/images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
def is_resource_deleted(self, id):
try:
- self.get_image_meta(id)
+ if self.check_image(id)['status'] == 'deleted':
+ return True
except lib_exc.NotFound:
return True
return False
@@ -240,7 +228,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_shared_images(self, tenant_id):
"""List shared images with the specified tenant"""
@@ -248,7 +236,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def add_member(self, member_id, image_id, **kwargs):
"""Add a member to an image.
@@ -260,47 +248,10 @@
body = json.dumps({'member': kwargs})
resp, __ = self.put(url, body)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def delete_member(self, member_id, image_id):
url = 'v1/images/%s/members/%s' % (image_id, member_id)
resp, __ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- # NOTE(afazekas): just for the wait function
- def _get_image_status(self, image_id):
- meta = self.get_image_meta(image_id)
- status = meta['status']
- return status
-
- # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
- def wait_for_image_status(self, image_id, status):
- """Waits for a Image to reach a given status."""
- start_time = time.time()
- old_value = value = self._get_image_status(image_id)
- while True:
- dtime = time.time() - start_time
- time.sleep(self.build_interval)
- if value != old_value:
- LOG.info('Value transition from "%s" to "%s"'
- 'in %d second(s).', old_value,
- value, dtime)
- if value == status:
- return value
-
- if value == 'killed':
- raise exceptions.ImageKilledException(image_id=image_id,
- status=status)
- if dtime > self.build_timeout:
- message = ('Time Limit Exceeded! (%ds)'
- 'while waiting for %s, '
- 'but we got %s.' %
- (self.build_timeout, status, value))
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
- time.sleep(self.build_interval)
- old_value = value
- value = self._get_image_status(image_id)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 72b203a..a61f31d 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -15,32 +15,20 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import glance_http
-from tempest.common import service_client
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class ImagesClientV2(service_client.ServiceClient):
+class ImagesClient(rest_client.RestClient):
- def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
- super(ImagesClientV2, self).__init__(
- auth_provider,
- catalog_type,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ def __init__(self, auth_provider, catalog_type, region, **kwargs):
+ super(ImagesClient, self).__init__(
+ auth_provider, catalog_type, region, **kwargs)
self._http = None
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
+ self.dscv = kwargs.get("disable_ssl_certificate_validation")
+ self.ca_certs = kwargs.get("ca_certs")
def _get_http(self):
return glance_http.HTTPClient(auth_provider=self.auth_provider,
@@ -66,7 +54,7 @@
resp, body = self.patch('v2/images/%s' % image_id, data, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_image(self, **kwargs):
"""Create an image.
@@ -78,25 +66,25 @@
resp, body = self.post('v2/images', data)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def deactivate_image(self, image_id):
url = 'v2/images/%s/actions/deactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reactivate_image(self, image_id):
url = 'v2/images/%s/actions/reactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_images(self, params=None):
url = 'v2/images'
@@ -107,14 +95,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
@@ -134,32 +122,32 @@
resp, body = self.http.raw_request('PUT', url, headers=headers,
body=data)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
url = 'v2/images/%s/file' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_image_members(self, image_id):
url = 'v2/images/%s/members' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_image_member(self, image_id, **kwargs):
"""Create an image member.
@@ -172,7 +160,7 @@
resp, body = self.post(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_image_member(self, image_id, member_id, **kwargs):
"""Update an image member.
@@ -185,33 +173,33 @@
resp, body = self.put(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def delete_image_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def show_schema(self, schema):
url = 'v2/schemas/%s' % schema
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_resource_types(self):
url = '/v2/metadefs/resource_types'
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_namespace(self, **kwargs):
"""Create a namespace.
@@ -223,14 +211,14 @@
resp, body = self.post('/v2/metadefs/namespaces', data)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_namespace(self, namespace, **kwargs):
"""Update a namespace.
@@ -247,10 +235,10 @@
resp, body = self.put(url, body=data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/messaging/__init__.py b/tempest/services/messaging/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/messaging/__init__.py
+++ /dev/null
diff --git a/tempest/services/messaging/json/__init__.py b/tempest/services/messaging/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/messaging/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/messaging/json/messaging_client.py b/tempest/services/messaging/json/messaging_client.py
deleted file mode 100644
index 5a43841..0000000
--- a/tempest/services/messaging/json/messaging_client.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 uuid
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.api_schema.response.messaging.v1 import queues as queues_schema
-from tempest.common import service_client
-
-
-class MessagingClient(service_client.ServiceClient):
-
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
- dscv = disable_ssl_certificate_validation
- super(MessagingClient, self).__init__(
- auth_provider, service, region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs,
- trace_requests=trace_requests)
-
- self.version = '1'
- self.uri_prefix = 'v{0}'.format(self.version)
-
- client_id = uuid.uuid4().hex
- self.headers = {'Client-ID': client_id}
-
- def list_queues(self):
- uri = '{0}/queues'.format(self.uri_prefix)
- resp, body = self.get(uri)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.list_queues, resp, body)
- return resp, body
-
- def create_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.put(uri, body=None)
- self.expected_success(201, resp.status)
- return resp, body
-
- def show_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def head_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.head(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def delete_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.delete(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def show_queue_stats(self, queue_name):
- uri = '{0}/queues/{1}/stats'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- body = json.loads(body)
- self.validate_response(queues_schema.queue_stats, resp, body)
- return resp, body
-
- def show_queue_metadata(self, queue_name):
- uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return resp, body
-
- def set_queue_metadata(self, queue_name, rbody):
- uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
- resp, body = self.put(uri, body=json.dumps(rbody))
- self.expected_success(204, resp.status)
- return resp, body
-
- def post_messages(self, queue_name, rbody):
- uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
- resp, body = self.post(uri, body=json.dumps(rbody),
- extra_headers=True,
- headers=self.headers)
-
- body = json.loads(body)
- self.validate_response(queues_schema.post_messages, resp, body)
- return resp, body
-
- def list_messages(self, queue_name):
- uri = '{0}/queues/{1}/messages?echo=True'.format(self.uri_prefix,
- queue_name)
- resp, body = self.get(uri, extra_headers=True, headers=self.headers)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.list_messages, resp, body)
-
- return resp, body
-
- def show_single_message(self, message_uri):
- resp, body = self.get(message_uri, extra_headers=True,
- headers=self.headers)
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.get_single_message, resp,
- body)
- return resp, body
-
- def show_multiple_messages(self, message_uri):
- resp, body = self.get(message_uri, extra_headers=True,
- headers=self.headers)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.get_multiple_messages,
- resp,
- body)
-
- return resp, body
-
- def delete_messages(self, message_uri):
- resp, body = self.delete(message_uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def post_claims(self, queue_name, rbody, url_params=False):
- uri = '{0}/queues/{1}/claims'.format(self.uri_prefix, queue_name)
- if url_params:
- uri += '?%s' % urllib.urlencode(url_params)
-
- resp, body = self.post(uri, body=json.dumps(rbody),
- extra_headers=True,
- headers=self.headers)
-
- body = json.loads(body)
- self.validate_response(queues_schema.claim_messages, resp, body)
- return resp, body
-
- def query_claim(self, claim_uri):
- resp, body = self.get(claim_uri)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.query_claim, resp, body)
- return resp, body
-
- def update_claim(self, claim_uri, rbody):
- resp, body = self.patch(claim_uri, body=json.dumps(rbody))
- self.expected_success(204, resp.status)
- return resp, body
-
- def delete_claim(self, claim_uri):
- resp, body = self.delete(claim_uri)
- self.expected_success(204, resp.status)
- return resp, body
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
deleted file mode 100644
index 6b0b1f9..0000000
--- a/tempest/services/network/json/network_client.py
+++ /dev/null
@@ -1,232 +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 time
-
-from tempest_lib.common.utils import misc
-from tempest_lib import exceptions as lib_exc
-
-from tempest import exceptions
-from tempest.services.network.json import base
-
-
-class NetworkClient(base.BaseNetworkClient):
-
- """Tempest REST client for Neutron.
-
- Uses v2 of the Neutron API, since the V1 API has been removed from the
- code base.
-
- Implements create, delete, update, list and show for the basic Neutron
- abstractions (networks, sub-networks, routers, ports and floating IP):
-
- Implements add/remove interface to router using subnet ID / port ID
-
- It also implements list, show, update and reset for OpenStack Networking
- quotas
- """
-
- def create_bulk_network(self, **kwargs):
- """create bulk network
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateNetwork
- """
- uri = '/networks'
- return self.create_resource(uri, kwargs)
-
- def create_bulk_subnet(self, **kwargs):
- """create bulk subnet
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateSubnet
- """
- uri = '/subnets'
- return self.create_resource(uri, kwargs)
-
- def create_bulk_port(self, **kwargs):
- """create bulk port
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreatePorts
- """
- uri = '/ports'
- return self.create_resource(uri, kwargs)
-
- def wait_for_resource_deletion(self, resource_type, id, client=None):
- """Waits for a resource to be deleted."""
- start_time = int(time.time())
- while True:
- if self.is_resource_deleted(resource_type, id, client=client):
- return
- if int(time.time()) - start_time >= self.build_timeout:
- raise exceptions.TimeoutException
- time.sleep(self.build_interval)
-
- def is_resource_deleted(self, resource_type, id, client=None):
- if client is None:
- client = self
- method = 'show_' + resource_type
- try:
- getattr(client, method)(id)
- except AttributeError:
- raise Exception("Unknown resource type %s " % resource_type)
- except lib_exc.NotFound:
- return True
- return False
-
- def wait_for_resource_status(self, fetch, status, interval=None,
- timeout=None):
- """Waits for a network resource to reach a status
-
- @param fetch: the callable to be used to query the resource status
- @type fecth: callable that takes no parameters and returns the resource
- @param status: the status that the resource has to reach
- @type status: String
- @param interval: the number of seconds to wait between each status
- query
- @type interval: Integer
- @param timeout: the maximum number of seconds to wait for the resource
- to reach the desired status
- @type timeout: Integer
- """
- if not interval:
- interval = self.build_interval
- if not timeout:
- timeout = self.build_timeout
- start_time = time.time()
-
- while time.time() - start_time <= timeout:
- resource = fetch()
- if resource['status'] == status:
- return
- time.sleep(interval)
-
- # At this point, the wait has timed out
- message = 'Resource %s' % (str(resource))
- message += ' failed to reach status %s' % status
- message += ' (current: %s)' % resource['status']
- message += ' within the required time %s' % timeout
- caller = misc.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
-
- def create_router(self, name, admin_state_up=True, **kwargs):
- post_body = {'router': kwargs}
- post_body['router']['name'] = name
- post_body['router']['admin_state_up'] = admin_state_up
- uri = '/routers'
- return self.create_resource(uri, post_body)
-
- def _update_router(self, router_id, set_enable_snat, **kwargs):
- uri = '/routers/%s' % router_id
- body = self.show_resource(uri)
- update_body = {}
- update_body['name'] = kwargs.get('name', body['router']['name'])
- update_body['admin_state_up'] = kwargs.get(
- 'admin_state_up', body['router']['admin_state_up'])
- cur_gw_info = body['router']['external_gateway_info']
- if cur_gw_info:
- # TODO(kevinbenton): setting the external gateway info is not
- # allowed for a regular tenant. If the ability to update is also
- # merged, a test case for this will need to be added similar to
- # the SNAT case.
- cur_gw_info.pop('external_fixed_ips', None)
- if not set_enable_snat:
- cur_gw_info.pop('enable_snat', None)
- update_body['external_gateway_info'] = kwargs.get(
- 'external_gateway_info', body['router']['external_gateway_info'])
- if 'distributed' in kwargs:
- update_body['distributed'] = kwargs['distributed']
- update_body = dict(router=update_body)
- return self.update_resource(uri, update_body)
-
- def update_router(self, router_id, **kwargs):
- """Update a router leaving enable_snat to its default value."""
- # If external_gateway_info contains enable_snat the request will fail
- # with 404 unless executed with admin client, and therefore we instruct
- # _update_router to not set this attribute
- # NOTE(salv-orlando): The above applies as long as Neutron's default
- # policy is to restrict enable_snat usage to admins only.
- return self._update_router(router_id, set_enable_snat=False, **kwargs)
-
- def show_router(self, router_id, **fields):
- uri = '/routers/%s' % router_id
- return self.show_resource(uri, **fields)
-
- def delete_router(self, router_id):
- uri = '/routers/%s' % router_id
- return self.delete_resource(uri)
-
- def list_routers(self, **filters):
- uri = '/routers'
- return self.list_resources(uri, **filters)
-
- def update_router_with_snat_gw_info(self, router_id, **kwargs):
- """Update a router passing also the enable_snat attribute.
-
- This method must be execute with admin credentials, otherwise the API
- call will return a 404 error.
- """
- return self._update_router(router_id, set_enable_snat=True, **kwargs)
-
- def add_router_interface_with_subnet_id(self, router_id, subnet_id):
- uri = '/routers/%s/add_router_interface' % router_id
- update_body = {"subnet_id": subnet_id}
- return self.update_resource(uri, update_body)
-
- def add_router_interface_with_port_id(self, router_id, port_id):
- uri = '/routers/%s/add_router_interface' % router_id
- update_body = {"port_id": port_id}
- return self.update_resource(uri, update_body)
-
- def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
- uri = '/routers/%s/remove_router_interface' % router_id
- update_body = {"subnet_id": subnet_id}
- return self.update_resource(uri, update_body)
-
- def remove_router_interface_with_port_id(self, router_id, port_id):
- uri = '/routers/%s/remove_router_interface' % router_id
- update_body = {"port_id": port_id}
- return self.update_resource(uri, update_body)
-
- def list_router_interfaces(self, uuid):
- uri = '/ports?device_id=%s' % uuid
- return self.list_resources(uri)
-
- def list_l3_agents_hosting_router(self, router_id):
- uri = '/routers/%s/l3-agents' % router_id
- return self.list_resources(uri)
-
- def list_dhcp_agent_hosting_network(self, network_id):
- uri = '/networks/%s/dhcp-agents' % network_id
- return self.list_resources(uri)
-
- def update_extra_routes(self, router_id, **kwargs):
- """Update Extra routes.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#updateExtraRoutes
- """
- uri = '/routers/%s' % router_id
- put_body = {'router': kwargs}
- return self.update_resource(uri, put_body)
-
- def delete_extra_routes(self, router_id):
- uri = '/routers/%s' % router_id
- put_body = {
- 'router': {
- 'routes': None
- }
- }
- return self.update_resource(uri, put_body)
diff --git a/tempest/services/network/json/routers_client.py b/tempest/services/network/json/routers_client.py
new file mode 100644
index 0000000..725dd76
--- /dev/null
+++ b/tempest/services/network/json/routers_client.py
@@ -0,0 +1,119 @@
+# 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 base
+
+
+class RoutersClient(base.BaseNetworkClient):
+
+ def create_router(self, **kwargs):
+ """Create a router.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#createRouter
+ """
+ post_body = {'router': kwargs}
+ uri = '/routers'
+ return self.create_resource(uri, post_body)
+
+ def _update_router(self, router_id, set_enable_snat, **kwargs):
+ uri = '/routers/%s' % router_id
+ body = self.show_resource(uri)
+ update_body = {}
+ update_body['name'] = kwargs.get('name', body['router']['name'])
+ update_body['admin_state_up'] = kwargs.get(
+ 'admin_state_up', body['router']['admin_state_up'])
+ cur_gw_info = body['router']['external_gateway_info']
+ if cur_gw_info:
+ # TODO(kevinbenton): setting the external gateway info is not
+ # allowed for a regular tenant. If the ability to update is also
+ # merged, a test case for this will need to be added similar to
+ # the SNAT case.
+ cur_gw_info.pop('external_fixed_ips', None)
+ if not set_enable_snat:
+ cur_gw_info.pop('enable_snat', None)
+ update_body['external_gateway_info'] = kwargs.get(
+ 'external_gateway_info', body['router']['external_gateway_info'])
+ if 'distributed' in kwargs:
+ update_body['distributed'] = kwargs['distributed']
+ update_body = dict(router=update_body)
+ return self.update_resource(uri, update_body)
+
+ def update_router(self, router_id, **kwargs):
+ """Update a router leaving enable_snat to its default value."""
+ # If external_gateway_info contains enable_snat the request will fail
+ # with 404 unless executed with admin client, and therefore we instruct
+ # _update_router to not set this attribute
+ # NOTE(salv-orlando): The above applies as long as Neutron's default
+ # policy is to restrict enable_snat usage to admins only.
+ return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+ def show_router(self, router_id, **fields):
+ uri = '/routers/%s' % router_id
+ return self.show_resource(uri, **fields)
+
+ def delete_router(self, router_id):
+ uri = '/routers/%s' % router_id
+ return self.delete_resource(uri)
+
+ def list_routers(self, **filters):
+ uri = '/routers'
+ return self.list_resources(uri, **filters)
+
+ def update_extra_routes(self, router_id, **kwargs):
+ """Update Extra routes.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#updateExtraRoutes
+ """
+ uri = '/routers/%s' % router_id
+ put_body = {'router': kwargs}
+ return self.update_resource(uri, put_body)
+
+ def delete_extra_routes(self, router_id):
+ uri = '/routers/%s' % router_id
+ put_body = {
+ 'router': {
+ 'routes': None
+ }
+ }
+ return self.update_resource(uri, put_body)
+
+ def update_router_with_snat_gw_info(self, router_id, **kwargs):
+ """Update a router passing also the enable_snat attribute.
+
+ This method must be execute with admin credentials, otherwise the API
+ call will return a 404 error.
+ """
+ return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
+ def add_router_interface(self, router_id, **kwargs):
+ """Add router interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#addRouterInterface
+ """
+ uri = '/routers/%s/add_router_interface' % router_id
+ return self.update_resource(uri, kwargs)
+
+ def remove_router_interface(self, router_id, **kwargs):
+ """Remove router interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#removeRouterInterface
+ """
+ uri = '/routers/%s/remove_router_interface' % router_id
+ return self.update_resource(uri, kwargs)
+
+ def list_l3_agents_hosting_router(self, router_id):
+ uri = '/routers/%s/l3-agents' % router_id
+ return self.list_resources(uri)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 2c7fe29..6012a92 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -18,10 +18,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class AccountClient(service_client.ServiceClient):
+class AccountClient(rest_client.RestClient):
def create_account(self, data=None,
params=None,
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index 73c25db..5a26bfc 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -18,10 +18,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ContainerClient(service_client.ServiceClient):
+class ContainerClient(rest_client.RestClient):
def create_container(
self, container_name,
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 5890e33..33dba6e 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -17,10 +17,11 @@
from six.moves import http_client as httplib
from six.moves.urllib import parse as urlparse
-from tempest.common import service_client
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
-class ObjectClient(service_client.ServiceClient):
+class ObjectClient(rest_client.RestClient):
def create_object(self, container, object_name, data,
params=None, metadata=None, headers=None):
@@ -148,56 +149,94 @@
self.expected_success(201, resp.status)
return resp, body
- def put_object_with_chunk(self, container, name, contents, chunk_size):
- """Put an object with Transfer-Encoding header"""
- if self.base_url is None:
- self._set_auth()
+ def put_object_with_chunk(self, container, name, contents):
+ """Put an object with Transfer-Encoding header
+ :param container: name of the container
+ :type container: string
+ :param name: name of the object
+ :type name: string
+ :param contents: object data
+ :type contents: iterable
+ """
headers = {'Transfer-Encoding': 'chunked'}
if self.token:
headers['X-Auth-Token'] = self.token
- conn = put_object_connection(self.base_url, container, name, contents,
- chunk_size, headers)
-
- resp = conn.getresponse()
- body = resp.read()
-
- resp_headers = {}
- for header, value in resp.getheaders():
- resp_headers[header.lower()] = value
+ url = "%s/%s" % (container, name)
+ resp, body = self.put(
+ url, headers=headers,
+ body=contents,
+ chunked=True
+ )
self._error_checker('PUT', None, headers, contents, resp, body)
self.expected_success(201, resp.status)
- return resp.status, resp.reason, resp_headers
+ return resp.status, resp.reason, resp
def create_object_continue(self, container, object_name,
data, metadata=None):
- """Create storage object."""
+ """Put an object using Expect:100-continue"""
headers = {}
if metadata:
for key in metadata:
headers[str(key)] = metadata[key]
- if not data:
- headers['content-length'] = '0'
-
- if self.base_url is None:
- self._set_auth()
headers['X-Auth-Token'] = self.token
+ headers['content-length'] = 0 if data is None else len(data)
+ headers['Expect'] = '100-continue'
- conn = put_object_connection(self.base_url, str(container),
- str(object_name), data, None, headers)
+ parsed = urlparse.urlparse(self.base_url)
+ path = str(parsed.path) + "/"
+ path += "%s/%s" % (str(container), str(object_name))
+ conn = create_connection(parsed)
+
+ # Send the PUT request and the headers including the "Expect" header
+ conn.putrequest('PUT', path)
+
+ for header, value in six.iteritems(headers):
+ conn.putheader(header, value)
+ conn.endheaders()
+
+ # Read the 100 status prior to sending the data
response = conn.response_class(conn.sock,
strict=conn.strict,
method=conn._method)
- version, status, reason = response._read_status()
- resp = {'version': version,
- 'status': str(status),
- 'reason': reason}
+ _, status, _ = response._read_status()
- return resp
+ # toss the CRLF at the end of the response
+ response._safe_read(2)
+
+ # Expecting a 100 here, if not close and throw an exception
+ if status != 100:
+ conn.close()
+ pattern = "%s %s" % (
+ """Unexpected http success status code {0}.""",
+ """The expected status code is {1}""")
+ details = pattern.format(status, 100)
+ raise exceptions.UnexpectedResponseCode(details)
+
+ # If a continue was received go ahead and send the data
+ # and get the final response
+ conn.send(data)
+
+ resp = conn.getresponse()
+
+ return resp.status, resp.reason
+
+
+def create_connection(parsed_url):
+ """Helper function to create connection with httplib
+
+ :param parsed_url: parsed url of the remote location
+ """
+ if parsed_url.scheme == 'https':
+ conn = httplib.HTTPSConnection(parsed_url.netloc)
+ else:
+ conn = httplib.HTTPConnection(parsed_url.netloc)
+
+ return conn
def put_object_connection(base_url, container, name, contents=None,
@@ -210,49 +249,25 @@
:param contents: a string or a file like object to read object data
from; if None, a zero-byte put will be done
:param chunk_size: chunk size of data to write; it defaults to 65536;
- used only if the the contents object has a 'read'
+ used only if the contents object has a 'read'
method, eg. file-like objects, ignored otherwise
:param headers: additional headers to include in the request, if any
:param query_string: if set will be appended with '?' to generated path
"""
parsed = urlparse.urlparse(base_url)
- if parsed.scheme == 'https':
- conn = httplib.HTTPSConnection(parsed.netloc)
- else:
- conn = httplib.HTTPConnection(parsed.netloc)
+
path = str(parsed.path) + "/"
path += "%s/%s" % (str(container), str(name))
+ conn = create_connection(parsed)
+
if query_string:
path += '?' + query_string
if headers:
headers = dict(headers)
else:
headers = {}
- if hasattr(contents, 'read'):
- conn.putrequest('PUT', path)
- for header, value in six.iteritems(headers):
- conn.putheader(header, value)
- if 'Content-Length' not in headers:
- if 'Transfer-Encoding' not in headers:
- conn.putheader('Transfer-Encoding', 'chunked')
- conn.endheaders()
- chunk = contents.read(chunk_size)
- while chunk:
- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- chunk = contents.read(chunk_size)
- conn.send('0\r\n\r\n')
- else:
- conn.endheaders()
- left = headers['Content-Length']
- while left > 0:
- size = chunk_size
- if size > left:
- size = left
- chunk = contents.read(size)
- conn.send(chunk)
- left -= len(chunk)
- else:
- conn.request('PUT', path, contents, headers)
+
+ conn.request('PUT', path, contents, headers)
return conn
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 22e53f5..ea5dbe5 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -18,13 +18,13 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class OrchestrationClient(service_client.ServiceClient):
+class OrchestrationClient(rest_client.RestClient):
def list_stacks(self, params=None):
"""Lists all stacks for a user."""
@@ -36,7 +36,7 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_stack(self, name, disable_rollback=True, parameters=None,
timeout_mins=60, template=None, template_url=None,
@@ -56,7 +56,7 @@
resp, body = self.post(uri, headers=headers, body=body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_stack(self, stack_identifier, name, disable_rollback=True,
parameters=None, timeout_mins=60, template=None,
@@ -75,7 +75,7 @@
uri = "stacks/%s" % stack_identifier
resp, body = self.put(uri, headers=headers, body=body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _prepare_update_create(self, name, disable_rollback=True,
parameters=None, timeout_mins=60,
@@ -111,7 +111,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def suspend_stack(self, stack_identifier):
"""Suspend a stack."""
@@ -119,7 +119,7 @@
body = {'suspend': None}
resp, body = self.post(url, json.dumps(body))
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def resume_stack(self, stack_identifier):
"""Resume a stack."""
@@ -127,7 +127,7 @@
body = {'resume': None}
resp, body = self.post(url, json.dumps(body))
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_resources(self, stack_identifier):
"""Returns the details of a single resource."""
@@ -135,7 +135,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource(self, stack_identifier, resource_name):
"""Returns the details of a single resource."""
@@ -143,49 +143,13 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_stack(self, stack_identifier):
"""Deletes the specified Stack."""
resp, _ = self.delete("stacks/%s" % str(stack_identifier))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def wait_for_resource_status(self, stack_identifier, resource_name,
- status, failure_pattern='^.*_FAILED$'):
- """Waits for a Resource to reach a given status."""
- start = int(time.time())
- fail_regexp = re.compile(failure_pattern)
-
- while True:
- try:
- body = self.show_resource(
- stack_identifier, resource_name)['resource']
- except lib_exc.NotFound:
- # ignore this, as the resource may not have
- # been created yet
- pass
- else:
- resource_name = body['resource_name']
- resource_status = body['resource_status']
- if resource_status == status:
- return
- if fail_regexp.search(resource_status):
- raise exceptions.StackResourceBuildErrorException(
- resource_name=resource_name,
- stack_identifier=stack_identifier,
- resource_status=resource_status,
- resource_status_reason=body['resource_status_reason'])
-
- if int(time.time()) - start >= self.build_timeout:
- message = ('Resource %s failed to reach %s status '
- '(current %s) within the required time (%s s).' %
- (resource_name,
- status,
- resource_status,
- self.build_timeout))
- raise exceptions.TimeoutException(message)
- time.sleep(self.build_interval)
+ return rest_client.ResponseBody(resp)
def wait_for_stack_status(self, stack_identifier, status,
failure_pattern='^.*_FAILED$'):
@@ -224,7 +188,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_events(self, stack_identifier):
"""Returns list of all events for a stack."""
@@ -232,7 +196,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_resource_events(self, stack_identifier, resource_name):
"""Returns list of all events for a resource from stack."""
@@ -241,7 +205,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_event(self, stack_identifier, resource_name, event_id):
"""Returns the details of a single stack's event."""
@@ -250,7 +214,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_template(self, stack_identifier):
"""Returns the template for the stack."""
@@ -258,7 +222,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _validate_template(self, post_body):
"""Returns the validation request result."""
@@ -266,7 +230,7 @@
resp, body = self.post('validate', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def validate_template(self, template, parameters=None):
"""Returns the validation result for a template with parameters."""
@@ -293,21 +257,21 @@
resp, body = self.get('resource_types')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource_type(self, resource_type_name):
"""Return the schema of a resource type."""
url = 'resource_types/%s' % resource_type_name
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def show_resource_type_template(self, resource_type_name):
"""Return the template of a resource type."""
url = 'resource_types/%s/template' % resource_type_name
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def create_software_config(self, name=None, config=None, group=None,
inputs=None, outputs=None, options=None):
@@ -318,7 +282,7 @@
resp, body = self.post(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_config(self, conf_id):
"""Returns a software configuration resource."""
@@ -326,14 +290,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_software_config(self, conf_id):
"""Deletes a specific software configuration."""
url = 'software_configs/%s' % str(conf_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def create_software_deploy(self, server_id=None, config_id=None,
action=None, status=None,
@@ -348,7 +312,7 @@
resp, body = self.post(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_software_deploy(self, deploy_id=None, server_id=None,
config_id=None, action=None, status=None,
@@ -363,7 +327,7 @@
resp, body = self.put(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_software_deployments(self):
"""Returns a list of all deployments."""
@@ -371,7 +335,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_deployment(self, deploy_id):
"""Returns a specific software deployment."""
@@ -379,7 +343,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_deployment_metadata(self, server_id):
"""Return a config metadata for a specific server."""
@@ -387,14 +351,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_software_deploy(self, deploy_id):
"""Deletes a specific software deployment."""
url = 'software_deployments/%s' % str(deploy_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def _prep_software_config_create(self, name=None, conf=None, group=None,
inputs=None, outputs=None, options=None):
diff --git a/tempest/services/telemetry/__init__.py b/tempest/services/telemetry/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/telemetry/__init__.py
+++ /dev/null
diff --git a/tempest/services/telemetry/json/__init__.py b/tempest/services/telemetry/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/telemetry/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/telemetry/json/alarming_client.py b/tempest/services/telemetry/json/alarming_client.py
deleted file mode 100644
index ce14211..0000000
--- a/tempest/services/telemetry/json/alarming_client.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# Copyright 2014 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 oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.common import service_client
-
-
-class AlarmingClient(service_client.ServiceClient):
-
- version = '2'
- uri_prefix = "v2"
-
- def deserialize(self, body):
- return json.loads(body.replace("\n", ""))
-
- def serialize(self, body):
- return json.dumps(body)
-
- def list_alarms(self, query=None):
- uri = '%s/alarms' % self.uri_prefix
- uri_dict = {}
- if query:
- uri_dict = {'q.field': query[0],
- 'q.op': query[1],
- 'q.value': query[2]}
- if uri_dict:
- uri += "?%s" % urllib.urlencode(uri_dict)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
-
- def show_alarm(self, alarm_id):
- uri = '%s/alarms/%s' % (self.uri_prefix, alarm_id)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def show_alarm_history(self, alarm_id):
- uri = "%s/alarms/%s/history" % (self.uri_prefix, alarm_id)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
-
- def delete_alarm(self, alarm_id):
- uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
- resp, body = self.delete(uri)
- self.expected_success(204, resp.status)
- if body:
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def create_alarm(self, **kwargs):
- uri = "%s/alarms" % self.uri_prefix
- body = self.serialize(kwargs)
- resp, body = self.post(uri, body)
- self.expected_success(201, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def update_alarm(self, alarm_id, **kwargs):
- uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
- body = self.serialize(kwargs)
- resp, body = self.put(uri, body)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def show_alarm_state(self, alarm_id):
- uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyData(resp, body)
-
- def alarm_set_state(self, alarm_id, state):
- uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
- body = self.serialize(state)
- resp, body = self.put(uri, body)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyData(resp, body)
diff --git a/tempest/services/telemetry/json/telemetry_client.py b/tempest/services/telemetry/json/telemetry_client.py
deleted file mode 100644
index abdeba2..0000000
--- a/tempest/services/telemetry/json/telemetry_client.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright 2014 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 oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.common import service_client
-
-
-class TelemetryClient(service_client.ServiceClient):
-
- version = '2'
- uri_prefix = "v2"
-
- def deserialize(self, body):
- return json.loads(body.replace("\n", ""))
-
- def serialize(self, body):
- return json.dumps(body)
-
- def create_sample(self, meter_name, sample_list):
- uri = "%s/meters/%s" % (self.uri_prefix, meter_name)
- body = self.serialize(sample_list)
- resp, body = self.post(uri, body)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def _helper_list(self, uri, query=None, period=None):
- uri_dict = {}
- if query:
- uri_dict = {'q.field': query[0],
- 'q.op': query[1],
- 'q.value': query[2]}
- if period:
- uri_dict['period'] = period
- if uri_dict:
- uri += "?%s" % urllib.urlencode(uri_dict)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
-
- def list_resources(self, query=None):
- uri = '%s/resources' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def list_meters(self, query=None):
- uri = '%s/meters' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def list_statistics(self, meter, period=None, query=None):
- uri = "%s/meters/%s/statistics" % (self.uri_prefix, meter)
- return self._helper_list(uri, query, period)
-
- def list_samples(self, meter_id, query=None):
- uri = '%s/meters/%s' % (self.uri_prefix, meter_id)
- return self._helper_list(uri, query)
-
- def list_events(self, query=None):
- uri = '%s/events' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def show_resource(self, resource_id):
- uri = '%s/resources/%s' % (self.uri_prefix, resource_id)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_hosts_client.py b/tempest/services/volume/base/admin/base_hosts_client.py
index 074f87f..382e9a8 100644
--- a/tempest/services/volume/base/admin/base_hosts_client.py
+++ b/tempest/services/volume/base/admin/base_hosts_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseHostsClient(service_client.ServiceClient):
+class BaseHostsClient(rest_client.RestClient):
"""Client class to send CRUD Volume Hosts API requests"""
def list_hosts(self, **params):
@@ -32,4 +32,4 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_quotas_client.py b/tempest/services/volume/base/admin/base_quotas_client.py
index e063a31..83816f2 100644
--- a/tempest/services/volume/base/admin/base_quotas_client.py
+++ b/tempest/services/volume/base/admin/base_quotas_client.py
@@ -15,10 +15,10 @@
from oslo_serialization import jsonutils
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseQuotasClient(service_client.ServiceClient):
+class BaseQuotasClient(rest_client.RestClient):
"""Client class to send CRUD Volume Quotas API requests"""
TYPE = "json"
@@ -30,7 +30,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_quota_set(self, tenant_id, params=None):
"""List the quota set for a tenant."""
@@ -42,7 +42,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_quota_usage(self, tenant_id):
"""List the quota set for a tenant."""
@@ -60,10 +60,10 @@
resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_quota_set(self, tenant_id):
"""Delete the tenant's quota set."""
resp, body = self.delete('os-quota-sets/%s' % tenant_id)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_services_client.py b/tempest/services/volume/base/admin/base_services_client.py
index 3626469..861eb92 100644
--- a/tempest/services/volume/base/admin/base_services_client.py
+++ b/tempest/services/volume/base/admin/base_services_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseServicesClient(service_client.ServiceClient):
+class BaseServicesClient(rest_client.RestClient):
def list_services(self, **params):
url = 'os-services'
@@ -29,4 +29,4 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
index 867273e..95ddff6 100644
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ b/tempest/services/volume/base/admin/base_types_client.py
@@ -15,12 +15,12 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseTypesClient(service_client.ServiceClient):
+class BaseTypesClient(rest_client.RestClient):
"""Client class to send CRUD Volume Types API requests"""
def is_resource_deleted(self, resource):
@@ -56,7 +56,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_type(self, volume_id):
"""Returns the details of a single volume_type."""
@@ -64,7 +64,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_type(self, **kwargs):
"""Create volume type.
@@ -76,13 +76,13 @@
resp, body = self.post('types', post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_type(self, volume_id):
"""Deletes the Specified Volume_type."""
resp, body = self.delete("types/%s" % str(volume_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_volume_types_extra_specs(self, vol_type_id, **params):
"""List all the volume_types extra specs created.
@@ -100,7 +100,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_type_extra_specs(self, vol_type_id, extra_specs_name):
"""Returns the details of a single volume_type extra spec."""
@@ -109,7 +109,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_type_extra_specs(self, vol_type_id, extra_specs):
"""Creates a new Volume_type extra spec.
@@ -122,14 +122,14 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
"""Deletes the Specified Volume_type extra spec."""
resp, body = self.delete("types/%s/extra_specs/%s" % (
(str(vol_id)), str(extra_spec_name)))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
extra_specs):
@@ -146,7 +146,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_encryption_type(self, vol_type_id):
"""Get the volume encryption type for the specified volume type.
@@ -157,7 +157,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_encryption_type(self, vol_type_id, **kwargs):
"""Create encryption type.
@@ -171,11 +171,11 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_encryption_type(self, vol_type_id):
"""Delete the encryption type for the specified volume-type."""
resp, body = self.delete(
"/types/%s/encryption/provider" % str(vol_type_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_availability_zone_client.py b/tempest/services/volume/base/base_availability_zone_client.py
index b63fdc2..1c2deba 100644
--- a/tempest/services/volume/base/base_availability_zone_client.py
+++ b/tempest/services/volume/base/base_availability_zone_client.py
@@ -15,13 +15,13 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseAvailabilityZoneClient(service_client.ServiceClient):
+class BaseAvailabilityZoneClient(rest_client.RestClient):
def list_availability_zones(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index fc9a40a..780da7b 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -16,13 +16,13 @@
import time
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseBackupsClient(service_client.ServiceClient):
+class BaseBackupsClient(rest_client.RestClient):
"""Client class to send CRUD Volume backup API requests"""
def create_backup(self, **kwargs):
@@ -31,7 +31,7 @@
resp, body = self.post('backups', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def restore_backup(self, backup_id, **kwargs):
"""Restore volume from backup."""
@@ -39,13 +39,13 @@
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % (str(backup_id)))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
"""Returns the details of a single backup."""
@@ -53,7 +53,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_backups(self, detail=False):
"""Information for all the tenant's backups."""
@@ -63,7 +63,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def export_backup(self, backup_id):
"""Export backup metadata record."""
@@ -71,7 +71,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def import_backup(self, **kwargs):
"""Import backup metadata record."""
@@ -79,7 +79,7 @@
resp, body = self.post("backups/import_record", post_body)
body = json.loads(body)
self.expected_success(201, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def wait_for_backup_status(self, backup_id, status):
"""Waits for a Backup to reach a given status."""
diff --git a/tempest/services/volume/base/base_extensions_client.py b/tempest/services/volume/base/base_extensions_client.py
index afc3f6b..b90fe94 100644
--- a/tempest/services/volume/base/base_extensions_client.py
+++ b/tempest/services/volume/base/base_extensions_client.py
@@ -15,14 +15,14 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseExtensionsClient(service_client.ServiceClient):
+class BaseExtensionsClient(rest_client.RestClient):
def list_extensions(self):
url = 'extensions'
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_qos_client.py b/tempest/services/volume/base/base_qos_client.py
index 697e902..2d9f02a 100644
--- a/tempest/services/volume/base/base_qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -15,13 +15,13 @@
import time
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseQosSpecsClient(service_client.ServiceClient):
+class BaseQosSpecsClient(rest_client.RestClient):
"""Client class to send CRUD QoS API requests"""
def is_resource_deleted(self, qos_id):
@@ -77,14 +77,14 @@
resp, body = self.post('qos-specs', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_qos(self, qos_id, force=False):
"""Delete the specified QoS specification."""
resp, body = self.delete(
"qos-specs/%s?force=%s" % (str(qos_id), force))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_qos(self):
"""List all the QoS specifications created."""
@@ -92,7 +92,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_qos(self, qos_id):
"""Get the specified QoS specification."""
@@ -100,7 +100,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def set_qos_key(self, qos_id, **kwargs):
"""Set the specified keys/values of QoS specification.
@@ -112,7 +112,7 @@
resp, body = self.put('qos-specs/%s' % qos_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def unset_qos_key(self, qos_id, keys):
"""Unset the specified keys of QoS specification.
@@ -124,7 +124,7 @@
put_body = json.dumps({'keys': keys})
resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def associate_qos(self, qos_id, vol_type_id):
"""Associate the specified QoS with specified volume-type."""
@@ -132,7 +132,7 @@
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_association_qos(self, qos_id):
"""Get the association of the specified QoS specification."""
@@ -140,7 +140,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def disassociate_qos(self, qos_id, vol_type_id):
"""Disassociate the specified QoS with specified volume-type."""
@@ -148,11 +148,11 @@
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def disassociate_all_qos(self, qos_id):
"""Disassociate the specified QoS with all associations."""
url = "qos-specs/%s/disassociate_all" % str(qos_id)
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
index 1388e9c..68503dd 100644
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -10,21 +10,18 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
-from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
-class BaseSnapshotsClient(service_client.ServiceClient):
+class BaseSnapshotsClient(rest_client.RestClient):
"""Base Client class to send CRUD Volume API requests."""
create_resp = 200
@@ -40,7 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot."""
@@ -48,7 +45,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_snapshot(self, **kwargs):
"""Creates a new snapshot.
@@ -60,7 +57,7 @@
resp, body = self.post('snapshots', post_body)
body = json.loads(body)
self.expected_success(self.create_resp, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot(self, snapshot_id, **kwargs):
"""Updates a snapshot."""
@@ -68,50 +65,13 @@
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
-
- # NOTE(afazekas): just for the wait function
- def _get_snapshot_status(self, snapshot_id):
- body = self.show_snapshot(snapshot_id)['snapshot']
- status = body['status']
- # NOTE(afazekas): snapshot can reach an "error"
- # state in a "normal" lifecycle
- if (status == 'error'):
- raise exceptions.SnapshotBuildErrorException(
- snapshot_id=snapshot_id)
-
- return status
-
- # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
- def wait_for_snapshot_status(self, snapshot_id, status):
- """Waits for a Snapshot to reach a given status."""
- start_time = time.time()
- old_value = value = self._get_snapshot_status(snapshot_id)
- while True:
- dtime = time.time() - start_time
- time.sleep(self.build_interval)
- if value != old_value:
- LOG.info('Value transition from "%s" to "%s"'
- 'in %d second(s).', old_value,
- value, dtime)
- if (value == status):
- return value
-
- if dtime > self.build_timeout:
- message = ('Time Limit Exceeded! (%ds)'
- 'while waiting for %s, '
- 'but we got %s.' %
- (self.build_timeout, status, value))
- raise exceptions.TimeoutException(message)
- time.sleep(self.build_interval)
- old_value = value
- value = self._get_snapshot_status(snapshot_id)
+ return rest_client.ResponseBody(resp, body)
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot."""
resp, body = self.delete("snapshots/%s" % str(snapshot_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
@@ -130,7 +90,7 @@
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_status(self, snapshot_id, **kwargs):
"""Update the specified snapshot's status."""
@@ -143,7 +103,7 @@
url = 'snapshots/%s/action' % str(snapshot_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_snapshot_metadata(self, snapshot_id, metadata):
"""Create metadata for the snapshot."""
@@ -152,7 +112,7 @@
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata(self, snapshot_id):
"""Get metadata of the snapshot."""
@@ -160,7 +120,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata(self, snapshot_id, **kwargs):
"""Update metadata for the snapshot."""
@@ -173,7 +133,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
"""Update metadata item for the snapshot."""
@@ -186,18 +146,18 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
index d4435bc..6237745 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -16,13 +16,12 @@
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
-from tempest.common import service_client
-from tempest.common import waiters
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseVolumesClient(service_client.ServiceClient):
+class BaseVolumesClient(rest_client.RestClient):
"""Base client class to send CRUD Volume API requests"""
create_resp = 200
@@ -61,7 +60,18 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_pools(self, detail=False):
+ # List all the volumes pools (hosts)
+ url = 'scheduler-stats/get_pools'
+ if detail:
+ url += '?detail=True'
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
def show_volume(self, volume_id):
"""Returns the details of a single volume."""
@@ -69,7 +79,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume(self, **kwargs):
"""Creates a new Volume.
@@ -83,7 +93,7 @@
resp, body = self.post('volumes', post_body)
body = json.loads(body)
self.expected_success(self.create_resp, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume."""
@@ -91,13 +101,13 @@
resp, body = self.put('volumes/%s' % volume_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
resp, body = self.delete("volumes/%s" % str(volume_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def upload_volume(self, volume_id, **kwargs):
"""Uploads a volume in Glance."""
@@ -106,7 +116,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def attach_volume(self, volume_id, **kwargs):
"""Attaches a volume to a given instance on a given mountpoint."""
@@ -114,7 +124,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
"""set a bootable flag for a volume - true or false."""
@@ -122,7 +132,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def detach_volume(self, volume_id):
"""Detaches a volume from an instance."""
@@ -130,7 +140,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reserve_volume(self, volume_id):
"""Reserves a volume."""
@@ -138,7 +148,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def unreserve_volume(self, volume_id):
"""Restore a reserved volume ."""
@@ -146,11 +156,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status."""
- waiters.wait_for_volume_status(self, volume_id, status)
+ return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
@@ -170,14 +176,14 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reset_volume_status(self, volume_id, **kwargs):
"""Reset the Specified Volume's Status."""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def volume_begin_detaching(self, volume_id):
"""Volume Begin Detaching."""
@@ -185,7 +191,7 @@
post_body = json.dumps({'os-begin_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def volume_roll_detaching(self, volume_id):
"""Volume Roll Detaching."""
@@ -193,7 +199,7 @@
post_body = json.dumps({'os-roll_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer."""
@@ -201,7 +207,7 @@
resp, body = self.post('os-volume-transfer', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_transfer(self, transfer_id):
"""Returns the details of a volume transfer."""
@@ -209,7 +215,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_volume_transfers(self, **params):
"""List all the volume transfers created."""
@@ -219,13 +225,13 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_transfer(self, transfer_id):
"""Delete a volume transfer."""
resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def accept_volume_transfer(self, transfer_id, **kwargs):
"""Accept a volume transfer."""
@@ -234,7 +240,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_readonly(self, volume_id, **kwargs):
"""Update the Specified Volume readonly."""
@@ -242,14 +248,14 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
"""Create metadata for the volume."""
@@ -258,7 +264,7 @@
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_metadata(self, volume_id):
"""Get metadata of the volume."""
@@ -266,7 +272,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
"""Update metadata for the volume."""
@@ -275,7 +281,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
@@ -284,14 +290,14 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
"""Updates volume with new volume type."""
diff --git a/tempest/stress/cleanup.py b/tempest/stress/cleanup.py
index 1c1fb46..3b0a937 100644
--- a/tempest/stress/cleanup.py
+++ b/tempest/stress/cleanup.py
@@ -88,8 +88,8 @@
LOG.info("Cleanup::remove %s snapshots" % len(snaps))
for v in snaps:
try:
- admin_manager.snapshots_client.\
- wait_for_snapshot_status(v['id'], 'available')
+ waiters.wait_for_snapshot_status(
+ admin_manager.snapshots_client, v['id'], 'available')
admin_manager.snapshots_client.delete_snapshot(v['id'])
except Exception:
pass
@@ -105,8 +105,8 @@
LOG.info("Cleanup::remove %s volumes" % len(vols))
for v in vols:
try:
- admin_manager.volumes_client.\
- wait_for_volume_status(v['id'], 'available')
+ waiters.wait_for_volume_status(
+ admin_manager.volumes_client, v['id'], 'available')
admin_manager.volumes_client.delete_volume(v['id'])
except Exception:
pass
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 02cb901..2beaaa9 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -21,7 +21,6 @@
from oslo_utils import importutils
import six
from six import moves
-from tempest_lib.common import ssh
from tempest import clients
@@ -30,6 +29,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib.common import ssh
from tempest.stress import cleanup
CONF = config.CONF
@@ -135,10 +135,13 @@
break
if skip:
break
+ # TODO(andreaf) This has to be reworked to use the credential
+ # provider interface. For now only tests marked as 'use_admin' will
+ # work.
if test.get('use_admin', False):
manager = admin_manager
else:
- manager = credentials.ConfiguredUserManager()
+ raise NotImplemented('Non admin tests are not supported')
for p_number in moves.xrange(test.get('threads', default_thread_num)):
if test.get('use_isolated_tenants', False):
username = data_utils.rand_name("stress_user")
@@ -149,16 +152,18 @@
projects_client = admin_manager.tenants_client
roles_client = admin_manager.roles_client
users_client = admin_manager.users_client
+ domains_client = None
else:
identity_client = admin_manager.identity_v3_client
- projects_client = None
- roles_client = None
- users_client = None
+ projects_client = admin_manager.projects_client
+ roles_client = admin_manager.roles_v3_client
+ users_client = admin_manager.users_v3_client
+ domains_client = admin_manager.domains_client
domain = (identity_client.auth_provider.credentials.
get('project_domain_name', 'Default'))
credentials_client = cred_client.get_creds_client(
- identity_client, projects_client, roles_client,
- users_client, project_domain_name=domain)
+ identity_client, projects_client, users_client,
+ roles_client, domains_client, project_domain_name=domain)
project = credentials_client.create_project(
name=tenant_name, description=tenant_name)
user = credentials_client.create_user(username, password,
diff --git a/tempest/stress/stressaction.py b/tempest/stress/stressaction.py
index c8bd652..cf0a08a 100644
--- a/tempest/stress/stressaction.py
+++ b/tempest/stress/stressaction.py
@@ -85,8 +85,8 @@
finally:
shared_statistic['runs'] += 1
if self.stop_on_error and (shared_statistic['fails'] > 1):
- self.logger.warn("Stop process due to"
- "\"stop-on-error\" argument")
+ self.logger.warning("Stop process due to"
+ "\"stop-on-error\" argument")
self.tearDown()
sys.exit(1)
diff --git a/tempest/test.py b/tempest/test.py
index dfed947..aefe1a9 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -19,7 +19,6 @@
import re
import sys
import time
-import uuid
import fixtures
from oslo_log import log as logging
@@ -27,7 +26,6 @@
from oslo_utils import importutils
import six
from six.moves import urllib
-from tempest_lib import decorators
import testscenarios
import testtools
@@ -39,6 +37,8 @@
import tempest.common.validation_resources as vresources
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
LOG = logging.getLogger(__name__)
@@ -78,7 +78,6 @@
'identity': True,
'object_storage': CONF.service_available.swift,
'dashboard': CONF.service_available.horizon,
- 'telemetry': CONF.service_available.ceilometer,
'data_processing': CONF.service_available.sahara,
'database': CONF.service_available.trove
}
@@ -94,7 +93,7 @@
def decorator(f):
services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
'network', 'identity', 'object_storage', 'dashboard',
- 'telemetry', 'data_processing', 'database']
+ 'data_processing', 'database']
for service in args:
if service not in services:
raise exceptions.InvalidServiceTag('%s is not a valid '
@@ -180,6 +179,19 @@
return False
+def is_scheduler_filter_enabled(filter_name):
+ """Check the list of enabled compute scheduler filters from config. """
+
+ filters = CONF.compute_feature_enabled.scheduler_available_filters
+ if len(filters) == 0:
+ return False
+ if 'all' in filters:
+ return True
+ if filter_name in filters:
+ return True
+ return False
+
+
at_exit_set = set()
@@ -226,7 +238,6 @@
# Resources required to validate a server using ssh
validation_resources = {}
network_resources = {}
- services_microversion = {}
# NOTE(sdague): log_format is defined inline here instead of using the oslo
# default because going through the config path recouples config to the
@@ -234,6 +245,12 @@
log_format = ('%(asctime)s %(process)d %(levelname)-8s '
'[%(name)s] %(message)s')
+ # Client manager class to use in this test case.
+ client_manager = clients.Manager
+
+ # A way to adjust slow test classes
+ TIMEOUT_SCALING_FACTOR = 1
+
@classmethod
def setUpClass(cls):
# It should never be overridden by descendants
@@ -293,7 +310,7 @@
LOG.exception("teardown of %s failed: %s" % (name, te))
if not etype:
etype, value, trace = sys_exec_info
- # If exceptions were raised during teardown, an not before, re-raise
+ # If exceptions were raised during teardown, and not before, re-raise
# the first one
if re_raise and etype is not None:
try:
@@ -332,11 +349,14 @@
@classmethod
def setup_credentials(cls):
- """Allocate credentials and the client managers from them.
+ """Allocate credentials and create the client managers from them.
- A test class that requires network resources must override
- setup_credentials and defined the required resources before super
- is invoked.
+ For every element of credentials param function creates tenant/user,
+ Then it creates client manager for that credential.
+
+ Network related tests must override this function with
+ set_network_resources() method, otherwise it will create
+ network resources(network resources are created in a later step).
"""
for credentials_type in cls.credentials:
# This may raise an exception in case credentials are not available
@@ -404,7 +424,7 @@
at_exit_set.add(self.__class__)
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
- test_timeout = int(test_timeout)
+ test_timeout = int(test_timeout) * self.TIMEOUT_SCALING_FACTOR
except ValueError:
test_timeout = 0
if test_timeout > 0:
@@ -437,14 +457,16 @@
"""
if CONF.identity.auth_version == 'v2':
client = self.os_admin.identity_client
+ users_client = self.os_admin.users_client
project_client = self.os_admin.tenants_client
roles_client = self.os_admin.roles_client
- users_client = self.os_admin.users_client
+ domains_client = None
else:
client = self.os_admin.identity_v3_client
- project_client = None
- roles_client = None
- users_client = None
+ users_client = self.os_admin.users_v3_client
+ project_client = self.os_admin.projects_client
+ roles_client = self.os_admin.roles_v3_client
+ domains_client = self.os_admin.domains_client
try:
domain = client.auth_provider.credentials.project_domain_name
@@ -452,8 +474,9 @@
domain = 'Default'
return cred_client.get_creds_client(client, project_client,
- roles_client,
users_client,
+ roles_client,
+ domains_client,
project_domain_name=domain)
@classmethod
@@ -519,8 +542,7 @@
else:
raise exceptions.InvalidCredentials(
"Invalid credentials type %s" % credential_type)
- return clients.Manager(credentials=creds, service=cls._service,
- api_microversions=cls.services_microversion)
+ return cls.client_manager(credentials=creds, service=cls._service)
@classmethod
def clear_credentials(cls):
@@ -591,24 +613,35 @@
'dhcp': dhcp}
@classmethod
- def get_tenant_network(cls):
+ def get_tenant_network(cls, credentials_type='primary'):
"""Get the network to be used in testing
+ :param credentials_type: The type of credentials for which to get the
+ tenant network
+
:return: network dict including 'id' and 'name'
"""
+ # Get a manager for the given credentials_type, but at least
+ # always fall back on getting the manager for primary credentials
+ if isinstance(credentials_type, six.string_types):
+ manager = cls.get_client_manager(credential_type=credentials_type)
+ elif isinstance(credentials_type, list):
+ manager = cls.get_client_manager(roles=credentials_type[1:])
+ else:
+ manager = cls.get_client_manager()
+
# Make sure cred_provider exists and get a network client
- networks_client = cls.get_client_manager().compute_networks_client
+ networks_client = manager.compute_networks_client
cred_provider = cls._get_credentials_provider()
# In case of nova network, isolated tenants are not able to list the
- # network configured in fixed_network_name, even if the can use it
+ # network configured in fixed_network_name, even if they can use it
# for their servers, so using an admin network client to validate
# the network name
if (not CONF.service_available.neutron and
credentials.is_admin_available(
identity_version=cls.get_identity_version())):
admin_creds = cred_provider.get_admin_creds()
- admin_manager = clients.Manager(
- admin_creds, api_microversions=cls.services_microversion)
+ admin_manager = clients.Manager(admin_creds)
networks_client = admin_manager.compute_networks_client
return fixed_network.get_tenant_network(
cred_provider, networks_client, CONF.compute.fixed_network_name)
@@ -683,10 +716,10 @@
resource = resource['name']
LOG.debug("Add resource to test %s" % resource)
scn_name = "inv_res_%s" % (resource)
- scenario_list.append((scn_name, {"resource": (resource,
- str(uuid.uuid4())),
- "expected_result": expected_result
- }))
+ scenario_list.append((scn_name, {
+ "resource": (resource, data_utils.rand_uuid()),
+ "expected_result": expected_result
+ }))
if schema is not None:
for scenario in generator.generate_scenarios(schema):
scenario_list.append((scenario['_negtest_name'],
@@ -790,9 +823,9 @@
@classmethod
def set_resource(cls, name, resource):
- """Register a resoruce for a test
+ """Register a resource for a test
- This function can be used in setUpClass context to register a resoruce
+ This function can be used in setUpClass context to register a resource
for a test.
:param name: The name of the kind of resource such as "flavor", "role",
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index 108b50d..d604b28 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -17,8 +17,8 @@
import six
import stevedore
-from tempest_lib.common.utils import misc
+from tempest.lib.common.utils import misc
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index fe9268e..ca81d4d 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -14,17 +14,10 @@
import mock
from oslotest import base
-from oslotest import moxstubout
class TestCase(base.BaseTestCase):
- def setUp(self):
- super(TestCase, self).setUp()
- mox_fixture = self.useFixture(moxstubout.MoxStubout())
- self.mox = mox_fixture.mox
- self.stubs = mox_fixture.stubs
-
def patch(self, target, **kwargs):
"""Returns a started `mock.patch` object for the supplied target.
@@ -42,3 +35,15 @@
m = p.start()
self.addCleanup(p.stop)
return m
+
+ def patchobject(self, target, attribute, new=mock.DEFAULT):
+ """Convenient wrapper around `mock.patch.object`
+
+ Returns a started mock that will be automatically stopped after the
+ test ran.
+ """
+
+ p = mock.patch.object(target, attribute, new)
+ m = p.start()
+ self.addCleanup(p.stop)
+ return m
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index ab6a7a0..2d0256a 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -14,9 +14,9 @@
import mock
from oslotest import mockpatch
-from tempest_lib import exceptions as lib_exc
from tempest.cmd import javelin
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
@@ -213,8 +213,8 @@
name=self.fake_object['name'],
ip_version=fake_version)
- def test_create_volumes(self):
-
+ @mock.patch("tempest.common.waiters.wait_for_volume_status")
+ def test_create_volumes(self, mock_wait_for_volume_status):
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
@@ -228,12 +228,12 @@
mocked_function.assert_called_once_with(
size=self.fake_object['gb'],
display_name=self.fake_object['name'])
- mocked_function = self.fake_client.volumes.wait_for_volume_status
- mocked_function.assert_called_once_with(
- self.fake_object.body['volume']['id'],
+ mock_wait_for_volume_status.assert_called_once_with(
+ self.fake_client.volumes, self.fake_object.body['volume']['id'],
'available')
- def test_create_volume_existing(self):
+ @mock.patch("tempest.common.waiters.wait_for_volume_status")
+ def test_create_volume_existing(self, mock_wait_for_volume_status):
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
@@ -245,22 +245,21 @@
mocked_function = self.fake_client.volumes.create_volume
self.assertFalse(mocked_function.called)
- mocked_function = self.fake_client.volumes.wait_for_volume_status
- self.assertFalse(mocked_function.called)
+ self.assertFalse(mock_wait_for_volume_status.called)
def test_create_router(self):
- self.fake_client.networks.list_routers.return_value = {'routers': []}
+ self.fake_client.routers.list_routers.return_value = {'routers': []}
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
javelin.create_routers([self.fake_object])
mocked_function = self.fake_client.networks.create_router
- mocked_function.assert_called_once_with(self.fake_object['name'])
+ mocked_function.assert_called_once_with(name=self.fake_object['name'])
def test_create_router_existing(self):
- self.fake_client.networks.list_routers.return_value = {
+ self.fake_client.routers.list_routers.return_value = {
'routers': [self.fake_object]}
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
@@ -405,7 +404,7 @@
javelin.destroy_routers([self.fake_object])
- mocked_function = self.fake_client.networks.delete_router
+ mocked_function = self.fake_client.routers.delete_router
mocked_function.assert_called_once_with(
self.fake_object['router_id'])
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 1048a52..685a0b3 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -36,9 +36,8 @@
testr_conf_file = init.TESTR_CONF % (top_level_path, discover_path)
conf_path = conf_dir.join('.testr.conf')
- conf_file = open(conf_path, 'r')
- self.addCleanup(conf_file.close)
- self.assertEqual(conf_file.read(), testr_conf_file)
+ with open(conf_path, 'r') as conf_file:
+ self.assertEqual(conf_file.read(), testr_conf_file)
def test_generate_sample_config(self):
local_dir = self.useFixture(fixtures.TempDir())
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 193abc7..70cbf87 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -12,28 +12,56 @@
# License for the specific language governing permissions and limitations
# under the License.
-
import mock
from oslo_serialization import jsonutils as json
from oslotest import mockpatch
from tempest.cmd import verify_tempest_config
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest.tests import base
from tempest.tests import fake_config
class TestGetAPIVersions(base.TestCase):
+ def test_remove_version_project(self):
+ f = verify_tempest_config._remove_version_project
+ self.assertEqual('/', f('/v2.1/%s/' % data_utils.rand_uuid_hex()))
+ self.assertEqual('', f('/v2.1/tenant_id'))
+ self.assertEqual('', f('/v3'))
+ self.assertEqual('/', f('/v3/'))
+ self.assertEqual('/something/', f('/something/v2.1/tenant_id/'))
+ self.assertEqual('/something', f('/something/v2.1/tenant_id'))
+ self.assertEqual('/something', f('/something/v3'))
+ self.assertEqual('/something/', f('/something/v3/'))
+ self.assertEqual('/', f('/')) # http://localhost/
+ self.assertEqual('', f('')) # http://localhost
+
def test_url_grab_versioned_nova_nossl(self):
base_url = 'http://127.0.0.1:8774/v2/'
endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
- self.assertEqual('http://127.0.0.1:8774', endpoint)
+ self.assertEqual('http://127.0.0.1:8774/', endpoint)
def test_url_grab_versioned_nova_ssl(self):
base_url = 'https://127.0.0.1:8774/v3/'
endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
- self.assertEqual('https://127.0.0.1:8774', endpoint)
+ self.assertEqual('https://127.0.0.1:8774/', endpoint)
+
+ def test_get_unversioned_endpoint_base(self):
+ base_url = 'https://127.0.0.1:5000/'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1:5000/', endpoint)
+
+ def test_get_unversioned_endpoint_subpath(self):
+ base_url = 'https://127.0.0.1/identity/v3'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1/identity', endpoint)
+
+ def test_get_unversioned_endpoint_subpath_trailing_solidus(self):
+ base_url = 'https://127.0.0.1/identity/v3/'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1/identity/', endpoint)
class TestDiscovery(base.TestCase):
@@ -41,7 +69,8 @@
def setUp(self):
super(TestDiscovery, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
def test_get_keystone_api_versions(self):
self.useFixture(mockpatch.PatchObject(
@@ -49,8 +78,9 @@
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('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.http.ClosingHttp.request',
+ return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
versions = verify_tempest_config._get_api_versions(fake_os, 'keystone')
self.assertIn('v2.0', versions)
@@ -62,8 +92,9 @@
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('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.http.ClosingHttp.request',
+ return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
versions = verify_tempest_config._get_api_versions(fake_os, 'cinder')
self.assertIn('v1.0', versions)
@@ -75,13 +106,37 @@
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('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.http.ClosingHttp.request',
+ return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
versions = verify_tempest_config._get_api_versions(fake_os, 'nova')
self.assertIn('v2.0', versions)
self.assertIn('v3.0', versions)
+ 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(
+ verify_tempest_config.LOG, 'error')).mock
+
+ self.useFixture(mockpatch.PatchObject(
+ 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(
+ 'tempest.lib.common.http.ClosingHttp.request',
+ return_value=(None, sample_body)))
+
+ # service value doesn't matter, just needs to match what
+ # _get_api_versions puts in its client_dict.
+ self.assertRaises(ValueError, verify_tempest_config._get_api_versions,
+ os=mock.MagicMock(), service='keystone')
+ self.assertTrue(mock_log_error.called)
+
def test_verify_api_versions(self):
api_services = ['cinder', 'glance', 'keystone']
fake_os = mock.MagicMock()
@@ -100,14 +155,14 @@
verify_tempest_config.verify_api_versions(fake_os, 'foo', True)
self.assertFalse(verify_mock.called)
- def test_verify_keystone_api_versions_no_v3(self):
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_verify_keystone_api_versions_no_v3(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': {'values': [{'id': 'v2.0'}]}}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
@@ -116,14 +171,14 @@
'identity-feature-enabled',
False, True)
- def test_verify_keystone_api_versions_no_v2(self):
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_verify_keystone_api_versions_no_v2(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': {'values': [{'id': 'v3.0'}]}}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
@@ -132,14 +187,14 @@
'identity-feature-enabled',
False, True)
- def test_verify_cinder_api_versions_no_v2(self):
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_verify_cinder_api_versions_no_v2(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v1.0'}]}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
@@ -147,14 +202,14 @@
print_mock.assert_called_once_with('api_v2', 'volume-feature-enabled',
False, True)
- def test_verify_cinder_api_versions_no_v1(self):
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_verify_cinder_api_versions_no_v1(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
fake_resp = {'versions': [{'id': 'v2.0'}]}
fake_resp = json.dumps(fake_resp)
- self.useFixture(mockpatch.Patch('httplib2.Http.request',
- return_value=(None, fake_resp)))
+ mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
diff --git a/tempest/tests/common/test_admin_available.py b/tempest/tests/common/test_admin_available.py
index 75401db..01a9cd0 100644
--- a/tempest/tests/common/test_admin_available.py
+++ b/tempest/tests/common/test_admin_available.py
@@ -28,7 +28,8 @@
def setUp(self):
super(TestAdminAvailable, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
def run_test(self, dynamic_creds, use_accounts_file, admin_creds):
@@ -36,19 +37,19 @@
dynamic_creds, group='auth')
if use_accounts_file:
accounts = [{'username': 'u1',
- 'tenant_name': 't1',
+ 'project_name': 't1',
'password': 'p'},
{'username': 'u2',
- 'tenant_name': 't2',
+ 'project_name': 't2',
'password': 'p'}]
if admin_creds == 'role':
accounts.append({'username': 'admin',
- 'tenant_name': 'admin',
+ 'project_name': 'admin',
'password': 'p',
'roles': ['admin']})
elif admin_creds == 'type':
accounts.append({'username': 'admin',
- 'tenant_name': 'admin',
+ 'project_name': 'admin',
'password': 'p',
'types': ['admin']})
self.useFixture(mockpatch.Patch(
@@ -63,17 +64,17 @@
return_value=False))
if admin_creds:
username = 'u'
- tenant = 't'
+ project = 't'
password = 'p'
domain = 'd'
else:
username = None
- tenant = None
+ project = None
password = None
domain = None
cfg.CONF.set_default('admin_username', username, group='auth')
- cfg.CONF.set_default('admin_tenant_name', tenant, group='auth')
+ cfg.CONF.set_default('admin_project_name', project, group='auth')
cfg.CONF.set_default('admin_password', password, group='auth')
cfg.CONF.set_default('admin_domain_name', domain, group='auth')
diff --git a/tempest/tests/common/test_alt_available.py b/tempest/tests/common/test_alt_available.py
index db3f5ec..27db95c 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -28,7 +28,8 @@
def setUp(self):
super(TestAltAvailable, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
def run_test(self, dynamic_creds, use_accounts_file, creds):
@@ -36,7 +37,7 @@
dynamic_creds, group='auth')
if use_accounts_file:
accounts = [dict(username="u%s" % ii,
- tenant_name="t%s" % ii,
+ project_name="t%s" % ii,
password="p") for ii in creds]
self.useFixture(mockpatch.Patch(
'tempest.common.preprov_creds.read_accounts_yaml',
@@ -48,28 +49,6 @@
else:
self.useFixture(mockpatch.Patch('os.path.isfile',
return_value=False))
- cred_prefix = ['', 'alt_']
- for ii in range(0, 2):
- if len(creds) > ii:
- username = 'u%s' % creds[ii]
- tenant = 't%s' % creds[ii]
- password = 'p'
- domain = 'd'
- else:
- username = None
- tenant = None
- password = None
- domain = None
-
- cfg.CONF.set_default('%susername' % cred_prefix[ii], username,
- group='identity')
- cfg.CONF.set_default('%stenant_name' % cred_prefix[ii], tenant,
- group='identity')
- cfg.CONF.set_default('%spassword' % cred_prefix[ii], password,
- group='identity')
- cfg.CONF.set_default('%sdomain_name' % cred_prefix[ii], domain,
- group='identity')
-
expected = len(set(creds)) > 1 or dynamic_creds
observed = credentials.is_alt_available(
identity_version=self.identity_version)
@@ -96,21 +75,6 @@
use_accounts_file=True,
creds=['1', '1'])
- def test__no_dynamic_creds__no_accounts_file__one_user(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '2'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users_identical(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '1'])
-
class TestAltAvailableV3(TestAltAvailable):
diff --git a/tempest/tests/common/test_configured_creds.py b/tempest/tests/common/test_configured_creds.py
deleted file mode 100644
index 96b75fd..0000000
--- a/tempest/tests/common/test_configured_creds.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# 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_config import cfg
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.identity.v2 import token_client as v2_client
-from tempest_lib.services.identity.v3 import token_client as v3_client
-
-from tempest.common import credentials_factory as common_creds
-from tempest.common import tempest_fixtures as fixtures
-from tempest import config
-from tempest.tests import base
-from tempest.tests import fake_config
-from tempest.tests import fake_identity
-
-
-class ConfiguredV2CredentialsTests(base.TestCase):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'tenant_name': 'fake_tenant_name'
- }
-
- identity_response = fake_identity._fake_v2_response
- credentials_class = auth.KeystoneV2Credentials
- tokenclient_class = v2_client.TokenClient
- identity_version = 'v2'
-
- def setUp(self):
- super(ConfiguredV2CredentialsTests, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
- self.stubs.Set(self.tokenclient_class, 'raw_request',
- self.identity_response)
-
- def _get_credentials(self, attributes=None):
- if attributes is None:
- attributes = self.attributes
- return self.credentials_class(**attributes)
-
- def _check(self, credentials, credentials_class, filled):
- # Check the right version of credentials has been returned
- self.assertIsInstance(credentials, credentials_class)
- # Check the id attributes are filled in
- attributes = [x for x in credentials.ATTRIBUTES if (
- '_id' in x and x != 'domain_id')]
- for attr in attributes:
- if filled:
- self.assertIsNotNone(getattr(credentials, attr))
- else:
- self.assertIsNone(getattr(credentials, attr))
-
- def _verify_credentials(self, credentials_class, filled=True,
- identity_version=None):
- for ctype in common_creds.CREDENTIAL_TYPES:
- if identity_version is None:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled)
- else:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled,
- identity_version=identity_version)
- self._check(creds, credentials_class, filled)
-
- def test_create(self):
- creds = self._get_credentials()
- self.assertEqual(self.attributes, creds._initial)
-
- def test_create_invalid_attr(self):
- self.assertRaises(lib_exc.InvalidCredentials,
- self._get_credentials,
- attributes=dict(invalid='fake'))
-
- def test_get_configured_credentials(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class)
-
- def test_get_configured_credentials_unfilled(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- filled=False)
-
- def test_get_configured_credentials_version(self):
- # version specified and not loaded from config
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- identity_version=self.identity_version)
-
- def test_is_valid(self):
- creds = self._get_credentials()
- self.assertTrue(creds.is_valid())
-
-
-class ConfiguredV3CredentialsTests(ConfiguredV2CredentialsTests):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'project_name': 'fake_project_name',
- 'user_domain_name': 'fake_domain_name'
- }
-
- credentials_class = auth.KeystoneV3Credentials
- identity_response = fake_identity._fake_v3_response
- tokenclient_class = v3_client.V3TokenClient
- identity_version = 'v3'
-
- def setUp(self):
- super(ConfiguredV3CredentialsTests, self).setUp()
- # Additional config items reset by cfg fixture after each test
- cfg.CONF.set_default('auth_version', 'v3', group='identity')
- # Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- cfg.CONF.set_default(prefix + 'domain_name', 'fake_domain_name',
- group=group)
diff --git a/tempest/tests/common/test_credentials.py b/tempest/tests/common/test_credentials.py
deleted file mode 100644
index 136ac02..0000000
--- a/tempest/tests/common/test_credentials.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# 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.common import credentials_factory as credentials
-from tempest import config
-from tempest import exceptions
-from tempest.tests import base
-from tempest.tests import fake_config
-
-
-class TestLegacyCredentialsProvider(base.TestCase):
-
- fixed_params = {'identity_version': 'v2'}
-
- def setUp(self):
- super(TestLegacyCredentialsProvider, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
-
- def test_get_creds_roles_legacy_invalid(self):
- test_accounts_class = credentials.LegacyCredentialProvider(
- **self.fixed_params)
- self.assertRaises(exceptions.InvalidConfiguration,
- test_accounts_class.get_creds_by_roles,
- ['fake_role'])
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 4379756..8d4f33b 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -15,13 +15,13 @@
import mock
from oslo_config import cfg
from oslotest import mockpatch
-from tempest_lib.services.identity.v2 import token_client as json_token_client
from tempest.common import credentials_factory as credentials
from tempest.common import dynamic_creds
-from tempest.common import service_client
from tempest import config
from tempest import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib.services.identity.v2 import token_client as json_token_client
from tempest.services.identity.v2.json import identity_client as \
json_iden_client
from tempest.services.identity.v2.json import roles_client as \
@@ -30,11 +30,11 @@
json_tenants_client
from tempest.services.identity.v2.json import users_client as \
json_users_client
-from tempest.services.network.json import network_client as json_network_client
+from tempest.services.network.json import routers_client
from tempest.tests import base
from tempest.tests import fake_config
-from tempest.tests import fake_http
-from tempest.tests import fake_identity
+from tempest.tests.lib import fake_http
+from tempest.tests.lib import fake_identity
class TestDynamicCredentialProvider(base.TestCase):
@@ -46,10 +46,10 @@
def setUp(self):
super(TestDynamicCredentialProvider, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
- self.fake_http = fake_http.fake_httplib2(return_type=200)
- self.stubs.Set(json_token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
+ self.patchobject(json_token_client.TokenClient, 'raw_request',
+ fake_identity._fake_v2_response)
cfg.CONF.set_default('operator_role', 'FakeRole',
group='object-storage')
self._mock_list_ec2_credentials('fake_user_id', 'fake_tenant_id')
@@ -58,10 +58,8 @@
def test_tempest_client(self):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
- self.assertTrue(isinstance(creds.identity_admin_client,
- json_iden_client.IdentityClient))
- self.assertTrue(isinstance(creds.network_admin_client,
- json_network_client.NetworkClient))
+ self.assertIsInstance(creds.identity_admin_client,
+ json_iden_client.IdentityClient)
def _get_fake_admin_creds(self):
return credentials.get_credentials(
@@ -74,7 +72,7 @@
user_fix = self.useFixture(mockpatch.PatchObject(
json_users_client.UsersClient,
'create_user',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'user': {'id': id, 'name': name}}))))
return user_fix
@@ -82,7 +80,7 @@
tenant_fix = self.useFixture(mockpatch.PatchObject(
json_tenants_client.TenantsClient,
'create_tenant',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'tenant': {'id': id, 'name': name}}))))
return tenant_fix
@@ -90,7 +88,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200,
{'roles': [{'id': id, 'name': name},
{'id': '1', 'name': 'FakeRole'},
@@ -101,7 +99,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200,
{'roles': [{'id': '1234', 'name': 'role1'},
{'id': '1', 'name': 'FakeRole'},
@@ -112,7 +110,7 @@
tenant_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'assign_user_role',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {}))))
return tenant_fix
@@ -120,7 +118,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'roles': [{'id': '1',
'name': 'FakeRole'}]}))))
return roles_fix
@@ -129,7 +127,7 @@
ec2_creds_fix = self.useFixture(mockpatch.PatchObject(
json_users_client.UsersClient,
'list_user_ec2_credentials',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'credentials': [{
'access': 'fake_access',
'secret': 'fake_secret',
@@ -154,12 +152,12 @@
def _mock_router_create(self, id, name):
router_fix = self.useFixture(mockpatch.PatchObject(
- json_network_client.NetworkClient,
+ routers_client.RoutersClient,
'create_router',
return_value={'router': {'id': id, 'name': name}}))
return router_fix
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_primary_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -174,7 +172,7 @@
self.assertEqual(primary_creds.tenant_id, '1234')
self.assertEqual(primary_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_admin_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -197,7 +195,7 @@
self.assertEqual(admin_creds.tenant_id, '1234')
self.assertEqual(admin_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_role_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -226,7 +224,7 @@
self.assertEqual(role_creds.tenant_id, '1234')
self.assertEqual(role_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_all_cred_cleanup(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -266,7 +264,7 @@
self.assertIn('12345', args)
self.assertIn('123456', args)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_alt_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -281,7 +279,7 @@
self.assertEqual(alt_creds.tenant_id, '1234')
self.assertEqual(alt_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_no_network_creation_with_config_set(self, MockRestClient):
cfg.CONF.set_default('create_isolated_networks', False, group='auth')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -295,7 +293,7 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
@@ -310,7 +308,7 @@
self.assertIsNone(subnet)
self.assertIsNone(router)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -321,10 +319,10 @@
self._mock_subnet_create(creds, '1234', 'fake_subnet')
self._mock_router_create('1234', 'fake_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
- 'add_router_interface_with_subnet_id')
+ 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'add_router_interface')
primary_creds = creds.get_primary_creds()
- router_interface_mock.assert_called_once_with('1234', '1234')
+ router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
network = primary_creds.network
subnet = primary_creds.subnet
router = primary_creds.router
@@ -335,7 +333,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_cleanup(self, MockRestClient):
def side_effect(**args):
return {"security_groups": [{"tenant_id": args['tenant_id'],
@@ -353,10 +351,10 @@
self._mock_subnet_create(creds, '1234', 'fake_subnet')
self._mock_router_create('1234', 'fake_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
- 'add_router_interface_with_subnet_id')
+ 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'add_router_interface')
creds.get_primary_creds()
- router_interface_mock.assert_called_once_with('1234', '1234')
+ router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
router_interface_mock.reset_mock()
# Create alternate tenant and network
self._mock_user_create('12345', 'fake_alt_user')
@@ -365,7 +363,8 @@
self._mock_subnet_create(creds, '12345', 'fake_alt_subnet')
self._mock_router_create('12345', 'fake_alt_router')
creds.get_alt_creds()
- router_interface_mock.assert_called_once_with('12345', '12345')
+ router_interface_mock.assert_called_once_with('12345',
+ subnet_id='12345')
router_interface_mock.reset_mock()
# Create admin tenant and networks
self._mock_user_create('123456', 'fake_admin_user')
@@ -385,12 +384,12 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
remove_router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
- 'remove_router_interface_with_subnet_id')
+ 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'remove_router_interface')
return_values = ({'status': 200}, {'ports': []})
port_list_mock = mock.patch.object(creds.ports_admin_client,
'list_ports',
@@ -403,9 +402,9 @@
side_effect=side_effect)
secgroup_list_mock.start()
- return_values = (fake_http.fake_httplib({}, status=204), {})
+ return_values = fake_http.fake_http_response({}, status=204), ''
remove_secgroup_mock = self.patch(
- 'tempest.services.network.json.security_groups_client.'
+ 'tempest.lib.services.network.security_groups_client.'
'SecurityGroupsClient.delete', return_value=return_values)
creds.clear_creds()
# Verify default security group delete
@@ -419,11 +418,11 @@
# Verify remove router interface calls
calls = remove_router_interface_mock.mock_calls
self.assertEqual(len(calls), 3)
- args = map(lambda x: x[1], calls)
+ args = map(lambda x: (x[1][0], x[2]), calls)
args = list(args)
- self.assertIn(('1234', '1234'), args)
- self.assertIn(('12345', '12345'), args)
- self.assertIn(('123456', '123456'), args)
+ self.assertIn(('1234', {'subnet_id': '1234'}), args)
+ self.assertIn(('12345', {'subnet_id': '12345'}), args)
+ self.assertIn(('123456', {'subnet_id': '123456'}), args)
# Verify network delete calls
calls = net_mock.mock_calls
self.assertEqual(len(calls), 3)
@@ -449,7 +448,7 @@
self.assertIn('12345', args)
self.assertIn('123456', args)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_alt_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -460,10 +459,10 @@
self._mock_subnet_create(creds, '1234', 'fake_alt_subnet')
self._mock_router_create('1234', 'fake_alt_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
- 'add_router_interface_with_subnet_id')
+ 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'add_router_interface')
alt_creds = creds.get_alt_creds()
- router_interface_mock.assert_called_once_with('1234', '1234')
+ router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
network = alt_creds.network
subnet = alt_creds.subnet
router = alt_creds.router
@@ -474,7 +473,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_alt_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_admin_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -484,11 +483,11 @@
self._mock_subnet_create(creds, '1234', 'fake_admin_subnet')
self._mock_router_create('1234', 'fake_admin_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
- 'add_router_interface_with_subnet_id')
+ 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'add_router_interface')
self._mock_list_roles('123456', 'admin')
admin_creds = creds.get_admin_creds()
- router_interface_mock.assert_called_once_with('1234', '1234')
+ router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
network = admin_creds.network
subnet = admin_creds.subnet
router = admin_creds.router
@@ -499,7 +498,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_admin_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_no_network_resources(self, MockRestClient):
net_dict = {
'network': False,
@@ -520,7 +519,7 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
@@ -535,7 +534,7 @@
self.assertIsNone(subnet)
self.assertIsNone(router)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_router_without_network(self, MockRestClient):
net_dict = {
'network': False,
@@ -553,7 +552,7 @@
self.assertRaises(exceptions.InvalidConfiguration,
creds.get_primary_creds)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_subnet_without_network(self, MockRestClient):
net_dict = {
'network': False,
@@ -571,7 +570,7 @@
self.assertRaises(exceptions.InvalidConfiguration,
creds.get_primary_creds)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_dhcp_without_subnet(self, MockRestClient):
net_dict = {
'network': False,
diff --git a/tempest/tests/common/test_preprov_creds.py b/tempest/tests/common/test_preprov_creds.py
index fd7df16..873c4c4 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/common/test_preprov_creds.py
@@ -21,17 +21,15 @@
from oslotest import mockpatch
import shutil
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.identity.v2 import token_client
from tempest.common import cred_provider
from tempest.common import preprov_creds
from tempest import config
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
from tempest.tests import fake_config
-from tempest.tests import fake_http
-from tempest.tests import fake_identity
+from tempest.tests.lib import fake_identity
class TestPreProvisionedCredentials(base.TestCase):
@@ -44,40 +42,46 @@
'object_storage_operator_role': 'operator',
'object_storage_reseller_admin_role': 'reseller'}
- def setUp(self):
- super(TestPreProvisionedCredentials, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
- self.fake_http = fake_http.fake_httplib2(return_type=200)
- self.stubs.Set(token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
- self.useFixture(lockutils_fixtures.ExternalLockFixture())
- self.test_accounts = [
+ identity_response = fake_identity._fake_v2_response
+ token_client = ('tempest.lib.services.identity.v2.token_client'
+ '.TokenClient.raw_request')
+
+ @classmethod
+ def _fake_accounts(cls, admin_role):
+ return [
{'username': 'test_user1', 'tenant_name': 'test_tenant1',
'password': 'p'},
- {'username': 'test_user2', 'tenant_name': 'test_tenant2',
+ {'username': 'test_user2', 'project_name': 'test_tenant2',
'password': 'p'},
{'username': 'test_user3', 'tenant_name': 'test_tenant3',
'password': 'p'},
- {'username': 'test_user4', 'tenant_name': 'test_tenant4',
+ {'username': 'test_user4', 'project_name': 'test_tenant4',
'password': 'p'},
{'username': 'test_user5', 'tenant_name': 'test_tenant5',
'password': 'p'},
- {'username': 'test_user6', 'tenant_name': 'test_tenant6',
+ {'username': 'test_user6', 'project_name': 'test_tenant6',
'password': 'p', 'roles': ['role1', 'role2']},
{'username': 'test_user7', 'tenant_name': 'test_tenant7',
'password': 'p', 'roles': ['role2', 'role3']},
- {'username': 'test_user8', 'tenant_name': 'test_tenant8',
+ {'username': 'test_user8', 'project_name': 'test_tenant8',
'password': 'p', 'roles': ['role4', 'role1']},
{'username': 'test_user9', 'tenant_name': 'test_tenant9',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
- {'username': 'test_user10', 'tenant_name': 'test_tenant10',
+ {'username': 'test_user10', 'project_name': 'test_tenant10',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user11', 'tenant_name': 'test_tenant11',
- 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
- {'username': 'test_user12', 'tenant_name': 'test_tenant12',
- 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
- ]
+ 'password': 'p', 'roles': [admin_role]},
+ {'username': 'test_user12', 'project_name': 'test_tenant12',
+ 'password': 'p', 'roles': [admin_role]}]
+
+ def setUp(self):
+ super(TestPreProvisionedCredentials, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
+ 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',
return_value=self.test_accounts))
@@ -90,24 +94,33 @@
def _get_hash_list(self, accounts_list):
hash_list = []
+ hash_fields = (
+ preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
for account in accounts_list:
hash = hashlib.md5()
- hash.update(six.text_type(account).encode('utf-8'))
+ account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
+ if k in hash_fields)
+ hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash = hash.hexdigest()
hash_list.append(temp_hash)
return hash_list
def test_get_hash(self):
- self.stubs.Set(token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
- test_account_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- hash_list = self._get_hash_list(self.test_accounts)
- test_cred_dict = self.test_accounts[3]
- test_creds = auth.get_credentials(fake_identity.FAKE_AUTH_URL,
- **test_cred_dict)
- results = test_account_class.get_hash(test_creds)
- self.assertEqual(hash_list[3], results)
+ # Test with all accounts to make sure we try all combinations
+ # and hide no race conditions
+ hash_index = 0
+ for test_cred_dict in self.test_accounts:
+ test_account_class = (
+ preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params))
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_creds = auth.get_credentials(
+ fake_identity.FAKE_AUTH_URL,
+ identity_version=self.fixed_params['identity_version'],
+ **test_cred_dict)
+ results = test_account_class.get_hash(test_creds)
+ self.assertEqual(hash_list[hash_index], results)
+ hash_index += 1
def test_get_hash_dict(self):
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
@@ -190,7 +203,7 @@
return False
return True
- self.stubs.Set(os.path, 'isfile', _fake_is_file)
+ self.patchobject(os.path, 'isfile', _fake_is_file)
with mock.patch('six.moves.builtins.open', mock.mock_open(),
create=True) as open_mock:
test_account_class._get_free_hash(hash_list)
@@ -319,16 +332,66 @@
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
- with mock.patch('tempest_lib.services.compute.networks_client.'
+ with mock.patch('tempest.lib.services.compute.networks_client.'
'NetworksClient.list_networks',
return_value={'networks': [{'name': 'network-2',
'id': 'fake-id',
'label': 'network-2'}]}):
creds = test_accounts_class.get_creds_by_roles(['role-7'])
- self.assertTrue(isinstance(creds, cred_provider.TestResources))
+ self.assertIsInstance(creds, cred_provider.TestResources)
network = creds.network
self.assertIsNotNone(network)
self.assertIn('name', network)
self.assertIn('id', network)
self.assertEqual('fake-id', network['id'])
self.assertEqual('network-2', network['name'])
+
+
+class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials):
+
+ fixed_params = {'name': 'test class',
+ 'identity_version': 'v3',
+ 'test_accounts_file': 'fake_accounts_file',
+ 'accounts_lock_dir': 'fake_locks_dir_v3',
+ 'admin_role': 'admin',
+ 'object_storage_operator_role': 'operator',
+ 'object_storage_reseller_admin_role': 'reseller'}
+
+ identity_response = fake_identity._fake_v3_response
+ token_client = ('tempest.lib.services.identity.v3.token_client'
+ '.V3TokenClient.raw_request')
+
+ @classmethod
+ def _fake_accounts(cls, admin_role):
+ return [
+ {'username': 'test_user1', 'project_name': 'test_project1',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user2', 'project_name': 'test_project2',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user3', 'project_name': 'test_project3',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user4', 'project_name': 'test_project4',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user5', 'project_name': 'test_project5',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user6', 'project_name': 'test_project6',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2']},
+ {'username': 'test_user7', 'project_name': 'test_project7',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role2', 'role3']},
+ {'username': 'test_user8', 'project_name': 'test_project8',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role4', 'role1']},
+ {'username': 'test_user9', 'project_name': 'test_project9',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_user10', 'project_name': 'test_project10',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_user11', 'project_name': 'test_project11',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': [admin_role]},
+ {'username': 'test_user12', 'project_name': 'test_project12',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': [admin_role]}]
diff --git a/tempest/tests/common/test_service_clients.py b/tempest/tests/common/test_service_clients.py
deleted file mode 100644
index f248957..0000000
--- a/tempest/tests/common/test_service_clients.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import mock
-import random
-import six
-
-from tempest.services.baremetal.v1.json import baremetal_client
-from tempest.services.data_processing.v1_1 import data_processing_client
-from tempest.services.database.json import flavors_client as db_flavor_client
-from tempest.services.database.json import versions_client as db_version_client
-from tempest.services.identity.v2.json import identity_client as \
- identity_v2_identity_client
-from tempest.services.identity.v3.json import credentials_client
-from tempest.services.identity.v3.json import endpoints_client
-from tempest.services.identity.v3.json import identity_client as \
- identity_v3_identity_client
-from tempest.services.identity.v3.json import policies_client
-from tempest.services.identity.v3.json import regions_client
-from tempest.services.identity.v3.json import services_client
-from tempest.services.image.v1.json import images_client
-from tempest.services.image.v2.json import images_client as images_v2_client
-from tempest.services.messaging.json import messaging_client
-from tempest.services.network.json import network_client
-from tempest.services.object_storage import account_client
-from tempest.services.object_storage import container_client
-from tempest.services.object_storage import object_client
-from tempest.services.orchestration.json import orchestration_client
-from tempest.services.telemetry.json import alarming_client
-from tempest.services.telemetry.json import telemetry_client
-from tempest.services.volume.v1.json.admin import hosts_client \
- as volume_hosts_client
-from tempest.services.volume.v1.json.admin import quotas_client \
- as volume_quotas_client
-from tempest.services.volume.v1.json.admin import services_client \
- as volume_services_client
-from tempest.services.volume.v1.json.admin import types_client \
- as volume_types_client
-from tempest.services.volume.v1.json import availability_zone_client \
- as volume_az_client
-from tempest.services.volume.v1.json import backups_client
-from tempest.services.volume.v1.json import extensions_client \
- as volume_extensions_client
-from tempest.services.volume.v1.json import qos_client
-from tempest.services.volume.v1.json import snapshots_client
-from tempest.services.volume.v1.json import volumes_client
-from tempest.services.volume.v2.json.admin import hosts_client \
- as volume_v2_hosts_client
-from tempest.services.volume.v2.json.admin import quotas_client \
- as volume_v2_quotas_client
-from tempest.services.volume.v2.json.admin import services_client \
- as volume_v2_services_client
-from tempest.services.volume.v2.json.admin import types_client \
- as volume_v2_types_client
-from tempest.services.volume.v2.json import availability_zone_client \
- as volume_v2_az_client
-from tempest.services.volume.v2.json import backups_client \
- as volume_v2_backups_client
-from tempest.services.volume.v2.json import extensions_client \
- as volume_v2_extensions_client
-from tempest.services.volume.v2.json import qos_client as volume_v2_qos_client
-from tempest.services.volume.v2.json import snapshots_client \
- as volume_v2_snapshots_client
-from tempest.services.volume.v2.json import volumes_client as \
- volume_v2_volumes_client
-from tempest.tests import base
-
-
-class TestServiceClient(base.TestCase):
-
- @mock.patch('tempest_lib.common.rest_client.RestClient.__init__')
- def test_service_client_creations_with_specified_args(self, mock_init):
- test_clients = [
- baremetal_client.BaremetalClient,
- data_processing_client.DataProcessingClient,
- db_flavor_client.DatabaseFlavorsClient,
- db_version_client.DatabaseVersionsClient,
- messaging_client.MessagingClient,
- network_client.NetworkClient,
- account_client.AccountClient,
- container_client.ContainerClient,
- object_client.ObjectClient,
- orchestration_client.OrchestrationClient,
- telemetry_client.TelemetryClient,
- alarming_client.AlarmingClient,
- qos_client.QosSpecsClient,
- volume_hosts_client.HostsClient,
- volume_quotas_client.QuotasClient,
- volume_services_client.ServicesClient,
- volume_types_client.TypesClient,
- volume_az_client.AvailabilityZoneClient,
- backups_client.BackupsClient,
- volume_extensions_client.ExtensionsClient,
- snapshots_client.SnapshotsClient,
- volumes_client.VolumesClient,
- volume_v2_hosts_client.HostsClient,
- volume_v2_quotas_client.QuotasClient,
- volume_v2_services_client.ServicesClient,
- volume_v2_types_client.TypesClient,
- volume_v2_az_client.AvailabilityZoneClient,
- volume_v2_backups_client.BackupsClient,
- volume_v2_extensions_client.ExtensionsClient,
- volume_v2_qos_client.QosSpecsClient,
- volume_v2_snapshots_client.SnapshotsClient,
- volume_v2_volumes_client.VolumesClient,
- identity_v2_identity_client.IdentityClient,
- credentials_client.CredentialsClient,
- endpoints_client.EndPointClient,
- identity_v3_identity_client.IdentityV3Client,
- policies_client.PoliciesClient,
- regions_client.RegionsClient,
- services_client.ServicesClient,
- images_client.ImagesClient,
- images_v2_client.ImagesClientV2
- ]
-
- for client in test_clients:
- fake_string = six.text_type(random.randint(1, 0x7fffffff))
- auth = 'auth' + fake_string
- service = 'service' + fake_string
- region = 'region' + fake_string
- params = {
- 'endpoint_type': 'URL' + fake_string,
- 'build_interval': random.randint(1, 100),
- 'build_timeout': random.randint(1, 100),
- 'disable_ssl_certificate_validation':
- True if random.randint(0, 1) else False,
- 'ca_certs': None,
- 'trace_requests': 'foo' + fake_string
- }
- client(auth, service, region, **params)
- mock_init.assert_called_once_with(auth, service, region, **params)
- mock_init.reset_mock()
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index c7cc638..a56f837 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -20,6 +20,7 @@
from tempest import exceptions
from tempest.services.volume.base import base_volumes_client
from tempest.tests import base
+import tempest.tests.utils as utils
class TestImageWaiters(base.TestCase):
@@ -38,6 +39,9 @@
self.assertTrue((end_time - start_time) < 10)
def test_wait_for_image_status_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
self.client.show_image.return_value = ({'status': 'saving'})
self.assertRaises(exceptions.TimeoutException,
waiters.wait_for_image_status,
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 9c2b99e..7d625cf 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
import time
from oslo_config import cfg
@@ -19,15 +20,45 @@
from tempest.common.utils.linux import remote_client
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
from tempest.tests import fake_config
+SERVER = {
+ 'id': 'server_uuid',
+ 'name': 'fake_server',
+ 'status': 'ACTIVE'
+}
+
+BROKEN_SERVER = {
+ 'id': 'broken_server_uuid',
+ 'name': 'broken_server',
+ 'status': 'ERROR'
+}
+
+
+class FakeServersClient(object):
+
+ CONSOLE_OUTPUT = "Console output for %s"
+
+ def get_console_output(self, server_id):
+ status = 'ERROR'
+ for s in SERVER, BROKEN_SERVER:
+ if s['id'] == server_id:
+ status = s['status']
+ if status == 'ERROR':
+ raise lib_exc.BadRequest('Server in ERROR state')
+ else:
+ return dict(output=self.CONSOLE_OUTPUT % server_id)
+
+
class TestRemoteClient(base.TestCase):
def setUp(self):
super(TestRemoteClient, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
cfg.CONF.set_default('ip_version_for_ssh', 4, group='validation')
cfg.CONF.set_default('network_for_ssh', 'public', group='validation')
cfg.CONF.set_default('connect_timeout', 1, group='validation')
@@ -154,3 +185,78 @@
self.conn.set_nic_state(nic, "down")
self._assert_exec_called_with(
'sudo ip link set %s down' % nic)
+
+
+class TestRemoteClientWithServer(base.TestCase):
+
+ server = SERVER
+
+ def setUp(self):
+ super(TestRemoteClientWithServer, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
+ cfg.CONF.set_default('ip_version_for_ssh', 4, group='validation')
+ cfg.CONF.set_default('network_for_ssh', 'public',
+ group='validation')
+ cfg.CONF.set_default('connect_timeout', 1, group='validation')
+ cfg.CONF.set_default('console_output', True,
+ group='compute-feature-enabled')
+
+ self.conn = remote_client.RemoteClient(
+ '127.0.0.1', 'user', 'pass',
+ server=self.server, servers_client=FakeServersClient())
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.ssh.Client._get_ssh_connection',
+ side_effect=lib_exc.SSHTimeout(host='127.0.0.1',
+ user='user',
+ password='pass')))
+ self.log = self.useFixture(fixtures.FakeLogger(
+ name='tempest.common.utils.linux.remote_client',
+ level='DEBUG'))
+
+ def test_validate_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.validate_authentication)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithServer:test_validate_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ self.assertIn('Console output for', self.log.output)
+
+ def test_exec_command_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.exec_command, 'fake command')
+ self.assertIn('fake command', self.log.output)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithServer:test_exec_command_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ self.assertIn('Console output for', self.log.output)
+
+
+class TestRemoteClientWithBrokenServer(TestRemoteClientWithServer):
+
+ server = BROKEN_SERVER
+
+ def test_validate_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.validate_authentication)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithBrokenServer:test_validate_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ msg = 'Could not get console_log for server %s' % self.server['id']
+ self.assertIn(msg, self.log.output)
+
+ def test_exec_command_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.exec_command, 'fake command')
+ self.assertIn('fake command', self.log.output)
+ caller = ":".join(['TestRemoteClientWithBrokenServer',
+ 'test_exec_command_debug_ssh_console'])
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ caller, self.server)
+ self.assertIn(msg, self.log.output)
+ msg = 'Could not get console_log for server %s' % self.server['id']
+ self.assertIn(msg, self.log.output)
diff --git a/tempest/tests/common/utils/test_file_utils.py b/tempest/tests/common/utils/test_file_utils.py
deleted file mode 100644
index 937aefa..0000000
--- a/tempest/tests/common/utils/test_file_utils.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2014 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 tempest.common.utils import file_utils
-from tempest.tests import base
-
-
-class TestFileUtils(base.TestCase):
-
- def test_have_effective_read_path(self):
- with mock.patch('six.moves.builtins.open', mock.mock_open(),
- create=True):
- result = file_utils.have_effective_read_access('fake_path')
- self.assertTrue(result)
-
- def test_not_effective_read_path(self):
- result = file_utils.have_effective_read_access('fake_path')
- self.assertFalse(result)
diff --git a/tempest/tests/fake_auth_provider.py b/tempest/tests/fake_auth_provider.py
index bc68d26..769f6a6 100644
--- a/tempest/tests/fake_auth_provider.py
+++ b/tempest/tests/fake_auth_provider.py
@@ -18,3 +18,9 @@
def auth_request(self, method, url, headers=None, body=None, filters=None):
return url, headers, body
+
+ def get_token(self):
+ return "faketoken"
+
+ def base_url(self, filters, auth_data=None):
+ return "https://example.com"
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index c45f6da..65164a0 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -46,16 +46,11 @@
lock_path=str(os.environ.get('OS_TEST_LOCK_PATH')),
)
self.conf.set_default('auth_version', 'v2', group='identity')
- for config_option in ['username', 'password', 'tenant_name']:
+ for config_option in ['username', 'password', 'project_name']:
# Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- self.conf.set_default(prefix + config_option,
- 'fake_' + config_option,
- group=group)
+ self.conf.set_default('admin_' + config_option,
+ 'fake_' + config_option,
+ group='auth')
class FakePrivate(config.TempestConfigPrivate):
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/cli/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/cli/__init__.py
diff --git a/tempest/tests/lib/cli/test_command_failed.py b/tempest/tests/lib/cli/test_command_failed.py
new file mode 100644
index 0000000..388028a
--- /dev/null
+++ b/tempest/tests/lib/cli/test_command_failed.py
@@ -0,0 +1,30 @@
+# 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 import exceptions
+from tempest.tests import base
+
+
+class TestOutputParser(base.TestCase):
+
+ def test_command_failed_exception(self):
+ returncode = 1
+ cmd = "foo"
+ stdout = "output"
+ stderr = "error"
+ try:
+ raise exceptions.CommandFailed(returncode, cmd, stdout, stderr)
+ except exceptions.CommandFailed as e:
+ self.assertIn(str(returncode), str(e))
+ self.assertIn(cmd, str(e))
+ self.assertIn(stdout, str(e))
+ self.assertIn(stderr, str(e))
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
new file mode 100644
index 0000000..cc9c94c
--- /dev/null
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -0,0 +1,76 @@
+#
+# 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
+import subprocess
+
+from tempest.lib.cli import base as cli_base
+from tempest.lib import exceptions
+from tempest.tests import base
+
+
+class TestExecute(base.TestCase):
+
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_success(self, mock_popen):
+ mock_popen.return_value.returncode = 0
+ mock_popen.return_value.communicate.return_value = (
+ "__init__.py", "")
+ result = cli_base.execute("/bin/ls", action="tempest",
+ flags="-l -a")
+ args, kwargs = mock_popen.call_args
+ # Check merge_stderr == False
+ self.assertEqual(subprocess.PIPE, kwargs['stderr'])
+ # Check action and flags are passed
+ args = args[0]
+ # We just tests that all pieces are passed through, we cannot make
+ # assumptions about the order
+ self.assertIn("/bin/ls", args)
+ self.assertIn("-l", args)
+ self.assertIn("-a", args)
+ self.assertIn("tempest", args)
+ # The result is mocked - checking that the mock was invoked correctly
+ self.assertIsInstance(result, str)
+ self.assertIn("__init__.py", result)
+
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_failure(self, mock_popen):
+ mock_popen.return_value.returncode = 1
+ mock_popen.return_value.communicate.return_value = (
+ "No such option --foobar", "")
+ result = cli_base.execute("/bin/ls", action="tempest.lib",
+ flags="--foobar", merge_stderr=True,
+ fail_ok=True)
+ args, kwargs = mock_popen.call_args
+ # Check the merge_stderr
+ self.assertEqual(subprocess.STDOUT, kwargs['stderr'])
+ # Check action and flags are passed
+ args = args[0]
+ # We just tests that all pieces are passed through, we cannot make
+ # assumptions about the order
+ self.assertIn("/bin/ls", args)
+ self.assertIn("--foobar", args)
+ self.assertIn("tempest.lib", args)
+ # The result is mocked - checking that the mock was invoked correctly
+ self.assertIsInstance(result, str)
+ self.assertIn("--foobar", result)
+
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_failure_raise_exception(self, mock_popen):
+ mock_popen.return_value.returncode = 1
+ mock_popen.return_value.communicate.return_value = (
+ "No such option --foobar", "")
+ self.assertRaises(exceptions.CommandFailed, cli_base.execute,
+ "/bin/ls", action="tempest", flags="--foobar",
+ merge_stderr=True)
diff --git a/tempest/tests/lib/cli/test_output_parser.py b/tempest/tests/lib/cli/test_output_parser.py
new file mode 100644
index 0000000..d88dfc3
--- /dev/null
+++ b/tempest/tests/lib/cli/test_output_parser.py
@@ -0,0 +1,177 @@
+# 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.lib.cli import output_parser
+from tempest.lib import exceptions
+from tempest.tests import base
+
+
+class TestOutputParser(base.TestCase):
+ OUTPUT_LINES = """
++----+------+---------+
+| ID | Name | Status |
++----+------+---------+
+| 11 | foo | BUILD |
+| 21 | bar | ERROR |
+| 31 | bee | None |
++----+------+---------+
+"""
+ OUTPUT_LINES2 = """
++----+-------+---------+
+| ID | Name2 | Status2 |
++----+-------+---------+
+| 41 | aaa | SSSSS |
+| 51 | bbb | TTTTT |
+| 61 | ccc | AAAAA |
++----+-------+---------+
+"""
+
+ EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'],
+ 'values': [['11', 'foo', 'BUILD'],
+ ['21', 'bar', 'ERROR'],
+ ['31', 'bee', 'None']]}
+ EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'],
+ 'values': [['41', 'aaa', 'SSSSS'],
+ ['51', 'bbb', 'TTTTT'],
+ ['61', 'ccc', 'AAAAA']]}
+
+ def test_table_with_normal_values(self):
+ actual = output_parser.table(self.OUTPUT_LINES)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_table_with_list(self):
+ output_lines = self.OUTPUT_LINES.split('\n')
+ actual = output_parser.table(output_lines)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_table_with_invalid_line(self):
+ output_lines = self.OUTPUT_LINES + "aaaa"
+ actual = output_parser.table(output_lines)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_tables_with_normal_values(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2)
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_tables_with_invalid_values(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2 + '\n')
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_tables_with_invalid_line(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2 +
+ '+----+-------+---------+')
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ LISTING_OUTPUT = """
++----+
+| ID |
++----+
+| 11 |
+| 21 |
+| 31 |
++----+
+"""
+
+ def test_listing(self):
+ expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}]
+ actual = output_parser.listing(self.LISTING_OUTPUT)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_invalid_line(self):
+ self.assertRaises(exceptions.InvalidStructure,
+ output_parser.details_multiple,
+ self.OUTPUT_LINES)
+
+ DETAILS_LINES1 = """First Table
++----------+--------+
+| Property | Value |
++----------+--------+
+| foo | BUILD |
+| bar | ERROR |
+| bee | None |
++----------+--------+
+"""
+ DETAILS_LINES2 = """Second Table
++----------+--------+
+| Property | Value |
++----------+--------+
+| aaa | VVVVV |
+| bbb | WWWWW |
+| ccc | XXXXX |
++----------+--------+
+"""
+
+ def test_details_with_normal_line_label_false(self):
+ expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
+ actual = output_parser.details(self.DETAILS_LINES1)
+ self.assertEqual(expected, actual)
+
+ def test_details_with_normal_line_label_true(self):
+ expected = {'__label': 'First Table',
+ 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
+ actual = output_parser.details(self.DETAILS_LINES1, with_label=True)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_normal_line_label_false(self):
+ expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
+ {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
+ actual = output_parser.details_multiple(self.DETAILS_LINES1 +
+ self.DETAILS_LINES2)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_normal_line_label_true(self):
+ expected = [{'__label': 'First Table',
+ 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
+ {'__label': 'Second Table',
+ 'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
+ actual = output_parser.details_multiple(self.DETAILS_LINES1 +
+ self.DETAILS_LINES2,
+ with_label=True)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/common/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/common/__init__.py
diff --git a/tempest/tests/common/test_api_version_request.py b/tempest/tests/lib/common/test_api_version_request.py
similarity index 98%
rename from tempest/tests/common/test_api_version_request.py
rename to tempest/tests/lib/common/test_api_version_request.py
index 38fbfc1..58e7040 100644
--- a/tempest/tests/common/test_api_version_request.py
+++ b/tempest/tests/lib/common/test_api_version_request.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common import api_version_request
-from tempest import exceptions
+from tempest.lib.common import api_version_request
+from tempest.lib import exceptions
from tempest.tests import base
diff --git a/tempest/tests/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py
similarity index 97%
rename from tempest/tests/common/test_api_version_utils.py
rename to tempest/tests/lib/common/test_api_version_utils.py
index 501f954..6206379 100644
--- a/tempest/tests/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -14,8 +14,8 @@
import testtools
-from tempest.common import api_version_utils
-from tempest import exceptions
+from tempest.lib.common import api_version_utils
+from tempest.lib import exceptions
from tempest.tests import base
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/common/utils/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/common/utils/__init__.py
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
new file mode 100644
index 0000000..399c4af
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -0,0 +1,178 @@
+# 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.
+
+import netaddr
+
+from tempest.lib.common.utils import data_utils
+from tempest.tests import base
+
+
+class TestDataUtils(base.TestCase):
+
+ def test_rand_uuid(self):
+ actual = data_utils.rand_uuid()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]"
+ "{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
+ actual2 = data_utils.rand_uuid()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_uuid_hex(self):
+ actual = data_utils.rand_uuid_hex()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^[0-9a-f]{32}$")
+
+ actual2 = data_utils.rand_uuid_hex()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_name(self):
+ actual = data_utils.rand_name()
+ self.assertIsInstance(actual, str)
+ actual2 = data_utils.rand_name()
+ self.assertNotEqual(actual, actual2)
+
+ actual = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ actual2 = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_name_with_prefix(self):
+ actual = data_utils.rand_name(prefix='prefix-str')
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^prefix-str-")
+ actual2 = data_utils.rand_name(prefix='prefix-str')
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password(self):
+ actual = data_utils.rand_password()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{15,}")
+ actual2 = data_utils.rand_password()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password_with_len(self):
+ actual = data_utils.rand_password(8)
+ self.assertIsInstance(actual, str)
+ self.assertEqual(len(actual), 8)
+ self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{8}")
+ actual2 = data_utils.rand_password(8)
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password_with_len_2(self):
+ actual = data_utils.rand_password(2)
+ self.assertIsInstance(actual, str)
+ self.assertEqual(len(actual), 3)
+ self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{3}")
+ actual2 = data_utils.rand_password(2)
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_url(self):
+ actual = data_utils.rand_url()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^https://url-[0-9]*\.com$")
+ actual2 = data_utils.rand_url()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_int(self):
+ actual = data_utils.rand_int_id()
+ self.assertIsInstance(actual, int)
+
+ actual2 = data_utils.rand_int_id()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_infiniband_guid_address(self):
+ actual = data_utils.rand_infiniband_guid_address()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^([0-9a-f][0-9a-f]:){7}"
+ "[0-9a-f][0-9a-f]$")
+
+ actual2 = data_utils.rand_infiniband_guid_address()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_mac_address(self):
+ actual = data_utils.rand_mac_address()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^([0-9a-f][0-9a-f]:){5}"
+ "[0-9a-f][0-9a-f]$")
+
+ actual2 = data_utils.rand_mac_address()
+ self.assertNotEqual(actual, actual2)
+
+ def test_parse_image_id(self):
+ actual = data_utils.parse_image_id("/foo/bar/deadbeaf")
+ self.assertEqual("deadbeaf", actual)
+
+ def test_arbitrary_string(self):
+ actual = data_utils.arbitrary_string()
+ self.assertEqual(actual, "test")
+ actual = data_utils.arbitrary_string(size=30, base_text="abc")
+ self.assertEqual(actual, "abc" * int(30 / len("abc")))
+ actual = data_utils.arbitrary_string(size=5, base_text="deadbeaf")
+ self.assertEqual(actual, "deadb")
+
+ def test_random_bytes(self):
+ actual = data_utils.random_bytes() # default size=1024
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^[\x00-\xFF]{1024}")
+ actual2 = data_utils.random_bytes()
+ self.assertNotEqual(actual, actual2)
+
+ actual = data_utils.random_bytes(size=2048)
+ self.assertRegex(actual, "^[\x00-\xFF]{2048}")
+
+ def test_get_ipv6_addr_by_EUI64(self):
+ actual = data_utils.get_ipv6_addr_by_EUI64('2001:db8::',
+ '00:16:3e:33:44:55')
+ self.assertIsInstance(actual, netaddr.IPAddress)
+ self.assertEqual(actual,
+ netaddr.IPAddress('2001:db8::216:3eff:fe33:4455'))
+
+ def test_get_ipv6_addr_by_EUI64_with_IPv4_prefix(self):
+ ipv4_prefix = '10.0.8'
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ ipv4_prefix, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_cidr_type(self):
+ bad_cidr = 123
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ bad_cidr, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_cidr_value(self):
+ bad_cidr = 'bb'
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ bad_cidr, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_mac_value(self):
+ cidr = '2001:db8::'
+ bad_mac = '00:16:3e:33:44:5Z'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ cidr, bad_mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_mac_type(self):
+ cidr = '2001:db8::'
+ bad_mac = 99999999999999999999
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ cidr, bad_mac)
+
+ def test_chunkify(self):
+ data = "aaa"
+ chunks = data_utils.chunkify(data, 2)
+ self.assertEqual("aa", next(chunks))
+ self.assertEqual("a", next(chunks))
+ self.assertRaises(StopIteration, next, chunks)
diff --git a/tempest/tests/lib/common/utils/test_misc.py b/tempest/tests/lib/common/utils/test_misc.py
new file mode 100644
index 0000000..9597f5b
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_misc.py
@@ -0,0 +1,88 @@
+# 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.lib.common.utils import misc
+from tempest.tests import base
+
+
+@misc.singleton
+class TestFoo(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+@misc.singleton
+class TestBar(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+class TestMisc(base.TestCase):
+
+ def test_singleton(self):
+ test = TestFoo()
+ self.assertEqual(0, test.count)
+ self.assertEqual(1, test.increment())
+ test2 = TestFoo()
+ self.assertEqual(1, test.count)
+ self.assertEqual(1, test2.count)
+ self.assertEqual(test, test2)
+ test3 = TestBar()
+ self.assertNotEqual(test, test3)
+
+ def test_find_test_caller_test_case(self):
+ # Calling it from here should give us the method we're in.
+ self.assertEqual('TestMisc:test_find_test_caller_test_case',
+ misc.find_test_caller())
+
+ def test_find_test_caller_setup_self(self):
+ def setUp(self):
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:setUp', setUp(self))
+
+ def test_find_test_caller_setup_no_self(self):
+ def setUp():
+ return misc.find_test_caller()
+ self.assertEqual(':setUp', setUp())
+
+ def test_find_test_caller_setupclass_cls(self):
+ def setUpClass(cls): # noqa
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:setUpClass', setUpClass(self.__class__))
+
+ def test_find_test_caller_teardown_self(self):
+ def tearDown(self):
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:tearDown', tearDown(self))
+
+ def test_find_test_caller_teardown_no_self(self):
+ def tearDown():
+ return misc.find_test_caller()
+ self.assertEqual(':tearDown', tearDown())
+
+ def test_find_test_caller_teardown_class(self):
+ def tearDownClass(cls): # noqa
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:tearDownClass',
+ tearDownClass(self.__class__))
diff --git a/tempest/tests/lib/fake_auth_provider.py b/tempest/tests/lib/fake_auth_provider.py
new file mode 100644
index 0000000..8095453
--- /dev/null
+++ b/tempest/tests/lib/fake_auth_provider.py
@@ -0,0 +1,35 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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.
+
+
+class FakeAuthProvider(object):
+
+ def __init__(self, creds_dict=None, fake_base_url=None):
+ creds_dict = creds_dict or {}
+ self.credentials = FakeCredentials(creds_dict)
+ self.fake_base_url = fake_base_url
+
+ def auth_request(self, method, url, headers=None, body=None, filters=None):
+ return url, headers, body
+
+ def base_url(self, filters, auth_data=None):
+ return self.fake_base_url or "https://example.com"
+
+
+class FakeCredentials(object):
+
+ def __init__(self, creds_dict):
+ for key in creds_dict.keys():
+ setattr(self, key, creds_dict[key])
diff --git a/tempest/tests/lib/fake_credentials.py b/tempest/tests/lib/fake_credentials.py
new file mode 100644
index 0000000..eac4ada
--- /dev/null
+++ b/tempest/tests/lib/fake_credentials.py
@@ -0,0 +1,74 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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 import auth
+
+
+class FakeCredentials(auth.Credentials):
+
+ def is_valid(self):
+ return True
+
+
+class FakeKeystoneV2Credentials(auth.KeystoneV2Credentials):
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ tenant_name='fake_tenant_name'
+ )
+ super(FakeKeystoneV2Credentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3Credentials(auth.KeystoneV3Credentials):
+ """Fake credentials suitable for the Keystone Identity V3 API"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name',
+ project_name='fake_tenant_name',
+ project_domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3Credentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3DomainCredentials(auth.KeystoneV3Credentials):
+ """Fake credentials for the Keystone Identity V3 API, with no scope"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3DomainCredentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3AllCredentials(auth.KeystoneV3Credentials):
+ """Fake credentials for the Keystone Identity V3 API, with no scope"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name',
+ project_name='fake_tenant_name',
+ project_domain_name='fake_domain_name',
+ domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3AllCredentials, self).__init__(**creds)
diff --git a/tempest/tests/fake_http.py b/tempest/tests/lib/fake_http.py
similarity index 80%
rename from tempest/tests/fake_http.py
rename to tempest/tests/lib/fake_http.py
index d714055..cfa4b93 100644
--- a/tempest/tests/fake_http.py
+++ b/tempest/tests/lib/fake_http.py
@@ -14,8 +14,6 @@
import copy
-import httplib2
-
class fake_httplib2(object):
@@ -23,9 +21,9 @@
self.return_type = return_type
def request(self, uri, method="GET", body=None, headers=None,
- redirections=5, connection_type=None):
+ redirections=5, connection_type=None, chunked=False):
if not self.return_type:
- fake_headers = httplib2.Response(headers)
+ fake_headers = fake_http_response(headers)
return_obj = {
'uri': uri,
'method': method,
@@ -34,23 +32,23 @@
}
return (fake_headers, return_obj)
elif isinstance(self.return_type, int):
- body = "fake_body"
+ body = body or "fake_body"
header_info = {
'content-type': 'text/plain',
- 'status': str(self.return_type),
'content-length': len(body)
}
- resp_header = httplib2.Response(header_info)
+ resp_header = fake_http_response(header_info,
+ status=self.return_type)
return (resp_header, body)
else:
msg = "unsupported return type %s" % self.return_type
raise TypeError(msg)
-class fake_httplib(object):
+class fake_http_response(dict):
def __init__(self, headers, body=None,
version=1.0, status=200, reason="Ok"):
- """Initialization of fake httplib
+ """Initialization of fake HTTP Response
:param headers: dict representing HTTP response headers
:param body: file-like object
@@ -60,10 +58,15 @@
"""
self.body = body
self.status = status
+ self['status'] = str(self.status)
self.reason = reason
self.version = version
self.headers = headers
+ if headers:
+ for key, value in headers.items():
+ self[key.lower()] = value
+
def getheaders(self):
return copy.deepcopy(self.headers).items()
diff --git a/tempest/tests/fake_identity.py b/tempest/tests/lib/fake_identity.py
similarity index 67%
rename from tempest/tests/fake_identity.py
rename to tempest/tests/lib/fake_identity.py
index d0de927..c903e47 100644
--- a/tempest/tests/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import httplib2
from oslo_serialization import jsonutils as json
+from tempest.tests.lib import fake_http
+
FAKE_AUTH_URL = 'http://fake_uri.com/auth'
TOKEN = "fake_token"
@@ -49,11 +50,11 @@
"expires": "2020-01-01T00:00:10Z",
"id": ALT_TOKEN,
"tenant": {
- "id": "fake_tenant_id"
+ "id": "fake_alt_tenant_id"
},
},
"user": {
- "id": "fake_user_id",
+ "id": "fake_alt_user_id",
},
"serviceCatalog": CATALOG_V2,
},
@@ -132,22 +133,86 @@
}
}
+IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ "catalog": CATALOG_V3
+ }
+}
+
+IDENTITY_V3_RESPONSE_NO_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ }
+}
+
ALT_IDENTITY_V3 = IDENTITY_V3_RESPONSE
def _fake_v3_response(self, uri, method="GET", body=None, headers=None,
redirections=5, connection_type=None):
fake_headers = {
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE))
+
+
+def _fake_v3_response_domain_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
"status": "201",
"x-subject-token": TOKEN
}
- return (httplib2.Response(fake_headers),
- json.dumps(IDENTITY_V3_RESPONSE))
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_DOMAIN_SCOPE))
+
+
+def _fake_v3_response_no_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_NO_SCOPE))
def _fake_v2_response(self, uri, method="GET", body=None, headers=None,
redirections=5, connection_type=None):
- return (httplib2.Response({"status": "200"}),
+ return (fake_http.fake_http_response({}, status=200),
json.dumps(IDENTITY_V2_RESPONSE))
@@ -160,4 +225,4 @@
"code": "401"
}
}
- return httplib2.Response({"status": "401"}), json.dumps(body)
+ return fake_http.fake_http_response({}, status=401), json.dumps(body)
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/__init__.py
diff --git a/tempest/services/compute/__init__.py b/tempest/tests/lib/services/compute/__init__.py
similarity index 100%
copy from tempest/services/compute/__init__.py
copy to tempest/tests/lib/services/compute/__init__.py
diff --git a/tempest/tests/services/compute/base.py b/tempest/tests/lib/services/compute/base.py
similarity index 82%
rename from tempest/tests/services/compute/base.py
rename to tempest/tests/lib/services/compute/base.py
index a35a87c..e77b436 100644
--- a/tempest/tests/services/compute/base.py
+++ b/tempest/tests/lib/services/compute/base.py
@@ -12,28 +12,27 @@
# License for the specific language governing permissions and limitations
# under the License.
-import httplib2
-
from oslo_serialization import jsonutils as json
from oslotest import mockpatch
from tempest.tests import base
+from tempest.tests.lib import fake_http
class BaseComputeServiceTest(base.TestCase):
- def create_response(self, body, to_utf=False, status=200):
+ def create_response(self, body, to_utf=False, status=200, headers=None):
json_body = {}
if body:
json_body = json.dumps(body)
if to_utf:
json_body = json_body.encode('utf-8')
- response = (httplib2.Response({'status': status}), json_body)
- return response
+ resp = fake_http.fake_http_response(headers, status=status), json_body
+ return resp
def check_service_client_function(self, function, function2mock,
body, to_utf=False, status=200,
- **kwargs):
- mocked_response = self.create_response(body, to_utf, status)
+ headers=None, **kwargs):
+ mocked_response = self.create_response(body, to_utf, status, headers)
self.useFixture(mockpatch.Patch(
function2mock, return_value=mocked_response))
if kwargs:
diff --git a/tempest/tests/lib/services/compute/test_agents_client.py b/tempest/tests/lib/services/compute/test_agents_client.py
new file mode 100644
index 0000000..3c5043d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_agents_client.py
@@ -0,0 +1,103 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import agents_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAgentsClient(base.BaseComputeServiceTest):
+ FAKE_CREATE_AGENT = {
+ "agent": {
+ "url": "http://foo.com",
+ "hypervisor": "kvm",
+ "md5hash": "md5",
+ "version": "2",
+ "architecture": "x86_64",
+ "os": "linux",
+ "agent_id": 1
+ }
+ }
+
+ FAKE_UPDATE_AGENT = {
+ "agent": {
+ "url": "http://foo.com",
+ "md5hash": "md5",
+ "version": "2",
+ "agent_id": 1
+ }
+ }
+
+ def setUp(self):
+ super(TestAgentsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = agents_client.AgentsClient(fake_auth,
+ 'compute', 'regionOne')
+
+ def _test_list_agents(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_agents,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"agents": []},
+ bytes_body)
+ self.check_service_client_function(
+ self.client.list_agents,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"agents": []},
+ bytes_body,
+ hypervisor="kvm")
+
+ def _test_create_agent(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_agent,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_AGENT,
+ bytes_body,
+ url="http://foo.com", hypervisor="kvm", md5hash="md5",
+ version="2", architecture="x86_64", os="linux")
+
+ def _test_delete_agent(self):
+ self.check_service_client_function(
+ self.client.delete_agent,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, agent_id="1")
+
+ def _test_update_agent(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_agent,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_AGENT,
+ bytes_body,
+ agent_id="1", url="http://foo.com", md5hash="md5", version="2")
+
+ def test_list_agents_with_str_body(self):
+ self._test_list_agents()
+
+ def test_list_agents_with_bytes_body(self):
+ self._test_list_agents(bytes_body=True)
+
+ def test_create_agent_with_str_body(self):
+ self._test_create_agent()
+
+ def test_create_agent_with_bytes_body(self):
+ self._test_create_agent(bytes_body=True)
+
+ def test_delete_agent(self):
+ self._test_delete_agent()
+
+ def test_update_agent_with_str_body(self):
+ self._test_update_agent()
+
+ def test_update_agent_with_bytes_body(self):
+ self._test_update_agent(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_aggregates_client.py b/tempest/tests/lib/services/compute/test_aggregates_client.py
new file mode 100644
index 0000000..a63380e
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_aggregates_client.py
@@ -0,0 +1,192 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import aggregates_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAggregatesClient(base.BaseComputeServiceTest):
+ FAKE_SHOW_AGGREGATE = {
+ "aggregate":
+ {
+ "name": "hoge",
+ "availability_zone": None,
+ "deleted": False,
+ "created_at":
+ "2015-07-16T03:07:32.000000",
+ "updated_at": None,
+ "hosts": [],
+ "deleted_at": None,
+ "id": 1,
+ "metadata": {}
+ }
+ }
+
+ FAKE_CREATE_AGGREGATE = {
+ "aggregate":
+ {
+ "name": u'\xf4',
+ "availability_zone": None,
+ "deleted": False,
+ "created_at": "2015-07-21T04:11:18.000000",
+ "updated_at": None,
+ "deleted_at": None,
+ "id": 1
+ }
+ }
+
+ FAKE_UPDATE_AGGREGATE = {
+ "aggregate":
+ {
+ "name": u'\xe9',
+ "availability_zone": None,
+ "deleted": False,
+ "created_at": "2015-07-16T03:07:32.000000",
+ "updated_at": "2015-07-23T05:16:29.000000",
+ "hosts": [],
+ "deleted_at": None,
+ "id": 1,
+ "metadata": {}
+ }
+ }
+
+ FAKE_AGGREGATE = {
+ "availability_zone": "nova",
+ "created_at": "2013-08-18T12:17:56.297823",
+ "deleted": False,
+ "deleted_at": None,
+ "hosts": [
+ "21549b2f665945baaa7101926a00143c"
+ ],
+ "id": 1,
+ "metadata": {
+ "availability_zone": "nova"
+ },
+ "name": u'\xe9',
+ "updated_at": None
+ }
+
+ FAKE_ADD_HOST = {'aggregate': FAKE_AGGREGATE}
+ FAKE_REMOVE_HOST = {'aggregate': FAKE_AGGREGATE}
+ FAKE_SET_METADATA = {'aggregate': FAKE_AGGREGATE}
+
+ def setUp(self):
+ super(TestAggregatesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = aggregates_client.AggregatesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_aggregates(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_aggregates,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"aggregates": []},
+ bytes_body)
+
+ def test_list_aggregates_with_str_body(self):
+ self._test_list_aggregates()
+
+ def test_list_aggregates_with_bytes_body(self):
+ self._test_list_aggregates(bytes_body=True)
+
+ def _test_show_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_AGGREGATE,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_show_aggregate_with_str_body(self):
+ self._test_show_aggregate()
+
+ def test_show_aggregate_with_bytes_body(self):
+ self._test_show_aggregate(bytes_body=True)
+
+ def _test_create_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_AGGREGATE,
+ bytes_body,
+ name='hoge')
+
+ def test_create_aggregate_with_str_body(self):
+ self._test_create_aggregate()
+
+ def test_create_aggregate_with_bytes_body(self):
+ self._test_create_aggregate(bytes_body=True)
+
+ def test_delete_aggregate(self):
+ self.check_service_client_function(
+ self.client.delete_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, aggregate_id="1")
+
+ def _test_update_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_AGGREGATE,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_update_aggregate_with_str_body(self):
+ self._test_update_aggregate()
+
+ def test_update_aggregate_with_bytes_body(self):
+ self._test_update_aggregate(bytes_body=True)
+
+ def _test_add_host(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_host,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_ADD_HOST,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_add_host_with_str_body(self):
+ self._test_add_host()
+
+ def test_add_host_with_bytes_body(self):
+ self._test_add_host(bytes_body=True)
+
+ def _test_remove_host(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.remove_host,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_REMOVE_HOST,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_remove_host_with_str_body(self):
+ self._test_remove_host()
+
+ def test_remove_host_with_bytes_body(self):
+ self._test_remove_host(bytes_body=True)
+
+ def _test_set_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SET_METADATA,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_set_metadata_with_str_body(self):
+ self._test_set_metadata()
+
+ def test_set_metadata_with_bytes_body(self):
+ self._test_set_metadata(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_availability_zone_client.py b/tempest/tests/lib/services/compute/test_availability_zone_client.py
new file mode 100644
index 0000000..d16cf0a
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_availability_zone_client.py
@@ -0,0 +1,51 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import availability_zone_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAvailabilityZoneClient(base.BaseComputeServiceTest):
+
+ FAKE_AVAILABIRITY_ZONE_INFO = {
+ "availabilityZoneInfo":
+ [
+ {
+ "zoneState": {
+ "available": True
+ },
+ "hosts": None,
+ "zoneName": u'\xf4'
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestAvailabilityZoneClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = availability_zone_client.AvailabilityZoneClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_availability_zones_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_availability_zones,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_AVAILABIRITY_ZONE_INFO)
+
+ def test_list_availability_zones_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_availability_zones,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_AVAILABIRITY_ZONE_INFO, to_utf=True)
diff --git a/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
new file mode 100644
index 0000000..a867c06
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
@@ -0,0 +1,74 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import baremetal_nodes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestBareMetalNodesClient(base.BaseComputeServiceTest):
+
+ FAKE_NODE_INFO = {'cpus': '8',
+ 'disk_gb': '64',
+ 'host': '10.0.2.15',
+ 'id': 'Identifier',
+ 'instance_uuid': "null",
+ 'interfaces': [
+ {
+ "address": "20::01",
+ "datapath_id": "null",
+ "id": 1,
+ "port_no": None
+ }
+ ],
+ 'memory_mb': '8192',
+ 'task_state': None}
+
+ def setUp(self):
+ super(TestBareMetalNodesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.baremetal_nodes_client = (baremetal_nodes_client.
+ BaremetalNodesClient
+ (fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_bareMetal_nodes(self, operation='list', bytes_body=False):
+ if operation != 'list':
+ expected = {"node": self.FAKE_NODE_INFO}
+ function = self.baremetal_nodes_client.show_baremetal_node
+ else:
+ node_info = copy.deepcopy(self.FAKE_NODE_INFO)
+ del node_info['instance_uuid']
+ expected = {"nodes": [node_info]}
+ function = self.baremetal_nodes_client.list_baremetal_nodes
+
+ self.check_service_client_function(
+ function,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body, 200,
+ baremetal_node_id='Identifier')
+
+ def test_list_bareMetal_nodes_with_str_body(self):
+ self._test_bareMetal_nodes()
+
+ def test_list_bareMetal_nodes_with_bytes_body(self):
+ self._test_bareMetal_nodes(bytes_body=True)
+
+ def test_show_bareMetal_node_with_str_body(self):
+ self._test_bareMetal_nodes('show')
+
+ def test_show_bareMetal_node_with_bytes_body(self):
+ self._test_bareMetal_nodes('show', True)
diff --git a/tempest/tests/lib/services/compute/test_base_compute_client.py b/tempest/tests/lib/services/compute/test_base_compute_client.py
new file mode 100644
index 0000000..49d29b3
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_base_compute_client.py
@@ -0,0 +1,206 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.lib.services.compute import base_compute_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services.compute import base
+
+
+class TestMicroversionHeaderCheck(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestMicroversionHeaderCheck, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_compute_client.BaseComputeClient(
+ fake_auth, 'compute', 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = '2.2'
+
+ def tearDown(self):
+ super(TestMicroversionHeaderCheck, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_correct_microverion_in_response(self, mock_request):
+ response = fake_http.fake_http_response(
+ headers={self.client.api_microversion_header_name: '2.2'},
+ )
+ mock_request.return_value = response, ''
+ self.client.get('fake_url')
+
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_incorrect_microverion_in_response(self, mock_request):
+ response = fake_http.fake_http_response(
+ headers={self.client.api_microversion_header_name: '2.3'},
+ )
+ mock_request.return_value = response, ''
+ self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+ self.client.get, 'fake_url')
+
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_no_microverion_header_in_response(self, mock_request):
+ response = fake_http.fake_http_response(
+ headers={},
+ )
+ mock_request.return_value = response, ''
+ self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+ self.client.get, 'fake_url')
+
+
+class DummyServiceClient1(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.1', 'schema': 'schemav21'},
+ {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'},
+ {'min': '2.10', 'max': None, 'schema': 'schemav210'}]
+
+ def return_selected_schema(self):
+ return self.get_schema(self.schema_versions_info)
+
+
+class TestSchemaVersionsNone(base.BaseComputeServiceTest):
+ api_microversion = None
+ expected_schema = 'schemav21'
+
+ def setUp(self):
+ super(TestSchemaVersionsNone, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = DummyServiceClient1(fake_auth, 'compute', 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = self.api_microversion
+
+ def tearDown(self):
+ super(TestSchemaVersionsNone, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ def test_schema(self):
+ self.assertEqual(self.expected_schema,
+ self.client.return_selected_schema())
+
+
+class TestSchemaVersionsV21(TestSchemaVersionsNone):
+ api_microversion = '2.1'
+ expected_schema = 'schemav21'
+
+
+class TestSchemaVersionsV22(TestSchemaVersionsNone):
+ api_microversion = '2.2'
+ expected_schema = 'schemav22'
+
+
+class TestSchemaVersionsV25(TestSchemaVersionsNone):
+ api_microversion = '2.5'
+ expected_schema = 'schemav22'
+
+
+class TestSchemaVersionsV29(TestSchemaVersionsNone):
+ api_microversion = '2.9'
+ expected_schema = 'schemav22'
+
+
+class TestSchemaVersionsV210(TestSchemaVersionsNone):
+ api_microversion = '2.10'
+ expected_schema = 'schemav210'
+
+
+class TestSchemaVersionsLatest(TestSchemaVersionsNone):
+ api_microversion = 'latest'
+ expected_schema = 'schemav210'
+
+
+class DummyServiceClient2(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.1', 'schema': 'schemav21'},
+ {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'}]
+
+ def return_selected_schema(self):
+ return self.get_schema(self.schema_versions_info)
+
+
+class TestSchemaVersionsNotFound(base.BaseComputeServiceTest):
+ api_microversion = '2.10'
+ expected_schema = 'schemav210'
+
+ def setUp(self):
+ super(TestSchemaVersionsNotFound, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = DummyServiceClient2(fake_auth, 'compute', 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = self.api_microversion
+
+ def tearDown(self):
+ super(TestSchemaVersionsNotFound, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ def test_schema(self):
+ self.assertRaises(exceptions.JSONSchemaNotFound,
+ self.client.return_selected_schema)
+
+
+class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestClientWithoutMicroversionHeader, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_compute_client.BaseComputeClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_no_microverion_header(self):
+ header = self.client.get_headers()
+ self.assertNotIn('X-OpenStack-Nova-API-Version', header)
+
+ def test_no_microverion_header_in_raw_request(self):
+ def raw_request(*args, **kwargs):
+ self.assertNotIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
+ return (fake_http.fake_http_response({}, status=200), '')
+
+ with mock.patch.object(rest_client.RestClient,
+ 'raw_request') as mock_get:
+ mock_get.side_effect = raw_request
+ self.client.get('fake_url')
+
+
+class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestClientWithMicroversionHeader, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_compute_client.BaseComputeClient(
+ fake_auth, 'compute', 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = '2.2'
+
+ def tearDown(self):
+ super(TestClientWithMicroversionHeader, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ def test_microverion_header(self):
+ header = self.client.get_headers()
+ self.assertIn('X-OpenStack-Nova-API-Version', header)
+ self.assertEqual('2.2',
+ header['X-OpenStack-Nova-API-Version'])
+
+ def test_microverion_header_in_raw_request(self):
+ def raw_request(*args, **kwargs):
+ self.assertIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
+ self.assertEqual('2.2',
+ kwargs['headers']['X-OpenStack-Nova-API-Version'])
+ return (fake_http.fake_http_response(
+ headers={self.client.api_microversion_header_name: '2.2'},
+ status=200), '')
+
+ with mock.patch.object(rest_client.RestClient,
+ 'raw_request') as mock_get:
+ mock_get.side_effect = raw_request
+ self.client.get('fake_url')
diff --git a/tempest/tests/lib/services/compute/test_certificates_client.py b/tempest/tests/lib/services/compute/test_certificates_client.py
new file mode 100644
index 0000000..e8123bb
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_certificates_client.py
@@ -0,0 +1,64 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import certificates_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestCertificatesClient(base.BaseComputeServiceTest):
+
+ FAKE_CERTIFICATE = {
+ "certificate": {
+ "data": "-----BEGIN----MIICyzCCAjSgAwI----END CERTIFICATE-----\n",
+ "private_key": None
+ }
+ }
+
+ def setUp(self):
+ super(TestCertificatesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = certificates_client.CertificatesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_certificate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_certificate,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CERTIFICATE,
+ bytes_body,
+ certificate_id="fake-id")
+
+ def test_show_certificate_with_str_body(self):
+ self._test_show_certificate()
+
+ def test_show_certificate_with_bytes_body(self):
+ self._test_show_certificate(bytes_body=True)
+
+ def _test_create_certificate(self, bytes_body=False):
+ cert = copy.deepcopy(self.FAKE_CERTIFICATE)
+ cert['certificate']['private_key'] = "my_private_key"
+ self.check_service_client_function(
+ self.client.create_certificate,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ cert,
+ bytes_body)
+
+ def test_create_certificate_with_str_body(self):
+ self._test_create_certificate()
+
+ def test_create_certificate_with_bytes_body(self):
+ self._test_create_certificate(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_extensions_client.py b/tempest/tests/lib/services/compute/test_extensions_client.py
new file mode 100644
index 0000000..7415988
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_extensions_client.py
@@ -0,0 +1,65 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import extensions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestExtensionsClient(base.BaseComputeServiceTest):
+
+ FAKE_SHOW_EXTENSION = {
+ "extension": {
+ "updated": "2011-06-09T00:00:00Z",
+ "name": "Multinic",
+ "links": [],
+ "namespace":
+ "http://docs.openstack.org/compute/ext/multinic/api/v1.1",
+ "alias": "NMN",
+ "description": u'\u2740(*\xb4\u25e1`*)\u2740'
+ }
+ }
+
+ def setUp(self):
+ super(TestExtensionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = extensions_client.ExtensionsClient(
+ fake_auth, 'compute', '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',
+ {"extensions": []},
+ 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)
+
+ def _test_show_extension(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_extension,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_EXTENSION,
+ bytes_body,
+ extension_alias="NMN")
+
+ 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/compute/test_fixedIPs_client.py b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
new file mode 100644
index 0000000..6999f24
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
@@ -0,0 +1,58 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import fixed_ips_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFixedIPsClient(base.BaseComputeServiceTest):
+ FIXED_IP_INFO = {"fixed_ip": {"address": "10.0.0.1",
+ "cidr": "10.11.12.0/24",
+ "host": "localhost",
+ "hostname": "OpenStack"}}
+
+ def setUp(self):
+ super(TestFixedIPsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.fixedIPsClient = (fixed_ips_client.
+ FixedIPsClient
+ (fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_show_fixed_ip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.fixedIPsClient.show_fixed_ip,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FIXED_IP_INFO, bytes_body,
+ status=200, fixed_ip='Identifier')
+
+ def test_show_fixed_ip_with_str_body(self):
+ self._test_show_fixed_ip()
+
+ def test_show_fixed_ip_with_bytes_body(self):
+ self._test_show_fixed_ip(True)
+
+ def _test_reserve_fixed_ip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.fixedIPsClient.reserve_fixed_ip,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, bytes_body,
+ status=202, fixed_ip='Identifier')
+
+ def test_reserve_fixed_ip_with_str_body(self):
+ self._test_reserve_fixed_ip()
+
+ def test_reserve_fixed_ip_with_bytes_body(self):
+ self._test_reserve_fixed_ip(True)
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
new file mode 100644
index 0000000..e22b4fe
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -0,0 +1,255 @@
+# Copyright 2015 IBM Corp.
+#
+# 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 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
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services.compute import base
+
+
+class TestFlavorsClient(base.BaseComputeServiceTest):
+
+ FAKE_FLAVOR = {
+ "disk": 1,
+ "id": "1",
+ "links": [{
+ "href": "http://openstack.example.com/v2/openstack/flavors/1",
+ "rel": "self"}, {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"}],
+ "name": "m1.tiny",
+ "ram": 512,
+ "swap": 1,
+ "vcpus": 1
+ }
+
+ EXTRA_SPECS = {"extra_specs": {
+ "key1": "value1",
+ "key2": "value2"}
+ }
+
+ FAKE_FLAVOR_ACCESS = {
+ "flavor_id": "10",
+ "tenant_id": "1a951d988e264818afe520e78697dcbf"
+ }
+
+ def setUp(self):
+ super(TestFlavorsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = flavors_client.FlavorsClient(fake_auth,
+ 'compute', 'regionOne')
+
+ def _test_list_flavors(self, bytes_body=False):
+ flavor = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+ # Remove extra attributes
+ for attribute in ('disk', 'vcpus', 'ram', 'swap'):
+ del flavor[attribute]
+ expected = {'flavors': [flavor]}
+ self.check_service_client_function(
+ self.client.list_flavors,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body)
+
+ def test_list_flavors_str_body(self):
+ self._test_list_flavors(bytes_body=False)
+
+ def test_list_flavors_byte_body(self):
+ self._test_list_flavors(bytes_body=True)
+
+ def _test_show_flavor(self, bytes_body=False):
+ expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+ self.check_service_client_function(
+ self.client.show_flavor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='fake-id')
+
+ def test_show_flavor_str_body(self):
+ self._test_show_flavor(bytes_body=False)
+
+ def test_show_flavor_byte_body(self):
+ self._test_show_flavor(bytes_body=True)
+
+ def _test_create_flavor(self, bytes_body=False):
+ expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+ request = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+ # The 'links' parameter should not be passed in
+ del request['links']
+ self.check_service_client_function(
+ self.client.create_flavor,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ **request)
+
+ def test_create_flavor_str_body(self):
+ self._test_create_flavor(bytes_body=False)
+
+ def test_create_flavor__byte_body(self):
+ self._test_create_flavor(bytes_body=True)
+
+ def test_delete_flavor(self):
+ self.check_service_client_function(
+ self.client.delete_flavor,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def _test_is_resource_deleted(self, flavor_id, is_deleted=True,
+ bytes_body=False):
+ body = json.dumps({'flavors': [TestFlavorsClient.FAKE_FLAVOR]})
+ if bytes_body:
+ body = body.encode('utf-8')
+ response = fake_http.fake_http_response({}, status=200), body
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.rest_client.RestClient.get',
+ return_value=response))
+ self.assertEqual(is_deleted,
+ self.client.is_resource_deleted(flavor_id))
+
+ def test_is_resource_deleted_true_str_body(self):
+ self._test_is_resource_deleted('2', bytes_body=False)
+
+ def test_is_resource_deleted_true_byte_body(self):
+ self._test_is_resource_deleted('2', bytes_body=True)
+
+ def test_is_resource_deleted_false_str_body(self):
+ self._test_is_resource_deleted('1', is_deleted=False, bytes_body=False)
+
+ def test_is_resource_deleted_false_byte_body(self):
+ self._test_is_resource_deleted('1', is_deleted=False, bytes_body=True)
+
+ def _test_set_flavor_extra_spec(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ TestFlavorsClient.EXTRA_SPECS,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ **TestFlavorsClient.EXTRA_SPECS)
+
+ def test_set_flavor_extra_spec_str_body(self):
+ self._test_set_flavor_extra_spec(bytes_body=False)
+
+ def test_set_flavor_extra_spec_byte_body(self):
+ self._test_set_flavor_extra_spec(bytes_body=True)
+
+ def _test_list_flavor_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_flavor_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ TestFlavorsClient.EXTRA_SPECS,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+ def test_list_flavor_extra_specs_str_body(self):
+ self._test_list_flavor_extra_specs(bytes_body=False)
+
+ def test_list_flavor_extra_specs__byte_body(self):
+ self._test_list_flavor_extra_specs(bytes_body=True)
+
+ def _test_show_flavor_extra_spec(self, bytes_body=False):
+ expected = {"key": "value"}
+ self.check_service_client_function(
+ self.client.show_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ key='key')
+
+ def test_show_flavor_extra_spec_str_body(self):
+ self._test_show_flavor_extra_spec(bytes_body=False)
+
+ def test_show_flavor_extra_spec__byte_body(self):
+ self._test_show_flavor_extra_spec(bytes_body=True)
+
+ def _test_update_flavor_extra_spec(self, bytes_body=False):
+ expected = {"key1": "value"}
+ self.check_service_client_function(
+ self.client.update_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ key='key1', **expected)
+
+ def test_update_flavor_extra_spec_str_body(self):
+ self._test_update_flavor_extra_spec(bytes_body=False)
+
+ def test_update_flavor_extra_spec_byte_body(self):
+ self._test_update_flavor_extra_spec(bytes_body=True)
+
+ def test_unset_flavor_extra_spec(self):
+ self.check_service_client_function(
+ self.client.unset_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.delete', {},
+ flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b', key='key')
+
+ def _test_list_flavor_access(self, bytes_body=False):
+ expected = {'flavor_access': [TestFlavorsClient.FAKE_FLAVOR_ACCESS]}
+ self.check_service_client_function(
+ self.client.list_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+ def test_list_flavor_access_str_body(self):
+ self._test_list_flavor_access(bytes_body=False)
+
+ def test_list_flavor_access_byte_body(self):
+ self._test_list_flavor_access(bytes_body=True)
+
+ def _test_add_flavor_access(self, bytes_body=False):
+ expected = {
+ "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+ }
+ self.check_service_client_function(
+ self.client.add_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ tenant_id='1a951d988e264818afe520e78697dcbf')
+
+ def test_add_flavor_access_str_body(self):
+ self._test_add_flavor_access(bytes_body=False)
+
+ def test_add_flavor_access_byte_body(self):
+ self._test_add_flavor_access(bytes_body=True)
+
+ def _test_remove_flavor_access(self, bytes_body=False):
+ expected = {
+ "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+ }
+ self.check_service_client_function(
+ self.client.remove_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ flavor_id='10',
+ tenant_id='a6edd4d66ad04245b5d2d8716ecc91e3')
+
+ def test_remove_flavor_access_str_body(self):
+ self._test_remove_flavor_access(bytes_body=False)
+
+ def test_remove_flavor_access_byte_body(self):
+ self._test_remove_flavor_access(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
new file mode 100644
index 0000000..f30719e
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
@@ -0,0 +1,46 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import floating_ip_pools_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIPPoolsClient(base.BaseComputeServiceTest):
+
+ FAKE_FLOATING_IP_POOLS = {
+ "floating_ip_pools":
+ [
+ {"name": u'\u3042'},
+ {"name": u'\u3044'}
+ ]
+ }
+
+ def setUp(self):
+ super(TestFloatingIPPoolsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ip_pools_client.FloatingIPPoolsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_floating_ip_pools_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_floating_ip_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FLOATING_IP_POOLS)
+
+ def test_list_floating_ip_pools_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_floating_ip_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FLOATING_IP_POOLS, to_utf=True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
new file mode 100644
index 0000000..c16c985
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
@@ -0,0 +1,88 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.tests.lib import fake_auth_provider
+
+from tempest.lib.services.compute import floating_ips_bulk_client
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIPsBulkClient(base.BaseComputeServiceTest):
+
+ FAKE_FIP_BULK_LIST = {"floating_ip_info": [{
+ "address": "10.10.10.1",
+ "instance_uuid": None,
+ "fixed_ip": None,
+ "interface": "eth0",
+ "pool": "nova",
+ "project_id": None
+ },
+ {
+ "address": "10.10.10.2",
+ "instance_uuid": None,
+ "fixed_ip": None,
+ "interface": "eth0",
+ "pool": "nova",
+ "project_id": None
+ }]}
+
+ def setUp(self):
+ super(TestFloatingIPsBulkClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ips_bulk_client.FloatingIPsBulkClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_floating_ips_bulk(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FIP_BULK_LIST,
+ to_utf=bytes_body)
+
+ def _test_create_floating_ips_bulk(self, bytes_body=False):
+ fake_fip_create_data = {"floating_ips_bulk_create": {
+ "ip_range": "192.168.1.0/24", "pool": "nova", "interface": "eth0"}}
+ self.check_service_client_function(
+ self.client.create_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ fake_fip_create_data,
+ to_utf=bytes_body,
+ ip_range="192.168.1.0/24", pool="nova", interface="eth0")
+
+ def _test_delete_floating_ips_bulk(self, bytes_body=False):
+ fake_fip_delete_data = {"floating_ips_bulk_delete": "192.168.1.0/24"}
+ self.check_service_client_function(
+ self.client.delete_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_fip_delete_data,
+ to_utf=bytes_body,
+ ip_range="192.168.1.0/24")
+
+ def test_list_floating_ips_bulk_with_str_body(self):
+ self._test_list_floating_ips_bulk()
+
+ def test_list_floating_ips_bulk_with_bytes_body(self):
+ self._test_list_floating_ips_bulk(True)
+
+ def test_create_floating_ips_bulk_with_str_body(self):
+ self._test_create_floating_ips_bulk()
+
+ def test_create_floating_ips_bulk_with_bytes_body(self):
+ self._test_create_floating_ips_bulk(True)
+
+ def test_delete_floating_ips_bulk_with_str_body(self):
+ self._test_delete_floating_ips_bulk()
+
+ def test_delete_floating_ips_bulk_with_bytes_body(self):
+ self._test_delete_floating_ips_bulk(True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_client.py b/tempest/tests/lib/services/compute/test_floating_ips_client.py
new file mode 100644
index 0000000..3844ba8
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ips_client.py
@@ -0,0 +1,113 @@
+# Copyright 2015 IBM Corp.
+#
+# 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 oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import floating_ips_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIpsClient(base.BaseComputeServiceTest):
+
+ floating_ip = {"fixed_ip": None,
+ "id": "46d61064-13ba-4bf0-9557-69de824c3d6f",
+ "instance_id": "a1daa443-a6bb-463e-aea2-104b7d912eb8",
+ "ip": "10.10.10.1",
+ "pool": "nova"}
+
+ def setUp(self):
+ super(TestFloatingIpsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ips_client.FloatingIPsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_floating_ips(self, bytes_body=False):
+ expected = {'floating_ips': [TestFloatingIpsClient.floating_ip]}
+ self.check_service_client_function(
+ self.client.list_floating_ips,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body)
+
+ def test_list_floating_ips_str_body(self):
+ self._test_list_floating_ips(bytes_body=False)
+
+ def test_list_floating_ips_byte_body(self):
+ self._test_list_floating_ips(bytes_body=True)
+
+ def _test_show_floating_ip(self, bytes_body=False):
+ expected = {"floating_ip": TestFloatingIpsClient.floating_ip}
+ self.check_service_client_function(
+ self.client.show_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ floating_ip_id='a1daa443-a6bb-463e-aea2-104b7d912eb8')
+
+ def test_show_floating_ip_str_body(self):
+ self._test_show_floating_ip(bytes_body=False)
+
+ def test_show_floating_ip_byte_body(self):
+ self._test_show_floating_ip(bytes_body=True)
+
+ def _test_create_floating_ip(self, bytes_body=False):
+ expected = {"floating_ip": TestFloatingIpsClient.floating_ip}
+ self.check_service_client_function(
+ self.client.create_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ pool_name='nova')
+
+ def test_create_floating_ip_str_body(self):
+ self._test_create_floating_ip(bytes_body=False)
+
+ def test_create_floating_ip_byte_body(self):
+ self._test_create_floating_ip(bytes_body=True)
+
+ def test_delete_floating_ip(self):
+ self.check_service_client_function(
+ self.client.delete_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, floating_ip_id='fake-id')
+
+ def test_associate_floating_ip_to_server(self):
+ self.check_service_client_function(
+ self.client.associate_floating_ip_to_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202, floating_ip='10.10.10.1',
+ server_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def test_disassociate_floating_ip_from_server(self):
+ self.check_service_client_function(
+ self.client.disassociate_floating_ip_from_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202, floating_ip='10.10.10.1',
+ server_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def test_is_resource_deleted_true(self):
+ self.useFixture(mockpatch.Patch(
+ '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(
+ 'tempest.lib.services.compute.floating_ips_client.'
+ 'FloatingIPsClient.show_floating_ip',
+ return_value={"floating_ip": TestFloatingIpsClient.floating_ip}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_hosts_client.py b/tempest/tests/lib/services/compute/test_hosts_client.py
new file mode 100644
index 0000000..d9ff513
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_hosts_client.py
@@ -0,0 +1,147 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import hosts_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestHostsClient(base.BaseComputeServiceTest):
+ FAKE_HOST_DATA = {
+ "host": {
+ "resource": {
+ "cpu": 1,
+ "disk_gb": 1028,
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "memory_mb": 8192,
+ "project": "(total)"
+ }
+ },
+ "hosts": {
+ "host_name": "c1a7de0ac9d94e4baceae031d05caae3",
+ "service": "conductor",
+ "zone": "internal"
+ },
+ "enable_hosts": {
+ "host": "65c5d5b7e3bd44308e67fc50f362aee6",
+ "maintenance_mode": "off_maintenance",
+ "status": "enabled"
+ }
+ }
+
+ FAKE_CONTROL_DATA = {
+ "shutdown": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "shutdown"
+ },
+ "startup": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "startup"
+ },
+ "reboot": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "reboot"
+ }}
+
+ HOST_DATA = {'host': [FAKE_HOST_DATA['host']]}
+ HOSTS_DATA = {'hosts': [FAKE_HOST_DATA['hosts']]}
+ ENABLE_HOST_DATA = FAKE_HOST_DATA['enable_hosts']
+ HOST_ID = "c1a7de0ac9d94e4baceae031d05caae3"
+ TEST_HOST_DATA = {
+ "status": "enable",
+ "maintenance_mode": "disable"
+ }
+
+ def setUp(self):
+ super(TestHostsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = hosts_client.HostsClient(fake_auth, 'compute',
+ 'regionOne')
+ self.params = {'hostname': self.HOST_ID}
+ self.func2mock = {
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'put': 'tempest.lib.common.rest_client.RestClient.put'}
+
+ def _test_host_data(self, test_type='list', bytes_body=False):
+ expected_resp = self.HOST_DATA
+ if test_type != 'list':
+ function_call = self.client.show_host
+ else:
+ expected_resp = self.HOSTS_DATA
+ function_call = self.client.list_hosts
+ self.params = {'host_name': self.HOST_ID}
+
+ self.check_service_client_function(
+ function_call, self.func2mock['get'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def _test_update_hosts(self, bytes_body=False):
+ expected_resp = self.ENABLE_HOST_DATA
+ self.check_service_client_function(
+ self.client.update_host, self.func2mock['put'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def _test_control_host(self, control_op='reboot', bytes_body=False):
+ if control_op == 'start':
+ expected_resp = self.FAKE_CONTROL_DATA['startup']
+ function_call = self.client.startup_host
+ elif control_op == 'stop':
+ expected_resp = self.FAKE_CONTROL_DATA['shutdown']
+ function_call = self.client.shutdown_host
+ else:
+ expected_resp = self.FAKE_CONTROL_DATA['reboot']
+ function_call = self.client.reboot_host
+
+ self.check_service_client_function(
+ function_call, self.func2mock['get'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def test_show_host_with_str_body(self):
+ self._test_host_data('show')
+
+ def test_show_host_with_bytes_body(self):
+ self._test_host_data('show', True)
+
+ def test_list_host_with_str_body(self):
+ self._test_host_data()
+
+ def test_list_host_with_bytes_body(self):
+ self._test_host_data(bytes_body=True)
+
+ def test_start_host_with_str_body(self):
+ self._test_control_host('start')
+
+ def test_start_host_with_bytes_body(self):
+ self._test_control_host('start', True)
+
+ def test_stop_host_with_str_body(self):
+ self._test_control_host('stop')
+
+ def test_stop_host_with_bytes_body(self):
+ self._test_control_host('stop', True)
+
+ def test_reboot_host_with_str_body(self):
+ self._test_control_host('reboot')
+
+ def test_reboot_host_with_bytes_body(self):
+ self._test_control_host('reboot', True)
+
+ def test_update_host_with_str_body(self):
+ self._test_update_hosts()
+
+ def test_update_host_with_bytes_body(self):
+ self._test_update_hosts(True)
diff --git a/tempest/tests/lib/services/compute/test_hypervisor_client.py b/tempest/tests/lib/services/compute/test_hypervisor_client.py
new file mode 100644
index 0000000..fab34da
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_hypervisor_client.py
@@ -0,0 +1,167 @@
+# Copyright 2015 IBM Corp.
+#
+# 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.compute import hypervisor_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestHypervisorClient(base.BaseComputeServiceTest):
+
+ hypervisor_id = "1"
+ hypervisor_name = "hyper.hostname.com"
+
+ def setUp(self):
+ super(TestHypervisorClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = hypervisor_client.HypervisorClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_hypervisor_str_body(self):
+ self._test_list_hypervisor(bytes_body=False)
+
+ def test_list_hypervisor_byte_body(self):
+ self._test_list_hypervisor(bytes_body=True)
+
+ def _test_list_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 1,
+ "hypervisor_hostname": "hypervisor1.hostname.com"},
+ {
+ "id": 2,
+ "hypervisor_hostname": "hypervisor2.hostname.com"}]}
+ self.check_service_client_function(
+ self.client.list_hypervisors,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_show_hypervisor_str_body(self):
+ self._test_show_hypervisor(bytes_body=False)
+
+ def test_show_hypervisor_byte_body(self):
+ self._test_show_hypervisor(bytes_body=True)
+
+ def _test_show_hypervisor(self, bytes_body=False):
+ expected = {"hypervisor": {
+ "cpu_info": "?",
+ "current_workload": 0,
+ "disk_available_least": 1,
+ "host_ip": "10.10.10.10",
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "hypervisor_hostname": "fake-mini",
+ "hypervisor_type": "fake",
+ "hypervisor_version": 1,
+ "id": 1,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "service": {
+ "host": "fake_host",
+ "id": 2},
+ "vcpus": 1,
+ "vcpus_used": 0}}
+ self.check_service_client_function(
+ self.client.show_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_id=self.hypervisor_id)
+
+ def test_list_servers_on_hypervisor_str_body(self):
+ self._test_list_servers_on_hypervisor(bytes_body=False)
+
+ def test_list_servers_on_hypervisor_byte_body(self):
+ self._test_list_servers_on_hypervisor(bytes_body=True)
+
+ def _test_list_servers_on_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 1,
+ "hypervisor_hostname": "hyper.hostname.com",
+ "servers": [{
+ "uuid": "e1ae8fc4-b72d-4c2f-a427-30dd420b6277",
+ "name": "instance-00000001"},
+ {
+ "uuid": "e1ae8fc4-b72d-4c2f-a427-30dd42066666",
+ "name": "instance-00000002"}
+ ]}
+ ]}
+ self.check_service_client_function(
+ self.client.list_servers_on_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_name=self.hypervisor_name)
+
+ def test_show_hypervisor_statistics_str_body(self):
+ self._test_show_hypervisor_statistics(bytes_body=False)
+
+ def test_show_hypervisor_statistics_byte_body(self):
+ self._test_show_hypervisor_statistics(bytes_body=True)
+
+ def _test_show_hypervisor_statistics(self, bytes_body=False):
+ expected = {
+ "hypervisor_statistics": {
+ "count": 1,
+ "current_workload": 0,
+ "disk_available_least": 0,
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "vcpus": 1,
+ "vcpus_used": 0}}
+ self.check_service_client_function(
+ self.client.show_hypervisor_statistics,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_show_hypervisor_uptime_str_body(self):
+ self._test_show_hypervisor_uptime(bytes_body=False)
+
+ def test_show_hypervisor_uptime_byte_body(self):
+ self._test_show_hypervisor_uptime(bytes_body=True)
+
+ def _test_show_hypervisor_uptime(self, bytes_body=False):
+ expected = {
+ "hypervisor": {
+ "hypervisor_hostname": "fake-mini",
+ "id": 1,
+ "uptime": (" 08:32:11 up 93 days, 18:25, 12 users, "
+ " load average: 0.20, 0.12, 0.14")
+ }}
+ self.check_service_client_function(
+ self.client.show_hypervisor_uptime,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_id=self.hypervisor_id)
+
+ def test_search_hypervisor_str_body(self):
+ self._test_search_hypervisor(bytes_body=False)
+
+ def test_search_hypervisor_byte_body(self):
+ self._test_search_hypervisor(bytes_body=True)
+
+ def _test_search_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 2,
+ "hypervisor_hostname": "hyper.hostname.com"}]}
+ self.check_service_client_function(
+ self.client.search_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_name=self.hypervisor_name)
diff --git a/tempest/tests/lib/services/compute/test_images_client.py b/tempest/tests/lib/services/compute/test_images_client.py
new file mode 100644
index 0000000..3ebc27f
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_images_client.py
@@ -0,0 +1,265 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import images_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestImagesClient(base.BaseComputeServiceTest):
+ # Data Dictionaries used for testing #
+ FAKE_IMAGE_METADATA = {
+ "list":
+ {"metadata": {
+ "auto_disk_config": "True",
+ "Label": "Changed"
+ }},
+ "set_item":
+ {"meta": {
+ "auto_disk_config": "True"
+ }},
+ "show_item":
+ {"meta": {
+ "kernel_id": "nokernel",
+ }},
+ "update":
+ {"metadata": {
+ "kernel_id": "False",
+ "Label": "UpdatedImage"
+ }},
+ "set":
+ {"metadata": {
+ "Label": "Changed",
+ "auto_disk_config": "True"
+ }},
+ "delete_item": {}
+ }
+
+ FAKE_IMAGE_DATA = {
+ "list":
+ {"images": [
+ {"id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {"href": "http://openstack.example.com/v2/openstack" +
+ "/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "self"
+ }
+ ],
+ "name": "fakeimage7"
+ }]},
+ "show": {"image": {
+ "created": "2011-01-01T01:02:03Z",
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack" +
+ "/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "self"
+ },
+ ],
+ "metadata": {
+ "architecture": "x86_64",
+ "auto_disk_config": "True",
+ "kernel_id": "nokernel",
+ "ramdisk_id": "nokernel"
+ },
+ "minDisk": 0,
+ "minRam": 0,
+ "name": "fakeimage7",
+ "progress": 100,
+ "status": "ACTIVE",
+ "updated": "2011-01-01T01:02:03Z"}},
+ "create": {},
+ "delete": {}
+ }
+ func2mock = {
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'post': 'tempest.lib.common.rest_client.RestClient.post',
+ 'put': 'tempest.lib.common.rest_client.RestClient.put',
+ 'delete': 'tempest.lib.common.rest_client.RestClient.delete'}
+ # Variable definition
+ FAKE_IMAGE_ID = FAKE_IMAGE_DATA['show']['image']['id']
+ FAKE_SERVER_ID = "80a599e0-31e7-49b7-b260-868f441e343f"
+ FAKE_CREATE_INFO = {'location': 'None'}
+ FAKE_METADATA = FAKE_IMAGE_METADATA['show_item']['meta']
+
+ def setUp(self):
+ super(TestImagesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = images_client.ImagesClient(fake_auth,
+ "compute", "regionOne")
+
+ def _test_image_operation(self, operation="delete", bytes_body=False):
+ response_code = 200
+ mock_operation = self.func2mock['get']
+ expected_op = self.FAKE_IMAGE_DATA[operation]
+ params = {"image_id": self.FAKE_IMAGE_ID}
+ headers = None
+ if operation == 'list':
+ function = self.client.list_images
+ elif operation == 'show':
+ function = self.client.show_image
+ elif operation == 'create':
+ function = self.client.create_image
+ mock_operation = self.func2mock['post']
+ params = {"server_id": self.FAKE_SERVER_ID}
+ response_code = 202
+ headers = {
+ 'connection': 'keep-alive',
+ 'content-length': '0',
+ 'content-type': 'application/json',
+ 'status': '202',
+ 'x-compute-request-id': 'req-fake',
+ 'vary': 'accept-encoding',
+ 'x-openstack-nova-api-version': 'v2.1',
+ 'date': '13 Oct 2015 05:55:36 GMT',
+ 'location': 'http://fake.com/images/fake'
+ }
+ else:
+ function = self.client.delete_image
+ mock_operation = self.func2mock['delete']
+ response_code = 204
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, headers, **params)
+
+ def _test_image_metadata(self, operation="set_item", bytes_body=False):
+ response_code = 200
+ expected_op = self.FAKE_IMAGE_METADATA[operation]
+ if operation == 'list':
+ function = self.client.list_image_metadata
+ mock_operation = self.func2mock['get']
+ params = {"image_id": self.FAKE_IMAGE_ID}
+
+ elif operation == 'set':
+ function = self.client.set_image_metadata
+ mock_operation = self.func2mock['put']
+ params = {"image_id": "_dummy_data",
+ "meta": self.FAKE_METADATA}
+
+ elif operation == 'update':
+ function = self.client.update_image_metadata
+ mock_operation = self.func2mock['post']
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "meta": self.FAKE_METADATA}
+
+ elif operation == 'show_item':
+ mock_operation = self.func2mock['get']
+ function = self.client.show_image_metadata_item
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123"}
+
+ elif operation == 'delete_item':
+ function = self.client.delete_image_metadata_item
+ mock_operation = self.func2mock['delete']
+ response_code = 204
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123"}
+
+ else:
+ function = self.client.set_image_metadata_item
+ mock_operation = self.func2mock['put']
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123",
+ "meta": self.FAKE_METADATA}
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, **params)
+
+ 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'
+ '.images_client.ImagesClient.show_image',
+ 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'
+ '.images_client.ImagesClient.show_image',
+ return_value=expected_op))
+ self.assertEqual(False, self.client.is_resource_deleted(**params))
+
+ def test_list_images_with_str_body(self):
+ self._test_image_operation('list')
+
+ def test_list_images_with_bytes_body(self):
+ self._test_image_operation('list', True)
+
+ def test_show_image_with_str_body(self):
+ self._test_image_operation('show')
+
+ def test_show_image_with_bytes_body(self):
+ self._test_image_operation('show', True)
+
+ def test_create_image_with_str_body(self):
+ self._test_image_operation('create')
+
+ def test_create_image_with_bytes_body(self):
+ self._test_image_operation('create', True)
+
+ def test_delete_image_with_str_body(self):
+ self._test_image_operation('delete')
+
+ def test_delete_image_with_bytes_body(self):
+ self._test_image_operation('delete', True)
+
+ def test_list_image_metadata_with_str_body(self):
+ self._test_image_metadata('list')
+
+ def test_list_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('list', True)
+
+ def test_set_image_metadata_with_str_body(self):
+ self._test_image_metadata('set')
+
+ def test_set_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('set', True)
+
+ def test_update_image_metadata_with_str_body(self):
+ self._test_image_metadata('update')
+
+ def test_update_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('update', True)
+
+ def test_set_image_metadata_item_with_str_body(self):
+ self._test_image_metadata()
+
+ def test_set_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata(bytes_body=True)
+
+ def test_show_image_metadata_item_with_str_body(self):
+ self._test_image_metadata('show_item')
+
+ def test_show_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata('show_item', True)
+
+ def test_delete_image_metadata_item_with_str_body(self):
+ self._test_image_metadata('delete_item')
+
+ def test_delete_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata('delete_item', True)
+
+ def test_resource_delete_with_str_body(self):
+ self._test_resource_deleted()
+
+ def test_resource_delete_with_bytes_body(self):
+ self._test_resource_deleted(True)
diff --git a/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py b/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py
new file mode 100644
index 0000000..e8c22f1
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py
@@ -0,0 +1,73 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.lib.services.compute import instance_usage_audit_log_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestInstanceUsagesAuditLogClient(base.BaseComputeServiceTest):
+
+ FAKE_AUDIT_LOG = {
+ "hosts_not_run": [
+ "f4eb7cfd155f4574967f8b55a7faed75"
+ ],
+ "log": {},
+ "num_hosts": 1,
+ "num_hosts_done": 0,
+ "num_hosts_not_run": 1,
+ "num_hosts_running": 0,
+ "overall_status": "0 of 1 hosts done. 0 errors.",
+ "period_beginning": "2012-12-01 00:00:00",
+ "period_ending": "2013-01-01 00:00:00",
+ "total_errors": 0,
+ "total_instances": 0
+ }
+
+ def setUp(self):
+ super(TestInstanceUsagesAuditLogClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = (instance_usage_audit_log_client.
+ InstanceUsagesAuditLogClient(fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_list_instance_usage_audit_logs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_instance_usage_audit_logs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"instance_usage_audit_logs": self.FAKE_AUDIT_LOG},
+ bytes_body)
+
+ def test_list_instance_usage_audit_logs_with_str_body(self):
+ self._test_list_instance_usage_audit_logs()
+
+ def test_list_instance_usage_audit_logs_with_bytes_body(self):
+ self._test_list_instance_usage_audit_logs(bytes_body=True)
+
+ def _test_show_instance_usage_audit_log(self, bytes_body=False):
+ before_time = datetime.datetime(2012, 12, 1, 0, 0)
+ self.check_service_client_function(
+ self.client.show_instance_usage_audit_log,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"instance_usage_audit_log": self.FAKE_AUDIT_LOG},
+ bytes_body,
+ time_before=before_time)
+
+ def test_show_instance_usage_audit_log_with_str_body(self):
+ self._test_show_instance_usage_audit_log()
+
+ def test_show_network_with_bytes_body_with_bytes_body(self):
+ self._test_show_instance_usage_audit_log(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_interfaces_client.py b/tempest/tests/lib/services/compute/test_interfaces_client.py
new file mode 100644
index 0000000..de8e268
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_interfaces_client.py
@@ -0,0 +1,98 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import interfaces_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestInterfacesClient(base.BaseComputeServiceTest):
+ # Data Values to be used for testing #
+ FAKE_INTERFACE_DATA = {
+ "fixed_ips": [{
+ "ip_address": "192.168.1.1",
+ "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
+ }],
+ "mac_addr": "fa:16:3e:4c:2c:30",
+ "net_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
+ "port_id": "ce531f90-199f-48c0-816c-13e38010b442",
+ "port_state": "ACTIVE"}
+
+ FAKE_SHOW_DATA = {
+ "interfaceAttachment": FAKE_INTERFACE_DATA}
+ FAKE_LIST_DATA = {
+ "interfaceAttachments": [FAKE_INTERFACE_DATA]}
+
+ FAKE_SERVER_ID = "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5"
+ FAKE_PORT_ID = FAKE_SHOW_DATA['interfaceAttachment']['port_id']
+ func2mock = {
+ 'delete': 'tempest.lib.common.rest_client.RestClient.delete',
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'post': 'tempest.lib.common.rest_client.RestClient.post'}
+
+ def setUp(self):
+ super(TestInterfacesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = interfaces_client.InterfacesClient(fake_auth,
+ "compute",
+ "regionOne")
+
+ def _test_interface_operation(self, operation="create", bytes_body=False):
+ response_code = 200
+ expected_op = self.FAKE_SHOW_DATA
+ mock_operation = self.func2mock['get']
+ params = {'server_id': self.FAKE_SERVER_ID,
+ 'port_id': self.FAKE_PORT_ID}
+ if operation == 'list':
+ expected_op = self.FAKE_LIST_DATA
+ function = self.client.list_interfaces
+ params = {'server_id': self.FAKE_SERVER_ID}
+ elif operation == 'show':
+ function = self.client.show_interface
+ elif operation == 'delete':
+ expected_op = {}
+ mock_operation = self.func2mock['delete']
+ function = self.client.delete_interface
+ response_code = 202
+ else:
+ function = self.client.create_interface
+ mock_operation = self.func2mock['post']
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, **params)
+
+ def test_list_interfaces_with_str_body(self):
+ self._test_interface_operation('list')
+
+ def test_list_interfaces_with_bytes_body(self):
+ self._test_interface_operation('list', True)
+
+ def test_show_interface_with_str_body(self):
+ self._test_interface_operation('show')
+
+ def test_show_interface_with_bytes_body(self):
+ self._test_interface_operation('show', True)
+
+ def test_delete_interface_with_str_body(self):
+ self._test_interface_operation('delete')
+
+ def test_delete_interface_with_bytes_body(self):
+ self._test_interface_operation('delete', True)
+
+ def test_create_interface_with_str_body(self):
+ self._test_interface_operation()
+
+ def test_create_interface_with_bytes_body(self):
+ self._test_interface_operation(bytes_body=True)
diff --git a/tempest/tests/services/compute/test_keypairs_client.py b/tempest/tests/lib/services/compute/test_keypairs_client.py
similarity index 87%
rename from tempest/tests/services/compute/test_keypairs_client.py
rename to tempest/tests/lib/services/compute/test_keypairs_client.py
index 03aee53..7c595ca 100644
--- a/tempest/tests/services/compute/test_keypairs_client.py
+++ b/tempest/tests/lib/services/compute/test_keypairs_client.py
@@ -14,10 +14,9 @@
import copy
-from tempest_lib.tests import fake_auth_provider
-
-from tempest.services.compute.json import keypairs_client
-from tempest.tests.services.compute import base
+from tempest.lib.services.compute import keypairs_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
class TestKeyPairsClient(base.BaseComputeServiceTest):
@@ -38,7 +37,7 @@
def _test_list_keypairs(self, bytes_body=False):
self.check_service_client_function(
self.client.list_keypairs,
- 'tempest_lib.common.rest_client.RestClient.get',
+ 'tempest.lib.common.rest_client.RestClient.get',
{"keypairs": []},
bytes_body)
@@ -60,7 +59,7 @@
self.check_service_client_function(
self.client.show_keypair,
- 'tempest_lib.common.rest_client.RestClient.get',
+ 'tempest.lib.common.rest_client.RestClient.get',
fake_keypair,
bytes_body,
keypair_name="test")
@@ -77,7 +76,7 @@
self.check_service_client_function(
self.client.create_keypair,
- 'tempest_lib.common.rest_client.RestClient.post',
+ 'tempest.lib.common.rest_client.RestClient.post',
fake_keypair,
bytes_body,
name="test")
@@ -91,5 +90,5 @@
def test_delete_keypair(self):
self.check_service_client_function(
self.client.delete_keypair,
- 'tempest_lib.common.rest_client.RestClient.delete',
+ 'tempest.lib.common.rest_client.RestClient.delete',
{}, status=202, keypair_name='test')
diff --git a/tempest/tests/lib/services/compute/test_limits_client.py b/tempest/tests/lib/services/compute/test_limits_client.py
new file mode 100644
index 0000000..d3f0aee
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_limits_client.py
@@ -0,0 +1,66 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import limits_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestLimitsClient(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestLimitsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = limits_client.LimitsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_limits(self, bytes_body=False):
+ expected = {
+ "limits": {
+ "rate": [],
+ "absolute": {
+ "maxServerMeta": 128,
+ "maxPersonality": 5,
+ "totalServerGroupsUsed": 0,
+ "maxImageMeta": 128,
+ "maxPersonalitySize": 10240,
+ "maxServerGroups": 10,
+ "maxSecurityGroupRules": 20,
+ "maxTotalKeypairs": 100,
+ "totalCoresUsed": 0,
+ "totalRAMUsed": 0,
+ "totalInstancesUsed": 0,
+ "maxSecurityGroups": 10,
+ "totalFloatingIpsUsed": 0,
+ "maxTotalCores": 20,
+ "totalSecurityGroupsUsed": 0,
+ "maxTotalFloatingIps": 10,
+ "maxTotalInstances": 10,
+ "maxTotalRAMSize": 51200,
+ "maxServerGroupMembers": 10
+ }
+ }
+ }
+
+ self.check_service_client_function(
+ self.client.show_limits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ 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/compute/test_migrations_client.py b/tempest/tests/lib/services/compute/test_migrations_client.py
new file mode 100644
index 0000000..5b1578d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_migrations_client.py
@@ -0,0 +1,52 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import migrations_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestMigrationsClient(base.BaseComputeServiceTest):
+ FAKE_MIGRATION_INFO = {"migrations": [{
+ "created_at": "2012-10-29T13:42:02",
+ "dest_compute": "compute2",
+ "dest_host": "1.2.3.4",
+ "dest_node": "node2",
+ "id": 1234,
+ "instance_uuid": "e9e4fdd7-f956-44ff-bfeb-d654a96ab3a2",
+ "new_instance_type_id": 2,
+ "old_instance_type_id": 1,
+ "source_compute": "compute1",
+ "source_node": "node1",
+ "status": "finished",
+ "updated_at": "2012-10-29T13:42:02"}]}
+
+ def setUp(self):
+ super(TestMigrationsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.mg_client_obj = migrations_client.MigrationsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_migrations(self, bytes_body=False):
+ self.check_service_client_function(
+ self.mg_client_obj.list_migrations,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_MIGRATION_INFO,
+ bytes_body)
+
+ def test_list_migration_with_str_body(self):
+ self._test_list_migrations()
+
+ def test_list_migration_with_bytes_body(self):
+ self._test_list_migrations(True)
diff --git a/tempest/tests/lib/services/compute/test_networks_client.py b/tempest/tests/lib/services/compute/test_networks_client.py
new file mode 100644
index 0000000..4f5c8b9
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_networks_client.py
@@ -0,0 +1,94 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestNetworksClient(base.BaseComputeServiceTest):
+
+ FAKE_NETWORK = {
+ "bridge": None,
+ "vpn_public_port": None,
+ "dhcp_start": None,
+ "bridge_interface": None,
+ "share_address": None,
+ "updated_at": None,
+ "id": "34d5ae1e-5659-49cf-af80-73bccd7d7ad3",
+ "cidr_v6": None,
+ "deleted_at": None,
+ "gateway": None,
+ "rxtx_base": None,
+ "label": u'30d7',
+ "priority": None,
+ "project_id": None,
+ "vpn_private_address": None,
+ "deleted": None,
+ "vlan": None,
+ "broadcast": None,
+ "netmask": None,
+ "injected": None,
+ "cidr": None,
+ "vpn_public_address": None,
+ "multi_host": None,
+ "enable_dhcp": None,
+ "dns2": None,
+ "created_at": None,
+ "host": None,
+ "mtu": None,
+ "gateway_v6": None,
+ "netmask_v6": None,
+ "dhcp_server": None,
+ "dns1": None
+ }
+
+ network_id = "34d5ae1e-5659-49cf-af80-73bccd7d7ad3"
+
+ FAKE_NETWORKS = [FAKE_NETWORK]
+
+ def setUp(self):
+ super(TestNetworksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = networks_client.NetworksClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_networks(self, bytes_body=False):
+ fake_list = {"networks": self.FAKE_NETWORKS}
+ self.check_service_client_function(
+ self.client.list_networks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_list,
+ bytes_body)
+
+ def test_list_networks_with_str_body(self):
+ self._test_list_networks()
+
+ def test_list_networks_with_bytes_body(self):
+ self._test_list_networks(bytes_body=True)
+
+ def _test_show_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"network": self.FAKE_NETWORK},
+ bytes_body,
+ network_id=self.network_id
+ )
+
+ def test_show_network_with_str_body(self):
+ self._test_show_network()
+
+ def test_show_network_with_bytes_body(self):
+ self._test_show_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_quota_classes_client.py b/tempest/tests/lib/services/compute/test_quota_classes_client.py
new file mode 100644
index 0000000..4b67576
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_quota_classes_client.py
@@ -0,0 +1,71 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import quota_classes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestQuotaClassesClient(base.BaseComputeServiceTest):
+
+ FAKE_QUOTA_CLASS_SET = {
+ "injected_file_content_bytes": 10240,
+ "metadata_items": 128,
+ "server_group_members": 10,
+ "server_groups": 10,
+ "ram": 51200,
+ "floating_ips": 10,
+ "key_pairs": 100,
+ "id": u'\u2740(*\xb4\u25e1`*)\u2740',
+ "instances": 10,
+ "security_group_rules": 20,
+ "security_groups": 10,
+ "injected_files": 5,
+ "cores": 20,
+ "fixed_ips": -1,
+ "injected_file_path_bytes": 255,
+ }
+
+ def setUp(self):
+ super(TestQuotaClassesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = quota_classes_client.QuotaClassesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_quota_class_set(self, bytes_body=False):
+ fake_body = {'quota_class_set': self.FAKE_QUOTA_CLASS_SET}
+ self.check_service_client_function(
+ self.client.show_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_body,
+ bytes_body,
+ quota_class_id="test")
+
+ def test_show_quota_class_set_with_str_body(self):
+ self._test_show_quota_class_set()
+
+ def test_show_quota_class_set_with_bytes_body(self):
+ self._test_show_quota_class_set(bytes_body=True)
+
+ def test_update_quota_class_set(self):
+ fake_quota_class_set = copy.deepcopy(self.FAKE_QUOTA_CLASS_SET)
+ fake_quota_class_set.pop("id")
+ fake_body = {'quota_class_set': fake_quota_class_set}
+ self.check_service_client_function(
+ self.client.update_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_body,
+ quota_class_id="test")
diff --git a/tempest/tests/lib/services/compute/test_quotas_client.py b/tempest/tests/lib/services/compute/test_quotas_client.py
new file mode 100644
index 0000000..9f5d1f6
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_quotas_client.py
@@ -0,0 +1,130 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import quotas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestQuotasClient(base.BaseComputeServiceTest):
+
+ FAKE_QUOTA_SET = {
+ "quota_set": {
+ "injected_file_content_bytes": 10240,
+ "metadata_items": 128,
+ "server_group_members": 10,
+ "server_groups": 10,
+ "ram": 51200,
+ "floating_ips": 10,
+ "key_pairs": 100,
+ "id": "8421f7be61064f50b680465c07f334af",
+ "instances": 10,
+ "security_group_rules": 20,
+ "injected_files": 5,
+ "cores": 20,
+ "fixed_ips": -1,
+ "injected_file_path_bytes": 255,
+ "security_groups": 10}
+ }
+
+ project_id = "8421f7be61064f50b680465c07f334af"
+ fake_user_id = "65f09168cbb04eb593f3138b63b67b67"
+
+ def setUp(self):
+ super(TestQuotasClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = quotas_client.QuotasClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_quota_set(self, bytes_body=False, user_id=None):
+ if user_id:
+ self.check_service_client_function(
+ self.client.show_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id,
+ user_id=user_id)
+ else:
+ self.check_service_client_function(
+ self.client.show_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_show_quota_set_with_str_body(self):
+ self._test_show_quota_set()
+
+ def test_show_quota_set_with_bytes_body(self):
+ self._test_show_quota_set(bytes_body=True)
+
+ def test_show_quota_set_for_user_with_str_body(self):
+ self._test_show_quota_set(user_id=self.fake_user_id)
+
+ def test_show_quota_set_for_user_with_bytes_body(self):
+ self._test_show_quota_set(bytes_body=True, user_id=self.fake_user_id)
+
+ def _test_show_default_quota_set(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_default_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_show_default_quota_set_with_str_body(self):
+ self._test_show_default_quota_set()
+
+ def test_show_default_quota_set_with_bytes_body(self):
+ self._test_show_default_quota_set(bytes_body=True)
+
+ def _test_update_quota_set(self, bytes_body=False, user_id=None):
+ fake_quota_set = copy.deepcopy(self.FAKE_QUOTA_SET)
+ fake_quota_set['quota_set'].pop("id")
+ if user_id:
+ self.check_service_client_function(
+ self.client.update_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_quota_set,
+ to_utf=bytes_body,
+ tenant_id=self.project_id,
+ user_id=user_id)
+ else:
+ self.check_service_client_function(
+ self.client.update_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_quota_set,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_update_quota_set_with_str_body(self):
+ self._test_update_quota_set()
+
+ def test_update_quota_set_with_bytes_body(self):
+ self._test_update_quota_set(bytes_body=True)
+
+ def test_update_quota_set_for_user_with_str_body(self):
+ self._test_update_quota_set(user_id=self.fake_user_id)
+
+ def test_update_quota_set_for_user_with_bytes_body(self):
+ self._test_update_quota_set(bytes_body=True, user_id=self.fake_user_id)
+
+ def test_delete_quota_set(self):
+ self.check_service_client_function(
+ self.client.delete_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, tenant_id=self.project_id)
diff --git a/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py b/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py
new file mode 100644
index 0000000..581f7b1
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py
@@ -0,0 +1,88 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import security_group_default_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupDefaultRulesClient(base.BaseComputeServiceTest):
+ FAKE_RULE = {
+ "from_port": 80,
+ "id": 1,
+ "ip_protocol": "TCP",
+ "ip_range": {
+ "cidr": "10.10.10.0/24"
+ },
+ "to_port": 80
+ }
+
+ def setUp(self):
+ super(TestSecurityGroupDefaultRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = (security_group_default_rules_client.
+ SecurityGroupDefaultRulesClient(fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_list_security_group_default_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_security_group_default_rules,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group_default_rules": [self.FAKE_RULE]},
+ to_utf=bytes_body)
+
+ def test_list_security_group_default_rules_with_str_body(self):
+ self._test_list_security_group_default_rules()
+
+ def test_list_security_group_default_rules_with_bytes_body(self):
+ self._test_list_security_group_default_rules(bytes_body=True)
+
+ def _test_show_security_group_default_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group_default_rule,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group_default_rule": self.FAKE_RULE},
+ to_utf=bytes_body,
+ security_group_default_rule_id=1)
+
+ def test_show_security_group_default_rule_with_str_body(self):
+ self._test_show_security_group_default_rule()
+
+ def test_show_security_group_default_rule_with_bytes_body(self):
+ self._test_show_security_group_default_rule(bytes_body=True)
+
+ def _test_create_security_default_group_rule(self, bytes_body=False):
+ request_body = {
+ "to_port": 80,
+ "from_port": 80,
+ "ip_protocol": "TCP",
+ "cidr": "10.10.10.0/24"
+ }
+ self.check_service_client_function(
+ self.client.create_security_default_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"security_group_default_rule": self.FAKE_RULE},
+ to_utf=bytes_body, **request_body)
+
+ def test_create_security_default_group_rule_with_str_body(self):
+ self._test_create_security_default_group_rule()
+
+ def test_create_security_default_group_rule_with_bytes_body(self):
+ self._test_create_security_default_group_rule(bytes_body=True)
+
+ def test_delete_security_group_default_rule(self):
+ self.check_service_client_function(
+ self.client.delete_security_group_default_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=204, security_group_default_rule_id=1)
diff --git a/tempest/tests/lib/services/compute/test_security_group_rules_client.py b/tempest/tests/lib/services/compute/test_security_group_rules_client.py
new file mode 100644
index 0000000..9a7c04d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_group_rules_client.py
@@ -0,0 +1,66 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import security_group_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupRulesClient(base.BaseComputeServiceTest):
+
+ FAKE_SECURITY_GROUP_RULE = {
+ "security_group_rule": {
+ "id": "2d021cf1-ce4b-4292-994f-7a785d62a144",
+ "ip_range": {
+ "cidr": "0.0.0.0/0"
+ },
+ "parent_group_id": "48700ff3-30b8-4e63-845f-a79c9633e9fb",
+ "to_port": 443,
+ "ip_protocol": "tcp",
+ "group": {},
+ "from_port": 443
+ }
+ }
+
+ def setUp(self):
+ super(TestSecurityGroupRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_group_rules_client.SecurityGroupRulesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_security_group_rule(self, bytes_body=False):
+ req_body = {
+ "from_port": "443",
+ "ip_protocol": "tcp",
+ "to_port": "443",
+ "cidr": "0.0.0.0/0",
+ "parent_group_id": "48700ff3-30b8-4e63-845f-a79c9633e9fb"
+ }
+ self.check_service_client_function(
+ self.client.create_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SECURITY_GROUP_RULE,
+ to_utf=bytes_body, **req_body)
+
+ 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_delete_security_group_rule(self):
+ self.check_service_client_function(
+ self.client.delete_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, group_rule_id='group-id')
diff --git a/tempest/tests/lib/services/compute/test_security_groups_client.py b/tempest/tests/lib/services/compute/test_security_groups_client.py
new file mode 100644
index 0000000..6a11c29
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_groups_client.py
@@ -0,0 +1,113 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import security_groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupsClient(base.BaseComputeServiceTest):
+
+ FAKE_SECURITY_GROUP_INFO = [{
+ "description": "default",
+ "id": "3fb26eb3-581b-4420-9963-b0879a026506",
+ "name": "default",
+ "rules": [],
+ "tenant_id": "openstack"
+ }]
+
+ def setUp(self):
+ super(TestSecurityGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_groups_client.SecurityGroupsClient(
+ fake_auth, 'compute', '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',
+ {"security_groups": self.FAKE_SECURITY_GROUP_INFO},
+ to_utf=bytes_body)
+
+ 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_show_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ security_group_id='fake-id')
+
+ 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_create_security_group(self, bytes_body=False):
+ post_body = {"name": "test", "description": "test_group"}
+ self.check_service_client_function(
+ self.client.create_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ kwargs=post_body)
+
+ 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_update_security_group(self, bytes_body=False):
+ req_body = {"name": "test", "description": "test_group"}
+ self.check_service_client_function(
+ self.client.update_security_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ security_group_id='fake-id',
+ kwargs=req_body)
+
+ 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=202, security_group_id='fake-id')
+
+ 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.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.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
new file mode 100644
index 0000000..cb163a8
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_server_groups_client.py
@@ -0,0 +1,83 @@
+# Copyright 2015 IBM Corp.
+#
+# 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 oslotest import mockpatch
+from tempest.tests.lib import fake_auth_provider
+
+from tempest.lib.services.compute import server_groups_client
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services.compute import base
+
+
+class TestServerGroupsClient(base.BaseComputeServiceTest):
+
+ server_group = {
+ "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
+ "name": "test",
+ "policies": ["anti-affinity"],
+ "members": [],
+ "metadata": {}}
+
+ def setUp(self):
+ super(TestServerGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = server_groups_client.ServerGroupsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.create_server_group,
+ 'tempest.lib.common.rest_client.RestClient.post', expected,
+ bytes_body, name='fake-group', policies=['affinity'])
+
+ def test_create_server_group_str_body(self):
+ self._test_create_server_group(bytes_body=False)
+
+ def test_create_server_group_byte_body(self):
+ self._test_create_server_group(bytes_body=True)
+
+ def test_delete_server_group(self):
+ response = fake_http.fake_http_response({}, status=204), ''
+ self.useFixture(mockpatch.Patch(
+ '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]}
+ self.check_service_client_function(
+ self.client.list_server_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_list_server_groups_str_body(self):
+ self._test_list_server_groups(bytes_body=False)
+
+ def test_list_server_groups_byte_body(self):
+ self._test_list_server_groups(bytes_body=True)
+
+ def _test_show_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.show_server_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ server_group_id='5bbcc3c4-1da2-4437-a48a-66f15b1b13f9')
+
+ def test_show_server_group_str_body(self):
+ self._test_show_server_group(bytes_body=False)
+
+ def test_show_server_group_byte_body(self):
+ self._test_show_server_group(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
new file mode 100644
index 0000000..0078497
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -0,0 +1,1011 @@
+# Copyright 2015 IBM Corp.
+#
+# 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.compute import servers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestServersClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVERS = {'servers': [{
+ "id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "links": [
+ {
+ "href": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "self"
+ },
+ {
+ "href": "http://os.co/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "bookmark"
+ }
+ ],
+ "name": u"new\u1234-server-test"}]
+ }
+
+ FAKE_SERVER_DIAGNOSTICS = {
+ "cpu0_time": 17300000000,
+ "memory": 524288,
+ "vda_errors": -1,
+ "vda_read": 262144,
+ "vda_read_req": 112,
+ "vda_write": 5778432,
+ "vda_write_req": 488,
+ "vnet1_rx": 2070139,
+ "vnet1_rx_drop": 0,
+ "vnet1_rx_errors": 0,
+ "vnet1_rx_packets": 26701,
+ "vnet1_tx": 140208,
+ "vnet1_tx_drop": 0,
+ "vnet1_tx_errors": 0,
+ "vnet1_tx_packets": 662
+ }
+
+ FAKE_SERVER_GET = {'server': {
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2012-08-20T21:11:09Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://os.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "65201c14a29663e06d0748e561207d998b343e1d164bfa0aafa9c45d",
+ "id": "893c7791-f1df-4c3d-8383-3caae9656c62",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://imgs/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://v2/srvs/893c7791-f1df-4c3d-8383-3caae9656c62",
+ "rel": "self"
+ },
+ {
+ "href": "http://srvs/893c7791-f1df-4c3d-8383-3caae9656c62",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ u"My Server N\u1234me": u"Apa\u1234che1"
+ },
+ "name": u"new\u1234-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2012-08-20T21:11:09Z",
+ "user_id": "fake"}
+ }
+
+ FAKE_SERVER_POST = {"server": {
+ "id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "adminPass": "fake-admin-pass",
+ "security_groups": [
+ 'fake-security-group-1',
+ 'fake-security-group-2'
+ ],
+ "links": [
+ {
+ "href": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "self"
+ },
+ {
+ "href": "http://os.co/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "bookmark"
+ }
+ ],
+ "OS-DCF:diskConfig": "fake-disk-config"}
+ }
+
+ FAKE_ADDRESS = {"addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]}
+ }
+
+ FAKE_COMMON_VOLUME = {
+ "id": "a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb",
+ "device": "fake-device",
+ "volumeId": "a6b0875b-46ca-475e-917e-0c3aa62e5fdb",
+ "serverId": "616fb98f-46ca-475e-917e-2563e5a8cd19"
+ }
+
+ FAKE_VIRTUAL_INTERFACES = {
+ "id": "a6b0875b-46ca-475e-917e-0c3aa62e5fdb",
+ "mac_address": "00:25:90:5b:f8:c3",
+ "OS-EXT-VIF-NET:net_id": "fake-os-net-id"
+ }
+
+ FAKE_INSTANCE_ACTIONS = {
+ "action": "fake-action",
+ "request_id": "16fb98f-46ca-475e-917e-2563e5a8cd19",
+ "user_id": "16fb98f-46ca-475e-917e-2563e5a8cd12",
+ "project_id": "16fb98f-46ca-475e-917e-2563e5a8cd34",
+ "start_time": "09MAR2015 11:15",
+ "message": "fake-msg",
+ "instance_uuid": "16fb98f-46ca-475e-917e-2563e5a8cd12"
+ }
+
+ FAKE_VNC_CONSOLE = {
+ "type": "fake-type",
+ "url": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19"
+ }
+
+ FAKE_INSTANCE_ACTION_EVENTS = {
+ "event": "fake-event",
+ "start_time": "09MAR2015 11:15",
+ "finish_time": "09MAR2015 11:15",
+ "result": "fake-result",
+ "traceback": "fake-trace-back"
+ }
+
+ FAKE_INSTANCE_WITH_EVENTS = copy.deepcopy(FAKE_INSTANCE_ACTIONS)
+ FAKE_INSTANCE_WITH_EVENTS['events'] = [FAKE_INSTANCE_ACTION_EVENTS]
+
+ FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET)
+ FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass'
+
+ server_id = FAKE_SERVER_GET['server']['id']
+ network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb'
+
+ def setUp(self):
+ super(TestServersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = servers_client.ServersClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_servers_with_str_body(self):
+ self._test_list_servers()
+
+ def test_list_servers_with_bytes_body(self):
+ self._test_list_servers(bytes_body=True)
+
+ def _test_list_servers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_servers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVERS,
+ bytes_body)
+
+ def test_show_server_with_str_body(self):
+ self._test_show_server()
+
+ def test_show_server_with_bytes_body(self):
+ self._test_show_server(bytes_body=True)
+
+ def _test_show_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVER_GET,
+ bytes_body,
+ server_id=self.server_id
+ )
+
+ def test_delete_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_server,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ server_id=self.server_id
+ )
+
+ def test_create_server_with_str_body(self):
+ self._test_create_server()
+
+ def test_create_server_with_bytes_body(self):
+ self._test_create_server(True)
+
+ def _test_create_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SERVER_POST,
+ bytes_body,
+ status=202,
+ name='fake-name',
+ imageRef='fake-image-ref',
+ flavorRef='fake-flavor-ref'
+ )
+
+ def test_list_addresses_with_str_body(self):
+ self._test_list_addresses()
+
+ def test_list_addresses_with_bytes_body(self):
+ self._test_list_addresses(True)
+
+ def _test_list_addresses(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_addresses,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ADDRESS,
+ bytes_body,
+ server_id=self.server_id
+ )
+
+ def test_list_addresses_by_network_with_str_body(self):
+ self._test_list_addresses_by_network()
+
+ def test_list_addresses_by_network_with_bytes_body(self):
+ self._test_list_addresses_by_network(True)
+
+ def _test_list_addresses_by_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_addresses_by_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ADDRESS['addresses'],
+ server_id=self.server_id,
+ network_id=self.network_id
+ )
+
+ def test_action_with_str_body(self):
+ self._test_action()
+
+ def test_action_with_bytes_body(self):
+ self._test_action(True)
+
+ def _test_action(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.action,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ server_id=self.server_id,
+ action_name='fake-action-name',
+ schema={'status_code': 200}
+ )
+
+ def test_create_backup_with_str_body(self):
+ self._test_create_backup()
+
+ def test_create_backup_with_bytes_body(self):
+ self._test_create_backup(True)
+
+ def _test_create_backup(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_backup,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ backup_type='fake-backup',
+ rotation='fake-rotation',
+ name='fake-name'
+ )
+
+ def test_change_password_with_str_body(self):
+ self._test_change_password()
+
+ def test_change_password_with_bytes_body(self):
+ self._test_change_password(True)
+
+ def _test_change_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.change_password,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ adminPass='fake-admin-pass'
+ )
+
+ def test_show_password_with_str_body(self):
+ self._test_show_password()
+
+ def test_show_password_with_bytes_body(self):
+ self._test_show_password(True)
+
+ def _test_show_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_password,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'password': 'fake-password'},
+ 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):
+ self.check_service_client_function(
+ self.client.delete_password,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ 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):
+ self.check_service_client_function(
+ self.client.reboot_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ type='fake-reboot-type'
+ )
+
+ def test_rebuild_server_with_str_body(self):
+ self._test_rebuild_server()
+
+ def test_rebuild_server_with_bytes_body(self):
+ self._test_rebuild_server(True)
+
+ def _test_rebuild_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.rebuild_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_REBUILD_SERVER,
+ 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):
+ self.check_service_client_function(
+ self.client.resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ 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):
+ self.check_service_client_function(
+ self.client.confirm_resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=204,
+ 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):
+ self.check_service_client_function(
+ self.client.revert_resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_list_server_metadata_with_str_body(self):
+ self._test_list_server_metadata()
+
+ def test_list_server_metadata_with_bytes_body(self):
+ self._test_list_server_metadata()
+
+ def _test_list_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id
+ )
+
+ def test_set_server_metadata_with_str_body(self):
+ self._test_set_server_metadata()
+
+ def test_set_server_metadata_with_bytes_body(self):
+ self._test_set_server_metadata(True)
+
+ def _test_set_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ meta='fake-meta'
+ )
+
+ def test_update_server_metadata_with_str_body(self):
+ self._test_update_server_metadata()
+
+ def test_update_server_metadata_with_bytes_body(self):
+ self._test_update_server_metadata(True)
+
+ def _test_update_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ meta='fake-meta'
+ )
+
+ def test_show_server_metadata_item_with_str_body(self):
+ self._test_show_server_metadata()
+
+ def test_show_server_metadata_item_with_bytes_body(self):
+ self._test_show_server_metadata(True)
+
+ def _test_show_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'meta': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ key='fake-key'
+ )
+
+ def test_set_server_metadata_item_with_str_body(self):
+ self._test_set_server_metadata_item()
+
+ def test_set_server_metadata_item_with_bytes_body(self):
+ self._test_set_server_metadata_item(True)
+
+ def _test_set_server_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {'meta': {'fake-key': 'fake-meta-data'}},
+ 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):
+ self.check_service_client_function(
+ self.client.delete_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ server_id=self.server_id,
+ 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):
+ self.check_service_client_function(
+ self.client.stop_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.start_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_attach_volume_with_str_body(self):
+ self._test_attach_volume_server()
+
+ def test_attach_volume_with_bytes_body(self):
+ self._test_attach_volume_server(True)
+
+ def _test_attach_volume_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.attach_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ server_id=self.server_id
+ )
+
+ def test_update_attached_volume(self):
+ self.check_service_client_function(
+ self.client.update_attached_volume,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ status=202,
+ server_id=self.server_id,
+ attachment_id='fake-attachment-id',
+ volumeId='fake-volume-id'
+ )
+
+ def test_detach_volume_with_str_body(self):
+ self._test_detach_volume_server()
+
+ def test_detach_volume_with_bytes_body(self):
+ self._test_detach_volume_server(True)
+
+ def _test_detach_volume_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.detach_volume,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=202,
+ server_id=self.server_id,
+ volume_id=self.FAKE_COMMON_VOLUME['volumeId']
+ )
+
+ def test_show_volume_attachment_with_str_body(self):
+ self._test_show_volume_attachment()
+
+ def test_show_volume_attachment_with_bytes_body(self):
+ self._test_show_volume_attachment(True)
+
+ def _test_show_volume_attachment(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_attachment,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ server_id=self.server_id,
+ volume_id=self.FAKE_COMMON_VOLUME['volumeId']
+ )
+
+ def test_list_volume_attachments_with_str_body(self):
+ self._test_list_volume_attachments()
+
+ def test_list_volume_attachments_with_bytes_body(self):
+ self._test_list_volume_attachments(True)
+
+ def _test_list_volume_attachments(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_volume_attachments,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'volumeAttachments': [self.FAKE_COMMON_VOLUME]},
+ server_id=self.server_id
+ )
+
+ def test_add_security_group_with_str_body(self):
+ self._test_add_security_group()
+
+ def test_add_security_group_with_bytes_body(self):
+ self._test_add_security_group(True)
+
+ def _test_add_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ 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):
+ self.check_service_client_function(
+ self.client.remove_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ name='fake-name'
+ )
+
+ def test_live_migrate_server_with_str_body(self):
+ self._test_live_migrate_server()
+
+ def test_live_migrate_server_with_bytes_body(self):
+ self._test_live_migrate_server(True)
+
+ def _test_live_migrate_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.live_migrate_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_migrate_server_with_str_body(self):
+ self._test_migrate_server()
+
+ def test_migrate_server_with_bytes_body(self):
+ self._test_migrate_server(True)
+
+ def _test_migrate_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.migrate_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ 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):
+ self.check_service_client_function(
+ self.client.lock_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.unlock_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.suspend_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.resume_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.pause_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.unpause_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.reset_state,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ 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):
+ self.check_service_client_function(
+ self.client.shelve_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.unshelve_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.shelve_offload_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_get_console_output_with_str_body(self):
+ self._test_get_console_output()
+
+ def test_get_console_output_with_bytes_body(self):
+ self._test_get_console_output(True)
+
+ def _test_get_console_output(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_console_output,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'output': 'fake-output'},
+ server_id=self.server_id,
+ length='fake-length'
+ )
+
+ def test_list_virtual_interfaces_with_str_body(self):
+ self._test_list_virtual_interfaces()
+
+ def test_list_virtual_interfaces_with_bytes_body(self):
+ self._test_list_virtual_interfaces(True)
+
+ def _test_list_virtual_interfaces(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_virtual_interfaces,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'virtual_interfaces': [self.FAKE_VIRTUAL_INTERFACES]},
+ server_id=self.server_id
+ )
+
+ def test_rescue_server_with_str_body(self):
+ self._test_rescue_server()
+
+ def test_rescue_server_with_bytes_body(self):
+ self._test_rescue_server(True)
+
+ def _test_rescue_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.rescue_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'adminPass': 'fake-admin-pass'},
+ 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):
+ self.check_service_client_function(
+ self.client.unrescue_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_show_server_diagnostics_with_str_body(self):
+ self._test_show_server_diagnostics()
+
+ def test_show_server_diagnostics_with_bytes_body(self):
+ self._test_show_server_diagnostics(True)
+
+ def _test_show_server_diagnostics(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server_diagnostics,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVER_DIAGNOSTICS,
+ status=200,
+ server_id=self.server_id
+ )
+
+ def test_list_instance_actions_with_str_body(self):
+ self._test_list_instance_actions()
+
+ def test_list_instance_actions_with_bytes_body(self):
+ self._test_list_instance_actions(True)
+
+ def _test_list_instance_actions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_instance_actions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'instanceActions': [self.FAKE_INSTANCE_ACTIONS]},
+ server_id=self.server_id
+ )
+
+ def test_show_instance_action_with_str_body(self):
+ self._test_show_instance_action()
+
+ def test_show_instance_action_with_bytes_body(self):
+ self._test_show_instance_action(True)
+
+ def _test_show_instance_action(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_instance_action,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'instanceAction': self.FAKE_INSTANCE_WITH_EVENTS},
+ 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):
+ self.check_service_client_function(
+ self.client.force_delete_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.restore_soft_deleted_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.reset_network,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ 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):
+ self.check_service_client_function(
+ self.client.inject_network_info,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_get_vnc_console_with_str_body(self):
+ self._test_get_vnc_console()
+
+ def test_get_vnc_console_with_bytes_body(self):
+ self._test_get_vnc_console(True)
+
+ def _test_get_vnc_console(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_vnc_console,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'console': self.FAKE_VNC_CONSOLE},
+ server_id=self.server_id,
+ type='fake-console-type'
+ )
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
new file mode 100644
index 0000000..7add187
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -0,0 +1,94 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestServicesClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVICES = {
+ "services":
+ [{
+ "status": "enabled",
+ "binary": "nova-conductor",
+ "zone": "internal",
+ "state": "up",
+ "updated_at": "2015-08-19T06:50:55.000000",
+ "host": "controller",
+ "disabled_reason": None,
+ "id": 1
+ }]
+ }
+
+ FAKE_SERVICE = {
+ "service":
+ {
+ "status": "enabled",
+ "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')
+
+ def test_list_services_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_services,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICES)
+
+ def test_list_services_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_services,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICES, to_utf=True)
+
+ def _test_enable_service(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.enable_service,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_SERVICE,
+ bytes_body,
+ host_name="nova-conductor", binary="controller")
+
+ def test_enable_service_with_str_body(self):
+ self._test_enable_service()
+
+ def test_enable_service_with_bytes_body(self):
+ self._test_enable_service(bytes_body=True)
+
+ def _test_disable_service(self, bytes_body=False):
+ fake_service = copy.deepcopy(self.FAKE_SERVICE)
+ fake_service["service"]["status"] = "disable"
+
+ self.check_service_client_function(
+ self.client.disable_service,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_service,
+ bytes_body,
+ host_name="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)
diff --git a/tempest/tests/lib/services/compute/test_snapshots_client.py b/tempest/tests/lib/services/compute/test_snapshots_client.py
new file mode 100644
index 0000000..b1d8ade
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_snapshots_client.py
@@ -0,0 +1,103 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import snapshots_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSnapshotsClient(base.BaseComputeServiceTest):
+
+ FAKE_SNAPSHOT = {
+ "createdAt": "2015-10-02T16:27:54.724209",
+ "displayDescription": u"Another \u1234.",
+ "displayName": u"v\u1234-001",
+ "id": "100",
+ "size": 100,
+ "status": "available",
+ "volumeId": "12"
+ }
+
+ FAKE_SNAPSHOTS = {"snapshots": [FAKE_SNAPSHOT]}
+
+ def setUp(self):
+ super(TestSnapshotsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = snapshots_client.SnapshotsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"snapshot": self.FAKE_SNAPSHOT},
+ to_utf=bytes_body, status=200,
+ volume_id=self.FAKE_SNAPSHOT["volumeId"])
+
+ def test_create_snapshot_with_str_body(self):
+ self._test_create_snapshot()
+
+ def test_create_shapshot_with_bytes_body(self):
+ self._test_create_snapshot(bytes_body=True)
+
+ def _test_show_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"snapshot": self.FAKE_SNAPSHOT},
+ to_utf=bytes_body, snapshot_id=self.FAKE_SNAPSHOT["id"])
+
+ def test_show_snapshot_with_str_body(self):
+ self._test_show_snapshot()
+
+ def test_show_snapshot_with_bytes_body(self):
+ self._test_show_snapshot(bytes_body=True)
+
+ def _test_list_snapshots(self, bytes_body=False, **params):
+ self.check_service_client_function(
+ self.client.list_snapshots,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SNAPSHOTS, to_utf=bytes_body, **params)
+
+ def test_list_snapshots_with_str_body(self):
+ self._test_list_snapshots()
+
+ def test_list_snapshots_with_byte_body(self):
+ self._test_list_snapshots(bytes_body=True)
+
+ def test_list_snapshots_with_params(self):
+ self._test_list_snapshots('fake')
+
+ def test_delete_snapshot(self):
+ self.check_service_client_function(
+ self.client.delete_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, snapshot_id=self.FAKE_SNAPSHOT['id'])
+
+ def test_is_resource_deleted_true(self):
+ module = ('tempest.lib.services.compute.snapshots_client.'
+ 'SnapshotsClient.show_snapshot')
+ self.useFixture(mockpatch.Patch(
+ 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(
+ module, return_value={}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_tenant_networks_client.py b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
new file mode 100644
index 0000000..cfb68cc
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
@@ -0,0 +1,63 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import tenant_networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestTenantNetworksClient(base.BaseComputeServiceTest):
+
+ FAKE_NETWORK = {
+ "cidr": "None",
+ "id": "c2329eb4-cc8e-4439-ac4c-932369309e36",
+ "label": u'\u30d7'
+ }
+
+ FAKE_NETWORKS = [FAKE_NETWORK]
+
+ NETWORK_ID = FAKE_NETWORK['id']
+
+ def setUp(self):
+ super(TestTenantNetworksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tenant_networks_client.TenantNetworksClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_tenant_networks(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenant_networks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"networks": self.FAKE_NETWORKS},
+ bytes_body)
+
+ def test_list_tenant_networks_with_str_body(self):
+ self._test_list_tenant_networks()
+
+ def test_list_tenant_networks_with_bytes_body(self):
+ self._test_list_tenant_networks(bytes_body=True)
+
+ def _test_show_tenant_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_tenant_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"network": self.FAKE_NETWORK},
+ bytes_body,
+ network_id=self.NETWORK_ID)
+
+ def test_show_tenant_network_with_str_body(self):
+ self._test_show_tenant_network()
+
+ def test_show_tenant_network_with_bytes_body(self):
+ self._test_show_tenant_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_tenant_usages_client.py b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
new file mode 100644
index 0000000..88d093d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
@@ -0,0 +1,79 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import tenant_usages_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestTenantUsagesClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVER_USAGES = [{
+ "ended_at": None,
+ "flavor": "m1.tiny",
+ "hours": 1.0,
+ "instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
+ "local_gb": 1,
+ "memory_mb": 512,
+ "name": "new-server-test",
+ "started_at": "2012-10-08T20:10:44.541277",
+ "state": "active",
+ "tenant_id": "openstack",
+ "uptime": 3600,
+ "vcpus": 1
+ }]
+
+ FAKE_TENANT_USAGES = [{
+ "server_usages": FAKE_SERVER_USAGES,
+ "start": "2012-10-08T21:10:44.587336",
+ "stop": "2012-10-08T22:10:44.587336",
+ "tenant_id": "openstack",
+ "total_hours": 1,
+ "total_local_gb_usage": 1,
+ "total_memory_mb_usage": 512,
+ "total_vcpus_usage": 1
+ }]
+
+ def setUp(self):
+ super(TestTenantUsagesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tenant_usages_client.TenantUsagesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_tenant_usages(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenant_usages,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"tenant_usages": self.FAKE_TENANT_USAGES},
+ to_utf=bytes_body)
+
+ def test_list_tenant_usages_with_str_body(self):
+ self._test_list_tenant_usages()
+
+ def test_list_tenant_usages_with_bytes_body(self):
+ self._test_list_tenant_usages(bytes_body=True)
+
+ def _test_show_tenant_usage(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_tenant_usage,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"tenant_usage": self.FAKE_TENANT_USAGES[0]},
+ to_utf=bytes_body,
+ tenant_id='openstack')
+
+ def test_show_tenant_usage_with_str_body(self):
+ self._test_show_tenant_usage()
+
+ def test_show_tenant_usage_with_bytes_body(self):
+ self._test_show_tenant_usage(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_versions_client.py b/tempest/tests/lib/services/compute/test_versions_client.py
new file mode 100644
index 0000000..fc6c1d2
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_versions_client.py
@@ -0,0 +1,135 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+from oslotest import mockpatch
+
+from tempest.lib.services.compute import versions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestVersionsClient(base.BaseComputeServiceTest):
+
+ FAKE_INIT_VERSION = {
+ "version": {
+ "id": "v2.1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2.1/",
+ "rel": "self"
+ },
+ {
+ "href": "http://docs.openstack.org/",
+ "rel": "describedby",
+ "type": "text/html"
+ }
+ ],
+ "status": "CURRENT",
+ "updated": "2013-07-23T11:33:21Z",
+ "version": "2.1",
+ "min_version": "2.1"
+ }
+ }
+
+ FAKE_VERSIONS_INFO = {
+ "versions": [FAKE_INIT_VERSION["version"]]
+ }
+
+ FAKE_VERSION_INFO = copy.deepcopy(FAKE_INIT_VERSION)
+
+ FAKE_VERSION_INFO["version"]["media-types"] = [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.compute+json;version=2.1"
+ }
+ ]
+
+ def setUp(self):
+ super(TestVersionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.versions_client = (
+ versions_client.VersionsClient
+ (fake_auth, 'compute', 'regionOne'))
+
+ def _test_versions_client(self, bytes_body=False):
+ self.check_service_client_function(
+ self.versions_client.list_versions,
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ self.FAKE_VERSIONS_INFO,
+ bytes_body,
+ 200)
+
+ def _test_get_version_by_url(self, bytes_body=False):
+ self.useFixture(mockpatch.Patch(
+ "tempest.lib.common.rest_client.RestClient.token",
+ return_value="Dummy Token"))
+ params = {"version_url": self.versions_client._get_base_version_url()}
+ self.check_service_client_function(
+ self.versions_client.get_version_by_url,
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ self.FAKE_VERSION_INFO,
+ bytes_body,
+ 200, **params)
+
+ def test_list_versions_client_with_str_body(self):
+ self._test_versions_client()
+
+ def test_list_versions_client_with_bytes_body(self):
+ self._test_versions_client(bytes_body=True)
+
+ def test_get_version_by_url_with_str_body(self):
+ self._test_get_version_by_url()
+
+ def test_get_version_by_url_with_bytes_body(self):
+ self._test_get_version_by_url(bytes_body=True)
+
+ def _test_get_base_version_url(self, url, expected_base_url):
+ auth = fake_auth_provider.FakeAuthProvider(fake_base_url=url)
+ client = versions_client.VersionsClient(auth, 'compute', 'regionOne')
+ self.assertEqual(expected_base_url, client._get_base_version_url())
+
+ def test_get_base_version_url(self):
+ self._test_get_base_version_url('https://bar.org/v2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v2.1/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v2.15/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v22.2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v22/123',
+ 'https://bar.org/')
+
+ def test_get_base_version_url_app_name(self):
+ self._test_get_base_version_url('https://bar.org/compute/v2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v2.1/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v2.15/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v22.2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v22/123',
+ 'https://bar.org/compute/')
+
+ def test_get_base_version_url_double_slash(self):
+ self._test_get_base_version_url('https://bar.org//v2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org//v2.1/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/compute//v2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute//v2.1/123',
+ 'https://bar.org/compute/')
diff --git a/tempest/tests/lib/services/compute/test_volumes_client.py b/tempest/tests/lib/services/compute/test_volumes_client.py
new file mode 100644
index 0000000..d16f5b0
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_volumes_client.py
@@ -0,0 +1,114 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestVolumesClient(base.BaseComputeServiceTest):
+
+ FAKE_VOLUME = {
+ "id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
+ "displayName": u"v\u12345ol-001",
+ "displayDescription": u"Another \u1234volume.",
+ "size": 30,
+ "status": "Active",
+ "volumeType": "289da7f8-6440-407c-9fb4-7db01ec49164",
+ "metadata": {
+ "contents": "junk"
+ },
+ "availabilityZone": "us-east1",
+ "snapshotId": None,
+ "attachments": [],
+ "createdAt": "2012-02-14T20:53:07Z"
+ }
+
+ FAKE_VOLUMES = {"volumes": [FAKE_VOLUME]}
+
+ def setUp(self):
+ super(TestVolumesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volumes_client.VolumesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_volumes(self, bytes_body=False, **params):
+ self.check_service_client_function(
+ self.client.list_volumes,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUMES, to_utf=bytes_body, **params)
+
+ def test_list_volumes_with_str_body(self):
+ self._test_list_volumes()
+
+ def test_list_volumes_with_byte_body(self):
+ self._test_list_volumes(bytes_body=True)
+
+ def test_list_volumes_with_params(self):
+ self._test_list_volumes(name='fake')
+
+ def _test_show_volume(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"volume": self.FAKE_VOLUME},
+ to_utf=bytes_body, volume_id=self.FAKE_VOLUME['id'])
+
+ def test_show_volume_with_str_body(self):
+ self._test_show_volume()
+
+ def test_show_volume_with_bytes_body(self):
+ self._test_show_volume(bytes_body=True)
+
+ def _test_create_volume(self, bytes_body=False):
+ post_body = copy.deepcopy(self.FAKE_VOLUME)
+ del post_body['id']
+ del post_body['createdAt']
+ del post_body['status']
+ self.check_service_client_function(
+ self.client.create_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"volume": self.FAKE_VOLUME},
+ to_utf=bytes_body, status=200, **post_body)
+
+ def test_create_volume_with_str_body(self):
+ self._test_create_volume()
+
+ def test_create_volume_with_bytes_body(self):
+ self._test_create_volume(bytes_body=True)
+
+ def test_delete_volume(self):
+ self.check_service_client_function(
+ self.client.delete_volume,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, volume_id=self.FAKE_VOLUME['id'])
+
+ def test_is_resource_deleted_true(self):
+ module = ('tempest.lib.services.compute.volumes_client.'
+ 'VolumesClient.show_volume')
+ self.useFixture(mockpatch.Patch(
+ 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(
+ module, return_value={}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/identity/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/identity/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/identity/v2/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/identity/v2/__init__.py
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
new file mode 100644
index 0000000..7925152
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -0,0 +1,107 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 json
+
+import mock
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client
+from tempest.tests import base
+from tempest.tests.lib import fake_http
+
+
+class TestTokenClientV2(base.TestCase):
+
+ def setUp(self):
+ super(TestTokenClientV2, self).setUp()
+
+ def test_init_without_authurl(self):
+ self.assertRaises(exceptions.IdentityError,
+ token_client.TokenClient, None)
+
+ def test_auth(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = {'access': {'token': 'fake_token'}}
+
+ with mock.patch.object(token_client_v2, 'post') as post_mock:
+ post_mock.return_value = response, body
+ resp = token_client_v2.auth('fake_user', 'fake_pass')
+
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'passwordCredentials': {
+ 'username': 'fake_user',
+ 'password': 'fake_pass',
+ },
+ }
+ }, sort_keys=True)
+ post_mock.assert_called_once_with('fake_url/tokens',
+ body=req_dict)
+
+ def test_auth_with_tenant(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = {'access': {'token': 'fake_token'}}
+
+ with mock.patch.object(token_client_v2, 'post') as post_mock:
+ post_mock.return_value = response, body
+ resp = token_client_v2.auth('fake_user', 'fake_pass',
+ 'fake_tenant')
+
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'tenantName': 'fake_tenant',
+ 'passwordCredentials': {
+ 'username': 'fake_user',
+ 'password': 'fake_pass',
+ },
+ }
+ }, sort_keys=True)
+ post_mock.assert_called_once_with('fake_url/tokens',
+ body=req_dict)
+
+ def test_request_with_str_body(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = str('{"access": {"token": "fake_token"}}')
+
+ with mock.patch.object(token_client_v2, 'raw_request') as mock_raw_r:
+ mock_raw_r.return_value = response, body
+ resp, body = token_client_v2.request('GET', 'fake_uri')
+ self.assertIsInstance(body, dict)
+
+ def test_request_with_bytes_body(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = b'{"access": {"token": "fake_token"}}'
+
+ with mock.patch.object(token_client_v2, 'raw_request') as mock_raw_r:
+ mock_raw_r.return_value = response, body
+ resp, body = token_client_v2.request('GET', 'fake_uri')
+
+ self.assertIsInstance(body, dict)
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/identity/v3/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/identity/v3/__init__.py
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
new file mode 100644
index 0000000..e9ef740
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -0,0 +1,167 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 json
+
+import mock
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v3 import token_client
+from tempest.tests import base
+from tempest.tests.lib import fake_http
+
+
+class TestTokenClientV3(base.TestCase):
+
+ def setUp(self):
+ super(TestTokenClientV3, self).setUp()
+
+ def test_init_without_authurl(self):
+ self.assertRaises(exceptions.IdentityError,
+ token_client.V3TokenClient, None)
+
+ def test_auth(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=201,
+ )
+ body = {'access': {'token': 'fake_token'}}
+
+ with mock.patch.object(token_client_v3, 'post') as post_mock:
+ post_mock.return_value = response, body
+ resp = token_client_v3.auth(username='fake_user',
+ password='fake_pass')
+
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ }
+ }
+ },
+ }
+ }, sort_keys=True)
+ post_mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_auth_with_project_id_and_domain_id(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=201,
+ )
+ body = {'access': {'token': 'fake_token'}}
+
+ with mock.patch.object(token_client_v3, 'post') as post_mock:
+ post_mock.return_value = response, body
+ resp = token_client_v3.auth(
+ username='fake_user', password='fake_pass',
+ project_id='fcac2a055a294e4c82d0a9c21c620eb4',
+ user_domain_id='14f4a9a99973404d8c20ba1d2af163ff',
+ project_domain_id='291f63ae9ac54ee292ca09e5f72d9676')
+
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ 'domain': {
+ 'id': '14f4a9a99973404d8c20ba1d2af163ff'
+ }
+ }
+ }
+ },
+ 'scope': {
+ 'project': {
+ 'id': 'fcac2a055a294e4c82d0a9c21c620eb4',
+ 'domain': {
+ 'id': '291f63ae9ac54ee292ca09e5f72d9676'
+ }
+ }
+ }
+ }
+ }, sort_keys=True)
+ post_mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_auth_with_tenant(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=201,
+ )
+ body = {'access': {'token': 'fake_token'}}
+
+ with mock.patch.object(token_client_v3, 'post') as post_mock:
+ post_mock.return_value = response, body
+ resp = token_client_v3.auth(username='fake_user',
+ password='fake_pass',
+ project_name='fake_tenant')
+
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ }
+ }},
+ 'scope': {
+ 'project': {
+ 'name': 'fake_tenant'
+ }
+ },
+ }
+ }, sort_keys=True)
+
+ post_mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_request_with_str_body(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = str('{"access": {"token": "fake_token"}}')
+
+ with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
+ mock_raw_r.return_value = response, body
+ resp, body = token_client_v3.request('GET', 'fake_uri')
+
+ self.assertIsInstance(body, dict)
+
+ def test_request_with_bytes_body(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+
+ response = fake_http.fake_http_response(
+ None, status=200,
+ )
+ body = b'{"access": {"token": "fake_token"}}'
+
+ with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
+ mock_raw_r.return_value = response, body
+ resp, body = token_client_v3.request('GET', 'fake_uri')
+
+ self.assertIsInstance(body, dict)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
new file mode 100644
index 0000000..c253187
--- /dev/null
+++ b/tempest/tests/lib/test_auth.py
@@ -0,0 +1,752 @@
+# Copyright 2014 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 copy
+import datetime
+import testtools
+
+from oslotest import mockpatch
+
+from tempest.lib import auth
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as v2_client
+from tempest.lib.services.identity.v3 import token_client as v3_client
+from tempest.tests import base
+from tempest.tests.lib import fake_credentials
+from tempest.tests.lib import fake_identity
+
+
+def fake_get_credentials(fill_in=True, identity_version='v2', **kwargs):
+ return fake_credentials.FakeCredentials()
+
+
+class BaseAuthTestsSetUp(base.TestCase):
+ _auth_provider_class = None
+ credentials = fake_credentials.FakeCredentials()
+
+ def _auth(self, credentials, auth_url, **params):
+ """returns auth method according to keystone"""
+ return self._auth_provider_class(credentials, auth_url, **params)
+
+ def setUp(self):
+ super(BaseAuthTestsSetUp, self).setUp()
+ self.patchobject(auth, 'get_credentials', fake_get_credentials)
+ self.auth_provider = self._auth(self.credentials,
+ fake_identity.FAKE_AUTH_URL)
+
+
+class TestBaseAuthProvider(BaseAuthTestsSetUp):
+ """Tests for base AuthProvider
+
+ This tests auth.AuthProvider class which is base for the other so we
+ obviously don't test not implemented method or the ones which strongly
+ depends on them.
+ """
+
+ class FakeAuthProviderImpl(auth.AuthProvider):
+ def _decorate_request(self):
+ pass
+
+ def _fill_credentials(self):
+ pass
+
+ def _get_auth(self):
+ pass
+
+ def base_url(self):
+ pass
+
+ def is_expired(self):
+ pass
+
+ _auth_provider_class = FakeAuthProviderImpl
+
+ def _auth(self, credentials, auth_url, **params):
+ """returns auth method according to keystone"""
+ return self._auth_provider_class(credentials, **params)
+
+ def test_check_credentials_bad_type(self):
+ self.assertFalse(self.auth_provider.check_credentials([]))
+
+ 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.assertEqual('foo', getattr(self.auth_provider, 'auth_data'))
+
+ def test_delete_auth_data_property_through_deleter(self):
+ self.auth_provider.cache = 'foo'
+ del self.auth_provider.auth_data
+ self.assertIsNone(self.auth_provider.cache)
+
+ def test_delete_auth_data_property_through_clear_auth(self):
+ self.auth_provider.cache = 'foo'
+ self.auth_provider.clear_auth()
+ self.assertIsNone(self.auth_provider.cache)
+
+ def test_set_and_reset_alt_auth_data(self):
+ self.auth_provider.set_alt_auth_data('foo', 'bar')
+ self.assertEqual(self.auth_provider.alt_part, 'foo')
+ self.assertEqual(self.auth_provider.alt_auth_data, 'bar')
+
+ self.auth_provider.reset_alt_auth_data()
+ self.assertIsNone(self.auth_provider.alt_part)
+ self.assertIsNone(self.auth_provider.alt_auth_data)
+
+ def test_auth_class(self):
+ self.assertRaises(TypeError,
+ auth.AuthProvider,
+ fake_credentials.FakeCredentials)
+
+
+class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
+ _endpoints = fake_identity.IDENTITY_V2_RESPONSE['access']['serviceCatalog']
+ _auth_provider_class = auth.KeystoneV2AuthProvider
+ credentials = fake_credentials.FakeKeystoneV2Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV2AuthProvider, self).setUp()
+ self.patchobject(v2_client.TokenClient, 'raw_request',
+ fake_identity._fake_v2_response)
+ self.target_url = 'test_api'
+
+ def _get_fake_identity(self):
+ return fake_identity.IDENTITY_V2_RESPONSE['access']
+
+ def _get_fake_alt_identity(self):
+ return fake_identity.ALT_IDENTITY_V2_RESPONSE['access']
+
+ def _get_result_url_from_endpoint(self, ep, endpoint_type='publicURL',
+ replacement=None):
+ if replacement:
+ return ep[endpoint_type].replace('v2', replacement)
+ return ep[endpoint_type]
+
+ def _get_token_from_fake_identity(self):
+ return fake_identity.TOKEN
+
+ def _get_from_fake_identity(self, attr):
+ access = fake_identity.IDENTITY_V2_RESPONSE['access']
+ if attr == 'user_id':
+ return access['user']['id']
+ elif attr == 'tenant_id':
+ return access['token']['tenant']['id']
+
+ def _test_request_helper(self, filters, expected):
+ url, headers, body = self.auth_provider.auth_request('GET',
+ self.target_url,
+ filters=filters)
+
+ self.assertEqual(expected['url'], url)
+ self.assertEqual(expected['token'], headers['X-Auth-Token'])
+ self.assertEqual(expected['body'], body)
+
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['token']['expires'] = date_as_string
+ return token, access
+
+ def test_request(self):
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+
+ url = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1]) + '/' + self.target_url
+
+ expected = {
+ 'body': None,
+ 'url': url,
+ 'token': self._get_token_from_fake_identity(),
+ }
+ self._test_request_helper(filters, expected)
+
+ def test_request_with_alt_auth_cleans_alt(self):
+ """Test alternate auth data for headers
+
+ Assert that when the alt data is provided for headers, after an
+ auth_request the data alt_data is cleaned-up.
+ """
+ self.auth_provider.set_alt_auth_data(
+ 'headers',
+ (fake_identity.ALT_TOKEN, self._get_fake_alt_identity()))
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.auth_request('GET', self.target_url,
+ filters=filters)
+
+ # Assert alt auth data is clear after it
+ self.assertIsNone(self.auth_provider.alt_part)
+ self.assertIsNone(self.auth_provider.alt_auth_data)
+
+ def _test_request_with_identical_alt_auth(self, part):
+ """Test alternate but identical auth data for headers
+
+ Assert that when the alt data is provided, but it's actually
+ identical, an exception is raised.
+ """
+ self.auth_provider.set_alt_auth_data(
+ part,
+ (fake_identity.TOKEN, self._get_fake_identity()))
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+
+ self.assertRaises(exceptions.BadAltAuth,
+ self.auth_provider.auth_request,
+ 'GET', self.target_url, filters=filters)
+
+ def test_request_with_identical_alt_auth_headers(self):
+ self._test_request_with_identical_alt_auth('headers')
+
+ def test_request_with_identical_alt_auth_url(self):
+ self._test_request_with_identical_alt_auth('url')
+
+ def test_request_with_identical_alt_auth_body(self):
+ self._test_request_with_identical_alt_auth('body')
+
+ def test_request_with_alt_part_without_alt_data(self):
+ """Test empty alternate auth data
+
+ Assert that when alt_part is defined, the corresponding original
+ request element is kept the same.
+ """
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.set_alt_auth_data('headers', None)
+
+ url, headers, body = self.auth_provider.auth_request('GET',
+ self.target_url,
+ filters=filters)
+ # The original headers where empty
+ self.assertNotEqual(url, self.target_url)
+ self.assertIsNone(headers)
+ self.assertEqual(body, None)
+
+ def _test_request_with_alt_part_without_alt_data_no_change(self, body):
+ """Test empty alternate auth data with no effect
+
+ Assert that when alt_part is defined, no auth_data is provided,
+ and the corresponding original request element was not going to
+ be changed anyways, and exception is raised
+ """
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.set_alt_auth_data('body', None)
+
+ self.assertRaises(exceptions.BadAltAuth,
+ self.auth_provider.auth_request,
+ 'GET', self.target_url, filters=filters)
+
+ def test_request_with_alt_part_without_alt_data_no_change_headers(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('headers')
+
+ def test_request_with_alt_part_without_alt_data_no_change_url(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('url')
+
+ def test_request_with_alt_part_without_alt_data_no_change_body(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('body')
+
+ def test_request_with_bad_service(self):
+ filters = {
+ 'service': 'BAD_SERVICE',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.auth_request, 'GET',
+ self.target_url, filters=filters)
+
+ def test_request_without_service(self):
+ filters = {
+ 'service': None,
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.auth_request, 'GET',
+ self.target_url, filters=filters)
+
+ def test_check_credentials_missing_attribute(self):
+ for attr in ['username', 'password']:
+ cred = copy.copy(self.credentials)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred))
+
+ def test_fill_credentials(self):
+ self.auth_provider.fill_credentials()
+ creds = self.auth_provider.credentials
+ for attr in ['user_id', 'tenant_id']:
+ self.assertEqual(self._get_from_fake_identity(attr),
+ getattr(creds, attr))
+
+ def _test_base_url_helper(self, expected_url, filters,
+ auth_data=None):
+
+ url = self.auth_provider.base_url(filters, auth_data)
+ self.assertEqual(url, expected_url)
+
+ def test_base_url(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_to_get_admin_endpoint(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'adminURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1], endpoint_type='adminURL')
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_unknown_region(self):
+ """If the region is unknown, the first endpoint is returned."""
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'AintNoBodyKnowThisRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][0])
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_non_existent_service(self):
+ self.filters = {
+ 'service': 'BAD_SERVICE',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_without_service(self):
+ self.filters = {
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_with_api_version_filter(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v12'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1], replacement='v12')
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_skip_path_filter(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'skip_path': True
+ }
+ expected = 'http://fake_url/'
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_unversioned_endpoint(self):
+ auth_data = {
+ 'serviceCatalog': [
+ {
+ 'type': 'identity',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'publicURL': 'http://fake_url'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
+ def test_token_not_expired(self):
+ expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=False)
+
+ def test_token_expired(self):
+ expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
+
+ def test_token_not_expired_to_be_renewed(self):
+ expiry_data = (datetime.datetime.utcnow() +
+ self.auth_provider.token_expiry_threshold / 2)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
+
+ def _verify_expiry(self, expiry_data, should_be_expired):
+ for expiry_format in self.auth_provider.EXPIRY_DATE_FORMATS:
+ auth_data = self._auth_data_with_expiry(
+ expiry_data.strftime(expiry_format))
+ self.assertEqual(self.auth_provider.is_expired(auth_data),
+ should_be_expired)
+
+ def test_set_scope_all_valid(self):
+ for scope in self.auth_provider.SCOPES:
+ self.auth_provider.scope = scope
+ self.assertEqual(scope, self.auth_provider.scope)
+
+ def test_set_scope_invalid(self):
+ with testtools.ExpectedException(exceptions.InvalidScope,
+ '.* invalid_scope .*'):
+ self.auth_provider.scope = 'invalid_scope'
+
+
+class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
+ _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
+ _auth_provider_class = auth.KeystoneV3AuthProvider
+ credentials = fake_credentials.FakeKeystoneV3Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV3AuthProvider, self).setUp()
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response)
+
+ def _get_fake_identity(self):
+ return fake_identity.IDENTITY_V3_RESPONSE['token']
+
+ def _get_fake_alt_identity(self):
+ return fake_identity.ALT_IDENTITY_V3['token']
+
+ def _get_result_url_from_endpoint(self, ep, replacement=None):
+ if replacement:
+ return ep['url'].replace('v3', replacement)
+ return ep['url']
+
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['expires_at'] = date_as_string
+ return token, access
+
+ def _get_from_fake_identity(self, attr):
+ token = fake_identity.IDENTITY_V3_RESPONSE['token']
+ if attr == 'user_id':
+ return token['user']['id']
+ elif attr == 'project_id':
+ return token['project']['id']
+ elif attr == 'user_domain_id':
+ return token['user']['domain']['id']
+ elif attr == 'project_domain_id':
+ return token['project']['domain']['id']
+
+ def test_check_credentials_missing_attribute(self):
+ # reset credentials to fresh ones
+ self.credentials.reset()
+ for attr in ['username', 'password', 'user_domain_name',
+ 'project_domain_name']:
+ cred = copy.copy(self.credentials)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred),
+ "Credentials should be invalid without %s" % attr)
+
+ def test_check_domain_credentials_missing_attribute(self):
+ # reset credentials to fresh ones
+ self.credentials.reset()
+ domain_creds = fake_credentials.FakeKeystoneV3DomainCredentials()
+ for attr in ['username', 'password', 'user_domain_name']:
+ cred = copy.copy(domain_creds)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred),
+ "Credentials should be invalid without %s" % attr)
+
+ def test_fill_credentials(self):
+ self.auth_provider.fill_credentials()
+ creds = self.auth_provider.credentials
+ for attr in ['user_id', 'project_id', 'user_domain_id',
+ 'project_domain_id']:
+ self.assertEqual(self._get_from_fake_identity(attr),
+ getattr(creds, attr))
+
+ # Overwrites v2 test
+ def test_base_url_to_get_admin_endpoint(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'admin',
+ 'region': 'MiddleEarthRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][2])
+ self._test_base_url_helper(expected, self.filters)
+
+ # Overwrites v2 test
+ def test_base_url_with_unversioned_endpoint(self):
+ auth_data = {
+ 'catalog': [
+ {
+ 'type': 'identity',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'url': 'http://fake_url',
+ 'interface': 'public'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v3'
+ }
+
+ expected = 'http://fake_url/v3'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
+ # Base URL test with scope only for V3
+ def test_base_url_scope_project(self):
+ self.auth_provider.scope = 'project'
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_identity(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = fake_identity.FAKE_AUTH_URL
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_other(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.base_url,
+ auth_data=self.auth_provider.auth_data,
+ filters=self.filters)
+
+ def test_auth_parameters_with_scope_unset(self):
+ # No scope defaults to 'project'
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_project_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'project'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_domain_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'domain'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_unscoped(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'unscoped'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_') or attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+
+class TestKeystoneV3Credentials(base.TestCase):
+ def testSetAttrUserDomain(self):
+ creds = auth.KeystoneV3Credentials()
+ creds.user_domain_name = 'user_domain'
+ creds.domain_name = 'domain'
+ self.assertEqual('user_domain', creds.user_domain_name)
+ creds = auth.KeystoneV3Credentials()
+ creds.domain_name = 'domain'
+ creds.user_domain_name = 'user_domain'
+ self.assertEqual('user_domain', creds.user_domain_name)
+
+ def testSetAttrProjectDomain(self):
+ creds = auth.KeystoneV3Credentials()
+ creds.project_domain_name = 'project_domain'
+ creds.domain_name = 'domain'
+ self.assertEqual('project_domain', creds.user_domain_name)
+ creds = auth.KeystoneV3Credentials()
+ creds.domain_name = 'domain'
+ creds.project_domain_name = 'project_domain'
+ self.assertEqual('project_domain', creds.project_domain_name)
+
+ def testProjectTenantNoCollision(self):
+ creds = auth.KeystoneV3Credentials(tenant_id='tenant')
+ self.assertEqual('tenant', creds.project_id)
+ creds = auth.KeystoneV3Credentials(project_id='project')
+ self.assertEqual('project', creds.tenant_id)
+ creds = auth.KeystoneV3Credentials(tenant_name='tenant')
+ self.assertEqual('tenant', creds.project_name)
+ creds = auth.KeystoneV3Credentials(project_name='project')
+ self.assertEqual('project', creds.tenant_name)
+
+ def testProjectTenantCollision(self):
+ attrs = {'tenant_id': 'tenant', 'project_id': 'project'}
+ self.assertRaises(
+ exceptions.InvalidCredentials, auth.KeystoneV3Credentials, **attrs)
+ attrs = {'tenant_name': 'tenant', 'project_name': 'project'}
+ self.assertRaises(
+ exceptions.InvalidCredentials, auth.KeystoneV3Credentials, **attrs)
+
+
+class TestReplaceVersion(base.TestCase):
+ def test_version_no_trailing_path(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0',
+ auth.replace_version('http://localhost:35357/v3', 'v2.0'))
+
+ def test_version_no_trailing_path_solidus(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0/',
+ auth.replace_version('http://localhost:35357/v3/', 'v2.0'))
+
+ def test_version_trailing_path(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0/uuid',
+ auth.replace_version('http://localhost:35357/v3/uuid', 'v2.0'))
+
+ def test_version_trailing_path_solidus(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0/uuid/',
+ auth.replace_version('http://localhost:35357/v3/uuid/', 'v2.0'))
+
+ def test_no_version_base(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0',
+ auth.replace_version('http://localhost:35357', 'v2.0'))
+
+ def test_no_version_base_solidus(self):
+ self.assertEqual(
+ 'http://localhost:35357/v2.0',
+ auth.replace_version('http://localhost:35357/', 'v2.0'))
+
+ def test_no_version_path(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0',
+ auth.replace_version('http://localhost/identity', 'v2.0'))
+
+ def test_no_version_path_solidus(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0',
+ auth.replace_version('http://localhost/identity/', 'v2.0'))
+
+ def test_path_version(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0',
+ auth.replace_version('http://localhost/identity/v3', 'v2.0'))
+
+ def test_path_version_solidus(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0/',
+ auth.replace_version('http://localhost/identity/v3/', 'v2.0'))
+
+ def test_path_version_trailing_path(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0/uuid',
+ auth.replace_version('http://localhost/identity/v3/uuid', 'v2.0'))
+
+ def test_path_version_trailing_path_solidus(self):
+ self.assertEqual(
+ 'http://localhost/identity/v2.0/uuid/',
+ auth.replace_version('http://localhost/identity/v3/uuid/', 'v2.0'))
+
+
+class TestKeystoneV3AuthProvider_DomainScope(BaseAuthTestsSetUp):
+ _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
+ _auth_provider_class = auth.KeystoneV3AuthProvider
+ credentials = fake_credentials.FakeKeystoneV3Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV3AuthProvider_DomainScope, self).setUp()
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_domain_scope)
+
+ def test_get_auth_with_domain_scope(self):
+ self.auth_provider.scope = 'domain'
+ _, auth_data = self.auth_provider.get_auth()
+ self.assertIn('domain', auth_data)
+ self.assertNotIn('project', auth_data)
diff --git a/tempest/tests/lib/test_base.py b/tempest/tests/lib/test_base.py
new file mode 100644
index 0000000..27cda1a
--- /dev/null
+++ b/tempest/tests/lib/test_base.py
@@ -0,0 +1,64 @@
+# Copyright 2014 Mirantis 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.
+
+import testtools
+
+from tempest.lib import base
+from tempest.lib import exceptions
+
+
+class TestAttr(base.BaseTestCase):
+
+ def test_has_no_attrs(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_no_attrs',
+ self.id()
+ )
+
+ @testtools.testcase.attr('foo')
+ def test_has_one_attr(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_one_attr[foo]',
+ self.id()
+ )
+
+ @testtools.testcase.attr('foo')
+ @testtools.testcase.attr('bar')
+ def test_has_two_attrs(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_two_attrs[bar,foo]',
+ self.id(),
+ )
+
+
+class TestSetUpClass(base.BaseTestCase):
+
+ @classmethod
+ def setUpClass(cls): # noqa
+ """Simulate absence of super() call."""
+
+ def setUp(self):
+ try:
+ # We expect here RuntimeError exception because 'setUpClass'
+ # has not called 'super'.
+ super(TestSetUpClass, self).setUp()
+ except RuntimeError:
+ pass
+ else:
+ raise exceptions.TempestException(
+ "If you see this, then expected exception was not raised.")
+
+ def test_setup_class_raises_runtime_error(self):
+ """No-op test just to call setUp."""
diff --git a/tempest/tests/lib/test_credentials.py b/tempest/tests/lib/test_credentials.py
new file mode 100644
index 0000000..b6f2cf6
--- /dev/null
+++ b/tempest/tests/lib/test_credentials.py
@@ -0,0 +1,182 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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 import auth
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as v2_client
+from tempest.lib.services.identity.v3 import token_client as v3_client
+from tempest.tests import base
+from tempest.tests.lib import fake_identity
+
+
+class CredentialsTests(base.TestCase):
+ attributes = {}
+ credentials_class = auth.Credentials
+
+ def _get_credentials(self, attributes=None):
+ if attributes is None:
+ attributes = self.attributes
+ return self.credentials_class(**attributes)
+
+ def _check(self, credentials, credentials_class, filled):
+ # Check the right version of credentials has been returned
+ self.assertIsInstance(credentials, credentials_class)
+ # Check the id attributes are filled in
+ # NOTE(andreaf) project_* attributes are accepted as input but
+ # never set on the credentials object
+ attributes = [x for x in credentials.ATTRIBUTES if (
+ '_id' in x and x != 'domain_id' and x != 'project_id')]
+ for attr in attributes:
+ if filled:
+ self.assertIsNotNone(getattr(credentials, attr))
+ else:
+ self.assertIsNone(getattr(credentials, attr))
+
+ def test_create(self):
+ creds = self._get_credentials()
+ self.assertEqual(self.attributes, creds._initial)
+
+ def test_create_invalid_attr(self):
+ self.assertRaises(exceptions.InvalidCredentials,
+ self._get_credentials,
+ attributes=dict(invalid='fake'))
+
+ def test_is_valid(self):
+ creds = self._get_credentials()
+ self.assertRaises(NotImplementedError, creds.is_valid)
+
+
+class KeystoneV2CredentialsTests(CredentialsTests):
+ attributes = {
+ 'username': 'fake_username',
+ 'password': 'fake_password',
+ 'tenant_name': 'fake_tenant_name'
+ }
+
+ identity_response = fake_identity._fake_v2_response
+ credentials_class = auth.KeystoneV2Credentials
+ tokenclient_class = v2_client.TokenClient
+ identity_version = 'v2'
+
+ def setUp(self):
+ super(KeystoneV2CredentialsTests, self).setUp()
+ self.patchobject(self.tokenclient_class, 'raw_request',
+ self.identity_response)
+
+ def _verify_credentials(self, credentials_class, creds_dict, filled=True):
+ creds = auth.get_credentials(fake_identity.FAKE_AUTH_URL,
+ fill_in=filled,
+ identity_version=self.identity_version,
+ **creds_dict)
+ self._check(creds, credentials_class, filled)
+
+ def test_get_credentials(self):
+ self._verify_credentials(credentials_class=self.credentials_class,
+ creds_dict=self.attributes)
+
+ def test_get_credentials_not_filled(self):
+ self._verify_credentials(credentials_class=self.credentials_class,
+ creds_dict=self.attributes,
+ filled=False)
+
+ def test_is_valid(self):
+ creds = self._get_credentials()
+ self.assertTrue(creds.is_valid())
+
+ def _test_is_not_valid(self, ignore_key):
+ creds = self._get_credentials()
+ for attr in self.attributes.keys():
+ if attr == ignore_key:
+ continue
+ temp_attr = getattr(creds, attr)
+ delattr(creds, attr)
+ self.assertFalse(creds.is_valid(),
+ "Credentials should be invalid without %s" % attr)
+ setattr(creds, attr, temp_attr)
+
+ def test_is_not_valid(self):
+ # NOTE(mtreinish): A KeystoneV2 credential object is valid without
+ # a tenant_name. So skip that check. See tempest.auth for the valid
+ # credential requirements
+ self._test_is_not_valid('tenant_name')
+
+ def test_reset_all_attributes(self):
+ creds = self._get_credentials()
+ initial_creds = copy.deepcopy(creds)
+ set_attr = creds.__dict__.keys()
+ missing_attr = set(creds.ATTRIBUTES).difference(set_attr)
+ # Set all unset attributes, then reset
+ for attr in missing_attr:
+ setattr(creds, attr, 'fake' + attr)
+ creds.reset()
+ # Check reset credentials are same as initial ones
+ self.assertEqual(creds, initial_creds)
+
+ def test_reset_single_attribute(self):
+ creds = self._get_credentials()
+ initial_creds = copy.deepcopy(creds)
+ set_attr = creds.__dict__.keys()
+ missing_attr = set(creds.ATTRIBUTES).difference(set_attr)
+ # Set one unset attributes, then reset
+ for attr in missing_attr:
+ setattr(creds, attr, 'fake' + attr)
+ creds.reset()
+ # Check reset credentials are same as initial ones
+ self.assertEqual(creds, initial_creds)
+
+
+class KeystoneV3CredentialsTests(KeystoneV2CredentialsTests):
+ attributes = {
+ 'username': 'fake_username',
+ 'password': 'fake_password',
+ 'project_name': 'fake_project_name',
+ 'user_domain_name': 'fake_domain_name'
+ }
+
+ credentials_class = auth.KeystoneV3Credentials
+ identity_response = fake_identity._fake_v3_response
+ tokenclient_class = v3_client.V3TokenClient
+ identity_version = 'v3'
+
+ def test_is_not_valid(self):
+ # NOTE(mtreinish) For a Keystone V3 credential object a project name
+ # is not required to be valid, so we skip that check. See tempest.auth
+ # for the valid credential requirements
+ self._test_is_not_valid('project_name')
+
+ def test_synced_attributes(self):
+ attributes = self.attributes
+ # Create V3 credentials with tenant instead of project, and user_domain
+ for attr in ['project_id', 'user_domain_id']:
+ attributes[attr] = 'fake_' + attr
+ creds = self._get_credentials(attributes)
+ self.assertEqual(creds.project_name, creds.tenant_name)
+ self.assertEqual(creds.project_id, creds.tenant_id)
+ self.assertEqual(creds.user_domain_name, creds.project_domain_name)
+ self.assertEqual(creds.user_domain_id, creds.project_domain_id)
+ # Replace user_domain with project_domain
+ del attributes['user_domain_name']
+ del attributes['user_domain_id']
+ del attributes['project_name']
+ del attributes['project_id']
+ for attr in ['project_domain_name', 'project_domain_id',
+ 'tenant_name', 'tenant_id']:
+ attributes[attr] = 'fake_' + attr
+ self.assertEqual(creds.tenant_name, creds.project_name)
+ self.assertEqual(creds.tenant_id, creds.project_id)
+ self.assertEqual(creds.project_domain_name, creds.user_domain_name)
+ self.assertEqual(creds.project_domain_id, creds.user_domain_id)
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
new file mode 100644
index 0000000..f3a4e9c
--- /dev/null
+++ b/tempest/tests/lib/test_decorators.py
@@ -0,0 +1,125 @@
+# Copyright 2013 IBM Corp
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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.lib import base as test
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.tests import base
+
+
+class TestSkipBecauseDecorator(base.TestCase):
+ def _test_skip_because_helper(self, expected_to_skip=True,
+ **decorator_args):
+ class TestFoo(test.BaseTestCase):
+ _interface = 'json'
+
+ @decorators.skip_because(**decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ else:
+ # assert that test_bar returned 0
+ self.assertEqual(TestFoo('test_bar').test_bar(), 0)
+
+ def test_skip_because_bug(self):
+ self._test_skip_because_helper(bug='12345')
+
+ def test_skip_because_bug_and_condition_true(self):
+ self._test_skip_because_helper(bug='12348', condition=True)
+
+ def test_skip_because_bug_and_condition_false(self):
+ self._test_skip_because_helper(expected_to_skip=False,
+ bug='12349', condition=False)
+
+ def test_skip_because_bug_without_bug_never_skips(self):
+ """Never skip without a bug parameter."""
+ self._test_skip_because_helper(expected_to_skip=False,
+ condition=True)
+ self._test_skip_because_helper(expected_to_skip=False)
+
+ def test_skip_because_invalid_bug_number(self):
+ """Raise ValueError if with an invalid bug number"""
+ self.assertRaises(ValueError, self._test_skip_because_helper,
+ bug='critical_bug')
+
+
+class TestIdempotentIdDecorator(base.TestCase):
+ def _test_helper(self, _id, **decorator_args):
+ @decorators.idempotent_id(_id)
+ def foo():
+ """Docstring"""
+ pass
+
+ return foo
+
+ def _test_helper_without_doc(self, _id, **decorator_args):
+ @decorators.idempotent_id(_id)
+ def foo():
+ pass
+
+ return foo
+
+ def test_positive(self):
+ _id = data_utils.rand_uuid()
+ foo = self._test_helper(_id)
+ self.assertIn('id-%s' % _id, getattr(foo, '__testtools_attrs'))
+ self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
+
+ def test_positive_without_doc(self):
+ _id = data_utils.rand_uuid()
+ foo = self._test_helper_without_doc(_id)
+ self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
+
+ def test_idempotent_id_not_str(self):
+ _id = 42
+ self.assertRaises(TypeError, self._test_helper, _id)
+
+ def test_idempotent_id_not_valid_uuid(self):
+ _id = '42'
+ self.assertRaises(ValueError, self._test_helper, _id)
+
+
+class TestSkipUnlessAttrDecorator(base.TestCase):
+ def _test_skip_unless_attr(self, attr, expected_to_skip=True):
+ class TestFoo(test.BaseTestCase):
+ expected_attr = not expected_to_skip
+
+ @decorators.skip_unless_attr(attr)
+ def test_foo(self):
+ pass
+
+ t = TestFoo('test_foo')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException,
+ t.test_foo)
+ else:
+ try:
+ t.test_foo()
+ except Exception:
+ raise testtools.TestCase.failureException()
+
+ def test_skip_attr_does_not_exist(self):
+ self._test_skip_unless_attr('unexpected_attr')
+
+ def test_skip_attr_false(self):
+ self._test_skip_unless_attr('expected_attr')
+
+ def test_no_skip_for_attr_exist_and_true(self):
+ self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
new file mode 100644
index 0000000..2a6fad5
--- /dev/null
+++ b/tempest/tests/lib/test_rest_client.py
@@ -0,0 +1,1136 @@
+# Copyright 2013 IBM Corp.
+#
+# 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 json
+
+import jsonschema
+from oslotest import mockpatch
+import six
+
+from tempest.lib.common import http
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.tests import base
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+import tempest.tests.utils as utils
+
+
+class BaseRestClientTestClass(base.TestCase):
+
+ url = 'fake_endpoint'
+
+ def setUp(self):
+ super(BaseRestClientTestClass, self).setUp()
+ self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
+ 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'))
+
+
+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'))
+
+ def test_post(self):
+ __, return_dict = self.rest_client.post(self.url, {}, {})
+ self.assertEqual('POST', return_dict['method'])
+
+ def test_get(self):
+ __, return_dict = self.rest_client.get(self.url)
+ self.assertEqual('GET', return_dict['method'])
+
+ def test_delete(self):
+ __, return_dict = self.rest_client.delete(self.url)
+ self.assertEqual('DELETE', return_dict['method'])
+
+ def test_patch(self):
+ __, return_dict = self.rest_client.patch(self.url, {}, {})
+ self.assertEqual('PATCH', return_dict['method'])
+
+ def test_put(self):
+ __, return_dict = self.rest_client.put(self.url, {}, {})
+ self.assertEqual('PUT', return_dict['method'])
+
+ def test_head(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+ __, return_dict = self.rest_client.head(self.url)
+ self.assertEqual('HEAD', return_dict['method'])
+
+ def test_copy(self):
+ __, return_dict = self.rest_client.copy(self.url)
+ self.assertEqual('COPY', return_dict['method'])
+
+
+class TestRestClientNotFoundHandling(BaseRestClientTestClass):
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2(404)
+ super(TestRestClientNotFoundHandling, self).setUp()
+
+ def test_post(self):
+ self.assertRaises(exceptions.NotFound, self.rest_client.post,
+ self.url, {}, {})
+
+
+class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
+ TYPE = "json"
+
+ def _verify_headers(self, resp):
+ self.assertEqual(self.rest_client._get_type(), self.TYPE)
+ resp = dict((k.lower(), v) for k, v in six.iteritems(resp))
+ self.assertEqual(self.header_value, resp['accept'])
+ self.assertEqual(self.header_value, resp['content-type'])
+
+ def setUp(self):
+ super(TestRestClientHeadersJSON, self).setUp()
+ self.rest_client.TYPE = self.TYPE
+ self.header_value = 'application/%s' % self.rest_client._get_type()
+
+ def test_post(self):
+ resp, __ = self.rest_client.post(self.url, {})
+ self._verify_headers(resp)
+
+ def test_get(self):
+ resp, __ = self.rest_client.get(self.url)
+ self._verify_headers(resp)
+
+ def test_delete(self):
+ resp, __ = self.rest_client.delete(self.url)
+ self._verify_headers(resp)
+
+ def test_patch(self):
+ resp, __ = self.rest_client.patch(self.url, {})
+ self._verify_headers(resp)
+
+ def test_put(self):
+ resp, __ = self.rest_client.put(self.url, {})
+ self._verify_headers(resp)
+
+ def test_head(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+ resp, __ = self.rest_client.head(self.url)
+ self._verify_headers(resp)
+
+ def test_copy(self):
+ resp, __ = self.rest_client.copy(self.url)
+ self._verify_headers(resp)
+
+
+class TestRestClientUpdateHeaders(BaseRestClientTestClass):
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientUpdateHeaders, self).setUp()
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ '_error_checker'))
+ self.headers = {'X-Configuration-Session': 'session_id'}
+
+ def test_post_update_headers(self):
+ __, return_dict = self.rest_client.post(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_get_update_headers(self):
+ __, return_dict = self.rest_client.get(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_delete_update_headers(self):
+ __, return_dict = self.rest_client.delete(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_patch_update_headers(self):
+ __, return_dict = self.rest_client.patch(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_put_update_headers(self):
+ __, return_dict = self.rest_client.put(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_head_update_headers(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+
+ __, return_dict = self.rest_client.head(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_copy_update_headers(self):
+ __, return_dict = self.rest_client.copy(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+
+class TestRestClientParseRespJSON(BaseRestClientTestClass):
+ TYPE = "json"
+
+ keys = ["fake_key1", "fake_key2"]
+ values = ["fake_value1", "fake_value2"]
+ item_expected = dict((key, value) for (key, value) in zip(keys, values))
+ list_expected = {"body_list": [
+ {keys[0]: values[0]},
+ {keys[1]: values[1]},
+ ]}
+ dict_expected = {"body_dict": {
+ keys[0]: values[0],
+ keys[1]: values[1],
+ }}
+ null_dict = {}
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientParseRespJSON, self).setUp()
+ self.rest_client.TYPE = self.TYPE
+
+ def test_parse_resp_body_item(self):
+ body = self.rest_client._parse_resp(json.dumps(self.item_expected))
+ self.assertEqual(self.item_expected, body)
+
+ def test_parse_resp_body_list(self):
+ body = self.rest_client._parse_resp(json.dumps(self.list_expected))
+ self.assertEqual(self.list_expected["body_list"], body)
+
+ def test_parse_resp_body_dict(self):
+ body = self.rest_client._parse_resp(json.dumps(self.dict_expected))
+ self.assertEqual(self.dict_expected["body_dict"], body)
+
+ def test_parse_resp_two_top_keys(self):
+ dict_two_keys = self.dict_expected.copy()
+ dict_two_keys.update({"second_key": ""})
+ body = self.rest_client._parse_resp(json.dumps(dict_two_keys))
+ self.assertEqual(dict_two_keys, body)
+
+ def test_parse_resp_one_top_key_without_list_or_dict(self):
+ data = {"one_top_key": "not_list_or_dict_value"}
+ body = self.rest_client._parse_resp(json.dumps(data))
+ self.assertEqual(data, body)
+
+ def test_parse_nullable_dict(self):
+ body = self.rest_client._parse_resp(json.dumps(self.null_dict))
+ self.assertEqual(self.null_dict, body)
+
+
+class TestRestClientErrorCheckerJSON(base.TestCase):
+ c_type = "application/json"
+
+ def set_data(self, r_code, enc=None, r_body=None, absolute_limit=True):
+ if enc is None:
+ enc = self.c_type
+ resp_dict = {'status': r_code, 'content-type': enc}
+ resp_body = {'resp_body': 'fake_resp_body'}
+
+ if absolute_limit is False:
+ resp_dict.update({'retry-after': 120})
+ resp_body.update({'overLimit': {'message': 'fake_message'}})
+ resp = fake_http.fake_http_response(headers=resp_dict,
+ status=int(r_code),
+ body=json.dumps(resp_body))
+ data = {
+ "method": "fake_method",
+ "url": "fake_url",
+ "headers": "fake_headers",
+ "body": "fake_body",
+ "resp": resp,
+ "resp_body": json.dumps(resp_body)
+ }
+ if r_body is not None:
+ data.update({"resp_body": r_body})
+ return data
+
+ def setUp(self):
+ super(TestRestClientErrorCheckerJSON, self).setUp()
+ self.rest_client = rest_client.RestClient(
+ fake_auth_provider.FakeAuthProvider(), None, None)
+
+ def test_response_less_than_400(self):
+ self.rest_client._error_checker(**self.set_data("399"))
+
+ def _test_error_checker(self, exception_type, data):
+ e = self.assertRaises(exception_type,
+ self.rest_client._error_checker,
+ **data)
+ self.assertEqual(e.resp, data['resp'])
+ self.assertTrue(hasattr(e, 'resp_body'))
+ return e
+
+ def test_response_400(self):
+ self._test_error_checker(exceptions.BadRequest, self.set_data("400"))
+
+ def test_response_401(self):
+ self._test_error_checker(exceptions.Unauthorized, self.set_data("401"))
+
+ def test_response_403(self):
+ self._test_error_checker(exceptions.Forbidden, self.set_data("403"))
+
+ def test_response_404(self):
+ self._test_error_checker(exceptions.NotFound, self.set_data("404"))
+
+ def test_response_409(self):
+ self._test_error_checker(exceptions.Conflict, self.set_data("409"))
+
+ def test_response_410(self):
+ self._test_error_checker(exceptions.Gone, self.set_data("410"))
+
+ def test_response_413(self):
+ self._test_error_checker(exceptions.OverLimit, self.set_data("413"))
+
+ def test_response_413_without_absolute_limit(self):
+ self._test_error_checker(exceptions.RateLimitExceeded,
+ self.set_data("413", absolute_limit=False))
+
+ def test_response_415(self):
+ self._test_error_checker(exceptions.InvalidContentType,
+ self.set_data("415"))
+
+ def test_response_422(self):
+ self._test_error_checker(exceptions.UnprocessableEntity,
+ self.set_data("422"))
+
+ def test_response_500_with_text(self):
+ # _parse_resp is expected to return 'str'
+ self._test_error_checker(exceptions.ServerFault, self.set_data("500"))
+
+ def test_response_501_with_text(self):
+ self._test_error_checker(exceptions.NotImplemented,
+ self.set_data("501"))
+
+ def test_response_400_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.BadRequest,
+ self.set_data("400", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_401_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Unauthorized,
+ self.set_data("401", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_403_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Forbidden,
+ self.set_data("403", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_404_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.NotFound,
+ self.set_data("404", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_404_with_invalid_dict(self):
+ r_body = '{"foo": "bar"]'
+ e = self._test_error_checker(exceptions.NotFound,
+ self.set_data("404", r_body=r_body))
+
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_410_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Gone,
+ self.set_data("410", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_410_with_invalid_dict(self):
+ r_body = '{"foo": "bar"]'
+ e = self._test_error_checker(exceptions.Gone,
+ self.set_data("410", r_body=r_body))
+
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_409_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Conflict,
+ self.set_data("409", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_500_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.ServerFault,
+ self.set_data("500", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_501_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ self._test_error_checker(exceptions.NotImplemented,
+ self.set_data("501", r_body=r_body))
+
+ def test_response_bigger_than_400(self):
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self._test_error_checker(exceptions.UnexpectedResponseCode,
+ self.set_data("402"))
+
+
+class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
+ c_type = "text/plain"
+
+ def test_fake_content_type(self):
+ # This test is required only in one exemplar
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self._test_error_checker(exceptions.UnexpectedContentType,
+ self.set_data("405", enc="fake_enc"))
+
+ def test_response_413_without_absolute_limit(self):
+ # Skip this test because rest_client cannot get overLimit message
+ # from text body.
+ pass
+
+
+class TestRestClientUtils(BaseRestClientTestClass):
+
+ def _is_resource_deleted(self, resource_id):
+ if not isinstance(self.retry_pass, int):
+ return False
+ if self.retry_count >= self.retry_pass:
+ return True
+ self.retry_count = self.retry_count + 1
+ return False
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientUtils, self).setUp()
+ self.retry_count = 0
+ self.retry_pass = None
+ self.original_deleted_method = self.rest_client.is_resource_deleted
+ self.rest_client.is_resource_deleted = self._is_resource_deleted
+
+ def test_wait_for_resource_deletion(self):
+ self.retry_pass = 2
+ # Ensure timeout long enough for loop execution to hit retry count
+ self.rest_client.build_timeout = 500
+ sleep_mock = self.patch('time.sleep')
+ self.rest_client.wait_for_resource_deletion('1234')
+ self.assertEqual(len(sleep_mock.mock_calls), 2)
+
+ def test_wait_for_resource_deletion_not_deleted(self):
+ self.patch('time.sleep')
+ # Set timeout to be very quick to force exception faster
+ timeout = 1
+ self.rest_client.build_timeout = timeout
+
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(timeout)
+
+ self.assertRaises(exceptions.TimeoutException,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ # time.time() should be called twice, first to start the timer
+ # and then to compute the timedelta
+ self.assertEqual(2, time_mock.call_count)
+
+ def test_wait_for_deletion_with_unimplemented_deleted_method(self):
+ self.rest_client.is_resource_deleted = self.original_deleted_method
+ self.assertRaises(NotImplementedError,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ def test_get_versions(self):
+ self.rest_client._parse_resp = lambda x: [{'id': 'v1'}, {'id': 'v2'}]
+ actual_resp, actual_versions = self.rest_client.get_versions()
+ self.assertEqual(['v1', 'v2'], list(actual_versions))
+
+ def test__str__(self):
+ def get_token():
+ return "deadbeef"
+
+ self.fake_auth_provider.get_token = get_token
+ self.assertIsNotNone(str(self.rest_client))
+
+
+class TestRateLimiting(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRateLimiting, self).setUp()
+
+ def test__get_retry_after_delay_with_integer(self):
+ resp = {'retry-after': '123'}
+ self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_with_http_date(self):
+ resp = {
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
+ }
+ self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_of_zero_with_integer(self):
+ resp = {'retry-after': '0'}
+ self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_of_zero_with_http_date(self):
+ resp = {
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ }
+ self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_with_missing_date_header(self):
+ resp = {
+ 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
+ }
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ resp)
+
+ def test__get_retry_after_delay_with_invalid_http_date(self):
+ resp = {
+ 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT',
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ }
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ resp)
+
+ def test__get_retry_after_delay_with_missing_retry_after_header(self):
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ {})
+
+ def test_is_absolute_limit_gives_false_with_retry_after(self):
+ resp = {'retry-after': 123}
+
+ # is_absolute_limit() requires the overLimit body to be unwrapped
+ resp_body = self.rest_client._parse_resp("""{
+ "overLimit": {
+ "message": ""
+ }
+ }""")
+ self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body))
+
+
+class TestProperties(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestProperties, self).setUp()
+ creds_dict = {
+ 'username': 'test-user',
+ 'user_id': 'test-user_id',
+ 'tenant_name': 'test-tenant_name',
+ 'tenant_id': 'test-tenant_id',
+ 'password': 'test-password'
+ }
+ self.rest_client = rest_client.RestClient(
+ fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
+ None, None)
+
+ def test_properties(self):
+ self.assertEqual('test-user', self.rest_client.user)
+ self.assertEqual('test-user_id', self.rest_client.user_id)
+ self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
+ self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
+ self.assertEqual('test-password', self.rest_client.password)
+
+ self.rest_client.api_version = 'v1'
+ expected = {'api_version': 'v1',
+ 'endpoint_type': 'publicURL',
+ 'region': None,
+ 'service': None,
+ 'skip_path': True}
+ self.rest_client.skip_path()
+ self.assertEqual(expected, self.rest_client.filters)
+
+ self.rest_client.reset_path()
+ self.rest_client.api_version = 'v1'
+ expected = {'api_version': 'v1',
+ 'endpoint_type': 'publicURL',
+ 'region': None,
+ 'service': None}
+ self.assertEqual(expected, self.rest_client.filters)
+
+
+class TestExpectedSuccess(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestExpectedSuccess, self).setUp()
+
+ def test_expected_succes_int_match(self):
+ expected_code = 202
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_int_no_match(self):
+ expected_code = 204
+ read_code = 202
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_expected_succes_list_match(self):
+ expected_code = [202, 204]
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_list_no_match(self):
+ expected_code = [202, 204]
+ read_code = 200
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_int(self):
+ expected_code = 404
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_list(self):
+ expected_code = [404, 202]
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+
+class TestResponseBody(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ body = {'key1': 'value1'}
+ actual = rest_client.ResponseBody(response, body)
+ self.assertEqual("response: %s\nBody: %s" % (response, body),
+ str(actual))
+
+
+class TestResponseBodyData(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ data = 'data1'
+ actual = rest_client.ResponseBodyData(response, data)
+ self.assertEqual("response: %s\nBody: %s" % (response, data),
+ str(actual))
+
+
+class TestResponseBodyList(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ body = ['value1', 'value2', 'value3']
+ actual = rest_client.ResponseBodyList(response, body)
+ self.assertEqual("response: %s\nBody: %s" % (response, body),
+ str(actual))
+
+
+class TestJSONSchemaValidationBase(base.TestCase):
+
+ class Response(dict):
+
+ def __getattr__(self, attr):
+ return self[attr]
+
+ def __setattr__(self, attr, value):
+ self[attr] = value
+
+ def setUp(self):
+ super(TestJSONSchemaValidationBase, self).setUp()
+ self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
+ self.rest_client = rest_client.RestClient(
+ self.fake_auth_provider, None, None)
+
+ def _test_validate_pass(self, schema, resp_body, status=200):
+ resp = self.Response()
+ resp.status = status
+ self.rest_client.validate_response(schema, resp, resp_body)
+
+ def _test_validate_fail(self, schema, resp_body, status=200,
+ error_msg="HTTP response body is invalid"):
+ resp = self.Response()
+ resp.status = status
+ ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
+ self.rest_client.validate_response,
+ schema, resp, resp_body)
+ self.assertIn(error_msg, ex._error_string)
+
+
+class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'integer',
+ },
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_pass_with_http_success_code(self):
+ body = {'foo': 12}
+ self._test_validate_pass(self.schema, body, status=200)
+
+ def test_validate_pass_with_http_redirect_code(self):
+ body = {'foo': 12}
+ schema = copy.deepcopy(self.schema)
+ schema['status_code'] = 300
+ self._test_validate_pass(schema, body, status=300)
+
+ def test_validate_not_http_success_code(self):
+ schema = {
+ 'status_code': [200]
+ }
+ body = {}
+ self._test_validate_pass(schema, body, status=400)
+
+ def test_validate_multiple_allowed_type(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': ['integer', 'string'],
+ },
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_pass(schema, body)
+ body = {'foo': '12'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_enable_additional_property_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': True,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12, 'foo2': 'foo2value'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_disable_additional_property_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_disable_additional_property_fail(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12, 'foo2': 'foo2value'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_wrong_status_code(self):
+ schema = {
+ 'status_code': [202]
+ }
+ body = {}
+ resp = self.Response()
+ resp.status = 200
+ ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.validate_response,
+ schema, resp, body)
+ self.assertIn("Unexpected http success status code", ex._error_string)
+
+ def test_validate_wrong_attribute_type(self):
+ body = {'foo': 1.2}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_unexpected_response_body(self):
+ schema = {
+ 'status_code': [200],
+ }
+ body = {'foo': 12}
+ self._test_validate_fail(
+ schema, body,
+ error_msg="HTTP response body should not exist")
+
+ def test_validate_missing_response_body(self):
+ body = {}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_missing_required_attribute(self):
+ body = {'notfoo': 12}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_response_body_not_list(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'list_items': {
+ 'type': 'array',
+ 'items': {'foo': {'type': 'integer'}}
+ }
+ },
+ 'required': ['list_items'],
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_response_body_list_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'list_items': {
+ 'type': 'array',
+ 'items': {'foo': {'type': 'integer'}}
+ }
+ },
+ 'required': ['list_items'],
+ }
+ }
+ body = {'list_items': [{'foo': 12}, {'foo': 10}]}
+ self._test_validate_pass(schema, body)
+
+
+class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_header': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_header_schema_pass(self):
+ resp_body = {}
+ resp = self.Response()
+ resp.status = 200
+ resp.foo = 12
+ self.rest_client.validate_response(self.schema, resp, resp_body)
+
+ def test_validate_header_schema_fail(self):
+ resp_body = {}
+ resp = self.Response()
+ resp.status = 200
+ resp.foo = 1.2
+ ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+ self.rest_client.validate_response,
+ self.schema, resp, resp_body)
+ self.assertIn("HTTP response header is invalid", ex._error_string)
+
+
+class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'format': 'email'
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_format_pass(self):
+ body = {'foo': 'example@example.com'}
+ self._test_validate_pass(self.schema, body)
+
+ def test_validate_format_fail(self):
+ body = {'foo': 'wrong_email'}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_formats_in_oneOf_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+ body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_oneOf_fail_both_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv4'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_in_oneOf_fail_no_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'wrong_ip_format'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_in_anyOf_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+ body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_anyOf_pass_both_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv4'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_anyOf_fail_no_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'wrong_ip_format'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_pass_for_unknow_format(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'format': 'UNKNOWN'
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'example@example.com'}
+ self._test_validate_pass(schema, body)
+
+
+class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'string'}
+ }
+ }
+ }
+
+ def test_current_json_schema_validator_version(self):
+ with mockpatch.PatchObject(jsonschema.Draft4Validator,
+ "check_schema") as chk_schema:
+ body = {'foo': 'test'}
+ self._test_validate_pass(self.schema, body)
+ chk_schema.mock.assert_called_once_with(
+ self.schema['response_body'])
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
new file mode 100644
index 0000000..b07f6bc
--- /dev/null
+++ b/tempest/tests/lib/test_ssh.py
@@ -0,0 +1,257 @@
+# Copyright 2014 OpenStack Foundation
+#
+# 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 io import StringIO
+import socket
+
+import mock
+import six
+import testtools
+
+from tempest.lib.common import ssh
+from tempest.lib import exceptions
+from tempest.tests import base
+import tempest.tests.utils as utils
+
+
+class TestSshClient(base.TestCase):
+
+ SELECT_POLLIN = 1
+
+ @mock.patch('paramiko.RSAKey.from_private_key')
+ @mock.patch('six.StringIO')
+ def test_pkey_calls_paramiko_RSAKey(self, cs_mock, rsa_mock):
+ cs_mock.return_value = mock.sentinel.csio
+ pkey = 'mykey'
+ ssh.Client('localhost', 'root', pkey=pkey)
+ rsa_mock.assert_called_once_with(mock.sentinel.csio)
+ cs_mock.assert_called_once_with('mykey')
+ rsa_mock.reset_mock()
+ cs_mock.reset_mock()
+ pkey = mock.sentinel.pkey
+ # Shouldn't call out to load a file from RSAKey, since
+ # a sentinel isn't a basestring...
+ ssh.Client('localhost', 'root', pkey=pkey)
+ self.assertEqual(0, rsa_mock.call_count)
+ self.assertEqual(0, cs_mock.call_count)
+
+ def _set_ssh_connection_mocks(self):
+ client_mock = mock.MagicMock()
+ client_mock.connect.return_value = True
+ return (self.patch('paramiko.SSHClient'),
+ self.patch('paramiko.AutoAddPolicy'),
+ client_mock)
+
+ def test_get_ssh_connection(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+ s_mock = self.patch('time.sleep')
+
+ c_mock.return_value = client_mock
+ aa_mock.return_value = mock.sentinel.aa
+
+ # Test normal case for successful connection on first try
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client._get_ssh_connection(sleep=1)
+
+ aa_mock.assert_called_once_with()
+ client_mock.set_missing_host_key_policy.assert_called_once_with(
+ mock.sentinel.aa)
+ expected_connect = [mock.call(
+ 'localhost',
+ username='root',
+ pkey=None,
+ key_filename=None,
+ look_for_keys=False,
+ timeout=10.0,
+ password=None
+ )]
+ self.assertEqual(expected_connect, client_mock.connect.mock_calls)
+ self.assertEqual(0, s_mock.call_count)
+
+ @mock.patch('time.sleep')
+ def test_get_ssh_connection_two_attemps(self, sleep_mock):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ mock.MagicMock()
+ ]
+
+ client = ssh.Client('localhost', 'root', timeout=1)
+ client._get_ssh_connection(sleep=1)
+ # We slept 2 seconds: because sleep is "1" and backoff is "1" too
+ sleep_mock.assert_called_once_with(2)
+ self.assertEqual(2, client_mock.connect.call_count)
+
+ def test_get_ssh_connection_timeout(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+
+ timeout = 2
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(timeout + 1)
+
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ socket.error,
+ socket.error,
+ ]
+
+ client = ssh.Client('localhost', 'root', timeout=timeout)
+ # We need to mock LOG here because LOG.info() calls time.time()
+ # in order to preprend a timestamp.
+ with mock.patch.object(ssh, 'LOG'):
+ self.assertRaises(exceptions.SSHTimeout,
+ client._get_ssh_connection)
+
+ # time.time() should be called twice, first to start the timer
+ # and then to compute the timedelta
+ self.assertEqual(2, time_mock.call_count)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_timeout_in_exec_command(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([0, 0, 0], True)
+
+ # Test for a timeout condition immediately raised
+ client = ssh.Client('localhost', 'root', timeout=2)
+ with testtools.ExpectedException(exceptions.TimeoutException):
+ client.exec_command("test")
+
+ chan_mock.fileno.assert_called_once_with()
+ chan_mock.exec_command.assert_called_once_with("test")
+ chan_mock.shutdown_write.assert_called_once_with()
+
+ poll_mock.register.assert_called_once_with(
+ chan_mock, self.SELECT_POLLIN)
+ poll_mock.poll.assert_called_once_with(10)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_command(self):
+ chan_mock, poll_mock, select_mock = (
+ self._set_mocks_for_select([[1, 0, 0]], True))
+
+ chan_mock.recv_exit_status.return_value = 0
+ chan_mock.recv.return_value = b''
+ chan_mock.recv_stderr.return_value = b''
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client.exec_command("test")
+
+ chan_mock.fileno.assert_called_once_with()
+ chan_mock.exec_command.assert_called_once_with("test")
+ chan_mock.shutdown_write.assert_called_once_with()
+
+ select_mock.assert_called_once_with()
+ poll_mock.register.assert_called_once_with(
+ chan_mock, self.SELECT_POLLIN)
+ poll_mock.poll.assert_called_once_with(10)
+ chan_mock.recv_ready.assert_called_once_with()
+ chan_mock.recv.assert_called_once_with(1024)
+ chan_mock.recv_stderr_ready.assert_called_once_with()
+ chan_mock.recv_stderr.assert_called_once_with(1024)
+ chan_mock.recv_exit_status.assert_called_once_with()
+
+ def _set_mocks_for_select(self, poll_data, ito_value=False):
+ gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
+ '_get_ssh_connection')
+ ito_mock = self.patch('tempest.lib.common.ssh.Client._is_timed_out')
+ csp_mock = self.patch(
+ 'tempest.lib.common.ssh.Client._can_system_poll')
+ csp_mock.return_value = True
+
+ select_mock = self.patch('select.poll', create=True)
+ client_mock = mock.MagicMock()
+ tran_mock = mock.MagicMock()
+ chan_mock = mock.MagicMock()
+ poll_mock = mock.MagicMock()
+
+ select_mock.return_value = poll_mock
+ gsc_mock.return_value = client_mock
+ ito_mock.return_value = ito_value
+ client_mock.get_transport.return_value = tran_mock
+ tran_mock.open_session().__enter__.return_value = chan_mock
+ if isinstance(poll_data[0], list):
+ poll_mock.poll.side_effect = poll_data
+ else:
+ poll_mock.poll.return_value = poll_data
+
+ return chan_mock, poll_mock, select_mock
+
+ _utf8_string = six.unichr(1071)
+ _utf8_bytes = _utf8_string.encode("utf-8")
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_good_command_output(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ chan_mock.recv_exit_status.return_value = 0
+ chan_mock.recv.side_effect = [self._utf8_bytes[0:1],
+ self._utf8_bytes[1:], b'R', b'']
+ chan_mock.recv_stderr.return_value = b''
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ out_data = client.exec_command("test")
+ self.assertEqual(self._utf8_string + 'R', out_data)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_bad_command_output(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ chan_mock.recv_exit_status.return_value = 1
+ chan_mock.recv.return_value = b''
+ chan_mock.recv_stderr.side_effect = [b'R', self._utf8_bytes[0:1],
+ self._utf8_bytes[1:], b'']
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ exc = self.assertRaises(exceptions.SSHExecCommandFailed,
+ client.exec_command, "test")
+ self.assertIn('R' + self._utf8_string, six.text_type(exc))
+
+ def test_exec_command_no_select(self):
+ gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
+ '_get_ssh_connection')
+ csp_mock = self.patch(
+ 'tempest.lib.common.ssh.Client._can_system_poll')
+ csp_mock.return_value = False
+
+ select_mock = self.patch('select.poll', create=True)
+ client_mock = mock.MagicMock()
+ tran_mock = mock.MagicMock()
+ chan_mock = mock.MagicMock()
+
+ # Test for proper reading of STDOUT and STDERROR
+
+ gsc_mock.return_value = client_mock
+ client_mock.get_transport.return_value = tran_mock
+ tran_mock.open_session().__enter__.return_value = chan_mock
+ chan_mock.recv_exit_status.return_value = 0
+
+ std_out_mock = mock.MagicMock(StringIO)
+ std_err_mock = mock.MagicMock(StringIO)
+ chan_mock.makefile.return_value = std_out_mock
+ chan_mock.makefile_stderr.return_value = std_err_mock
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client.exec_command("test")
+
+ chan_mock.makefile.assert_called_once_with('rb', 1024)
+ chan_mock.makefile_stderr.assert_called_once_with('rb', 1024)
+ std_out_mock.read.assert_called_once_with()
+ std_err_mock.read.assert_called_once_with()
+ self.assertFalse(select_mock.called)
diff --git a/tempest/tests/lib/test_tempest_lib.py b/tempest/tests/lib/test_tempest_lib.py
new file mode 100644
index 0000000..d70e53d
--- /dev/null
+++ b/tempest/tests/lib/test_tempest_lib.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+"""
+test_tempest.lib
+----------------------------------
+
+Tests for `tempest.lib` module.
+"""
+
+from tempest.tests import base
+
+
+class TestTempest_lib(base.TestCase):
+
+ def test_something(self):
+ pass
diff --git a/tempest/tests/negative/test_negative_auto_test.py b/tempest/tests/negative/test_negative_auto_test.py
index 7a127cd..44ce567 100644
--- a/tempest/tests/negative/test_negative_auto_test.py
+++ b/tempest/tests/negative/test_negative_auto_test.py
@@ -37,7 +37,8 @@
def setUp(self):
super(TestNegativeAutoTest, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
def _check_prop_entries(self, result, entry):
entries = [a for a in result if entry in a[0]]
diff --git a/tempest/tests/services/compute/test_base_compute_client.py b/tempest/tests/services/compute/test_base_compute_client.py
deleted file mode 100644
index 7a55cdb..0000000
--- a/tempest/tests/services/compute/test_base_compute_client.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import httplib2
-from oslotest import mockpatch
-from tempest_lib.common import rest_client
-
-from tempest import exceptions
-from tempest.services.compute.json import base_compute_client
-from tempest.tests import fake_auth_provider
-from tempest.tests.services.compute import base
-
-
-class TestMicroversionHeaderCheck(base.BaseComputeServiceTest):
-
- def setUp(self):
- super(TestMicroversionHeaderCheck, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = base_compute_client.BaseComputeClient(
- fake_auth, 'compute', 'regionOne')
- self.client.set_api_microversion('2.2')
-
- def _check_microverion_header_in_response(self, fake_response):
- def request(*args, **kwargs):
- return (httplib2.Response(fake_response), {})
-
- self.useFixture(mockpatch.PatchObject(
- rest_client.RestClient,
- 'request',
- side_effect=request))
-
- def test_correct_microverion_in_response(self):
- fake_response = {self.client.api_microversion_header_name: '2.2'}
- self._check_microverion_header_in_response(fake_response)
- self.client.get('fake_url')
-
- def test_incorrect_microverion_in_response(self):
- fake_response = {self.client.api_microversion_header_name: '2.3'}
- self._check_microverion_header_in_response(fake_response)
- self.assertRaises(exceptions.InvalidHTTPResponseHeader,
- self.client.get, 'fake_url')
-
- def test_no_microverion_header_in_response(self):
- self._check_microverion_header_in_response({})
- self.assertRaises(exceptions.InvalidHTTPResponseHeader,
- self.client.get, 'fake_url')
-
-
-class DummyServiceClient1(base_compute_client.BaseComputeClient):
- schema_versions_info = [
- {'min': None, 'max': '2.1', 'schema': 'schemav21'},
- {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'},
- {'min': '2.10', 'max': None, 'schema': 'schemav210'}]
-
- def return_selected_schema(self):
- return self.get_schema(self.schema_versions_info)
-
-
-class TestSchemaVersionsNone(base.BaseComputeServiceTest):
- api_microversion = None
- expected_schema = 'schemav21'
-
- def setUp(self):
- super(TestSchemaVersionsNone, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = DummyServiceClient1(fake_auth, 'compute', 'regionOne')
- self.client.api_microversion = self.api_microversion
-
- def test_schema(self):
- self.assertEqual(self.expected_schema,
- self.client.return_selected_schema())
-
-
-class TestSchemaVersionsV21(TestSchemaVersionsNone):
- api_microversion = '2.1'
- expected_schema = 'schemav21'
-
-
-class TestSchemaVersionsV22(TestSchemaVersionsNone):
- api_microversion = '2.2'
- expected_schema = 'schemav22'
-
-
-class TestSchemaVersionsV25(TestSchemaVersionsNone):
- api_microversion = '2.5'
- expected_schema = 'schemav22'
-
-
-class TestSchemaVersionsV29(TestSchemaVersionsNone):
- api_microversion = '2.9'
- expected_schema = 'schemav22'
-
-
-class TestSchemaVersionsV210(TestSchemaVersionsNone):
- api_microversion = '2.10'
- expected_schema = 'schemav210'
-
-
-class TestSchemaVersionsLatest(TestSchemaVersionsNone):
- api_microversion = 'latest'
- expected_schema = 'schemav210'
-
-
-class DummyServiceClient2(base_compute_client.BaseComputeClient):
- schema_versions_info = [
- {'min': None, 'max': '2.1', 'schema': 'schemav21'},
- {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'}]
-
- def return_selected_schema(self):
- return self.get_schema(self.schema_versions_info)
-
-
-class TestSchemaVersionsNotFound(base.BaseComputeServiceTest):
- api_microversion = '2.10'
- expected_schema = 'schemav210'
-
- def setUp(self):
- super(TestSchemaVersionsNotFound, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = DummyServiceClient2(fake_auth, 'compute', 'regionOne')
- self.client.api_microversion = self.api_microversion
-
- def test_schema(self):
- self.assertRaises(exceptions.JSONSchemaNotFound,
- self.client.return_selected_schema)
diff --git a/tempest/api_schema/response/__init__.py b/tempest/tests/services/object_storage/__init__.py
similarity index 100%
copy from tempest/api_schema/response/__init__.py
copy to tempest/tests/services/object_storage/__init__.py
diff --git a/tempest/tests/services/object_storage/test_object_client.py b/tempest/tests/services/object_storage/test_object_client.py
new file mode 100644
index 0000000..cd8c8f1
--- /dev/null
+++ b/tempest/tests/services/object_storage/test_object_client.py
@@ -0,0 +1,109 @@
+# 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.
+
+
+import mock
+import six
+
+from tempest.lib import exceptions
+from tempest.services.object_storage import object_client
+from tempest.tests import base
+from tempest.tests import fake_auth_provider
+
+
+class TestObjectClient(base.TestCase):
+
+ def setUp(self):
+ super(TestObjectClient, self).setUp()
+ self.fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.url = self.fake_auth.base_url(None)
+ self.object_client = object_client.ObjectClient(self.fake_auth,
+ 'swift', 'region1')
+
+ @mock.patch.object(object_client, 'create_connection')
+ def test_create_object_continue_no_data(self, mock_poc):
+ self._validate_create_object_continue(None, mock_poc)
+
+ @mock.patch.object(object_client, 'create_connection')
+ def test_create_object_continue_with_data(self, mock_poc):
+ self._validate_create_object_continue('hello', mock_poc)
+
+ @mock.patch.object(object_client, 'create_connection')
+ def test_create_continue_with_no_continue_received(self, mock_poc):
+ self._validate_create_object_continue('hello', mock_poc,
+ initial_status=201)
+
+ def _validate_create_object_continue(self, req_data,
+ mock_poc, initial_status=100):
+
+ expected_hdrs = {
+ 'X-Auth-Token': self.fake_auth.get_token(),
+ 'content-length': 0 if req_data is None else len(req_data),
+ 'Expect': '100-continue'}
+
+ # Setup the Mocks prior to invoking the object creation
+ mock_resp_cls = mock.Mock()
+ mock_resp_cls._read_status.return_value = ("1", initial_status, "OK")
+
+ mock_poc.return_value.response_class.return_value = mock_resp_cls
+
+ # This is the final expected return value
+ mock_poc.return_value.getresponse.return_value.status = 201
+ mock_poc.return_value.getresponse.return_value.reason = 'OK'
+
+ # Call method to PUT object using expect:100-continue
+ cnt = "container1"
+ obj = "object1"
+ path = "/%s/%s" % (cnt, obj)
+
+ # If the expected initial status is not 100, then an exception
+ # should be thrown and the connection closed
+ if initial_status is 100:
+ status, reason = \
+ self.object_client.create_object_continue(cnt, obj, req_data)
+ else:
+ self.assertRaises(exceptions.UnexpectedResponseCode,
+ self.object_client.create_object_continue, cnt,
+ obj, req_data)
+ mock_poc.return_value.close.assert_called_once_with()
+
+ # Verify that putrequest is called 1 time with the appropriate values
+ mock_poc.return_value.putrequest.assert_called_once_with('PUT', path)
+
+ # Verify that headers were written, including "Expect:100-continue"
+ calls = []
+
+ for header, value in six.iteritems(expected_hdrs):
+ calls.append(mock.call(header, value))
+
+ mock_poc.return_value.putheader.assert_has_calls(calls, False)
+ mock_poc.return_value.endheaders.assert_called_once_with()
+
+ # The following steps are only taken if the initial status is 100
+ if initial_status is 100:
+ # Verify that the method returned what it was supposed to
+ self.assertEqual(status, 201)
+
+ # Verify that _safe_read was called once to remove the CRLF
+ # after the 100 response
+ mock_rc = mock_poc.return_value.response_class.return_value
+ mock_rc._safe_read.assert_called_once_with(2)
+
+ # Verify the actual data was written via send
+ mock_poc.return_value.send.assert_called_once_with(req_data)
+
+ # Verify that the getresponse method was called to receive
+ # the final
+ mock_poc.return_value.getresponse.assert_called_once_with()
diff --git a/tempest/tests/services/test_base_microversion_client.py b/tempest/tests/services/test_base_microversion_client.py
deleted file mode 100644
index 11b8170..0000000
--- a/tempest/tests/services/test_base_microversion_client.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2016 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import httplib2
-import mock
-from tempest_lib.common import rest_client
-
-from tempest.services import base_microversion_client
-from tempest.tests import fake_auth_provider
-from tempest.tests.services.compute import base
-
-
-class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
-
- def setUp(self):
- super(TestClientWithoutMicroversionHeader, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = base_microversion_client.BaseMicroversionClient(
- fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
-
- def test_no_microverion_header(self):
- header = self.client.get_headers()
- self.assertNotIn(self.client.api_microversion_header_name, header)
-
- def test_no_microverion_header_in_raw_request(self):
- def raw_request(*args, **kwargs):
- self.assertNotIn(self.client.api_microversion_header_name,
- kwargs['headers'])
- return (httplib2.Response({'status': 200}), {})
-
- with mock.patch.object(rest_client.RestClient,
- 'raw_request') as mock_get:
- mock_get.side_effect = raw_request
- self.client.get('fake_url')
-
-
-class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
-
- def setUp(self):
- super(TestClientWithMicroversionHeader, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = base_microversion_client.BaseMicroversionClient(
- fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
- self.client.set_api_microversion('2.2')
-
- def test_microverion_header(self):
- header = self.client.get_headers()
- self.assertIn(self.client.api_microversion_header_name, header)
- self.assertEqual(self.client.api_microversion,
- header[self.client.api_microversion_header_name])
-
- def test_microverion_header_in_raw_request(self):
- def raw_request(*args, **kwargs):
- self.assertIn(self.client.api_microversion_header_name,
- kwargs['headers'])
- self.assertEqual(
- self.client.api_microversion,
- kwargs['headers'][self.client.api_microversion_header_name])
- return (httplib2.Response({'status': 200}), {})
-
- with mock.patch.object(rest_client.RestClient,
- 'raw_request') as mock_get:
- mock_get.side_effect = raw_request
- self.client.get('fake_url')
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index 0ec2a5d..dfe0291 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -16,9 +16,8 @@
import shlex
import subprocess
-from tempest_lib import exceptions
-
from oslo_log import log as logging
+from tempest.lib import exceptions
from tempest.tests import base
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
new file mode 100644
index 0000000..dc355b4
--- /dev/null
+++ b/tempest/tests/test_base_test.py
@@ -0,0 +1,110 @@
+# Copyright 2016 IBM Corp.
+#
+# 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 import clients
+from tempest.common import credentials_factory as credentials
+from tempest.common import fixed_network
+from tempest import config
+from tempest import test
+from tempest.tests import base
+from tempest.tests import fake_config
+
+
+class TestBaseTestCase(base.TestCase):
+ def setUp(self):
+ super(TestBaseTestCase, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.fixed_network_name = 'fixed-net'
+ config.CONF.compute.fixed_network_name = self.fixed_network_name
+ config.CONF.service_available.neutron = True
+
+ @mock.patch.object(test.BaseTestCase, 'get_client_manager')
+ @mock.patch.object(test.BaseTestCase, '_get_credentials_provider')
+ @mock.patch.object(fixed_network, 'get_tenant_network')
+ def test_get_tenant_network(self, mock_gtn, mock_gprov, mock_gcm):
+ net_client = mock.Mock()
+ mock_prov = mock.Mock()
+ mock_gcm.return_value.compute_networks_client = net_client
+ mock_gprov.return_value = mock_prov
+
+ test.BaseTestCase.get_tenant_network()
+
+ mock_gcm.assert_called_once_with(credential_type='primary')
+ mock_gprov.assert_called_once_with()
+ mock_gtn.assert_called_once_with(mock_prov, net_client,
+ self.fixed_network_name)
+
+ @mock.patch.object(test.BaseTestCase, 'get_client_manager')
+ @mock.patch.object(test.BaseTestCase, '_get_credentials_provider')
+ @mock.patch.object(fixed_network, 'get_tenant_network')
+ @mock.patch.object(test.BaseTestCase, 'get_identity_version')
+ @mock.patch.object(credentials, 'is_admin_available')
+ @mock.patch.object(clients, 'Manager')
+ def test_get_tenant_network_with_nova_net(self, mock_man, mock_iaa,
+ mock_giv, mock_gtn, mock_gcp,
+ mock_gcm):
+ config.CONF.service_available.neutron = False
+ mock_prov = mock.Mock()
+ mock_admin_man = mock.Mock()
+ mock_iaa.return_value = True
+ mock_gcp.return_value = mock_prov
+ mock_man.return_value = mock_admin_man
+
+ test.BaseTestCase.get_tenant_network()
+
+ mock_man.assert_called_once_with(
+ mock_prov.get_admin_creds.return_value)
+ mock_iaa.assert_called_once_with(
+ identity_version=mock_giv.return_value)
+ mock_gcp.assert_called_once_with()
+ mock_gtn.assert_called_once_with(
+ mock_prov, mock_admin_man.compute_networks_client,
+ self.fixed_network_name)
+
+ @mock.patch.object(test.BaseTestCase, 'get_client_manager')
+ @mock.patch.object(test.BaseTestCase, '_get_credentials_provider')
+ @mock.patch.object(fixed_network, 'get_tenant_network')
+ def test_get_tenant_network_with_alt_creds(self, mock_gtn, mock_gprov,
+ mock_gcm):
+ net_client = mock.Mock()
+ mock_prov = mock.Mock()
+ mock_gcm.return_value.compute_networks_client = net_client
+ mock_gprov.return_value = mock_prov
+
+ test.BaseTestCase.get_tenant_network(credentials_type='alt')
+
+ mock_gcm.assert_called_once_with(credential_type='alt')
+ mock_gprov.assert_called_once_with()
+ mock_gtn.assert_called_once_with(mock_prov, net_client,
+ self.fixed_network_name)
+
+ @mock.patch.object(test.BaseTestCase, 'get_client_manager')
+ @mock.patch.object(test.BaseTestCase, '_get_credentials_provider')
+ @mock.patch.object(fixed_network, 'get_tenant_network')
+ def test_get_tenant_network_with_role_creds(self, mock_gtn, mock_gprov,
+ mock_gcm):
+ net_client = mock.Mock()
+ mock_prov = mock.Mock()
+ mock_gcm.return_value.compute_networks_client = net_client
+ mock_gprov.return_value = mock_prov
+ creds = ['foo_type', 'role1']
+
+ test.BaseTestCase.get_tenant_network(credentials_type=creds)
+
+ mock_gcm.assert_called_once_with(roles=['role1'])
+ mock_gprov.assert_called_once_with()
+ mock_gtn.assert_called_once_with(mock_prov, net_client,
+ self.fixed_network_name)
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 98b045a..8c5d861 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
import mock
from oslo_config import cfg
from oslotest import mockpatch
@@ -21,6 +19,7 @@
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import data_utils
from tempest import test
from tempest.tests import base
from tempest.tests import fake_config
@@ -30,7 +29,8 @@
def setUp(self):
super(BaseDecoratorsTest, self).setUp()
self.config_fixture = self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
class TestAttrDecorator(BaseDecoratorsTest):
@@ -79,13 +79,13 @@
return foo
def test_positive(self):
- _id = str(uuid.uuid4())
+ _id = data_utils.rand_uuid()
foo = self._test_helper(_id)
self.assertIn('id-%s' % _id, getattr(foo, '__testtools_attrs'))
self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
def test_positive_without_doc(self):
- _id = str(uuid.uuid4())
+ _id = data_utils.rand_uuid()
foo = self._test_helper_without_doc(_id)
self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
@@ -201,7 +201,13 @@
if expected_to_skip:
self.assertRaises(testtools.TestCase.skipException, t.test_bar)
else:
- self.assertEqual(t.test_bar(), 0)
+ try:
+ self.assertEqual(t.test_bar(), 0)
+ except testtools.TestCase.skipException:
+ # We caught a skipException but we didn't expect to skip
+ # this test so raise a hard test failure instead.
+ raise testtools.TestCase.failureException(
+ "Not supposed to skip")
def test_requires_ext_decorator(self):
self._test_requires_ext_helper(expected_to_skip=False,
@@ -213,7 +219,7 @@
service='compute')
def test_requires_ext_decorator_with_all_ext_enabled(self):
- cfg.CONF.set_default('api_extensions', 'all',
+ cfg.CONF.set_default('api_extensions', ['all'],
group='compute-feature-enabled')
self._test_requires_ext_helper(expected_to_skip=False,
extension='random_ext',
@@ -240,3 +246,96 @@
self.assertIn("test_fake_negative", dir(obj))
obj.test_fake_negative()
mock.assert_called_once_with(self.FakeNegativeJSONTest._schema)
+
+
+class TestConfigDecorators(BaseDecoratorsTest):
+ def setUp(self):
+ super(TestConfigDecorators, self).setUp()
+ cfg.CONF.set_default('nova', True, 'service_available')
+ cfg.CONF.set_default('glance', False, 'service_available')
+
+ def _assert_skip_message(self, func, skip_msg):
+ try:
+ func()
+ self.fail()
+ except testtools.TestCase.skipException as skip_exc:
+ self.assertEqual(skip_exc.args[0], skip_msg)
+
+ def _test_skip_unless_config(self, expected_to_skip=True, *decorator_args):
+
+ class TestFoo(test.BaseTestCase):
+ @config.skip_unless_config(*decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ if (len(decorator_args) >= 3):
+ # decorator_args[2]: skip message specified
+ self._assert_skip_message(t.test_bar, decorator_args[2])
+ else:
+ try:
+ self.assertEqual(t.test_bar(), 0)
+ except testtools.TestCase.skipException:
+ # We caught a skipException but we didn't expect to skip
+ # this test so raise a hard test failure instead.
+ raise testtools.TestCase.failureException(
+ "Not supposed to skip")
+
+ def _test_skip_if_config(self, expected_to_skip=True,
+ *decorator_args):
+
+ class TestFoo(test.BaseTestCase):
+ @config.skip_if_config(*decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ if (len(decorator_args) >= 3):
+ # decorator_args[2]: skip message specified
+ self._assert_skip_message(t.test_bar, decorator_args[2])
+ else:
+ try:
+ self.assertEqual(t.test_bar(), 0)
+ except testtools.TestCase.skipException:
+ # We caught a skipException but we didn't expect to skip
+ # this test so raise a hard test failure instead.
+ raise testtools.TestCase.failureException(
+ "Not supposed to skip")
+
+ def test_skip_unless_no_group(self):
+ self._test_skip_unless_config(True, 'fake_group', 'an_option')
+
+ def test_skip_unless_no_option(self):
+ self._test_skip_unless_config(True, 'service_available',
+ 'not_an_option')
+
+ def test_skip_unless_false_option(self):
+ self._test_skip_unless_config(True, 'service_available', 'glance')
+
+ def test_skip_unless_false_option_msg(self):
+ self._test_skip_unless_config(True, 'service_available', 'glance',
+ 'skip message')
+
+ def test_skip_unless_true_option(self):
+ self._test_skip_unless_config(False,
+ 'service_available', 'nova')
+
+ def test_skip_if_no_group(self):
+ self._test_skip_if_config(False, 'fake_group', 'an_option')
+
+ def test_skip_if_no_option(self):
+ self._test_skip_if_config(False, 'service_available', 'not_an_option')
+
+ def test_skip_if_false_option(self):
+ self._test_skip_if_config(False, 'service_available', 'glance')
+
+ def test_skip_if_true_option(self):
+ self._test_skip_if_config(True, 'service_available', 'nova')
+
+ def test_skip_if_true_option_msg(self):
+ self._test_skip_if_config(True, 'service_available', 'nova',
+ 'skip message')
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index db9db34..768cd05 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import socket
+
import mock
from oslotest import mockpatch
import six
@@ -22,18 +24,13 @@
from tempest import exceptions
from tempest.tests import base
from tempest.tests import fake_auth_provider
-from tempest.tests import fake_http
+from tempest.tests.lib import fake_http
class TestGlanceHTTPClient(base.TestCase):
def setUp(self):
super(TestGlanceHTTPClient, self).setUp()
- self.fake_http = fake_http.fake_httplib2(return_type=200)
- # NOTE(maurosr): using http here implies that we will be using httplib
- # directly. With https glance_client would use an httpS version, but
- # the real backend would still be httplib anyway and since we mock it
- # that there is no reason to care.
self.endpoint = 'http://fake_url.com'
self.fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -42,12 +39,12 @@
self.useFixture(mockpatch.PatchObject(
httplib.HTTPConnection,
'request',
- side_effect=self.fake_http.request(self.endpoint)[1]))
+ side_effect=b'fake_body'))
self.client = glance_http.HTTPClient(self.fake_auth, {})
def _set_response_fixture(self, header, status, resp_body):
- resp = fake_http.fake_httplib(header, status=status,
- body=six.StringIO(resp_body))
+ resp = fake_http.fake_http_response(header, status=status,
+ body=six.StringIO(resp_body))
self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
'getresponse', return_value=resp))
return resp
@@ -91,15 +88,29 @@
self.assertEqual(httplib.HTTPConnection, conn_class)
def test_get_connection_http(self):
- self.assertTrue(isinstance(self.client._get_connection(),
- httplib.HTTPConnection))
+ self.assertIsInstance(self.client._get_connection(),
+ httplib.HTTPConnection)
def test_get_connection_https(self):
endpoint = 'https://fake_url.com'
self.fake_auth.base_url = mock.MagicMock(return_value=endpoint)
self.client = glance_http.HTTPClient(self.fake_auth, {})
- self.assertTrue(isinstance(self.client._get_connection(),
- glance_http.VerifiedHTTPSConnection))
+ self.assertIsInstance(self.client._get_connection(),
+ glance_http.VerifiedHTTPSConnection)
+
+ def test_get_connection_ipv4_https(self):
+ endpoint = 'https://127.0.0.1'
+ self.fake_auth.base_url = mock.MagicMock(return_value=endpoint)
+ self.client = glance_http.HTTPClient(self.fake_auth, {})
+ self.assertIsInstance(self.client._get_connection(),
+ glance_http.VerifiedHTTPSConnection)
+
+ def test_get_connection_ipv6_https(self):
+ endpoint = 'https://[::1]'
+ self.fake_auth.base_url = mock.MagicMock(return_value=endpoint)
+ self.client = glance_http.HTTPClient(self.fake_auth, {})
+ self.assertIsInstance(self.client._get_connection(),
+ glance_http.VerifiedHTTPSConnection)
def test_get_connection_url_not_fount(self):
self.useFixture(mockpatch.PatchObject(self.client, 'connection_class',
@@ -146,10 +157,68 @@
self.assertEqual(6, len(kwargs.keys()))
+class TestVerifiedHTTPSConnection(base.TestCase):
+
+ @mock.patch('socket.socket')
+ @mock.patch('tempest.common.glance_http.OpenSSLConnectionDelegator')
+ def test_connect_ipv4(self, mock_delegator, mock_socket):
+ connection = glance_http.VerifiedHTTPSConnection('127.0.0.1')
+ connection.connect()
+
+ mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM)
+ mock_delegator.assert_called_once_with(connection.context,
+ mock_socket.return_value)
+ mock_delegator.return_value.connect.assert_called_once_with(
+ (connection.host, 443))
+
+ @mock.patch('socket.socket')
+ @mock.patch('tempest.common.glance_http.OpenSSLConnectionDelegator')
+ def test_connect_ipv6(self, mock_delegator, mock_socket):
+ connection = glance_http.VerifiedHTTPSConnection('[::1]')
+ connection.connect()
+
+ mock_socket.assert_called_once_with(socket.AF_INET6,
+ socket.SOCK_STREAM)
+ mock_delegator.assert_called_once_with(connection.context,
+ mock_socket.return_value)
+ mock_delegator.return_value.connect.assert_called_once_with(
+ (connection.host, 443, 0, 0))
+
+ @mock.patch('tempest.common.glance_http.OpenSSLConnectionDelegator')
+ @mock.patch('socket.getaddrinfo',
+ side_effect=OSError('Gettaddrinfo failed'))
+ def test_connect_with_address_lookup_failure(self, mock_getaddrinfo,
+ mock_delegator):
+ connection = glance_http.VerifiedHTTPSConnection('127.0.0.1')
+ self.assertRaises(exceptions.RestClientException, connection.connect)
+
+ mock_getaddrinfo.assert_called_once_with(
+ connection.host, connection.port, 0, socket.SOCK_STREAM)
+
+ @mock.patch('socket.socket')
+ @mock.patch('socket.getaddrinfo',
+ return_value=[(2, 1, 6, '', ('127.0.0.1', 443))])
+ @mock.patch('tempest.common.glance_http.OpenSSLConnectionDelegator')
+ def test_connect_with_socket_failure(self, mock_delegator,
+ mock_getaddrinfo,
+ mock_socket):
+ mock_delegator.return_value.connect.side_effect = \
+ OSError('Connect failed')
+
+ connection = glance_http.VerifiedHTTPSConnection('127.0.0.1')
+ self.assertRaises(exceptions.RestClientException, connection.connect)
+
+ mock_getaddrinfo.assert_called_once_with(
+ connection.host, connection.port, 0, socket.SOCK_STREAM)
+ mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM)
+ mock_delegator.return_value.connect.\
+ assert_called_once_with((connection.host, 443))
+
+
class TestResponseBodyIterator(base.TestCase):
def test_iter_default_chunk_size_64k(self):
- resp = fake_http.fake_httplib({}, six.StringIO(
+ resp = fake_http.fake_http_response({}, six.StringIO(
'X' * (glance_http.CHUNKSIZE + 1)))
iterator = glance_http.ResponseBodyIterator(resp)
chunks = list(iterator)
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index 55f00ef..aba2aab 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -147,3 +147,23 @@
" @testtools.skipUnless(CONF.something, 'msg')"))))
self.assertEqual(0, len(list(checks.no_testtools_skip_decorator(
" @testtools.skipIf(CONF.something, 'msg')"))))
+
+ def test_dont_import_local_tempest_code_into_lib(self):
+ self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib(
+ "from tempest.common import waiters",
+ './tempest/common/compute.py'))))
+ self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib(
+ "from tempest import config",
+ './tempest/common/compute.py'))))
+ self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib(
+ "import tempest.exception",
+ './tempest/common/compute.py'))))
+ self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib(
+ "from tempest.common import waiters",
+ './tempest/lib/common/compute.py'))))
+ self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib(
+ "from tempest import config",
+ './tempest/lib/common/compute.py'))))
+ self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib(
+ "import tempest.exception",
+ './tempest/lib/common/compute.py'))))
diff --git a/tempest/tests/test_microversions.py b/tempest/tests/test_microversions.py
index 6738641..173accb 100644
--- a/tempest/tests/test_microversions.py
+++ b/tempest/tests/test_microversions.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base as compute_base
from tempest import config
-from tempest import exceptions
+from tempest.lib import exceptions
from tempest.tests import base
from tempest.tests import fake_config
@@ -57,16 +57,16 @@
def setUp(self):
super(TestMicroversionsTestsClass, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
def _test_version(self, cfg_min, cfg_max,
expected_pass_tests,
expected_skip_tests):
cfg.CONF.set_default('min_microversion',
- cfg_min, group='compute-feature-enabled')
+ cfg_min, group='compute')
cfg.CONF.set_default('max_microversion',
- cfg_max, group='compute-feature-enabled')
+ cfg_max, group='compute')
try:
for test_class in expected_pass_tests:
test_class.skip_checks()
@@ -138,16 +138,16 @@
def test_config_invalid_version(self):
cfg.CONF.set_default('min_microversion',
- '2.5', group='compute-feature-enabled')
+ '2.5', group='compute')
cfg.CONF.set_default('max_microversion',
- '2.1', group='compute-feature-enabled')
+ '2.1', group='compute')
self.assertRaises(exceptions.InvalidAPIVersionRange,
VersionTestNoneTolatest.skip_checks)
def test_config_version_invalid_test_version(self):
cfg.CONF.set_default('min_microversion',
- None, group='compute-feature-enabled')
+ None, group='compute')
cfg.CONF.set_default('max_microversion',
- '2.13', group='compute-feature-enabled')
+ '2.13', group='compute')
self.assertRaises(exceptions.InvalidAPIVersionRange,
InvalidVersionTest.skip_checks)
diff --git a/tempest/tests/test_negative_rest_client.py b/tempest/tests/test_negative_rest_client.py
index ce95739..9d9c20f 100644
--- a/tempest/tests/test_negative_rest_client.py
+++ b/tempest/tests/test_negative_rest_client.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import httplib2
+import mock
from oslotest import mockpatch
from tempest.common import negative_rest_client
@@ -23,7 +23,6 @@
from tempest.tests import base
from tempest.tests import fake_auth_provider
from tempest.tests import fake_config
-from tempest.tests import fake_http
class TestNegativeRestClient(base.TestCase):
@@ -31,59 +30,70 @@
url = 'fake_endpoint'
def setUp(self):
- self.fake_http = fake_http.fake_httplib2()
super(TestNegativeRestClient, self).setUp()
self.useFixture(fake_config.ConfigFixture())
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
- self.stubs.Set(httplib2.Http, 'request', self.fake_http.request)
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
self.negative_rest_client = negative_rest_client.NegativeRestClient(
fake_auth_provider.FakeAuthProvider(), None)
self.useFixture(mockpatch.PatchObject(self.negative_rest_client,
'_log_request'))
- def test_post(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.post',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_post(self, mock_post):
__, return_dict = self.negative_rest_client.send_request('POST',
self.url,
[], {})
- self.assertEqual('POST', return_dict['method'])
+ mock_post.assert_called_once_with(self.url, {})
- def test_get(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.get',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_get(self, mock_get):
__, return_dict = self.negative_rest_client.send_request('GET',
self.url,
[])
- self.assertEqual('GET', return_dict['method'])
+ mock_get.assert_called_once_with(self.url)
- def test_delete(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.delete',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_delete(self, mock_delete):
__, return_dict = self.negative_rest_client.send_request('DELETE',
self.url,
[])
- self.assertEqual('DELETE', return_dict['method'])
+ mock_delete.assert_called_once_with(self.url)
- def test_patch(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.patch',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_patch(self, mock_patch):
__, return_dict = self.negative_rest_client.send_request('PATCH',
self.url,
[], {})
- self.assertEqual('PATCH', return_dict['method'])
+ mock_patch.assert_called_once_with(self.url, {})
- def test_put(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.put',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_put(self, mock_put):
__, return_dict = self.negative_rest_client.send_request('PUT',
self.url,
[], {})
- self.assertEqual('PUT', return_dict['method'])
+ mock_put.assert_called_once_with(self.url, {})
- def test_head(self):
- self.useFixture(mockpatch.PatchObject(self.negative_rest_client,
- 'response_checker'))
+ @mock.patch('tempest.lib.common.rest_client.RestClient.head',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_head(self, mock_head):
__, return_dict = self.negative_rest_client.send_request('HEAD',
self.url,
[])
- self.assertEqual('HEAD', return_dict['method'])
+ mock_head.assert_called_once_with(self.url)
- def test_copy(self):
+ @mock.patch('tempest.lib.common.rest_client.RestClient.copy',
+ return_value=(mock.Mock(), mock.Mock()))
+ def test_copy(self, mock_copy):
__, return_dict = self.negative_rest_client.send_request('COPY',
self.url,
[])
- self.assertEqual('COPY', return_dict['method'])
+ mock_copy.assert_called_once_with(self.url)
def test_other(self):
self.assertRaises(AssertionError,
diff --git a/tempest/tests/utils.py b/tempest/tests/utils.py
new file mode 100644
index 0000000..9c3049d
--- /dev/null
+++ b/tempest/tests/utils.py
@@ -0,0 +1,29 @@
+# Copyright 2016 OpenStack Foundation
+#
+# 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.
+#
+
+
+def generate_timeout_series(timeout):
+ """Generate a series of times that exceeds the given timeout.
+
+ Yields a series of fake time.time() floating point numbers
+ such that the difference between each pair in the series just
+ exceeds the timeout value that is passed in. Useful for
+ mocking time.time() in methods that otherwise wait for timeout
+ seconds.
+ """
+ iteration = 0
+ while True:
+ iteration += 1
+ yield (iteration * timeout) + iteration
diff --git a/tempest/version.py b/tempest/version.py
new file mode 100644
index 0000000..bc9f651
--- /dev/null
+++ b/tempest/version.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# 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 pbr.version
+
+version_info = pbr.version.VersionInfo('tempest')
diff --git a/test-requirements.txt b/test-requirements.txt
index eb43f31..763f0ba 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -6,7 +6,7 @@
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
-mox>=0.5.3 # Apache-2.0
-mock>=1.2 # BSD
+reno>=1.6.2 # Apache2
+mock>=2.0 # BSD
coverage>=3.6 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
diff --git a/tools/check_logs.py b/tools/check_logs.py
index fa7129d..e34dec3 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -54,7 +54,6 @@
'q-meta',
'q-metering',
'q-svc',
- 'q-vpn',
's-proxy'])
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
new file mode 100644
index 0000000..03dbd9b
--- /dev/null
+++ b/tools/generate-tempest-plugins-list.py
@@ -0,0 +1,70 @@
+#! /usr/bin/env python
+
+# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P.
+#
+# 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 be run as part of a periodic proposal bot
+# job in OpenStack infrastructure.
+#
+# In order to function correctly, the environment in which the
+# script runs must have
+# * network access to the review.openstack.org Gerrit API
+# working directory
+# * network access to https://git.openstack.org/cgit
+
+import json
+import re
+import requests
+
+url = 'https://review.openstack.org/projects/'
+
+# This is what a project looks like
+'''
+ "openstack-attic/akanda": {
+ "id": "openstack-attic%2Fakanda",
+ "state": "READ_ONLY"
+ },
+'''
+
+
+def is_in_openstack_namespace(proj):
+ return proj.startswith('openstack/')
+
+# Rather than returning a 404 for a nonexistent file, cgit delivers a
+# 0-byte response to a GET request. It also does not provide a
+# Content-Length in a HEAD response, so the way we tell if a file exists
+# is to check the length of the entire GET response body.
+
+
+def has_tempest_plugin(proj):
+ r = requests.get(
+ "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+ p = re.compile('^tempest\.test_plugins', re.M)
+ if p.findall(r.text):
+ return True
+ else:
+ False
+
+r = requests.get(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:])))
+
+found_plugins = filter(has_tempest_plugin, projects)
+
+# Every element of the found_plugins list begins with "openstack/".
+# We drop those initial 10 octets when printing the list.
+for project in found_plugins:
+ print(project[10:])
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
new file mode 100755
index 0000000..ecff508
--- /dev/null
+++ b/tools/generate-tempest-plugins-list.sh
@@ -0,0 +1,64 @@
+#!/bin/bash -ex
+
+# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P.
+#
+# 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 be run as a periodic proposal bot job
+# in OpenStack infrastructure, though you can run it as a one-off.
+#
+# In order to function correctly, the environment in which the
+# script runs must have
+# * a writable doc/source directory relative to the current
+# working directory
+# AND ( (
+# * git
+# * all git repos meant to be searched for plugins cloned and
+# at the desired level of up-to-datedness
+# * the environment variable git_dir pointing to the location
+# * of said git repositories
+# ) OR (
+# * network access to the review.openstack.org Gerrit API
+# working directory
+# * network access to https://git.openstack.org/cgit
+# ))
+#
+# If a file named data/tempest-plugins-registry.header or
+# data/tempest-plugins-registry.footer is found relative to the
+# current working directory, it will be prepended or appended to
+# the generated reStructuredText plugins table respectively.
+
+(
+declare -A plugins
+
+if [[ -r data/tempest-plugins-registry.header ]]; then
+ cat data/tempest-plugins-registry.header
+fi
+
+sorted_plugins=$(python tools/generate-tempest-plugins-list.py)
+
+for k in ${sorted_plugins}; do
+ project=${k:0:28}
+ giturl="git://git.openstack.org/openstack/${k:0:26}"
+ printf "|%-28s|%-73s|\n" "${project}" "${giturl}"
+ printf "+----------------------------+-------------------------------------------------------------------------+\n"
+done
+
+if [[ -r data/tempest-plugins-registry.footer ]]; then
+ cat data/tempest-plugins-registry.footer
+fi
+) > doc/source/plugin-registry.rst
+
+if [[ -n ${1} ]]; then
+ cp doc/source/plugin-registry.rst ${1}/doc/source/plugin-registry.rst
+fi
diff --git a/tools/install_venv.py b/tools/install_venv.py
deleted file mode 100644
index d6d9c8e..0000000
--- a/tools/install_venv.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Copyright 2010 OpenStack Foundation
-# Copyright 2013 IBM Corp.
-#
-# 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
-import sys
-
-import install_venv_common as install_venv # noqa
-
-
-def print_help(venv, root):
- help = """
- OpenStack development environment setup is complete.
-
- OpenStack development uses virtualenv to track and manage Python
- dependencies while in development and testing.
-
- To activate the OpenStack virtualenv for the extent of your current shell
- session you can run:
-
- $ source %s/bin/activate
-
- Or, if you prefer, you can run commands in the virtualenv on a case by case
- basis by running:
-
- $ %s/tools/with_venv.sh <your command>
-
- Also, make test will automatically use the virtualenv.
- """
- print(help % (venv, root))
-
-
-def main(argv):
- root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-
- if os.environ.get('TOOLS_PATH'):
- root = os.environ['TOOLS_PATH']
- venv = os.path.join(root, '.venv')
- if os.environ.get('VENV'):
- venv = os.environ['VENV']
-
- pip_requires = os.path.join(root, 'requirements.txt')
- test_requires = os.path.join(root, 'test-requirements.txt')
- py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
- project = 'Tempest'
- install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
- py_version, project)
- options = install.parse_args(argv)
- install.check_python_version()
- install.check_dependencies()
- install.create_virtualenv(no_site_packages=options.no_site_packages)
- install.install_dependencies()
- print_help(venv, root)
-
-if __name__ == '__main__':
- main(sys.argv)
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
deleted file mode 100644
index d1643f8..0000000
--- a/tools/install_venv_common.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# Copyright 2013 IBM Corp.
-#
-# 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.
-
-"""Provides methods needed by installation script for OpenStack development
-virtual environments.
-
-Since this script is used to bootstrap a virtualenv from the system's Python
-environment, it should be kept strictly compatible with Python 2.6.
-
-Synced in from openstack-common
-"""
-
-from __future__ import print_function
-
-import optparse
-import os
-import subprocess
-import sys
-
-
-class InstallVenv(object):
-
- def __init__(self, root, venv, requirements,
- test_requirements, py_version,
- project):
- self.root = root
- self.venv = venv
- self.requirements = requirements
- self.test_requirements = test_requirements
- self.py_version = py_version
- self.project = project
-
- def die(self, message, *args):
- print(message % args, file=sys.stderr)
- sys.exit(1)
-
- def check_python_version(self):
- if sys.version_info < (2, 6):
- self.die("Need Python Version >= 2.6")
-
- def run_command_with_code(self, cmd, redirect_output=True,
- check_exit_code=True):
- """Runs a command in an out-of-process shell.
-
- Returns the output of that command. Working directory is self.root.
- """
- if redirect_output:
- stdout = subprocess.PIPE
- else:
- stdout = None
-
- proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
- output = proc.communicate()[0]
- if check_exit_code and proc.returncode != 0:
- self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
- return (output, proc.returncode)
-
- def run_command(self, cmd, redirect_output=True, check_exit_code=True):
- return self.run_command_with_code(cmd, redirect_output,
- check_exit_code)[0]
-
- def get_distro(self):
- if (os.path.exists('/etc/fedora-release') or
- os.path.exists('/etc/redhat-release')):
- return Fedora(
- self.root, self.venv, self.requirements,
- self.test_requirements, self.py_version, self.project)
- else:
- return Distro(
- self.root, self.venv, self.requirements,
- self.test_requirements, self.py_version, self.project)
-
- def check_dependencies(self):
- self.get_distro().install_virtualenv()
-
- def create_virtualenv(self, no_site_packages=True):
- """Creates the virtual environment and installs PIP.
-
- Creates the virtual environment and installs PIP only into the
- virtual environment.
- """
- if not os.path.isdir(self.venv):
- print('Creating venv...', end=' ')
- if no_site_packages:
- self.run_command(['virtualenv', '-q', '--no-site-packages',
- self.venv])
- else:
- self.run_command(['virtualenv', '-q', self.venv])
- print('done.')
- else:
- print("venv already exists...")
- pass
-
- def pip_install(self, *args):
- self.run_command(['tools/with_venv.sh',
- 'pip', 'install', '--upgrade'] + list(args),
- redirect_output=False)
-
- def install_dependencies(self):
- print('Installing dependencies with pip (this can take a while)...')
-
- # First things first, make sure our venv has the latest pip and
- # setuptools and pbr
- self.pip_install('pip<8,>=1.4')
- self.pip_install('setuptools')
- self.pip_install('pbr')
-
- self.pip_install('-r', self.requirements, '-r', self.test_requirements)
-
- def parse_args(self, argv):
- """Parses command-line arguments."""
- parser = optparse.OptionParser()
- parser.add_option('-n', '--no-site-packages',
- action='store_true',
- help="Do not inherit packages from global Python "
- "install.")
- return parser.parse_args(argv[1:])[0]
-
-
-class Distro(InstallVenv):
-
- def check_cmd(self, cmd):
- return bool(self.run_command(['which', cmd],
- check_exit_code=False).strip())
-
- def install_virtualenv(self):
- if self.check_cmd('virtualenv'):
- return
-
- if self.check_cmd('easy_install'):
- print('Installing virtualenv via easy_install...', end=' ')
- if self.run_command(['easy_install', 'virtualenv']):
- print('Succeeded')
- return
- else:
- print('Failed')
-
- self.die('ERROR: virtualenv not found.\n\n%s development'
- ' requires virtualenv, please install it using your'
- ' favorite package management tool' % self.project)
-
-
-class Fedora(Distro):
- """This covers all Fedora-based distributions.
-
- Includes: Fedora, RHEL, CentOS, Scientific Linux
- """
-
- def check_pkg(self, pkg):
- return self.run_command_with_code(['rpm', '-q', pkg],
- check_exit_code=False)[1] == 0
-
- def install_virtualenv(self):
- if self.check_cmd('virtualenv'):
- return
-
- if not self.check_pkg('python-virtualenv'):
- self.die("Please install 'python-virtualenv'.")
-
- super(Fedora, self).install_virtualenv()
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index a47e217..b554514 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -73,21 +73,23 @@
DEF_RE = re.compile(r'\s*def (\w+)\(')
bug_found = False
results = []
- lines = open(path, 'rb').readlines()
- for x, line in enumerate(lines):
- if not bug_found:
- res = BUG_RE.match(line)
- if res:
- bug_no = int(res.group(1))
- debug("Found bug skip %s on line %d", bug_no, x + 1)
- bug_found = True
- else:
- res = DEF_RE.match(line)
- if res:
- method = res.group(1)
- debug("Found test method %s skips for bug %d", method, bug_no)
- results.append((method, bug_no))
- bug_found = False
+ with open(path, 'rb') as content:
+ lines = content.readlines()
+ for x, line in enumerate(lines):
+ if not bug_found:
+ res = BUG_RE.match(line)
+ if res:
+ bug_no = int(res.group(1))
+ debug("Found bug skip %s on line %d", bug_no, x + 1)
+ bug_found = True
+ else:
+ res = DEF_RE.match(line)
+ if res:
+ method = res.group(1)
+ debug("Found test method %s skips for bug %d",
+ method, bug_no)
+ results.append((method, bug_no))
+ bug_found = False
return results
diff --git a/tools/use_tempest_lib.sh b/tools/use_tempest_lib.sh
deleted file mode 100755
index ca62c4a..0000000
--- a/tools/use_tempest_lib.sh
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/bash
-#
-# Use this script to use interfaces/files from tempest-lib.
-# Many files have been migrated to tempest-lib and tempest has
-# its own copy too.
-# This script helps to remove those from tempest and make use of tempest-lib.
-# It adds the change-id of each file on which they were migrated in lib.
-# This should only be done for files which were migrated to lib with
-# "Migrated" in commit message as done by tempest-lib/tools/migrate_from_tempest.sh script.
-# "Migrated" keyword is used to fetch the migration commit history from lib.
-# To use:
-# 1. Create a new branch in the tempest repo so not to destroy your current
-# working branch
-# 2. Run the script from the repo dir and specify the file paths relative to
-# the root tempest dir(only code and unit tests):
-#
-# tools/use_tempest_lib.sh.sh tempest/file1.py tempest/file2.py
-
-
-function usage {
- echo "Usage: $0 [OPTION] file1 file2 .."
- echo "Use files from tempest-lib"
- echo -e "Input files should be tempest files with path. \n Example- tempest/file1.py tempest/file2.py .."
- echo ""
- echo "-s, --service_client Specify if files are service clients."
- echo "-u, --tempest_lib_git_url Specify the repo to clone tempest-lib from."
-}
-
-function check_all_files_valid {
- failed=0
- for file in $files; do
- # Get the latest change-id for each file
- latest_commit_id=`git log -n1 -- $file | grep "^commit" | awk '{print $2}'`
- cd $tmpdir
- filename=`basename $file`
- lib_path=`find ./ -name $filename`
- if [ -z $lib_path ]; then
- echo "ERROR: $filename does not exist in tempest-lib."
- failed=$(( failed + 1))
- cd - > /dev/null
- continue
- fi
- # Get the CHANGE_ID of tempest-lib patch where file was migrated
- migration_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "Change-Id: " | awk '{print $2}'`
- MIGRATION_IDS=`echo -e "$MIGRATION_IDS\n * $filename: $migration_change_id"`
- # Get tempest CHANGE_ID of file which was migrated to lib
- migrated_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "* $filename"`
- migrated_change_id=${migrated_change_id#*:}
- cd - > /dev/null
- # Get the commit-id of tempest which was migrated to tempest-lib
- migrated_commit_id=`git log --grep "$migrated_change_id" -- $file | grep "^commit" | awk '{print $2}'`
- DIFF=$(git diff $latest_commit_id $migrated_commit_id $file)
- if [ "$DIFF" != "" ]; then
- echo "ERROR: $filename in tempest has been updated after migration to tempest-lib. First sync the file to tempest-lib."
- failed=$(( failed + 1))
- fi
- done
- if [[ $failed -gt 0 ]]; then
- echo "$failed files had issues"
- exit $failed
- fi
-}
-
-set -e
-
-service_client=0
-file_list=''
-
-while [ $# -gt 0 ]; do
- case "$1" in
- -h|--help) usage; exit;;
- -u|--tempest_lib_git_url) tempest_lib_git_url="$2"; shift;;
- -s|--service_client) service_client=1;;
- *) files="$files $1";;
- esac
- shift
-done
-
-if [ -z "$files" ]; then
- usage; exit
-fi
-
-TEMPEST_LIB_GIT_URL=${tempest_lib_git_url:-git://git.openstack.org/openstack/tempest-lib}
-
-tmpdir=$(mktemp -d -t use-tempest-lib.XXXX)
-
-# Clone tempest-lib
-git clone $TEMPEST_LIB_GIT_URL $tmpdir
-
-# Checks all provided files are present in lib and
-# not updated in tempest after migration to lib.
-check_all_files_valid
-
-for file in $files; do
- rm -f $file
- tempest_dir=`pwd`
- tempest_dir="$tempest_dir/tempest/"
- tempest_dirname=`dirname $file`
- lib_dirname=`echo $tempest_dirname | sed s@tempest\/@tempest_lib/\@`
- # Convert tempest dirname to import string
- tempest_import="${tempest_dirname//\//.}"
- tempest_import=${tempest_import:2:${#tempest_import}}
- if [ $service_client -eq 1 ]; then
- # Remove /json path because tempest-lib supports JSON only without XML
- lib_dirname=`echo $lib_dirname | sed s@\/json@@`
- fi
- # Convert tempest-lib dirname to import string
- tempest_lib_import="${lib_dirname//\//.}"
- tempest_lib_import=${tempest_lib_import:2:${#tempest_lib_import}}
- module_name=`basename $file .py`
- tempest_import1="from $tempest_import.$module_name"
- tempest_lib_import1="from $tempest_lib_import.$module_name"
- tempest_import2="from $tempest_import import $module_name"
- tempest_lib_import2="from $tempest_lib_import import $module_name"
- set +e
- grep -rl "$tempest_import1" $tempest_dir | xargs sed -i'' s/"$tempest_import1"/"$tempest_lib_import1"/g 2> /dev/null
- grep -rl "$tempest_import2" $tempest_dir | xargs sed -i'' s/"$tempest_import2"/"$tempest_lib_import2"/g 2> /dev/null
- set -e
- if [[ -z "$file_list" ]]; then
- file_list="$module_name"
- else
- tmp_file_list="$file_list, $module_name"
- char_size=`echo $tmp_file_list | wc -c`
- if [ $char_size -lt 27 ]; then
- file_list="$file_list, $module_name"
- fi
- fi
-done
-
-rm -rf $tmpdir
-echo "Completed. Run pep8 and fix error if any"
-
-git add -A tempest/
-# Generate a migration commit
-commit_message="Use $file_list from tempest-lib"
-pre_list=$"The files below have been migrated to tempest-lib\n"
-pre_list=`echo -e $pre_list`
-post_list=$"Now Tempest-lib provides those as stable interfaces. So Tempest should\nstart using those from lib and remove its own copy."
-post_list=`echo -e $post_list`
-
-git commit -m "$commit_message" -m "$pre_list" -m "$MIGRATION_IDS" -m "$post_list"
diff --git a/tox.ini b/tox.ini
index 95f2cf1..44162fd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,23 +5,27 @@
[tempestenv]
sitepackages = False
-setenv = VIRTUAL_ENV={envdir}
- OS_TEST_PATH=./tempest/test_discover
-deps = setuptools
- -r{toxinidir}/requirements.txt
+setenv =
+ VIRTUAL_ENV={envdir}
+ OS_TEST_PATH=./tempest/test_discover
+deps =
+ setuptools
+ -r{toxinidir}/requirements.txt
[testenv]
-setenv = VIRTUAL_ENV={envdir}
- OS_TEST_PATH=./tempest/tests
+setenv =
+ VIRTUAL_ENV={envdir}
+ OS_TEST_PATH=./tempest/tests
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
usedevelop = True
install_command = pip install -U {opts} {packages}
whitelist_externals = *
-deps = -r{toxinidir}/requirements.txt
- -r{toxinidir}/test-requirements.txt
+deps =
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '{posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox.sh '{posargs}'
[testenv:genconfig]
commands = oslo-config-generator --config-file etc/config-generator.tempest.conf
@@ -31,62 +35,72 @@
commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
[testenv:all]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
# 'all' includes slow tests
-setenv = {[tempestenv]setenv}
- OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
+setenv =
+ {[tempestenv]setenv}
+ OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
deps = {[tempestenv]deps}
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '{posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox.sh '{posargs}'
+
+[testenv:ostestr]
+sitepackages = {[tempestenv]sitepackages}
+# 'all' includes slow tests
+setenv =
+ {[tempestenv]setenv}
+ OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
+deps = {[tempestenv]deps}
+commands =
+ find . -type f -name "*.pyc" -delete
+ ostestr {posargs}
[testenv:all-plugin]
sitepackages = True
# 'all' includes slow tests
-setenv = {[tempestenv]setenv}
- OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
+setenv =
+ {[tempestenv]setenv}
+ OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
deps = {[tempestenv]deps}
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '{posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox.sh '{posargs}'
[testenv:full]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
[testenv:full-serial]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
-
-[testenv:large-ops]
-sitepackages = {[tempestenv]sitepackages}
-setenv = {[tempestenv]setenv}
-deps = {[tempestenv]deps}
-commands =
- find . -type f -name "*.pyc" -delete
- python setup.py testr --slowest --testr-args='tempest.scenario.test_large_ops {posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
[testenv:smoke]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '\[.*\bsmoke\b.*\] {posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox.sh '\[.*\bsmoke\b.*\] {posargs}'
[testenv:smoke-serial]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -94,10 +108,11 @@
# https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
# job would fail if we moved it to parallel.
commands =
- find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox_serial.sh '\[.*\bsmoke\b.*\] {posargs}'
+ find . -type f -name "*.pyc" -delete
+ bash tools/pretty_tox_serial.sh '\[.*\bsmoke\b.*\] {posargs}'
[testenv:stress]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -107,18 +122,25 @@
[testenv:venv]
commands = {posargs}
+[testenv:venv-tempest]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+commands = {posargs}
+
[testenv:docs]
commands =
- python setup.py build_sphinx {posargs}
+ python setup.py build_sphinx {posargs}
[testenv:pep8]
commands =
- flake8 {posargs}
- check-uuid
+ flake8 {posargs}
+ check-uuid
[testenv:uuidgen]
commands =
- check-uuid --fix
+ check-uuid --fix
[hacking]
local-check-factory = tempest.hacking.checks.factory
@@ -132,3 +154,6 @@
ignore = E125,E123,E129
show-source = True
exclude = .git,.venv,.tox,dist,doc,openstack,*egg
+
+[testenv:releasenotes]
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html