Merge "Remove virtualenv management scripts from oslo-incubator"
diff --git a/.gitignore b/.gitignore
index d58b162..5b87cec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,6 @@
!.coveragerc
cover/
doc/source/_static/tempest.conf.sample
+
+# Files created by releasenotes build
+releasenotes/build
diff --git a/HACKING.rst b/HACKING.rst
index 0962f80..b82f8c9 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -18,6 +18,8 @@
- [T109] Cannot use testtools.skip decorator; instead use
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
- [N322] Method's default argument shouldn't be mutable
Test Data/Configuration
@@ -212,9 +214,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
diff --git a/README.rst b/README.rst
index 71e185f..7da83cd 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
@@ -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..245386b 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -17,7 +17,7 @@
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.
+project name.
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
@@ -59,13 +59,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.
@@ -112,7 +112,7 @@
#. 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.
@@ -123,7 +123,7 @@
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
+non-project 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
@@ -131,13 +131,18 @@
#. ``username``
#. ``password``
- #. ``tenant_name``
+ #. ``project_name``
#. ``admin_username``
#. ``admin_password``
- #. ``admin_tenant_name``
+ #. ``admin_project_name``
#. ``alt_username``
#. ``alt_password``
- #. ``alt_tenant_name``
+ #. ``alt_project_name``
+
+If using Identity API v3, use the ``domain_name`` option to specify a
+domain other than the default domain. The ``auth_version`` setting is
+used to switch between v2 (``v2``) or v3 (``v3``) versions of the Identity
+API.
And in the ``auth`` section:
@@ -241,11 +246,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 +287,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 +311,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
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..64bd2c2
--- /dev/null
+++ b/doc/source/library.rst
@@ -0,0 +1,69 @@
+.. _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. Just as with
+tempest-lib 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
+---------
+Just as tempest-lib before it any code that lives in tempest/lib will be treated
+as a stable interface, nothing has changed in regards to interface stability.
+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/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..63ec04b
--- /dev/null
+++ b/doc/source/microversion_testing.rst
@@ -0,0 +1,207 @@
+===================================
+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
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..2622c22 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -55,6 +55,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
============
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/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/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/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/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/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..b617b22
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,16 @@
+===========================
+ tempest Release Notes
+===========================
+
+ .. toctree::
+ :maxdepth: 1
+
+ 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/requirements.txt b/requirements.txt
index 66e5696..a2ec16e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,26 +2,26 @@
# 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.16.0,>=1.15.0 # Apache-2.0
+cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
anyjson>=0.3.3 # BSD
-httplib2>=0.7.5 # MIT
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
testtools>=1.4.0 # MIT
paramiko>=1.16.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.5.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<2.0,>=1.3.1 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
-tempest-lib>=0.14.0 # Apache-2.0
PyYAML>=3.1.0 # MIT
stevedore>=1.5.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
+os-testr>=0.4.1 # Apache-2.0
+urllib3>=1.8.3 # MIT
diff --git a/setup.cfg b/setup.cfg
index cc3a365..0ddb898 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..8f04db9 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
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..96dedcf 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
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 5070fd7..3854973 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.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
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_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..f313f76 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.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
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 95310af..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):
@@ -132,6 +131,8 @@
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):
@@ -152,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_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..07a7a30 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -14,7 +14,6 @@
import uuid
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
@@ -22,6 +21,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 +68,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 +97,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,
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..8986db8 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.
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..105c4e3 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
@@ -15,11 +15,10 @@
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
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..c6c7347 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
@@ -15,11 +15,10 @@
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
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 8706566..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..4fb4e9a 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -15,11 +15,10 @@
import time
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
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 +44,7 @@
def setup_clients(cls):
super(AttachInterfacesTestJSON, cls).setup_clients()
cls.client = cls.os.interfaces_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 +108,18 @@
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_show_interface(self, server, ifs):
iface = ifs[0]
_iface = self.client.show_interface(
@@ -167,6 +179,9 @@
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)
+
_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..87f3c86 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
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..5b4417a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -16,8 +16,6 @@
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
@@ -25,6 +23,8 @@
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
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..74d34a2 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
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..8f0e430 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -15,13 +15,13 @@
import sys
-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
@@ -29,8 +29,6 @@
class ServersNegativeTestJSON(base.BaseV2ComputeTest):
- credentials = ['primary', 'alt']
-
def setUp(self):
super(ServersNegativeTestJSON, self).setUp()
try:
@@ -47,7 +45,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 +268,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 +288,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
@@ -519,3 +498,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..e038b82 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -15,9 +15,8 @@
import uuid
-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/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..e9c8e30 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -52,7 +52,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
@@ -77,15 +78,16 @@
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')
# 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'])
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 01a0baf..d1c48c4 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -15,11 +15,10 @@
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
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..bdb4383 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
@@ -58,6 +59,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..14f4306 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -15,10 +15,9 @@
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
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..a4c1afc 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -15,10 +15,9 @@
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
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..dee42b7 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -15,10 +15,9 @@
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
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 3dc3cb6..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,14 +31,14 @@
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)
@@ -72,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 1729dc9..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.show_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.show_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_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 010e4a0..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,7 +68,8 @@
@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 = []
@@ -94,7 +102,8 @@
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'])
@@ -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_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 79cfc91..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
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 468f169..12ef369 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,14 +25,14 @@
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.projects_client.create_project(
@@ -46,19 +46,19 @@
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.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 3e6533a..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
@@ -78,15 +77,17 @@
# 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 37611e0..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
@@ -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,7 +100,7 @@
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:
@@ -105,9 +108,9 @@
if 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):
@@ -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.list_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 f7f33b3..371da9c 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -79,20 +79,15 @@
self.users_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.
+ # 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)
@@ -117,13 +112,13 @@
# Delete the User at the end of this method
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.users_client.show_user(user_body['id'])['user']
- role = self.client.show_role(role_body['id'])['role']
+ 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.projects_client.create_project(
@@ -135,9 +130,9 @@
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.users_client.list_user_projects(user['id'])['projects']
for i in body:
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 9dd477b..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
@@ -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 1025de7..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]
@@ -102,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):
@@ -146,11 +150,14 @@
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
@@ -159,8 +166,8 @@
@classmethod
def resource_setup(cls):
super(BaseIdentityV3AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV3(cls.client, cls.projects_client,
- cls.users_client)
+ cls.data = DataGeneratorV3(cls.projects_client, cls.users_client,
+ cls.roles_client, cls.domains_client)
@classmethod
def resource_cleanup(cls):
@@ -168,32 +175,25 @@
super(BaseIdentityV3AdminTest, cls).resource_cleanup()
@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)
+ 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,
- users_client, roles_client=None):
- self.client = 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 or client
+ self.roles_client = roles_client
+ self.domains_client = domains_client
self.user_password = None
self.user = None
@@ -242,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):
@@ -277,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..62ddead 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -16,10 +16,9 @@
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.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import manager
from tempest import test
@@ -56,13 +55,9 @@
# 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 b42cf43..995b77e 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
@@ -34,10 +33,14 @@
# 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..593bf2a 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 29396a8..60fbe12 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -16,10 +16,9 @@
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.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import manager
from tempest import test
@@ -59,13 +58,9 @@
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_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..fc74326 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -16,9 +16,8 @@
import uuid
-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_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index a3f4ca8..1aa2d11 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -14,10 +14,9 @@
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
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 78d6aea..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(cls.router['id'],
- port_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(cls.router['id'],
- port_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 8b32a94..ea3d59a 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -17,20 +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
-from tempest_lib import exceptions as lib_exc
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
@@ -49,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,
@@ -57,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._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']:
@@ -72,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):
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 f209f89..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,22 +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(router_id,
- subnet_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(
+ 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):
@@ -275,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 3495b76f..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(router_id,
- subnet_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(router_id,
- subnet_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(router_id,
- port_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 dbb0d14..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,10 +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(port['device_id'],
- port_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'])
@@ -80,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):
@@ -338,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..2156e64 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -59,7 +59,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)
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..b6f9da7 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,17 +389,12 @@
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):
@@ -406,16 +403,16 @@
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
@@ -532,7 +529,7 @@
@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)
@@ -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 d7b220b..caf7f14 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -75,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]
@@ -90,12 +90,12 @@
def _get_ipaddress_from_tempest_conf(cls):
"""Return subnet with mask bits for configured CIDR """
if cls._ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- cidr.prefixlen = CONF.network.tenant_network_mask_bits
+ 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.prefixlen = CONF.network.tenant_network_v6_mask_bits
+ cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr)
+ cidr.prefixlen = CONF.network.project_network_v6_mask_bits
return cidr
@@ -131,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):
@@ -197,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(router['id'],
- port_id=port['port']['id'])
- self.addCleanup(self.client.remove_router_interface, router['id'],
- port_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']
@@ -425,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 0b64be4..3654b2e 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(router['id'],
- subnet_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,7 +159,7 @@
port_body = self.ports_client.create_port(
network_id=network['id'])
# add router interface to port created above
- interface = self.client.add_router_interface(
+ interface = self.routers_client.add_router_interface(
router['id'],
port_id=port_body['port']['id'])
self.addCleanup(self._remove_router_interface_with_port_id,
@@ -172,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)
@@ -198,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})
@@ -212,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,
@@ -227,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,
@@ -243,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(
@@ -257,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,
@@ -270,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 = []
@@ -301,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']))
@@ -323,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')
@@ -381,21 +388,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 7b07d42..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(self.router['id'],
- subnet_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..86d0b46 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,17 +15,16 @@
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 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):
@@ -111,7 +110,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,
@@ -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_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 315b3e0..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
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
index ff06810..7238098 100644
--- a/tempest/api/telemetry/base.py
+++ b/tempest/api/telemetry/base.py
@@ -13,13 +13,13 @@
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
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/telemetry/test_alarming_api.py b/tempest/api/telemetry/test_alarming_api.py
index daa0939..586bb42 100644
--- a/tempest/api/telemetry/test_alarming_api.py
+++ b/tempest/api/telemetry/test_alarming_api.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.telemetry 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/telemetry/test_alarming_api_negative.py b/tempest/api/telemetry/test_alarming_api_negative.py
index e945556..0701b54 100644
--- a/tempest/api/telemetry/test_alarming_api_negative.py
+++ b/tempest/api/telemetry/test_alarming_api_negative.py
@@ -14,8 +14,8 @@
from tempest.api.telemetry 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
import uuid
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
index a575125..53d457f 100644
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -10,11 +10,11 @@
# 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.lib import decorators
from tempest import test
CONF = config.CONF
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..f19717e 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
@@ -80,8 +81,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/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_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..7202881 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
@@ -66,12 +67,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(
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..29ce2e7 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
@@ -15,10 +15,9 @@
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
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index bc32fc9..bccf20e 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -15,9 +15,8 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
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..e52216f 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.addCleanup(self._cleanup_image, image_id)
self.image_client.wait_for_image_status(image_id, 'active')
- self.client.wait_for_volume_status(self.volume['id'], 'available')
+ 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..1b5e72a 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -15,11 +15,10 @@
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
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index c79235a..866e676 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -12,6 +12,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
@@ -37,7 +38,8 @@
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."""
@@ -66,13 +68,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,7 +172,8 @@
# 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)
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index d46c9b5..54459ac 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -12,11 +12,10 @@
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
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 1b89de1..0eded8b 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -16,85 +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.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_groups_client import \
- SecurityGroupsClient
-from tempest_lib.services.network.subnetpools_client import SubnetpoolsClient
-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 \
@@ -103,40 +105,34 @@
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
+ 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 as RegionsV3Client
+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.users_clients import UsersV3Client
+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.network_client import NetworkClient
-from tempest.services.network.json.security_group_rules_client import \
- SecurityGroupRulesClient
+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
@@ -204,30 +200,14 @@
}
default_params_with_timeout_values.update(default_params)
- def __init__(self, credentials, service=None, api_microversions=None):
+ def __init__(self, credentials, service=None):
"""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.
"""
super(Manager, self).__init__(credentials=credentials)
- self.api_microversions = api_microversions or {}
self._set_compute_clients()
self._set_database_clients()
self._set_identity_clients()
@@ -256,14 +236,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,
@@ -328,6 +300,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,
@@ -344,11 +324,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,
@@ -397,8 +372,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,
@@ -507,18 +480,16 @@
# 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
- 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()
@@ -534,20 +505,23 @@
# Clients below use the endpoint type of Keystone API v3
params_v3 = params.copy()
params_v3['endpoint_type'] = CONF.identity.v3_endpoint_type
+ self.domains_client = DomainsClient(self.auth_provider,
+ **params_v3)
self.identity_v3_client = IdentityV3Client(self.auth_provider,
**params_v3)
+ self.trusts_client = TrustsClient(self.auth_provider, **params_v3)
self.users_v3_client = UsersV3Client(self.auth_provider, **params_v3)
- self.endpoints_client = EndPointV3Client(self.auth_provider,
- **params_v3)
- self.identity_services_client = IdentityServicesV3Client(
+ 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.policies_client = PoliciesClient(self.auth_provider, **params_v3)
self.projects_client = ProjectsClient(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.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
@@ -630,15 +604,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 9e98d90..a154d0b 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:
+ except tempest.lib.exceptions.NotFound:
LOG.error("User: %s - not found" % u['user'])
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,14 +307,14 @@
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(router_id,
+ routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
network_name = name + "-network"
@@ -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 c8a8159..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,
@@ -280,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 33f19b1..f51fc53 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -33,7 +33,7 @@
CONF_TENANTS = None
CONF_USERS = None
-IS_CEILOMETER = None
+IS_AODH = None
IS_CINDER = None
IS_GLANCE = None
IS_HEAT = None
@@ -51,14 +51,14 @@
global CONF_PUB_ROUTER
global CONF_TENANTS
global CONF_USERS
- global IS_CEILOMETER
+ global IS_AODH
global IS_CINDER
global IS_GLANCE
global IS_HEAT
global IS_NEUTRON
global IS_NOVA
- IS_CEILOMETER = CONF.service_available.ceilometer
+ IS_AODH = CONF.service_available.aodh
IS_CINDER = CONF.service_available.cinder
IS_GLANCE = CONF.service_available.glance
IS_HEAT = CONF.service_available.heat
@@ -70,25 +70,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 +382,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 +389,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 +449,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,13 +460,14 @@
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(rid, port_id=port['id'])
@@ -709,7 +710,7 @@
class TelemetryAlarmService(BaseService):
def __init__(self, manager, **kwargs):
super(TelemetryAlarmService, self).__init__(kwargs)
- self.client = manager.telemetry_client
+ self.client = manager.alarming_client
def list(self):
client = self.client
@@ -908,7 +909,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']
@@ -940,7 +941,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
@@ -975,7 +976,7 @@
def get_tenant_cleanup_services():
tenant_services = []
- if IS_CEILOMETER:
+ if IS_AODH:
tenant_services.append(TelemetryAlarmService)
if IS_NOVA:
tenant_services.append(ServerService)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index e26a014..2a4e314 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -115,25 +115,27 @@
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
@@ -262,7 +264,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,
@@ -739,10 +757,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 +774,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 +842,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 +859,9 @@
for subnet in router['subnet']:
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
- client.networks.remove_router_interface(router_id,
- subnet_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 +874,13 @@
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
# connect routers to their subnets
- client.networks.add_router_interface(router_id,
- subnet_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 +1031,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/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..f29973d 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -15,7 +15,6 @@
# under the License.
import argparse
-import httplib2
import os
import sys
import traceback
@@ -29,6 +28,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
@@ -91,13 +91,21 @@
}
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:
@@ -269,7 +277,6 @@
'data_processing': 'sahara',
'baremetal': 'ironic',
'identity': 'keystone',
- 'messaging': 'zaqar',
'database': 'trove'
}
# Get catalog list for endpoints to use for validation
@@ -355,8 +362,6 @@
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)
@@ -379,8 +384,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..b5c4547 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,12 @@
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
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 5069807..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__)
@@ -32,14 +32,12 @@
"""
def __init__(self, identity_client, projects_client, users_client,
- roles_client=None):
+ roles_client):
# The client implies version and credentials
self.identity_client = identity_client
self.users_client = users_client
self.projects_client = projects_client
- # this is temporary until the v3 clients are
- # separated, then using *only* each client will become mandatory
- self.roles_client = roles_client or identity_client
+ self.roles_client = roles_client
def create_user(self, username, password, project, email):
user = self.users_client.create_user(
@@ -73,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']))
@@ -123,17 +120,23 @@
tenant_name=project['name'], tenant_id=project['id'],
password=password)
+ 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, projects_client, users_client,
- domain_name):
- super(V3CredsClient, self).__init__(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
@@ -162,19 +165,21 @@
project_domain_id=self.creds_domain['id'],
project_domain_name=self.creds_domain['name'])
- 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,
users_client,
- roles_client=None,
+ roles_client,
+ domains_client=None,
project_domain_name=None):
if isinstance(identity_client, v2_identity.IdentityClient):
return V2CredsClient(identity_client, projects_client, users_client,
roles_client)
else:
return V3CredsClient(identity_client, projects_client, users_client,
- project_domain_name)
+ roles_client, domains_client, project_domain_name)
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index 9dd89ea..a4b2ae8 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)
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 24c1198..58157ef 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -12,7 +12,6 @@
# limitations under the License.
from oslo_concurrency import lockutils
-from tempest_lib import auth
from tempest import clients
from tempest.common import cred_provider
@@ -20,6 +19,7 @@
from tempest.common import preprov_creds
from tempest import config
from tempest import exceptions
+from tempest.lib import auth
CONF = config.CONF
@@ -256,7 +256,7 @@
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')
@@ -269,6 +269,11 @@
params[attr] = getattr(_section, attr)
else:
params[attr] = getattr(_section, prefix + "_" + attr)
+ # NOTE(andreaf) v2 API still uses tenants, so we must translate project
+ # to tenant before building the Credentials object
+ if identity_version == 'v2':
+ params['tenant_name'] = params.get('project_name')
+ params.pop('project_name', None)
# 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,
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 1810c57..d374be4 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__)
@@ -61,8 +61,9 @@
self.tenants_admin_client,
self.users_admin_client,
self.roles_admin_client,
- self.network_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()
@@ -78,6 +79,7 @@
self.tenants_admin_client,
self.users_admin_client,
self.roles_admin_client,
+ self.domains_admin_client,
self.creds_domain_name)
def _get_admin_clients(self):
@@ -90,13 +92,14 @@
os = clients.Manager(self.default_admin_creds)
if self.identity_version == 'v2':
return (os.identity_client, os.tenants_client, os.users_client,
- os.roles_client, os.network_client, os.networks_client,
- os.subnets_client, os.ports_client,
- os.security_groups_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, os.projects_client,
- os.users_v3_client, None, os.network_client,
- os.networks_client, os.subnets_client, os.ports_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):
@@ -182,12 +185,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
@@ -197,8 +207,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:
@@ -228,14 +238,14 @@
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(router_id,
+ self.routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
def get_credentials(self, credential_type):
@@ -286,9 +296,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)
@@ -322,7 +332,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,
@@ -335,7 +345,7 @@
if (not self.network_resources or
(self.network_resources.get('router') and creds.subnet)):
try:
- net_client.remove_router_interface(
+ client.remove_router_interface(
creds.router['id'],
subnet_id=creds.subnet['id'])
except lib_exc.NotFound:
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/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..f3df387 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__)
@@ -331,6 +331,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.
@@ -356,4 +357,17 @@
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
+ # NOTE(andreaf) In case of v2, replace project with tenant if project
+ # is provided and tenant is not
+ if self.identity_version == 'v2':
+ if ('project_name' in creds_dict and
+ 'tenant_name' in creds_dict and
+ creds_dict['project_name'] != creds_dict['tenant_name']):
+ clean_creds = self._sanitize_creds(creds_dict)
+ msg = 'Cannot specify project and tenant at the same time %s'
+ raise exceptions.InvalidCredentials(msg % clean_creds)
+ if ('project_name' in creds_dict and
+ 'tenant_name' not in creds_dict):
+ creds_dict['tenant_name'] = creds_dict['project_name']
+ creds_dict.pop('project_name')
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 aad6373..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
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 8f5faef..3a215a0 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -15,11 +15,11 @@
import time
from oslo_log import log as logging
-from tempest_lib.common import ssh
-import tempest_lib.exceptions
from tempest import config
from tempest import exceptions
+from tempest.lib.common import ssh
+import tempest.lib.exceptions
CONF = config.CONF
@@ -102,7 +102,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()
@@ -113,7 +118,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)
@@ -142,7 +147,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()
@@ -181,7 +186,7 @@
cmd_mkfs = 'sudo /usr/sbin/mke2fs -t %s /dev/%s' % (fs, dev_name)
try:
self.exec_command(cmd_mkfs)
- except tempest_lib.exceptions.SSHExecCommandFailed:
+ 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))
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..95305f3 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -14,11 +14,11 @@
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
CONF = config.CONF
LOG = logging.getLogger(__name__)
diff --git a/tempest/config.py b/tempest/config.py
index 5523fe2..b787b19 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -56,7 +56,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 +81,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 "
+ "needed for authenticating requests made by project "
"isolation to create users and projects",
secret=True,
deprecated_group='identity'),
@@ -158,8 +161,9 @@
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.",
+ cfg.StrOpt('project_name',
+ deprecated_name='tenant_name',
+ help="Project name to use for Nova API requests.",
deprecated_for_removal=True),
cfg.StrOpt('admin_role',
default='admin',
@@ -176,8 +180,9 @@
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 "
+ cfg.StrOpt('alt_project_name',
+ deprecated_name='alt_tenant_name',
+ help="Alternate user's Project name to use for Nova API "
"requests.",
deprecated_for_removal=True),
cfg.StrOpt('alt_password',
@@ -246,10 +251,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 +286,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 +295,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 +305,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"),
@@ -404,6 +411,15 @@
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"),
+
]
@@ -482,21 +498,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',
@@ -561,41 +582,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')
@@ -775,7 +761,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')
]
@@ -984,7 +974,7 @@
DataProcessingFeaturesGroup = [
cfg.ListOpt('plugins',
- default=["vanilla", "hdp"],
+ default=["vanilla", "cdh"],
deprecated_group="data_processing-feature-enabled",
help="List of enabled data processing plugins")
]
@@ -1023,7 +1013,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.')
]
@@ -1058,11 +1048,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',
@@ -1113,9 +1098,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",
@@ -1242,7 +1224,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),
@@ -1283,7 +1264,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
@@ -1318,7 +1302,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
@@ -1371,7 +1354,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
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 86e8460..031df7f 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -176,20 +176,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..d1bc141 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -20,7 +20,7 @@
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
'trove', 'ironic', 'savanna', 'heat', 'ceilometer',
- 'zaqar', 'sahara']
+ 'sahara']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
@@ -69,10 +69,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):
@@ -149,7 +151,7 @@
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 +225,27 @@
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 factory(register):
register(import_no_clients_in_api_and_scenario_tests)
register(scenario_tests_need_service_tags)
@@ -234,3 +257,4 @@
register(no_testtools_skip_decorator)
register(get_resources_on_service_clients)
register(delete_resources_on_service_clients)
+ register(dont_import_local_tempest_into_lib)
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
index f1f21d1..380c173 100644
--- a/tempest/hacking/ignored_list_T110.txt
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -1,8 +1,7 @@
-./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/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..daab898
--- /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'}
+ },
+ '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_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/lib/auth.py b/tempest/lib/auth.py
new file mode 100644
index 0000000..71c4f4f
--- /dev/null
+++ b/tempest/lib/auth.py
@@ -0,0 +1,715 @@
+# 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__)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class AuthProvider(object):
+ """Provide authentication"""
+
+ def __init__(self, credentials):
+ """Auth provider __init__
+
+ :param credentials: credentials for authentication
+ """
+ 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.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):
+ return self.get_auth()
+
+ @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
+
+
+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):
+ super(KeystoneAuthProvider, self).__init__(credentials)
+ self.dsvm = disable_ssl_certificate_validation
+ self.ca_certs = ca_certs
+ self.trace_requests = trace_requests
+ 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.auth_data
+ 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):
+ 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.auth_data[0]
+
+
+class KeystoneV2AuthProvider(KeystoneAuthProvider):
+
+ 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):
+ 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.auth_data
+ 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))
+
+ parts = urlparse.urlparse(_base_url)
+ if filters.get('api_version', None) is not None:
+ version_path = '/%s' % filters['api_version']
+ path = re.sub(r'(^|/)+v\d+(?:\.\d+)?',
+ version_path,
+ parts.path,
+ count=1)
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ path or version_path,
+ parts.params,
+ parts.query,
+ parts.fragment))
+ if filters.get('skip_path', None) is not None and parts.path != '':
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ '/',
+ parts.params,
+ parts.query,
+ parts.fragment))
+
+ return _base_url
+
+ 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):
+
+ 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):
+ return dict(
+ user_id=self.credentials.user_id,
+ username=self.credentials.username,
+ password=self.credentials.password,
+ project_id=self.credentials.project_id,
+ project_name=self.credentials.project_name,
+ user_domain_id=self.credentials.user_domain_id,
+ user_domain_name=self.credentials.user_domain_name,
+ project_domain_id=self.credentials.project_domain_id,
+ project_domain_name=self.credentials.project_domain_name,
+ domain_id=self.credentials.domain_id,
+ domain_name=self.credentials.domain_name,
+ auth_data=True)
+
+ 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
+
+ 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.auth_data
+ 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['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:
+ # 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)
+
+ parts = urlparse.urlparse(_base_url)
+ if filters.get('api_version', None) is not None:
+ version_path = '/%s' % filters['api_version']
+ path = re.sub(r'(^|/)+v\d+(?:\.\d+)?',
+ version_path,
+ parts.path,
+ count=1)
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ path or version_path,
+ parts.params,
+ parts.query,
+ parts.fragment))
+ if filters.get('skip_path', None) is not None:
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ '/',
+ parts.params,
+ parts.query,
+ parts.fragment))
+
+ return _base_url
+
+ 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 = []
+
+ 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 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']
+
+ 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)
+
+
+COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
+
+
+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']
+
+ def _apply_credentials(self, attr):
+ for (key1, key2) in 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)
+ super(KeystoneV3Credentials, self)._apply_credentials(attr)
+
+ 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, scope)
+ - 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..54f35f4
--- /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 += ' --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..b7d6a24
--- /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 skiped 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..d001d27
--- /dev/null
+++ b/tempest/lib/common/rest_client.py
@@ -0,0 +1,893 @@
+# 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 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):
+ """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.
+ :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)
+
+ 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):
+ """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.
+ :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)
+
+ 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>'
+ 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),
+ 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):
+ """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)
+ 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):
+ """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
+ :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)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """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
+ :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)
+
+ 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 = int(resp['retry-after'])
+ 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 _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
+ over_limit = resp_body.get('overLimit', None)
+ if not over_limit:
+ return True
+ return 'exceed' in over_limit.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..511dd08
--- /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()
+ channel = transport.open_session()
+ 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 channel.closed and 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..01b6477
--- /dev/null
+++ b/tempest/lib/common/utils/data_utils.py
@@ -0,0 +1,186 @@
+# 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
+
+
+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 inclues 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 inclues 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 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)
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..b9b2ae9
--- /dev/null
+++ b/tempest/lib/exceptions.py
@@ -0,0 +1,219 @@
+# 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 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..9161abb
--- /dev/null
+++ b/tempest/lib/services/compute/base_compute_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.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):
+ resp, resp_body = super(BaseComputeClient, self).request(
+ method, url, extra_headers, headers, body)
+ 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..4a55ce7
--- /dev/null
+++ b/tempest/lib/services/compute/images_client.py
@@ -0,0 +1,143 @@
+# 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):
+ try:
+ self.show_image(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 '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..a37f167
--- /dev/null
+++ b/tempest/lib/services/compute/servers_client.py
@@ -0,0 +1,571 @@
+# 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.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class ServersClient(base_compute_client.BaseComputeClient):
+
+ 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 = dict(post_body.items() + hints.items())
+
+ 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)
+ 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)
+ 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 = 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')
+ 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/messaging/__init__.py b/tempest/lib/services/identity/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy 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..0350175
--- /dev/null
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -0,0 +1,121 @@
+# 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):
+ """A simple HTTP request interface."""
+ 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..f342a49
--- /dev/null
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -0,0 +1,183 @@
+# 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):
+ """A simple HTTP request interface."""
+ 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/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
new file mode 100644
index 0000000..c5d4c66
--- /dev/null
+++ b/tempest/lib/services/network/agents_client.py
@@ -0,0 +1,68 @@
+# 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.network import base
+
+
+class AgentsClient(base.BaseNetworkClient):
+
+ def update_agent(self, agent_id, **kwargs):
+ """Update agent."""
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526673
+ uri = '/agents/%s' % agent_id
+ return self.update_resource(uri, kwargs)
+
+ def show_agent(self, agent_id, **fields):
+ uri = '/agents/%s' % agent_id
+ return self.show_resource(uri, **fields)
+
+ def list_agents(self, **filters):
+ uri = '/agents'
+ return self.list_resources(uri, **filters)
+
+ def list_routers_on_l3_agent(self, agent_id):
+ uri = '/agents/%s/l3-routers' % agent_id
+ return self.list_resources(uri)
+
+ def create_router_on_l3_agent(self, agent_id, **kwargs):
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526670
+ uri = '/agents/%s/l3-routers' % agent_id
+ return self.create_resource(uri, kwargs)
+
+ def delete_router_from_l3_agent(self, agent_id, router_id):
+ uri = '/agents/%s/l3-routers/%s' % (agent_id, router_id)
+ return self.delete_resource(uri)
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '/agents/%s/dhcp-networks' % agent_id
+ return self.list_resources(uri)
+
+ def delete_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '/agents/%s/dhcp-networks/%s' % (agent_id,
+ network_id)
+ return self.delete_resource(uri)
+
+ def add_dhcp_agent_to_network(self, agent_id, **kwargs):
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+ uri = '/agents/%s/dhcp-networks' % agent_id
+ return self.create_resource(uri, kwargs)
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/lib/services/network/extensions_client.py b/tempest/lib/services/network/extensions_client.py
new file mode 100644
index 0000000..3910c84
--- /dev/null
+++ b/tempest/lib/services/network/extensions_client.py
@@ -0,0 +1,24 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 ExtensionsClient(base.BaseNetworkClient):
+
+ def show_extension(self, ext_alias, **fields):
+ uri = '/extensions/%s' % ext_alias
+ return self.show_resource(uri, **fields)
+
+ def list_extensions(self, **filters):
+ uri = '/extensions'
+ return self.list_resources(uri, **filters)
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/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
new file mode 100644
index 0000000..752b253
--- /dev/null
+++ b/tempest/lib/services/network/quotas_client.py
@@ -0,0 +1,37 @@
+# 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.network import base
+
+
+class QuotasClient(base.BaseNetworkClient):
+
+ def update_quotas(self, tenant_id, **kwargs):
+ put_body = {'quota': kwargs}
+ uri = '/quotas/%s' % tenant_id
+ return self.update_resource(uri, put_body)
+
+ 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)
+
+ def show_quotas(self, tenant_id, **fields):
+ uri = '/quotas/%s' % tenant_id
+ return self.show_resource(uri, **fields)
+
+ def list_quotas(self, **filters):
+ uri = '/quotas'
+ return self.list_resources(uri, **filters)
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/lib/services/network/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
new file mode 100644
index 0000000..0e25339
--- /dev/null
+++ b/tempest/lib/services/network/security_groups_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 SecurityGroupsClient(base.BaseNetworkClient):
+
+ def create_security_group(self, **kwargs):
+ uri = '/security-groups'
+ post_data = {'security_group': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_security_group(self, security_group_id, **kwargs):
+ uri = '/security-groups/%s' % security_group_id
+ post_data = {'security_group': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_security_group(self, security_group_id, **fields):
+ uri = '/security-groups/%s' % security_group_id
+ return self.show_resource(uri, **fields)
+
+ def delete_security_group(self, security_group_id):
+ uri = '/security-groups/%s' % security_group_id
+ return self.delete_resource(uri)
+
+ def list_security_groups(self, **filters):
+ uri = '/security-groups'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
new file mode 100644
index 0000000..12349b1
--- /dev/null
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -0,0 +1,40 @@
+# 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.network import base
+
+
+class SubnetpoolsClient(base.BaseNetworkClient):
+
+ def list_subnetpools(self, **filters):
+ uri = '/subnetpools'
+ return self.list_resources(uri, **filters)
+
+ def create_subnetpool(self, **kwargs):
+ uri = '/subnetpools'
+ post_data = {'subnetpool': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_subnetpool(self, subnetpool_id, **fields):
+ uri = '/subnetpools/%s' % subnetpool_id
+ return self.show_resource(uri, **fields)
+
+ def update_subnetpool(self, subnetpool_id, **kwargs):
+ uri = '/subnetpools/%s' % subnetpool_id
+ post_data = {'subnetpool': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def delete_subnetpool(self, subnetpool_id):
+ uri = '/subnetpools/%s' % subnetpool_id
+ return self.delete_resource(uri)
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..c97e0d1 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
@@ -50,8 +49,6 @@
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 = []
def get_auth_provider_class(credentials):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a996ffe..988ee1a 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,6 +27,8 @@
from tempest.common import waiters
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.network import resources as net_resources
import tempest.test
@@ -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']
@@ -393,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,
@@ -405,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):
@@ -485,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)
@@ -499,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'])
@@ -607,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,
@@ -689,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'])
+ networks_client=networks_client, routers_client=routers_client,
+ **result['network'])
self.assertEqual(network.name, name)
self.addCleanup(self.delete_wrapper, network.delete)
return network
@@ -719,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']
@@ -735,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
@@ -759,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
@@ -791,8 +791,8 @@
raise
self.assertIsNotNone(result, 'Unable to allocate tenant network')
subnet = net_resources.DeletableSubnet(
- network_client=client, subnets_client=subnets_client,
- **result['subnet'])
+ 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
@@ -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
@@ -992,7 +992,7 @@
sg_dict['tenant_id'] = tenant_id
result = client.create_security_group(**sg_dict)
secgroup = net_resources.DeletableSecurityGroup(
- client=client,
+ client=client, routers_client=self.routers_client,
**result['security_group']
)
self.assertEqual(secgroup.name, sg_name)
@@ -1128,7 +1128,7 @@
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
@@ -1147,14 +1147,14 @@
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,
+ router = net_resources.DeletableRouter(routers_client=client,
**result['router'])
self.assertEqual(router.name, name)
self.addCleanup(self.delete_wrapper, router.delete)
@@ -1164,15 +1164,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
@@ -1192,12 +1191,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/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 4d03ed7..dfa4815 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -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'])
@@ -680,7 +679,7 @@
# 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)
@@ -692,8 +691,8 @@
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)
@@ -776,7 +775,7 @@
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"])
+ spoof_nic = ssh_client.get_nic_name_by_mac(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"]
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_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_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_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 1d09fe7..e7223c7 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,13 +16,14 @@
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
@@ -67,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
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 4ce57db..25d825a 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
+
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
@@ -17,6 +19,7 @@
from tempest import test
CONF = config.CONF
+LOG = logging.getLogger(__name__)
class TestVolumeBootPattern(manager.ScenarioTest):
@@ -32,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()
@@ -79,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:
@@ -101,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..6e24801 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 = ''
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 8ab7464..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,17 +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 service_client.ResponseBody(resp_header, 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 4c19442..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,113 +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_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.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 service_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 service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_token(self, resp_token):
"""Get token details."""
@@ -142,192 +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 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 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 service_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 service_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 service_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 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
index 2fa822f..dc553d0 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/services/identity/v3/json/projects_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 ProjectsClient(service_client.ServiceClient):
+class ProjectsClient(rest_client.RestClient):
api_version = "v3"
def create_project(self, name, **kwargs):
@@ -37,7 +37,7 @@
resp, body = self.post('projects', 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_projects(self, params=None):
url = "projects"
@@ -46,7 +46,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_project(self, project_id, **kwargs):
body = self.show_project(project_id)['project']
@@ -65,17 +65,17 @@
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)
+ 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 service_client.ResponseBody(resp, 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 service_client.ResponseBody(resp, body)
+ 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
index 85c2e79..3ab8eab 100644
--- a/tempest/services/identity/v3/json/users_clients.py
+++ b/tempest/services/identity/v3/json/users_clients.py
@@ -15,10 +15,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 UsersV3Client(service_client.ServiceClient):
+class UsersClient(rest_client.RestClient):
api_version = "v3"
def create_user(self, user_name, password=None, project_id=None,
@@ -41,7 +41,7 @@
resp, body = self.post('users', 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_user(self, user_id, name, **kwargs):
"""Updates a user."""
@@ -70,7 +70,7 @@
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)
+ return rest_client.ResponseBody(resp, body)
def update_user_password(self, user_id, **kwargs):
"""Update a user password
@@ -81,14 +81,14 @@
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)
+ 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 service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_users(self, params=None):
"""Get the list of users."""
@@ -98,24 +98,24 @@
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_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):
"""Deletes 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_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)
+ 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..3f256ec 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -22,36 +22,24 @@
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.common.utils import misc as misc_utils
+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 +118,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 +127,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 +146,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 +160,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,20 +196,20 @@
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):
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):
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:
@@ -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,13 +248,13 @@
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)
+ return rest_client.ResponseBody(resp)
# NOTE(afazekas): just for the wait function
def _get_image_status(self, image_id):
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 72b203a..4e037af 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 ImagesClientV2(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(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)
+ 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 c6b22df..0000000
--- a/tempest/services/network/json/network_client.py
+++ /dev/null
@@ -1,230 +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(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_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/network/resources.py b/tempest/services/network/resources.py
index 0a7da92..329c54d 100644
--- a/tempest/services/network/resources.py
+++ b/tempest/services/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 fecth: 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,12 +121,12 @@
def add_to_router(self, router_id):
self._router_ids.add(router_id)
- self.network_client.add_router_interface(router_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(router_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)
@@ -109,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/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..78bda5d 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -17,10 +17,10 @@
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
-class ObjectClient(service_client.ServiceClient):
+class ObjectClient(rest_client.RestClient):
def create_object(self, container, object_name, data,
params=None, metadata=None, headers=None):
@@ -210,7 +210,7 @@
: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
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/json/alarming_client.py b/tempest/services/telemetry/json/alarming_client.py
index ce14211..703efdf 100644
--- a/tempest/services/telemetry/json/alarming_client.py
+++ b/tempest/services/telemetry/json/alarming_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 AlarmingClient(service_client.ServiceClient):
+class AlarmingClient(rest_client.RestClient):
version = '2'
uri_prefix = "v2"
@@ -42,21 +42,21 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
+ return rest_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)
+ return rest_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)
+ return rest_client.ResponseBodyList(resp, body)
def delete_alarm(self, alarm_id):
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
@@ -64,7 +64,7 @@
self.expected_success(204, resp.status)
if body:
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_alarm(self, **kwargs):
uri = "%s/alarms" % self.uri_prefix
@@ -72,7 +72,7 @@
resp, body = self.post(uri, body)
self.expected_success(201, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_alarm(self, alarm_id, **kwargs):
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
@@ -80,14 +80,14 @@
resp, body = self.put(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_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)
+ return rest_client.ResponseBodyData(resp, body)
def alarm_set_state(self, alarm_id, state):
uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
@@ -95,4 +95,4 @@
resp, body = self.put(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
diff --git a/tempest/services/telemetry/json/telemetry_client.py b/tempest/services/telemetry/json/telemetry_client.py
index abdeba2..df7d916 100644
--- a/tempest/services/telemetry/json/telemetry_client.py
+++ b/tempest/services/telemetry/json/telemetry_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 TelemetryClient(service_client.ServiceClient):
+class TelemetryClient(rest_client.RestClient):
version = '2'
uri_prefix = "v2"
@@ -36,7 +36,7 @@
resp, body = self.post(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _helper_list(self, uri, query=None, period=None):
uri_dict = {}
@@ -51,7 +51,7 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
+ return rest_client.ResponseBodyList(resp, body)
def list_resources(self, query=None):
uri = '%s/resources' % self.uri_prefix
@@ -78,4 +78,4 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_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..4344802 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,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(self, volume_id):
"""Returns the details of a single volume."""
@@ -69,7 +68,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 +82,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 +90,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 +105,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 +113,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 +121,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 +129,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 +137,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 +145,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 +165,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 +180,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 +188,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 +196,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 +204,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 +214,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 +229,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 +237,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 +253,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 +261,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 +270,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 +279,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 3c69a8b..382b851 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
@@ -149,16 +149,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 = admin_manager.projects_client
- roles_client = None
+ 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, users_client,
- roles_client, project_domain_name=domain)
+ 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 a676cc2..6ba4962 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -27,7 +27,6 @@
from oslo_utils import importutils
import six
from six.moves import urllib
-from tempest_lib import decorators
import testscenarios
import testtools
@@ -39,6 +38,7 @@
import tempest.common.validation_resources as vresources
from tempest import config
from tempest import exceptions
+from tempest.lib import decorators
LOG = logging.getLogger(__name__)
@@ -180,6 +180,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 +239,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 +246,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
@@ -404,7 +422,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:
@@ -440,11 +458,13 @@
users_client = self.os_admin.users_client
project_client = self.os_admin.tenants_client
roles_client = self.os_admin.roles_client
+ domains_client = None
else:
client = self.os_admin.identity_v3_client
- project_client = self.os_adm.projects_client
users_client = self.os_admin.users_v3_client
- roles_client = None
+ 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
@@ -454,6 +474,7 @@
return cred_client.get_creds_client(client, project_client,
users_client,
roles_client,
+ domains_client,
project_domain_name=domain)
@classmethod
@@ -519,8 +540,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,13 +611,25 @@
'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 they can use it
@@ -607,8 +639,7 @@
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)
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/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index ab6a7a0..b8c9969 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -14,10 +14,10 @@
import mock
from oslotest import mockpatch
-from tempest_lib import exceptions as lib_exc
from tempest.cmd import javelin
-from tempest.tests import base
+from tempest.lib import exceptions as lib_exc
+from tempest.tests.lib import base
class JavelinUnitTest(base.TestCase):
@@ -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_list_plugins.py b/tempest/tests/cmd/test_list_plugins.py
index 17ddb18..782dde7 100644
--- a/tempest/tests/cmd/test_list_plugins.py
+++ b/tempest/tests/cmd/test_list_plugins.py
@@ -14,7 +14,7 @@
import subprocess
-from tempest.tests import base
+from tempest.tests.lib import base
class TestTempestListPlugins(base.TestCase):
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 1048a52..6c5326a 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -18,7 +18,7 @@
import fixtures
from tempest.cmd import init
-from tempest.tests import base
+from tempest.tests.lib import base
class TestTempestInit(base.TestCase):
@@ -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..9df07a1 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -19,8 +19,8 @@
from tempest.cmd import verify_tempest_config
from tempest import config
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class TestGetAPIVersions(base.TestCase):
@@ -49,8 +49,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 +63,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 +77,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 +126,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 +142,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 +158,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 +173,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..c803541 100644
--- a/tempest/tests/common/test_admin_available.py
+++ b/tempest/tests/common/test_admin_available.py
@@ -17,8 +17,8 @@
from tempest.common import credentials_factory as credentials
from tempest import config
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class TestAdminAvailable(base.TestCase):
@@ -36,19 +36,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 +63,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..cb1de16 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -17,8 +17,8 @@
from tempest.common import credentials_factory as credentials
from tempest import config
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class TestAltAvailable(base.TestCase):
@@ -36,7 +36,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',
@@ -52,19 +52,19 @@
for ii in range(0, 2):
if len(creds) > ii:
username = 'u%s' % creds[ii]
- tenant = 't%s' % creds[ii]
+ project = 't%s' % creds[ii]
password = 'p'
domain = 'd'
else:
username = None
- tenant = None
+ project = 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('%sproject_name' % cred_prefix[ii],
+ project, group='identity')
cfg.CONF.set_default('%spassword' % cred_prefix[ii], password,
group='identity')
cfg.CONF.set_default('%sdomain_name' % cred_prefix[ii], domain,
diff --git a/tempest/tests/common/test_configured_creds.py b/tempest/tests/common/test_configured_creds.py
index 96b75fd..3c104b2 100644
--- a/tempest/tests/common/test_configured_creds.py
+++ b/tempest/tests/common/test_configured_creds.py
@@ -13,17 +13,17 @@
# 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.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.tests import fake_config
-from tempest.tests import fake_identity
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_identity
class ConfiguredV2CredentialsTests(base.TestCase):
diff --git a/tempest/tests/common/test_credentials.py b/tempest/tests/common/test_credentials.py
index 136ac02..6fc490e 100644
--- a/tempest/tests/common/test_credentials.py
+++ b/tempest/tests/common/test_credentials.py
@@ -15,8 +15,8 @@
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
+from tempest.tests.lib import base
class TestLegacyCredentialsProvider(base.TestCase):
diff --git a/tempest/tests/common/test_custom_matchers.py b/tempest/tests/common/test_custom_matchers.py
index 2656a47..d664961 100644
--- a/tempest/tests/common/test_custom_matchers.py
+++ b/tempest/tests/common/test_custom_matchers.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.common import custom_matchers
-from tempest.tests import base
+from tempest.tests.lib import base
from testtools.tests.matchers import helpers
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 7a21d96..e1d9023 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.tests import base
+from tempest.services.network.json import routers_client
from tempest.tests import fake_config
-from tempest.tests import fake_http
-from tempest.tests import fake_identity
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_http
+from tempest.tests.lib import fake_identity
class TestDynamicCredentialProvider(base.TestCase):
@@ -47,7 +47,6 @@
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)
cfg.CONF.set_default('operator_role', 'FakeRole',
@@ -58,10 +57,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 +71,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 +79,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 +87,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 +98,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 +109,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 +117,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 +126,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 +151,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 +171,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 +194,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 +223,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 +263,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 +278,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 +292,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 +307,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,7 +318,7 @@
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.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
primary_creds = creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -335,7 +332,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,7 +350,7 @@
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.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -386,11 +383,11 @@
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.'
+ '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,
@@ -404,9 +401,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_lib.services.network.security_groups_client.'
+ 'tempest.lib.services.network.security_groups_client.'
'SecurityGroupsClient.delete', return_value=return_values)
creds.clear_creds()
# Verify default security group delete
@@ -450,7 +447,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()
@@ -461,7 +458,7 @@
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.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
alt_creds = creds.get_alt_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -475,7 +472,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()
@@ -485,7 +482,7 @@
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.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
self._mock_list_roles('123456', 'admin')
admin_creds = creds.get_admin_creds()
@@ -500,7 +497,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,
@@ -521,7 +518,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()
@@ -536,7 +533,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,
@@ -554,7 +551,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,
@@ -572,7 +569,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..36d6c3d 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/common/test_preprov_creds.py
@@ -21,17 +21,16 @@
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.tests import base
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import token_client
from tempest.tests import fake_config
-from tempest.tests import fake_http
-from tempest.tests import fake_identity
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_identity
class TestPreProvisionedCredentials(base.TestCase):
@@ -48,7 +47,6 @@
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())
@@ -319,13 +317,13 @@
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)
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..e0cef62 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -19,7 +19,8 @@
from tempest.common import waiters
from tempest import exceptions
from tempest.services.volume.base import base_volumes_client
-from tempest.tests import base
+from tempest.tests.lib import base
+import tempest.tests.utils as utils
class TestImageWaiters(base.TestCase):
@@ -37,17 +38,24 @@
# Ensure waiter returns before build_timeout
self.assertTrue((end_time - start_time) < 10)
- def test_wait_for_image_status_timeout(self):
+ @mock.patch('time.sleep')
+ def test_wait_for_image_status_timeout(self, mock_sleep):
+ 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,
self.client, 'fake_image_id', 'active')
+ mock_sleep.assert_called_once_with(1)
- def test_wait_for_image_status_error_on_image_create(self):
+ @mock.patch('time.sleep')
+ def test_wait_for_image_status_error_on_image_create(self, mock_sleep):
self.client.show_image.return_value = ({'status': 'ERROR'})
self.assertRaises(exceptions.AddImageException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
+ mock_sleep.assert_called_once_with(1)
@mock.patch.object(time, 'sleep')
def test_wait_for_volume_status_error_restoring(self, mock_sleep):
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 9c2b99e..22cf47a 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -19,8 +19,8 @@
from tempest.common.utils.linux import remote_client
from tempest import config
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class TestRemoteClient(base.TestCase):
diff --git a/tempest/tests/common/utils/test_file_utils.py b/tempest/tests/common/utils/test_file_utils.py
index 937aefa..1a14592 100644
--- a/tempest/tests/common/utils/test_file_utils.py
+++ b/tempest/tests/common/utils/test_file_utils.py
@@ -16,7 +16,7 @@
import mock
from tempest.common.utils import file_utils
-from tempest.tests import base
+from tempest.tests.lib import base
class TestFileUtils(base.TestCase):
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index c45f6da..edd7186 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -46,7 +46,7 @@
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_':
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/tests/base.py b/tempest/tests/lib/base.py
similarity index 100%
rename from tempest/tests/base.py
rename to tempest/tests/lib/base.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..8ce34c2
--- /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.lib 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..b5f7145
--- /dev/null
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -0,0 +1,37 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 base as cli_base
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+
+
+class TestExecute(base.TestCase):
+ def test_execute_success(self):
+ result = cli_base.execute("/bin/ls", action="tempest",
+ flags="-l -a")
+ self.assertIsInstance(result, str)
+ self.assertIn("__init__.py", result)
+
+ def test_execute_failure(self):
+ result = cli_base.execute("/bin/ls", action="tempest.lib",
+ flags="--foobar", merge_stderr=True,
+ fail_ok=True)
+ self.assertIsInstance(result, str)
+ self.assertIn("--foobar", result)
+
+ def test_execute_failure_raise_exception(self):
+ 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..a2c1b2d
--- /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.lib 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 97%
rename from tempest/tests/common/test_api_version_request.py
rename to tempest/tests/lib/common/test_api_version_request.py
index 38fbfc1..bdaa936 100644
--- a/tempest/tests/common/test_api_version_request.py
+++ b/tempest/tests/lib/common/test_api_version_request.py
@@ -12,9 +12,9 @@
# 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.tests import base
+from tempest.lib.common import api_version_request
+from tempest.lib import exceptions
+from tempest.tests.lib import base
class APIVersionRequestTests(base.TestCase):
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..591b87e 100644
--- a/tempest/tests/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -14,9 +14,9 @@
import testtools
-from tempest.common import api_version_utils
-from tempest import exceptions
-from tempest.tests import base
+from tempest.lib.common import api_version_utils
+from tempest.lib import exceptions
+from tempest.tests.lib import base
class TestVersionSkipLogic(base.TestCase):
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..493df89
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -0,0 +1,162 @@
+# 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.lib 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_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)
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..e23d7fb
--- /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.lib 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..fb81bd6
--- /dev/null
+++ b/tempest/tests/lib/fake_credentials.py
@@ -0,0 +1,59 @@
+# 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)
diff --git a/tempest/tests/fake_http.py b/tempest/tests/lib/fake_http.py
similarity index 82%
rename from tempest/tests/fake_http.py
rename to tempest/tests/lib/fake_http.py
index d714055..397c856 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):
@@ -25,7 +23,7 @@
def request(self, uri, method="GET", body=None, headers=None,
redirections=5, connection_type=None):
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 92%
rename from tempest/tests/fake_identity.py
rename to tempest/tests/lib/fake_identity.py
index d0de927..5732065 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,
},
@@ -138,16 +139,15 @@
def _fake_v3_response(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),
+ return (fake_http.fake_http_response(fake_headers, status=201),
json.dumps(IDENTITY_V3_RESPONSE))
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 +160,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 80%
rename from tempest/tests/services/compute/base.py
rename to tempest/tests/lib/services/compute/base.py
index a35a87c..c805de2 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 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..28757c3
--- /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']['image']
+ 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..d91ecdc
--- /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.lib 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..52b8e01
--- /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.lib 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..5aeb47c
--- /dev/null
+++ b/tempest/tests/lib/test_auth.py
@@ -0,0 +1,570 @@
+# 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
+
+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.lib 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.stubs.Set(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.stubs.Set(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)
+
+
+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.stubs.Set(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))
+
+
+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)
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..791fbb5
--- /dev/null
+++ b/tempest/tests/lib/test_credentials.py
@@ -0,0 +1,180 @@
+# 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.lib 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
+ 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 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.stubs.Set(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..07b577c
--- /dev/null
+++ b/tempest/tests/lib/test_decorators.py
@@ -0,0 +1,126 @@
+# 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 uuid
+
+import testtools
+
+from tempest.lib import base as test
+from tempest.lib import decorators
+from tempest.tests.lib 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 = str(uuid.uuid4())
+ 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())
+ 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..90651b0
--- /dev/null
+++ b/tempest/tests/lib/test_rest_client.py
@@ -0,0 +1,1079 @@
+# 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.lib 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.stubs.Set(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 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..f6efd47
--- /dev/null
+++ b/tempest/tests/lib/test_ssh.py
@@ -0,0 +1,260 @@
+# 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.lib 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))
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ 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()
+ closed_prop.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.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.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..9731e96
--- /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.lib 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..c666bd3 100644
--- a/tempest/tests/negative/test_negative_auto_test.py
+++ b/tempest/tests/negative/test_negative_auto_test.py
@@ -15,8 +15,8 @@
from tempest import config
import tempest.test as test
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class TestNegativeAutoTest(base.TestCase):
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index 78fd80d..e0d7f42 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -22,7 +22,7 @@
from tempest.common.generator import base_generator
from tempest.common.generator import negative_generator
from tempest.common.generator import valid_generator
-from tempest.tests import base
+from tempest.tests.lib import base
class TestNegativeBasicGenerator(base.TestCase):
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/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..a35b4d7 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -16,10 +16,9 @@
import shlex
import subprocess
-from tempest_lib import exceptions
-
from oslo_log import log as logging
-from tempest.tests import base
+from tempest.lib import exceptions
+from tempest.tests.lib 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..9ffb7a1
--- /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 fake_config
+from tempest.tests.lib import base
+
+
+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..4c9a3b7 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -22,8 +22,8 @@
from tempest import config
from tempest import exceptions
from tempest import test
-from tempest.tests import base
from tempest.tests import fake_config
+from tempest.tests.lib import base
class BaseDecoratorsTest(base.TestCase):
@@ -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',
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index db9db34..fdbc2d2 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
@@ -20,20 +22,15 @@
from tempest.common import glance_http
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 base
+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..6b3aa0d 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -13,7 +13,7 @@
# under the License.
from tempest.hacking import checks
-from tempest.tests import base
+from tempest.tests.lib import base
class HackingTestCase(base.TestCase):
@@ -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_list_tests.py b/tempest/tests/test_list_tests.py
index 38d4c5c..69527b1 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -17,7 +17,7 @@
import six
import subprocess
-from tempest.tests import base
+from tempest.tests.lib import base
class TestTestList(base.TestCase):
diff --git a/tempest/tests/test_microversions.py b/tempest/tests/test_microversions.py
index 6738641..1ac1232 100644
--- a/tempest/tests/test_microversions.py
+++ b/tempest/tests/test_microversions.py
@@ -17,9 +17,9 @@
from tempest.api.compute import base as compute_base
from tempest import config
-from tempest import exceptions
-from tempest.tests import base
+from tempest.lib import exceptions
from tempest.tests import fake_config
+from tempest.tests.lib import base
class VersionTestNoneTolatest(compute_base.BaseV2ComputeTest):
@@ -64,9 +64,9 @@
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..a1b5f0e 100644
--- a/tempest/tests/test_negative_rest_client.py
+++ b/tempest/tests/test_negative_rest_client.py
@@ -15,15 +15,14 @@
# 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
from tempest import config
-from tempest.tests import base
from tempest.tests import fake_auth_provider
from tempest.tests import fake_config
-from tempest.tests import fake_http
+from tempest.tests.lib import base
class TestNegativeRestClient(base.TestCase):
@@ -31,59 +30,69 @@
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.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/test_tempest_plugin.py b/tempest/tests/test_tempest_plugin.py
index c07e98c..f66dfc8 100644
--- a/tempest/tests/test_tempest_plugin.py
+++ b/tempest/tests/test_tempest_plugin.py
@@ -14,8 +14,8 @@
# under the License.
from tempest.test_discover import plugins
-from tempest.tests import base
from tempest.tests import fake_tempest_plugin as fake_plugin
+from tempest.tests.lib import base
class TestPluginDiscovery(base.TestCase):
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index a4ef699..edb9061 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -19,7 +19,7 @@
import six
-from tempest.tests import base
+from tempest.tests.lib import base
DEVNULL = open(os.devnull, 'wb')
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..bb4b27f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -6,6 +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
+reno>=1.6.2 # Apache2
mox>=0.5.3 # Apache-2.0
mock>=1.2 # BSD
coverage>=3.6 # Apache-2.0
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/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..28dfe8b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -70,14 +70,6 @@
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}'
-
[testenv:smoke]
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
@@ -132,3 +124,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