Merge "Not to create security group when security_group ext is disabled"
diff --git a/HACKING.rst b/HACKING.rst
index d3ac5c6..480650c 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -21,6 +21,7 @@
- [T111] Check that service client names of DELETE should be consistent
- [T112] Check that tempest.lib should not import local tempest code
- [T113] Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
+- [T114] Check that tempest.lib does not use tempest config
- [N322] Method's default argument shouldn't be mutable
Test Data/Configuration
@@ -132,16 +133,18 @@
Set-up is split in a series of steps (setup stages), which can be overwritten
by test classes. Set-up stages are:
- - `skip_checks`
- - `setup_credentials`
- - `setup_clients`
- - `resource_setup`
+
+- `skip_checks`
+- `setup_credentials`
+- `setup_clients`
+- `resource_setup`
Tear-down is also split in a series of steps (teardown stages), which are
stacked for execution only if the corresponding setup stage had been
reached during the setup phase. Tear-down stages are:
- - `clear_credentials` (defined in the base test class)
- - `resource_cleanup`
+
+- `clear_credentials` (defined in the base test class)
+- `resource_cleanup`
Skipping Tests
--------------
diff --git a/README.rst b/README.rst
index 650a1ed..725a890 100644
--- a/README.rst
+++ b/README.rst
@@ -25,8 +25,7 @@
discover features of a cloud incorrectly, and give people an
incorrect assessment of their cloud. Explicit is always better.
- Tempest uses OpenStack public interfaces. Tests in Tempest should
- only touch public interfaces, API calls (native or 3rd party),
- or libraries.
+ only touch public OpenStack APIs.
- Tempest should not touch private or implementation specific
interfaces. This means not directly going to the database, not
directly hitting the hypervisors, not testing extensions not
@@ -163,7 +162,7 @@
Tempest also has a set of unit tests which test the Tempest code itself. These
tests can be run by specifying the test discovery path::
- $> OS_TEST_PATH=./tempest/tests testr run --parallel
+ $ OS_TEST_PATH=./tempest/tests testr run --parallel
By setting OS_TEST_PATH to ./tempest/tests it specifies that test discover
should only be run on the unit test directory. The default value of OS_TEST_PATH
@@ -214,8 +213,8 @@
To start you need to create a configuration file. The easiest way to create a
configuration file is to generate a sample in the ``etc/`` directory ::
- $> cd $TEMPEST_ROOT_DIR
- $> oslo-config-generator --config-file \
+ $ cd $TEMPEST_ROOT_DIR
+ $ oslo-config-generator --config-file \
etc/config-generator.tempest.conf \
--output-file etc/tempest.conf
@@ -237,21 +236,21 @@
After setting up your configuration file, you can execute the set of Tempest
tests by using ``testr`` ::
- $> testr run --parallel
+ $ testr run --parallel
To run one single test serially ::
- $> testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
+ $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
Alternatively, you can use the run_tempest.sh script which will create a venv
and run the tests or use tox to do the same. Tox also contains several existing
job configurations. For example::
- $> tox -efull
+ $ tox -efull
which will run the same set of tests as the OpenStack gate. (it's exactly how
the gate invokes Tempest) Or::
- $> tox -esmoke
+ $ tox -esmoke
to run the tests tagged as smoke.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index bcb1e3e..32cd3ef 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -27,21 +27,13 @@
- Generate test credentials on the fly (see `Dynamic Credentials`_)
Tempest allows for configuring pre-provisioned test credentials as well.
-This can be done in two different ways.
-
-One is to provide credentials is using the accounts.yaml file (see
+This can be done using the accounts.yaml file (see
`Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
number of users available to run tests with.
You can specify the location of the file in the ``auth`` section in the
tempest.conf file. To see the specific format used in the file please refer to
the accounts.yaml.sample file included in Tempest.
-A second way - now deprecated - is a set of configuration options in the
-tempest.conf file (see `Legacy Credentials`_). These options are clearly
-labelled in the ``identity`` section and let you specify a set of credentials
-for a regular user and an alternate user, consisting of a username, password,
-project and domain name.
-
Keystone Connection Info
^^^^^^^^^^^^^^^^^^^^^^^^
In order for Tempest to be able to talk to your OpenStack deployment you need
@@ -134,44 +126,6 @@
Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
-Legacy Credentials
-""""""""""""""""""
-**Starting in the Liberty release this mechanism was deprecated; it will be
-removed in a future release.**
-
-When Tempest was refactored to allow for locking test accounts, the original
-non-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
-options in the ``identity`` section:
-
- #. ``username``
- #. ``password``
- #. ``project_name``
- #. ``alt_username``
- #. ``alt_password``
- #. ``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:
-
- #. ``use_dynamic_credentials = False``
- #. Comment out ``test_accounts_file`` or keep it empty.
-
-It only makes sense to use this if parallel execution isn't needed, since
-Tempest won't be able to properly isolate tests using this. Additionally, using
-the traditional config options for credentials is not able to provide
-credentials to tests requiring specific roles on accounts. This is because the
-config options do not give sufficient flexibility to describe the roles assigned
-to a user for running the tests. There are additional limitations with regard to
-network configuration when using this credential provider mechanism - see the
-`Networking`_ section below.
-
Compute
-------
@@ -251,6 +205,8 @@
Enabling Remote Access to Created Servers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Network Creation/Usage for Servers
+""""""""""""""""""""""""""""""""""
When Tempest creates servers for testing, some tests require being able to
connect those servers. Depending on the configuration of the cloud, the methods
for doing this can be different. In certain configurations it is required to
@@ -260,23 +216,8 @@
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 projects and users must be able to see
@@ -298,7 +239,7 @@
Accounts File
-"""""""""""""
+'''''''''''''
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 project and user pair basis. This provides
@@ -323,7 +264,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
cases it shouldn't matter because nova-network should have no problem booting a
@@ -348,6 +289,34 @@
network available for the server creation, or use ``fixed_network_name`` to
inform Tempest which network to use.
+SSH Connection Configuration
+""""""""""""""""""""""""""""
+There are also several different ways to actually establish a connection and
+authenticate/login on the server. After a server is booted with a provided
+network there are still details needed to know how to actually connect to
+the server. The ``validation`` group gathers all the options regarding
+connecting to and remotely accessing the created servers.
+
+To enable remote access to servers, there are 3 options at a minimum that are used:
+
+ #. ``run_validation``
+ #. ``connect_method``
+ #. ``auth_method``
+
+The ``run_validation`` is used to enable or disable ssh connectivity for
+all tests (with the exception of scenario tests which do not have a flag for
+enabling or disabling ssh) To enable ssh connectivity this needs be set to ``true``.
+
+The ``connect_method`` option is used to tell tempest what kind of IP to use for
+establishing a connection to the server. Two methods are available: ``fixed``
+and ``floating``, the later being set by default. If this is set to floating
+tempest will create a floating ip for the server before attempted to connect
+to it. The IP for the floating ip is what is used for the connection.
+
+For the ``auth_method`` option there is currently, only one valid option,
+``keypair``. With this set to ``keypair`` tempest will create an ssh keypair
+and use that for authenticating against the created server.
+
Configuring Available Services
------------------------------
OpenStack is really a constellation of several different projects which
@@ -394,11 +363,14 @@
service catalog should be in a standard format (which is going to be
standardized at the keystone level).
Tempest expects URLs in the Service catalog in the following format:
- * ``http://example.com:1234/<version-info>``
+
+ * ``http://example.com:1234/<version-info>``
+
Examples:
- * Good - ``http://example.com:1234/v2.0``
- * Wouldn’t work - ``http://example.com:1234/xyz/v2.0/``
- (adding prefix/suffix around version etc)
+
+ * Good - ``http://example.com:1234/v2.0``
+ * Wouldn’t work - ``http://example.com:1234/xyz/v2.0/``
+ (adding prefix/suffix around version etc)
Service Feature Configuration
-----------------------------
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 10364db..98b006d 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -51,6 +51,7 @@
account_generator
cleanup
javelin
+ workspace
==================
Indices and tables
diff --git a/doc/source/library/auth.rst b/doc/source/library/auth.rst
new file mode 100644
index 0000000..e1d92ed
--- /dev/null
+++ b/doc/source/library/auth.rst
@@ -0,0 +1,11 @@
+.. _auth:
+
+Authentication Framework Usage
+==============================
+
+---------------
+The auth module
+---------------
+
+.. automodule:: tempest.lib.auth
+ :members:
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index fc05b12..17059e4 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -19,11 +19,13 @@
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
@@ -129,8 +131,9 @@
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.*
+.. 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::
@@ -159,7 +162,8 @@
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.
@@ -209,3 +213,7 @@
* `2.10`_
.. _2.10: http://docs.openstack.org/developer/nova/api_microversion_history.html#id9
+
+ * `2.20`_
+
+ .. _2.20: http://docs.openstack.org/developer/nova/api_microversion_history.html#id18
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index ad26741..9640469 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -15,10 +15,22 @@
doing this is that the interfaces exposed by tempest are not considered stable
(with the exception of configuration variables which ever effort goes into
ensuring backwards compatibility). You should not need to import anything from
-tempest itself except where explicitly noted. If there is an interface from
-tempest that you need to rely on in your plugin it likely needs to be migrated
-to tempest.lib. In that situation, file a bug, push a migration patch, etc. to
-expedite providing the interface in a reliable manner.
+tempest itself except where explicitly noted.
+
+Stable Tempest APIs plugins may use
+-----------------------------------
+
+As noted above, several tempest APIs are acceptable to use from plugins, while
+others are not. A list of stable APIs available to plugins is provided below:
+
+* tempest.lib.*
+* tempest.config
+* tempest.test_discover.plugins
+
+If there is an interface from tempest that you need to rely on in your plugin
+which is not listed above, it likely needs to be migrated to tempest.lib. In
+that situation, file a bug, push a migration patch, etc. to expedite providing
+the interface in a reliable manner.
Plugin Cookiecutter
-------------------
diff --git a/doc/source/workspace.rst b/doc/source/workspace.rst
new file mode 100644
index 0000000..41325b2
--- /dev/null
+++ b/doc/source/workspace.rst
@@ -0,0 +1,5 @@
+-----------------
+Tempest Workspace
+-----------------
+
+.. automodule:: tempest.cmd.workspace
diff --git a/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
new file mode 100644
index 0000000..297279f
--- /dev/null
+++ b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - Tempest library auth interface now supports scope. Scope allows to control
+ the scope of tokens requested via the identity API. Identity V2 supports
+ unscoped and project scoped tokens, but only the latter are implemented.
+ Identity V3 supports unscoped, project and domain scoped token, all three
+ are available.
\ No newline at end of file
diff --git a/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml b/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml
new file mode 100644
index 0000000..9a1cef6
--- /dev/null
+++ b/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Adds tempest workspaces command and WorkspaceManager.
+ This is used to have a centralized repository for managing
+ different tempest configurations.
diff --git a/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
new file mode 100644
index 0000000..b2190f3
--- /dev/null
+++ b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Tempest library auth interface now supports
+ filtering with catalog name. Note that filtering by
+ name is only successful if a known service type is
+ provided.
diff --git a/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
new file mode 100644
index 0000000..630f8ed
--- /dev/null
+++ b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - Define image service clients as libraries
+ The following image service clients are defined as library interface,
+ so the other projects can use these modules as stable libraries
+ without any maintenance changes.
+ **image_members_client**
+ **namespaces_client**
+ **resource_types_client**
+ **schemas_client**
diff --git a/releasenotes/notes/new-test-utils-module-adf34468c4d52719.yaml b/releasenotes/notes/new-test-utils-module-adf34468c4d52719.yaml
new file mode 100644
index 0000000..55df2b3
--- /dev/null
+++ b/releasenotes/notes/new-test-utils-module-adf34468c4d52719.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - A new `test_utils` module has been added to tempest.lib.common.utils. It
+ should hold any common utility functions that help writing Tempest tests.
+ - A new utility function called `call_and_ignore_notfound_exc` has been
+ added to the `test_utils` module. That function call another function
+ passed as parameter and ignore the NotFound exception if it raised.
+deprecations:
+ - tempest.lib.common.utils.misc.find_test_caller has been moved into the
+ tempest.lib.common.utils.test_utils module. Calling the find_test_caller
+ function with its old location is deprecated.
diff --git a/releasenotes/notes/remove-integrated-horizon-bb57551c1e5f5be3.yaml b/releasenotes/notes/remove-integrated-horizon-bb57551c1e5f5be3.yaml
new file mode 100644
index 0000000..294f6d9
--- /dev/null
+++ b/releasenotes/notes/remove-integrated-horizon-bb57551c1e5f5be3.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - The integrated dashboard scenario test has been
+ removed and is now in a separate tempest plugin
+ tempest-horizon. The removed test coverage can be
+ used by installing tempest-horizon on the server
+ where you run tempest.
diff --git a/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
new file mode 100644
index 0000000..89b3f41
--- /dev/null
+++ b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - The deprecated legacy credential provider has been removed. The only way to
+ configure credentials in tempest now is to use the dynamic or preprovisioned
+ credential providers
diff --git a/releasenotes/notes/routers-client-as-library-25a363379da351f6.yaml b/releasenotes/notes/routers-client-as-library-25a363379da351f6.yaml
new file mode 100644
index 0000000..35cf2c4
--- /dev/null
+++ b/releasenotes/notes/routers-client-as-library-25a363379da351f6.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Define routers_client as stable library interface.
+ The routers_client module is defined as library interface,
+ so the other projects can use the module as stable library
+ without any maintenance changes.
diff --git a/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml b/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml
new file mode 100644
index 0000000..eb45523
--- /dev/null
+++ b/releasenotes/notes/support-chunked-encoding-d71f53225f68edf3.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - The RestClient (in tempest.lib.common.rest_client) now supports POSTing
+ and PUTing data with chunked transfer encoding. Just pass an `iterable`
+ object as the `body` argument and set the `chunked` argument to `True`.
+ - A new generator called `chunkify` is added in
+ tempest.lib.common.utils.data_utils that yields fixed-size chunks (slices)
+ from a Python sequence.
+
diff --git a/requirements.txt b/requirements.txt
index d567082..45fe345 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,17 +9,17 @@
netaddr!=0.7.16,>=0.7.12 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
pyOpenSSL>=0.14 # Apache-2.0
-oslo.concurrency>=3.5.0 # Apache-2.0
-oslo.config>=3.9.0 # Apache-2.0
+oslo.concurrency>=3.8.0 # Apache-2.0
+oslo.config>=3.10.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.5.0 # Apache-2.0
+oslo.utils>=3.11.0 # Apache-2.0
six>=1.9.0 # MIT
-fixtures<2.0,>=1.3.1 # Apache-2.0/BSD
+fixtures>=3.0.0 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
PyYAML>=3.1.0 # MIT
stevedore>=1.10.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
-os-testr>=0.4.1 # Apache-2.0
+os-testr>=0.7.0 # Apache-2.0
urllib3>=1.15.1 # MIT
diff --git a/setup.cfg b/setup.cfg
index 0ddb898..0bf493c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
-home-page = http://www.openstack.org/
+home-page = http://docs.openstack.org/developer/tempest/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
@@ -41,6 +41,7 @@
run-stress = tempest.cmd.run_stress:TempestRunStress
list-plugins = tempest.cmd.list_plugins:TempestListPlugins
verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig
+ workspace = tempest.cmd.workspace:TempestWorkspace
oslo.config.opts =
tempest.config = tempest.config:list_opts
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 671b139..4f48ad0 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -16,7 +16,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
from tempest import test
LOG = log.getLogger(__name__)
@@ -41,9 +41,8 @@
def tearDown(self):
try:
- self.client.delete_agent(self.agent_id)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ self.client.delete_agent, self.agent_id)
except Exception:
LOG.exception('Exception raised deleting agent %s', self.agent_id)
super(AgentsAdminTestJSON, self).tearDown()
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index a2b1e2f..25efd2e 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -16,7 +16,7 @@
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.lib.common.utils import test_utils
from tempest import test
@@ -41,21 +41,14 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
- def _try_delete_aggregate(self, aggregate_id):
- # delete aggregate, if it exists
- try:
- self.client.delete_aggregate(aggregate_id)
- # if aggregate not found, it depict it was deleted in the test
- except lib_exc.NotFound:
- pass
-
@test.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
aggregate = (self.client.create_aggregate(name=aggregate_name)
['aggregate'])
- self.addCleanup(self._try_delete_aggregate, aggregate['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_aggregate, aggregate['id'])
self.assertEqual(aggregate_name, aggregate['name'])
self.assertIsNone(aggregate['availability_zone'])
@@ -69,7 +62,8 @@
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self.client.create_aggregate(
name=aggregate_name, availability_zone=az_name)['aggregate']
- self.addCleanup(self._try_delete_aggregate, aggregate['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_aggregate, aggregate['id'])
self.assertEqual(aggregate_name, aggregate['name'])
self.assertEqual(az_name, aggregate['availability_zone'])
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index 8986db8..a4ed8dc 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -59,7 +59,9 @@
return True
except e.InvalidHTTPResponseBody:
return False
- test.call_until_true(is_valid, duration, 1)
+ self.assertEqual(test.call_until_true(is_valid, duration, 1), True,
+ "%s not return valid response in %s secs" % (
+ func.__name__, duration))
return self.resp
@test.idempotent_id('062c8ae9-9912-4249-8b51-e38d664e926e')
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 77ed7cd..37aa5ac 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -24,6 +24,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common import api_version_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -134,11 +135,8 @@
server['id'] for server in cls.servers))
for server in cls.servers:
try:
- cls.servers_client.delete_server(server['id'])
- except lib_exc.NotFound:
- # Something else already cleaned up the server, nothing to be
- # worried about
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.servers_client.delete_server, server['id'])
except Exception:
LOG.exception('Deleting server %s failed' % server['id'])
@@ -177,10 +175,8 @@
LOG.debug('Clearing images: %s', ','.join(cls.images))
for image_id in cls.images:
try:
- cls.compute_images_client.delete_image(image_id)
- except lib_exc.NotFound:
- # The image may have already been deleted which is OK.
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.compute_images_client.delete_image, image_id)
except Exception:
LOG.exception('Exception raised deleting image %s' % image_id)
@@ -190,10 +186,8 @@
str(sg['id']) for sg in cls.security_groups))
for sg in cls.security_groups:
try:
- cls.security_groups_client.delete_security_group(sg['id'])
- except lib_exc.NotFound:
- # The security group may have already been deleted which is OK.
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.security_groups_client.delete_security_group, sg['id'])
except Exception as exc:
LOG.info('Exception raised deleting security group %s',
sg['id'])
@@ -204,10 +198,10 @@
LOG.debug('Clearing server groups: %s', ','.join(cls.server_groups))
for server_group_id in cls.server_groups:
try:
- cls.server_groups_client.delete_server_group(server_group_id)
- except lib_exc.NotFound:
- # The server-group may have already been deleted which is OK.
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.server_groups_client.delete_server_group,
+ server_group_id
+ )
except Exception:
LOG.exception('Exception raised deleting server-group %s',
server_group_id)
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 3d7f4f8..3508ba9 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_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.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -53,14 +54,6 @@
cls.client.delete_floating_ip(cls.floating_ip_id)
super(FloatingIPsTestJSON, cls).resource_cleanup()
- def _try_delete_floating_ip(self, floating_ip_id):
- # delete floating ip, if it exists
- try:
- self.client.delete_floating_ip(floating_ip_id)
- # if not found, it depicts it was deleted in the test
- except lib_exc.NotFound:
- pass
-
@test.idempotent_id('f7bfb946-297e-41b8-9e8c-aba8e9bb5194')
@test.services('network')
def test_allocate_floating_ip(self):
@@ -85,7 +78,8 @@
# Creating the floating IP that is to be deleted in this method
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'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_floating_ip, floating_ip_body['id'])
# Deleting the floating IP from the project
self.client.delete_floating_ip(floating_ip_body['id'])
# Check it was really deleted.
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 0724566..427a748 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -19,6 +19,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -36,7 +37,17 @@
@classmethod
def setup_clients(cls):
super(ImagesMetadataTestJSON, cls).setup_clients()
- cls.glance_client = cls.os.image_client
+ # Check if glance v1 is available to determine which client to use. We
+ # prefer glance v1 for the compute API tests since the compute image
+ # API proxy was written for glance v1.
+ if CONF.image_feature_enabled.api_v1:
+ cls.glance_client = cls.os.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ cls.glance_client = cls.os.image_client_v2
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
cls.client = cls.compute_images_client
@classmethod
@@ -45,14 +56,22 @@
cls.image_id = None
name = data_utils.rand_name('image')
+ if CONF.image_feature_enabled.api_v1:
+ kwargs = dict(is_public=False)
+ else:
+ kwargs = dict(visibility='private')
body = cls.glance_client.create_image(name=name,
container_format='bare',
disk_format='raw',
- is_public=False)['image']
+ **kwargs)
+ body = body['image'] if 'image' in body else body
cls.image_id = body['id']
cls.images.append(cls.image_id)
image_file = six.StringIO(('*' * 1024))
- cls.glance_client.update_image(cls.image_id, data=image_file)
+ if CONF.image_feature_enabled.api_v1:
+ cls.glance_client.update_image(cls.image_id, data=image_file)
+ else:
+ cls.glance_client.store_image_file(cls.image_id, data=image_file)
waiters.wait_for_image_status(cls.client, cls.image_id, 'ACTIVE')
def setUp(self):
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index af840cc..e74ca67 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -22,6 +22,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -40,7 +41,17 @@
def setup_clients(cls):
super(ListImageFiltersTestJSON, cls).setup_clients()
cls.client = cls.compute_images_client
- cls.glance_client = cls.os.image_client
+ # Check if glance v1 is available to determine which client to use. We
+ # prefer glance v1 for the compute API tests since the compute image
+ # API proxy was written for glance v1.
+ if CONF.image_feature_enabled.api_v1:
+ cls.glance_client = cls.os.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ cls.glance_client = cls.os.image_client_v2
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
@classmethod
def resource_setup(cls):
@@ -48,17 +59,25 @@
def _create_image():
name = data_utils.rand_name('image')
+ if CONF.image_feature_enabled.api_v1:
+ kwargs = dict(is_public=False)
+ else:
+ kwargs = dict(visibility='private')
body = cls.glance_client.create_image(name=name,
container_format='bare',
disk_format='raw',
- is_public=False)['image']
+ **kwargs)
+ body = body['image'] if 'image' in body else body
image_id = body['id']
cls.images.append(image_id)
# Wait 1 second between creation and upload to ensure a delta
# between created_at and updated_at.
time.sleep(1)
image_file = six.StringIO(('*' * 1024))
- cls.glance_client.update_image(image_id, data=image_file)
+ if CONF.image_feature_enabled.api_v1:
+ cls.glance_client.update_image(image_id, data=image_file)
+ else:
+ cls.glance_client.store_image_file(image_id, data=image_file)
waiters.wait_for_image_status(cls.client, image_id, 'ACTIVE')
body = cls.client.show_image(image_id)['image']
return body
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index c05045e..07423ff 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -204,10 +204,6 @@
@test.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2')
@testtools.skipUnless(CONF.service_available.neutron,
'Neutron service must be available.')
- # The below skipUnless should be removed once Kilo-eol happens.
- @testtools.skipUnless(CONF.compute_feature_enabled.
- allow_duplicate_networks,
- 'Duplicate networks must be allowed')
def test_verify_duplicate_network_nics(self):
# Verify that server creation does not fail when more than one nic
# is created on the same network.
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f01657b..3a4428a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -24,6 +24,7 @@
from tempest.common.utils.linux import remote_client
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 import test
@@ -226,6 +227,35 @@
self.client.start_server(self.server_id)
+ @test.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
+ @test.services('volume')
+ def test_rebuild_server_with_volume_attached(self):
+ # create a new volume and attach it to the server
+ volume = self.volumes_client.create_volume(
+ size=CONF.volume.volume_size)
+ volume = volume['volume']
+ self.addCleanup(self.volumes_client.delete_volume, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+
+ self.client.attach_volume(self.server_id, volumeId=volume['id'])
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ volume['id'], 'available')
+ self.addCleanup(self.client.detach_volume,
+ self.server_id, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'in-use')
+
+ # run general rebuild test
+ self.test_rebuild_server()
+
+ # make sure the volume is attached to the instance after rebuild
+ vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
+ vol_after_rebuild = vol_after_rebuild['volume']
+ self.assertEqual('in-use', vol_after_rebuild['status'])
+ self.assertEqual(self.server_id,
+ vol_after_rebuild['attachments'][0]['server_id'])
+
def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
@@ -291,6 +321,19 @@
def test_create_backup(self):
# Positive test:create backup successfully and rotate backups correctly
# create the first and the second backup
+
+ # Check if glance v1 is available to determine which client to use. We
+ # prefer glance v1 for the compute API tests since the compute image
+ # API proxy was written for glance v1.
+ if CONF.image_feature_enabled.api_v1:
+ glance_client = self.os.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ glance_client = self.os.image_client_v2
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
+
backup1 = data_utils.rand_name('backup-1')
resp = self.client.create_backup(self.server_id,
backup_type='daily',
@@ -302,7 +345,7 @@
def _clean_oldest_backup(oldest_backup):
if oldest_backup_exist:
try:
- self.os.image_client.delete_image(oldest_backup)
+ glance_client.delete_image(oldest_backup)
except lib_exc.NotFound:
pass
else:
@@ -312,7 +355,8 @@
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
- self.os.image_client.wait_for_image_status(image1_id, 'active')
+ waiters.wait_for_image_status(glance_client,
+ image1_id, 'active')
backup2 = data_utils.rand_name('backup-2')
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
@@ -321,8 +365,9 @@
rotation=2,
name=backup2).response
image2_id = data_utils.parse_image_id(resp['location'])
- self.addCleanup(self.os.image_client.delete_image, image2_id)
- self.os.image_client.wait_for_image_status(image2_id, 'active')
+ self.addCleanup(glance_client.delete_image, image2_id)
+ waiters.wait_for_image_status(glance_client,
+ image2_id, 'active')
# verify they have been created
properties = {
@@ -330,12 +375,20 @@
'backup_type': "daily",
'instance_uuid': self.server_id,
}
- image_list = self.os.image_client.list_images(
- detail=True,
- properties=properties,
- status='active',
- sort_key='created_at',
- sort_dir='asc')['images']
+ if CONF.image_feature_enabled.api_v1:
+ params = dict(
+ properties=properties, status='active',
+ sort_key='created_at', sort_dir='asc',)
+ image_list = glance_client.list_images(
+ detail=True,
+ **params)['images']
+ else:
+ # Additional properties are flattened in glance v2.
+ params = dict(
+ status='active', sort_key='created_at', sort_dir='asc')
+ params.update(properties)
+ image_list = glance_client.list_images(params)['images']
+
self.assertEqual(2, len(image_list))
self.assertEqual((backup1, backup2),
(image_list[0]['name'], image_list[1]['name']))
@@ -349,17 +402,16 @@
rotation=2,
name=backup3).response
image3_id = data_utils.parse_image_id(resp['location'])
- self.addCleanup(self.os.image_client.delete_image, image3_id)
+ self.addCleanup(glance_client.delete_image, image3_id)
# the first back up should be deleted
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
- self.os.image_client.wait_for_resource_deletion(image1_id)
+ glance_client.wait_for_resource_deletion(image1_id)
oldest_backup_exist = False
- image_list = self.os.image_client.list_images(
- detail=True,
- properties=properties,
- status='active',
- sort_key='created_at',
- sort_dir='asc')['images']
+ if CONF.image_feature_enabled.api_v1:
+ image_list = glance_client.list_images(
+ detail=True, **params)['images']
+ else:
+ image_list = glance_client.list_images(params)['images']
self.assertEqual(2, len(image_list),
'Unexpected number of images for '
'v2:test_create_backup; was the oldest backup not '
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 164caaf..decccf3 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -19,7 +19,7 @@
from tempest import config
from tempest import exceptions
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
import tempest.test
@@ -238,11 +238,7 @@
@staticmethod
def cleanup_resources(resource_id_list, method):
for resource_id in resource_id_list:
- try:
- method(resource_id)
- except lib_exc.NotFound:
- # ignore errors while auto removing created resource
- pass
+ test_utils.call_and_ignore_notfound_exc(method, resource_id)
@classmethod
def create_node_group_template(cls, name, plugin_name, hadoop_version,
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 46ecba1..5fda4c14 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -83,13 +83,14 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
self.alt_user, self.alt_password,
self.data.tenant['id'], self.alt_email)
- # Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
-
@test.attr(type=['negative'])
@test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
def test_create_user_with_enabled_non_bool(self):
@@ -119,11 +120,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac')
@@ -159,11 +161,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829')
@@ -229,8 +232,11 @@
# Request to get list of users without a valid token should fail
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.list_users)
- self.client.auth_provider.clear_auth()
@test.attr(type=['negative'])
@test.idempotent_id('f5d39046-fc5f-425c-b29e-bac2632da28e')
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 3bcae17..2d4ff17 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -17,7 +17,7 @@
from tempest.common.utils import data_utils
from tempest import config
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
import tempest.test
CONF = config.CONF
@@ -225,9 +225,7 @@
@staticmethod
def _try_wrapper(func, item, **kwargs):
try:
- func(item['id'], **kwargs)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(func, item['id'], **kwargs)
except Exception:
LOG.exception("Unexpected exception occurred in %s deletion. "
"But ignored here." % item['id'])
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 62ddead..79f2576 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -13,13 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
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
@@ -35,23 +33,26 @@
@test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity Client with new credentials, since
- # current non_admin_client token will be revoked after updating
- # password
- self.non_admin_users_client_for_cleanup = copy.copy(
- self.non_admin_users_client)
- self.non_admin_users_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
- old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_own_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
+ old_pass = self.creds.credentials.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_users_client_for_cleanup.update_user_own_password,
- user_id, original_password=new_pass, password=old_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
+
# user updates own password
self.non_admin_users_client.update_user_own_password(
user_id, password=new_pass, original_password=old_pass)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 60fbe12..76b46c0 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -13,13 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
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
@@ -35,24 +33,25 @@
@test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity V3 Client with new credentials, since
- # current non_admin_users_client token will be revoked after updating
- # password
- self.non_admin_users_client_for_cleanup = (
- copy.copy(self.non_admin_users_client))
- self.non_admin_users_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
+
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_users_client_for_cleanup.update_user_password,
- user_id,
- password=old_pass,
- original_password=new_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
# user updates own password
self.non_admin_users_client.update_user_password(
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 0683936..3fefc81 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -16,7 +16,7 @@
from tempest.common.utils import data_utils
from tempest import config
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
import tempest.test
CONF = config.CONF
@@ -47,10 +47,8 @@
@classmethod
def resource_cleanup(cls):
for image_id in cls.created_images:
- try:
- cls.client.delete_image(image_id)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.client.delete_image, image_id)
for image_id in cls.created_images:
cls.client.wait_for_resource_deletion(image_id)
@@ -95,12 +93,14 @@
@classmethod
def setup_clients(cls):
super(BaseV1ImageMembersTest, cls).setup_clients()
+ cls.image_member_client = cls.os.image_member_client
+ cls.alt_image_member_client = cls.os_alt.image_member_client
cls.alt_img_cli = cls.os_alt.image_client
@classmethod
def resource_setup(cls):
super(BaseV1ImageMembersTest, cls).resource_setup()
- cls.alt_tenant_id = cls.alt_img_cli.tenant_id
+ cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
def _create_image(self):
image_file = moves.cStringIO(data_utils.random_bytes())
@@ -125,6 +125,9 @@
def setup_clients(cls):
super(BaseV2ImageTest, cls).setup_clients()
cls.client = cls.os.image_client_v2
+ cls.namespaces_client = cls.os.namespaces_client
+ cls.resource_types_client = cls.os.resource_types_client
+ cls.schemas_client = cls.os.schemas_client
class BaseV2MemberImageTest(BaseV2ImageTest):
@@ -134,13 +137,14 @@
@classmethod
def setup_clients(cls):
super(BaseV2MemberImageTest, cls).setup_clients()
- cls.os_img_client = cls.os.image_client_v2
+ cls.image_member_client = cls.os.image_member_client_v2
+ cls.alt_image_member_client = cls.os_alt.image_member_client_v2
cls.alt_img_client = cls.os_alt.image_client_v2
@classmethod
def resource_setup(cls):
super(BaseV2MemberImageTest, cls).resource_setup()
- cls.alt_tenant_id = cls.alt_img_client.tenant_id
+ cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
def _list_image_ids_as_alt(self):
image_list = self.alt_img_client.list_images()['images']
@@ -149,11 +153,11 @@
def _create_image(self):
name = data_utils.rand_name('image')
- image = self.os_img_client.create_image(name=name,
- container_format='bare',
- disk_format='raw')
+ image = self.client.create_image(name=name,
+ container_format='bare',
+ disk_format='raw')
image_id = image['id']
- self.addCleanup(self.os_img_client.delete_image, image_id)
+ self.addCleanup(self.client.delete_image, image_id)
return image_id
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index eb6969b..0bad96a 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -22,8 +22,8 @@
@test.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
def test_add_image_member(self):
image = self._create_image()
- self.client.add_member(self.alt_tenant_id, image)
- body = self.client.list_image_members(image)
+ self.image_member_client.add_member(self.alt_tenant_id, image)
+ body = self.image_member_client.list_image_members(image)
members = body['members']
members = map(lambda x: x['member_id'], members)
self.assertIn(self.alt_tenant_id, members)
@@ -33,10 +33,11 @@
@test.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
def test_get_shared_images(self):
image = self._create_image()
- self.client.add_member(self.alt_tenant_id, image)
+ self.image_member_client.add_member(self.alt_tenant_id, image)
share_image = self._create_image()
- self.client.add_member(self.alt_tenant_id, share_image)
- body = self.client.list_shared_images(self.alt_tenant_id)
+ self.image_member_client.add_member(self.alt_tenant_id, share_image)
+ body = self.image_member_client.list_shared_images(
+ self.alt_tenant_id)
images = body['shared_images']
images = map(lambda x: x['image_id'], images)
self.assertIn(share_image, images)
@@ -45,8 +46,8 @@
@test.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
def test_remove_member(self):
image_id = self._create_image()
- self.client.add_member(self.alt_tenant_id, image_id)
- self.client.delete_member(self.alt_tenant_id, image_id)
- body = self.client.list_image_members(image_id)
+ self.image_member_client.add_member(self.alt_tenant_id, image_id)
+ self.image_member_client.delete_member(self.alt_tenant_id, image_id)
+ body = self.image_member_client.list_image_members(image_id)
members = body['members']
self.assertEqual(0, len(members), str(members))
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 16a4ba6..d46a836 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -25,7 +25,8 @@
def test_add_member_with_non_existing_image(self):
# Add member with non existing image.
non_exist_image = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound, self.client.add_member,
+ self.assertRaises(lib_exc.NotFound,
+ self.image_member_client.add_member,
self.alt_tenant_id, non_exist_image)
@test.attr(type=['negative'])
@@ -33,7 +34,8 @@
def test_delete_member_with_non_existing_image(self):
# Delete member with non existing image.
non_exist_image = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound, self.client.delete_member,
+ self.assertRaises(lib_exc.NotFound,
+ self.image_member_client.delete_member,
self.alt_tenant_id, non_exist_image)
@test.attr(type=['negative'])
@@ -42,7 +44,8 @@
# Delete member with non existing tenant.
image_id = self._create_image()
non_exist_tenant = data_utils.rand_uuid_hex()
- self.assertRaises(lib_exc.NotFound, self.client.delete_member,
+ self.assertRaises(lib_exc.NotFound,
+ self.image_member_client.delete_member,
non_exist_tenant, image_id)
@test.attr(type=['negative'])
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index cad22f3..6d5559d 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -17,6 +17,7 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest import test
@@ -95,7 +96,7 @@
image_id = body.get('id')
self.assertEqual('New Http Image', body.get('name'))
self.assertFalse(body.get('is_public'))
- self.client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(self.client, image_id, 'active')
self.client.show_image(image_id)
@test.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
@@ -305,7 +306,7 @@
@test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'value1'}
self.assertEqual(expected, resp_metadata['properties'])
@@ -313,12 +314,12 @@
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
- metadata = self.client.get_image_meta(self.image_id)
+ metadata = self.client.check_image(self.image_id)
self.assertEqual(metadata['properties'], {'key1': 'value1'})
metadata['properties'].update(req_metadata)
metadata = self.client.update_image(
self.image_id, properties=metadata['properties'])['image']
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'alt1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 04582c6..1fb9c52 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -254,12 +254,12 @@
def test_get_image_schema(self):
# Test to get image schema
schema = "image"
- body = self.client.show_schema(schema)
+ body = self.schemas_client.show_schema(schema)
self.assertEqual("image", body['name'])
@test.idempotent_id('25c8d7b2-df21-460f-87ac-93130bcdc684')
def test_get_images_schema(self):
# Test to get images schema
schema = "images"
- body = self.client.show_schema(schema)
+ body = self.schemas_client.show_schema(schema)
self.assertEqual("images", body['name'])
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index bb73318..fe8dd65 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -19,17 +19,17 @@
@test.idempotent_id('5934c6ea-27dc-4d6e-9421-eeb5e045494a')
def test_image_share_accept(self):
image_id = self._create_image()
- member = self.os_img_client.create_image_member(
+ member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
- self.alt_img_client.update_image_member(image_id,
- self.alt_tenant_id,
- status='accepted')
+ self.alt_image_member_client.update_image_member(image_id,
+ self.alt_tenant_id,
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- body = self.os_img_client.list_image_members(image_id)
+ body = self.image_member_client.list_image_members(image_id)
members = body['members']
member = members[0]
self.assertEqual(len(members), 1, str(members))
@@ -40,29 +40,29 @@
@test.idempotent_id('d9e83e5f-3524-4b38-a900-22abcb26e90e')
def test_image_share_reject(self):
image_id = self._create_image()
- member = self.os_img_client.create_image_member(
+ member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
- self.alt_img_client.update_image_member(image_id,
- self.alt_tenant_id,
- status='rejected')
+ self.alt_image_member_client.update_image_member(image_id,
+ self.alt_tenant_id,
+ status='rejected')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
@test.idempotent_id('a6ee18b9-4378-465e-9ad9-9a6de58a3287')
def test_get_image_member(self):
image_id = self._create_image()
- self.os_img_client.create_image_member(
+ self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
- self.alt_img_client.update_image_member(image_id,
- self.alt_tenant_id,
- status='accepted')
+ self.alt_image_member_client.update_image_member(image_id,
+ self.alt_tenant_id,
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- member = self.os_img_client.show_image_member(image_id,
- self.alt_tenant_id)
+ member = self.image_member_client.show_image_member(
+ image_id, self.alt_tenant_id)
self.assertEqual(self.alt_tenant_id, member['member_id'])
self.assertEqual(image_id, member['image_id'])
self.assertEqual('accepted', member['status'])
@@ -70,38 +70,40 @@
@test.idempotent_id('72989bc7-2268-48ed-af22-8821e835c914')
def test_remove_image_member(self):
image_id = self._create_image()
- self.os_img_client.create_image_member(
+ self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
- self.alt_img_client.update_image_member(image_id,
- self.alt_tenant_id,
- status='accepted')
+ self.alt_image_member_client.update_image_member(image_id,
+ self.alt_tenant_id,
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
+ self.image_member_client.delete_image_member(image_id,
+ self.alt_tenant_id)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
@test.idempotent_id('634dcc3f-f6e2-4409-b8fd-354a0bb25d83')
def test_get_image_member_schema(self):
- body = self.os_img_client.show_schema("member")
+ body = self.schemas_client.show_schema("member")
self.assertEqual("member", body['name'])
@test.idempotent_id('6ae916ef-1052-4e11-8d36-b3ae14853cbb')
def test_get_image_members_schema(self):
- body = self.os_img_client.show_schema("members")
+ body = self.schemas_client.show_schema("members")
self.assertEqual("members", body['name'])
@test.idempotent_id('cb961424-3f68-4d21-8e36-30ad66fb6bfb')
def test_get_private_image(self):
image_id = self._create_image()
- member = self.os_img_client.create_image_member(
+ member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
- self.alt_img_client.update_image_member(image_id,
- self.alt_tenant_id,
- status='accepted')
+ self.alt_image_member_client.update_image_member(image_id,
+ self.alt_tenant_id,
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
+ self.image_member_client.delete_image_member(image_id,
+ self.alt_tenant_id)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index 388eb08..fa29a92 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -21,11 +21,11 @@
@test.idempotent_id('b79efb37-820d-4cf0-b54c-308b00cf842c')
def test_image_share_invalid_status(self):
image_id = self._create_image()
- member = self.os_img_client.create_image_member(
+ member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertRaises(lib_exc.BadRequest,
- self.alt_img_client.update_image_member,
+ self.alt_image_member_client.update_image_member,
image_id, self.alt_tenant_id,
status='notavalidstatus')
@@ -33,11 +33,11 @@
@test.idempotent_id('27002f74-109e-4a37-acd0-f91cd4597967')
def test_image_share_owner_cannot_accept(self):
image_id = self._create_image()
- member = self.os_img_client.create_image_member(
+ member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.assertRaises(lib_exc.Forbidden,
- self.os_img_client.update_image_member,
+ self.image_member_client.update_image_member,
image_id, self.alt_tenant_id, status='accepted')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index de8299e..6fced00 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -15,6 +15,7 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -25,46 +26,47 @@
@test.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768')
def test_basic_metadata_definition_namespaces(self):
# get the available resource types and use one resource_type
- body = self.client.list_resource_types()
+ body = self.resource_types_client.list_resource_types()
resource_name = body['resource_types'][0]['name']
name = [{'name': resource_name}]
namespace_name = data_utils.rand_name('namespace')
# create the metadef namespace
- body = self.client.create_namespace(namespace=namespace_name,
- visibility='public',
- description='Tempest',
- display_name=namespace_name,
- resource_type_associations=name,
- protected=True)
- self.addCleanup(self._cleanup_namespace, namespace_name)
+ body = self.namespaces_client.create_namespace(
+ namespace=namespace_name,
+ visibility='public',
+ description='Tempest',
+ display_name=namespace_name,
+ resource_type_associations=name,
+ protected=True)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self._cleanup_namespace, namespace_name)
# get namespace details
- body = self.client.show_namespace(namespace_name)
+ body = self.namespaces_client.show_namespace(namespace_name)
self.assertEqual(namespace_name, body['namespace'])
self.assertEqual('public', body['visibility'])
# unable to delete protected namespace
- self.assertRaises(lib_exc.Forbidden, self.client.delete_namespace,
+ self.assertRaises(lib_exc.Forbidden,
+ self.namespaces_client.delete_namespace,
namespace_name)
# update the visibility to private and protected to False
- body = self.client.update_namespace(namespace=namespace_name,
- description='Tempest',
- visibility='private',
- display_name=namespace_name,
- protected=False)
+ body = self.namespaces_client.update_namespace(
+ namespace=namespace_name,
+ description='Tempest',
+ visibility='private',
+ display_name=namespace_name,
+ protected=False)
self.assertEqual('private', body['visibility'])
self.assertEqual(False, body['protected'])
# now able to delete the non-protected namespace
- self.client.delete_namespace(namespace_name)
+ self.namespaces_client.delete_namespace(namespace_name)
def _cleanup_namespace(self, namespace_name):
- # this is used to cleanup the resources
- try:
- body = self.client.show_namespace(namespace_name)
- self.assertEqual(namespace_name, body['namespace'])
- body = self.client.update_namespace(namespace=namespace_name,
- description='Tempest',
- visibility='private',
- display_name=namespace_name,
- protected=False)
- self.client.delete_namespace(namespace_name)
- except lib_exc.NotFound:
- pass
+ body = self.namespaces_client.show_namespace(namespace_name)
+ self.assertEqual(namespace_name, body['namespace'])
+ body = self.namespaces_client.update_namespace(
+ namespace=namespace_name,
+ description='Tempest',
+ visibility='private',
+ display_name=namespace_name,
+ protected=False)
+ self.namespaces_client.delete_namespace(namespace_name)
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index a32bfbc..2d53265 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -12,6 +12,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -97,7 +98,7 @@
body = self.admin_networks_client.create_network(
**{'router:external': True})
external_network = body['network']
- self.addCleanup(self._try_delete_resource,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_networks_client.delete_network,
external_network['id'])
subnet = self.create_subnet(
@@ -106,7 +107,7 @@
body = self.admin_floating_ips_client.create_floatingip(
floating_network_id=external_network['id'])
created_floating_ip = body['floatingip']
- self.addCleanup(self._try_delete_resource,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_floating_ips_client.delete_floatingip,
created_floating_ip['id'])
floatingip_list = self.admin_floating_ips_client.list_floatingips(
diff --git a/tempest/api/network/admin/test_external_networks_negative.py b/tempest/api/network/admin/test_external_networks_negative.py
index 57b144a..94d65c3 100644
--- a/tempest/api/network/admin/test_external_networks_negative.py
+++ b/tempest/api/network/admin/test_external_networks_negative.py
@@ -15,6 +15,7 @@
from tempest.api.network import base
from tempest import config
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -34,7 +35,7 @@
body = self.admin_floating_ips_client.create_floatingip(
floating_network_id=CONF.network.public_network_id)
created_floating_ip = body['floatingip']
- self.addCleanup(self._try_delete_resource,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_floating_ips_client.delete_floatingip,
created_floating_ip['id'])
floating_ip_address = created_floating_ip['floating_ip_address']
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 7a4547a..b2cb003 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -92,7 +92,7 @@
external_gateway_info = {
'network_id': CONF.network.public_network_id,
'enable_snat': True}
- cls.admin_routers_client.update_router_with_snat_gw_info(
+ cls.admin_routers_client.update_router(
cls.router['id'],
external_gateway_info=external_gateway_info)
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index ea3d59a..2ff31e0 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -17,7 +17,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -60,7 +60,8 @@
# Change quotas for project
quota_set = self.admin_quotas_client.update_quotas(
project_id, **new_quotas)['quota']
- self.addCleanup(self._cleanup_quotas, project_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.admin_quotas_client.reset_quotas, project_id)
for key, value in six.iteritems(new_quotas):
self.assertEqual(value, quota_set[key])
@@ -88,12 +89,3 @@
def test_quotas(self):
new_quotas = {'network': 0, 'security_group': 0}
self._check_quotas(new_quotas)
-
- def _cleanup_quotas(self, project_id):
- # try to clean up the resources.If it fails, then
- # assume that everything was already deleted, so
- # it is OK to continue.
- try:
- self.admin_quotas_client.reset_quotas(project_id)
- except lib_exc.NotFound:
- pass
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 9823345..89fa576 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -18,6 +18,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -97,7 +98,7 @@
if CONF.service_available.neutron:
# Clean up floating IPs
for floating_ip in cls.floating_ips:
- cls._try_delete_resource(
+ test_utils.call_and_ignore_notfound_exc(
cls.floating_ips_client.delete_floatingip,
floating_ip['id'])
@@ -106,53 +107,33 @@
if len(cls.metering_label_rules) > 0:
label_rules_client = cls.admin_metering_label_rules_client
for metering_label_rule in cls.metering_label_rules:
- cls._try_delete_resource(
+ test_utils.call_and_ignore_notfound_exc(
label_rules_client.delete_metering_label_rule,
metering_label_rule['id'])
# Clean up metering labels
for metering_label in cls.metering_labels:
- cls._try_delete_resource(
+ test_utils.call_and_ignore_notfound_exc(
cls.admin_metering_labels_client.delete_metering_label,
metering_label['id'])
# Clean up ports
for port in cls.ports:
- cls._try_delete_resource(cls.ports_client.delete_port,
- port['id'])
+ test_utils.call_and_ignore_notfound_exc(
+ cls.ports_client.delete_port, port['id'])
# Clean up routers
for router in cls.routers:
- cls._try_delete_resource(cls.delete_router,
- router)
+ test_utils.call_and_ignore_notfound_exc(
+ cls.delete_router, router)
# Clean up subnets
for subnet in cls.subnets:
- cls._try_delete_resource(cls.subnets_client.delete_subnet,
- subnet['id'])
+ test_utils.call_and_ignore_notfound_exc(
+ cls.subnets_client.delete_subnet, subnet['id'])
# Clean up networks
for network in cls.networks:
- cls._try_delete_resource(cls.networks_client.delete_network,
- network['id'])
+ test_utils.call_and_ignore_notfound_exc(
+ cls.networks_client.delete_network, network['id'])
super(BaseNetworkTest, cls).resource_cleanup()
@classmethod
- def _try_delete_resource(self, delete_callable, *args, **kwargs):
- """Cleanup resources in case of test-failure
-
- Some resources are explicitly deleted by the test.
- If the test failed to delete a resource, this method will execute
- the appropriate delete methods. Otherwise, the method ignores NotFound
- exceptions thrown for resources that were correctly deleted by the
- test.
-
- :param delete_callable: delete method
- :param args: arguments for delete method
- :param kwargs: keyword arguments for delete method
- """
- try:
- delete_callable(*args, **kwargs)
- # if resource is not found, this means it was deleted in the test
- except lib_exc.NotFound:
- pass
-
- @classmethod
def create_network(cls, network_name=None):
"""Wrapper utility that returns a test network."""
network_name = network_name or data_utils.rand_name('test-network-')
@@ -259,12 +240,9 @@
body = cls.ports_client.list_ports(device_id=router['id'])
interfaces = body['ports']
for i in interfaces:
- try:
- cls.routers_client.remove_router_interface(
- router['id'],
- subnet_id=i['fixed_ips'][0]['subnet_id'])
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.routers_client.remove_router_interface, router['id'],
+ subnet_id=i['fixed_ips'][0]['subnet_id'])
cls.routers_client.delete_router(router['id'])
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index a5fb25c..1d96439 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -20,6 +20,7 @@
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -273,14 +274,6 @@
for subnet in subnets:
self.assertEqual(sorted(subnet.keys()), sorted(fields))
- def _try_delete_network(self, net_id):
- # delete network, if it exists
- try:
- self.networks_client.delete_network(net_id)
- # if network is not found, this means it was deleted in the test
- except lib_exc.NotFound:
- pass
-
@test.idempotent_id('f04f61a9-b7f3-4194-90b2-9bcf660d1bfe')
def test_delete_network_with_subnet(self):
# Creates a network
@@ -288,7 +281,8 @@
body = self.networks_client.create_network(name=name)
network = body['network']
net_id = network['id']
- self.addCleanup(self._try_delete_network, net_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.networks_client.delete_network, net_id)
# Find a cidr that is not in use yet and create a subnet with it
subnet = self.create_subnet(network)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 46b068b..ba416e4 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -213,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_routers_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -228,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_routers_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -259,7 +259,7 @@
router = self._create_router(
data_utils.rand_name('router-'),
external_network_id=CONF.network.public_network_id)
- self.admin_routers_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -303,7 +303,7 @@
)
test_routes.sort(key=lambda x: x['destination'])
- extra_route = self.routers_client.update_extra_routes(
+ extra_route = self.routers_client.update_router(
router['id'], routes=test_routes)
show_body = self.routers_client.show_router(router['id'])
# Assert the number of routes
@@ -325,13 +325,13 @@
routes[i]['destination'])
self.assertEqual(test_routes[i]['nexthop'], routes[i]['nexthop'])
- self.routers_client.delete_extra_routes(router['id'])
+ self._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.routers_client.delete_extra_routes(router_id)
+ self.routers_client.update_router(router_id, routes=None)
@test.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
def test_update_router_admin_state(self):
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index b9765c8..a3b0a82 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -153,6 +153,7 @@
# Create rule for icmp protocol with invalid ports
states = [(1, 256, 'Invalid value for ICMP code'),
+ (-1, 25, 'Invalid value'),
(None, 6, 'ICMP type (port-range-min) is missing'),
(300, 1, 'Invalid value for ICMP type')]
for pmin, pmax, msg in states:
diff --git a/tempest/api/network/test_subnetpools_extensions.py b/tempest/api/network/test_subnetpools_extensions.py
index c6cc8e2..d574d72 100644
--- a/tempest/api/network/test_subnetpools_extensions.py
+++ b/tempest/api/network/test_subnetpools_extensions.py
@@ -15,6 +15,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -53,7 +54,9 @@
body = self.subnetpools_client.create_subnetpool(name=subnetpool_name,
prefixes=prefix)
subnetpool_id = body["subnetpool"]["id"]
- self.addCleanup(self._cleanup_subnetpools, subnetpool_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.subnetpools_client.delete_subnetpool,
+ subnetpool_id)
self.assertEqual(subnetpool_name, body["subnetpool"]["name"])
# get detail about subnet pool
body = self.subnetpools_client.show_subnetpool(subnetpool_id)
@@ -68,10 +71,3 @@
self.assertRaises(lib_exc.NotFound,
self.subnetpools_client.show_subnetpool,
subnetpool_id)
-
- def _cleanup_subnetpools(self, subnetpool_id):
- # this is used to cleanup the resources
- try:
- self.subnetpools_client.delete_subnetpool(subnetpool_id)
- except lib_exc.NotFound:
- pass
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 044e8c1..97d9eed 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -15,6 +15,7 @@
from tempest.common import custom_matchers
from tempest import config
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -80,10 +81,8 @@
objlist = container_client.list_all_container_objects(cont)
# delete every object in the container
for obj in objlist:
- try:
- object_client.delete_object(cont, obj['name'])
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ object_client.delete_object, cont, obj['name'])
container_client.delete_container(cont)
except lib_exc.NotFound:
pass
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 6bab9b3..5983c1f 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -226,6 +226,17 @@
self.assertEqual(len(container_list),
min(limit, self.containers_count - 2))
+ @test.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa')
+ def test_list_containers_with_prefix(self):
+ # list containers that have a name that starts with a prefix
+ prefix = '{0}-a'.format(CONF.resources_prefix)
+ params = {'prefix': prefix}
+ resp, container_list = self.account_client.list_account_containers(
+ params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+ for container in container_list:
+ self.assertEqual(True, container.startswith(prefix))
+
@test.attr(type='smoke')
@test.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80')
def test_list_account_metadata(self):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index dc926e0..a88e4f4 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -20,7 +20,6 @@
import zlib
import six
-from six import moves
from tempest.api.object_storage import base
from tempest.common import custom_matchers
@@ -201,8 +200,8 @@
status, _, resp_headers = self.object_client.put_object_with_chunk(
container=self.container_name,
name=object_name,
- contents=moves.cStringIO(data),
- chunk_size=512)
+ contents=data_utils.chunkify(data, 512)
+ )
self.assertHeaders(resp_headers, 'Object', 'PUT')
# check uploaded content
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 752f0b4..867159b 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -19,7 +19,7 @@
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.lib.common.utils import test_utils
from tempest import test
# Each segment, except for the final one, must be at least 1 megabyte
@@ -36,12 +36,9 @@
def tearDown(self):
for obj in self.objects:
- try:
- self.object_client.delete_object(
- self.container_name,
- obj)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ self.object_client.delete_object,
+ self.container_name, obj)
self.container_client.delete_container(self.container_name)
super(ObjectSloTest, self).tearDown()
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index d813263..3701b55 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -16,7 +16,7 @@
from tempest.common.utils import data_utils
from tempest import config
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
import tempest.test
CONF = config.CONF
@@ -83,17 +83,13 @@
@classmethod
def _clear_stacks(cls):
for stack_identifier in cls.stacks:
- try:
- cls.client.delete_stack(stack_identifier)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.client.delete_stack, stack_identifier)
for stack_identifier in cls.stacks:
- try:
- cls.client.wait_for_stack_status(
- stack_identifier, 'DELETE_COMPLETE')
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.client.wait_for_stack_status, stack_identifier,
+ 'DELETE_COMPLETE')
@classmethod
def _create_keypair(cls, name_start='keypair-heat-'):
@@ -124,10 +120,8 @@
@classmethod
def _clear_images(cls):
for image_id in cls.images:
- try:
- cls.images_v2_client.delete_image(image_id)
- except lib_exc.NotFound:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.images_v2_client.delete_image, image_id)
@classmethod
def read_template(cls, name, ext='yaml'):
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 00acc7d..5e1c20b 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -63,7 +63,7 @@
extra_specs = {spec_key_with_prefix: backend_name_key}
else:
extra_specs = {spec_key_without_prefix: backend_name_key}
- self.type = self.volume_types_client.create_volume_type(
+ self.type = self.admin_volume_types_client.create_volume_type(
name=type_name, extra_specs=extra_specs)['volume_type']
self.volume_type_id_list.append(self.type['id'])
@@ -95,7 +95,7 @@
# volume types deletion
volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
for volume_type_id in volume_type_id_list:
- cls.volume_types_client.delete_volume_type(volume_type_id)
+ cls.admin_volume_types_client.delete_volume_type(volume_type_id)
super(VolumeMultiBackendV2Test, cls).resource_cleanup()
diff --git a/tempest/api/volume/test_qos.py b/tempest/api/volume/admin/test_qos.py
similarity index 80%
rename from tempest/api/volume/test_qos.py
rename to tempest/api/volume/admin/test_qos.py
index 722a39a..68e57f5 100644
--- a/tempest/api/volume/test_qos.py
+++ b/tempest/api/volume/admin/test_qos.py
@@ -43,27 +43,27 @@
for key in ['name', 'consumer']:
self.assertEqual(qos[key], body[key])
- self.volume_qos_client.delete_qos(body['id'])
- self.volume_qos_client.wait_for_resource_deletion(body['id'])
+ self.admin_volume_qos_client.delete_qos(body['id'])
+ self.admin_volume_qos_client.wait_for_resource_deletion(body['id'])
# validate the deletion
- list_qos = self.volume_qos_client.list_qos()['qos_specs']
+ list_qos = self.admin_volume_qos_client.list_qos()['qos_specs']
self.assertNotIn(body, list_qos)
def _create_test_volume_type(self):
vol_type_name = utils.rand_name("volume-type")
- vol_type = self.volume_types_client.create_volume_type(
+ vol_type = self.admin_volume_types_client.create_volume_type(
name=vol_type_name)['volume_type']
- self.addCleanup(self.volume_types_client.delete_volume_type,
+ self.addCleanup(self.admin_volume_types_client.delete_volume_type,
vol_type['id'])
return vol_type
def _test_associate_qos(self, vol_type_id):
- self.volume_qos_client.associate_qos(
+ self.admin_volume_qos_client.associate_qos(
self.created_qos['id'], vol_type_id)
def _test_get_association_qos(self):
- body = self.volume_qos_client.show_association_qos(
+ body = self.admin_volume_qos_client.show_association_qos(
self.created_qos['id'])['qos_associations']
associations = []
@@ -99,7 +99,7 @@
@test.idempotent_id('7aa214cc-ac1a-4397-931f-3bb2e83bb0fd')
def test_get_qos(self):
"""Tests the detail of a given qos-specs"""
- body = self.volume_qos_client.show_qos(
+ body = self.admin_volume_qos_client.show_qos(
self.created_qos['id'])['qos_specs']
self.assertEqual(self.qos_name, body['name'])
self.assertEqual(self.qos_consumer, body['consumer'])
@@ -107,28 +107,29 @@
@test.idempotent_id('75e04226-bcf7-4595-a34b-fdf0736f38fc')
def test_list_qos(self):
"""Tests the list of all qos-specs"""
- body = self.volume_qos_client.list_qos()['qos_specs']
+ body = self.admin_volume_qos_client.list_qos()['qos_specs']
self.assertIn(self.created_qos, body)
@test.idempotent_id('ed00fd85-4494-45f2-8ceb-9e2048919aed')
def test_set_unset_qos_key(self):
"""Test the addition of a specs key to qos-specs"""
args = {'iops_bytes': '500'}
- body = self.volume_qos_client.set_qos_key(
+ body = self.admin_volume_qos_client.set_qos_key(
self.created_qos['id'],
iops_bytes='500')['qos_specs']
self.assertEqual(args, body)
- body = self.volume_qos_client.show_qos(
+ body = self.admin_volume_qos_client.show_qos(
self.created_qos['id'])['qos_specs']
self.assertEqual(args['iops_bytes'], body['specs']['iops_bytes'])
# test the deletion of a specs key from qos-specs
keys = ['iops_bytes']
- self.volume_qos_client.unset_qos_key(self.created_qos['id'], keys)
+ self.admin_volume_qos_client.unset_qos_key(self.created_qos['id'],
+ keys)
operation = 'qos-key-unset'
- self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
- operation, keys)
- body = self.volume_qos_client.show_qos(
+ self.admin_volume_qos_client.wait_for_qos_operations(
+ self.created_qos['id'], operation, keys)
+ body = self.admin_volume_qos_client.show_qos(
self.created_qos['id'])['qos_specs']
self.assertNotIn(keys[0], body['specs'])
@@ -158,21 +159,20 @@
self.assertIn(vol_type[i]['id'], associations)
# disassociate a volume-type with qos-specs
- self.volume_qos_client.disassociate_qos(
+ self.admin_volume_qos_client.disassociate_qos(
self.created_qos['id'], vol_type[0]['id'])
operation = 'disassociate'
- self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
- operation,
- vol_type[0]['id'])
+ self.admin_volume_qos_client.wait_for_qos_operations(
+ self.created_qos['id'], operation, vol_type[0]['id'])
associations = self._test_get_association_qos()
self.assertNotIn(vol_type[0]['id'], associations)
# disassociate all volume-types from qos-specs
- self.volume_qos_client.disassociate_all_qos(
+ self.admin_volume_qos_client.disassociate_all_qos(
self.created_qos['id'])
operation = 'disassociate-all'
- self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
- operation)
+ self.admin_volume_qos_client.wait_for_qos_operations(
+ self.created_qos['id'], operation)
associations = self._test_get_association_qos()
self.assertEmpty(associations)
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index b28488a..b58c525 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -21,7 +21,7 @@
@test.idempotent_id('d5f3efa2-6684-4190-9ced-1c2f526352ad')
def test_list_hosts(self):
- hosts = self.hosts_client.list_hosts()['hosts']
+ hosts = self.admin_hosts_client.list_hosts()['hosts']
self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2,"
"response of list hosts is: % s" % hosts)
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index b2e52bb..27ccae5 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -30,16 +30,21 @@
super(BaseVolumeQuotasAdminV2TestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os.credentials.tenant_id
+ def _delete_volume(self, volume_id):
+ # Delete the specified volume using admin credentials
+ self.admin_volume_client.delete_volume(volume_id)
+ self.admin_volume_client.wait_for_resource_deletion(volume_id)
+
@test.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0')
def test_list_quotas(self):
- quotas = (self.quotas_client.show_quota_set(self.demo_tenant_id)
+ quotas = (self.admin_quotas_client.show_quota_set(self.demo_tenant_id)
['quota_set'])
for key in QUOTA_KEYS:
self.assertIn(key, quotas)
@test.idempotent_id('2be020a2-5fdd-423d-8d35-a7ffbc36e9f7')
def test_list_default_quotas(self):
- quotas = self.quotas_client.show_default_quota_set(
+ quotas = self.admin_quotas_client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
for key in QUOTA_KEYS:
self.assertIn(key, quotas)
@@ -47,21 +52,21 @@
@test.idempotent_id('3d45c99e-cc42-4424-a56e-5cbd212b63a6')
def test_update_all_quota_resources_for_tenant(self):
# Admin can update all the resource quota limits for a tenant
- default_quota_set = self.quotas_client.show_default_quota_set(
+ default_quota_set = self.admin_quotas_client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
new_quota_set = {'gigabytes': 1009,
'volumes': 11,
'snapshots': 11}
# Update limits for all quota resources
- quota_set = self.quotas_client.update_quota_set(
+ quota_set = self.admin_quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)['quota_set']
cleanup_quota_set = dict(
(k, v) for k, v in six.iteritems(default_quota_set)
if k in QUOTA_KEYS)
- self.addCleanup(self.quotas_client.update_quota_set,
+ self.addCleanup(self.admin_quotas_client.update_quota_set,
self.demo_tenant_id, **cleanup_quota_set)
# test that the specific values we set are actually in
# the final result. There is nothing here that ensures there
@@ -70,7 +75,7 @@
@test.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed')
def test_show_quota_usage(self):
- quota_usage = self.quotas_client.show_quota_usage(
+ quota_usage = self.admin_quotas_client.show_quota_usage(
self.os_adm.credentials.tenant_id)['quota_set']
for key in QUOTA_KEYS:
self.assertIn(key, quota_usage)
@@ -79,14 +84,13 @@
@test.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f')
def test_quota_usage(self):
- quota_usage = self.quotas_client.show_quota_usage(
+ quota_usage = self.admin_quotas_client.show_quota_usage(
self.demo_tenant_id)['quota_set']
volume = self.create_volume()
- self.addCleanup(self.admin_volume_client.delete_volume,
- volume['id'])
+ self.addCleanup(self._delete_volume, volume['id'])
- new_quota_usage = self.quotas_client.show_quota_usage(
+ new_quota_usage = self.admin_quotas_client.show_quota_usage(
self.demo_tenant_id)['quota_set']
self.assertEqual(quota_usage['volumes']['in_use'] + 1,
@@ -105,15 +109,15 @@
description=description)
project_id = project['id']
self.addCleanup(self.identity_utils.delete_project, project_id)
- quota_set_default = self.quotas_client.show_default_quota_set(
+ quota_set_default = self.admin_quotas_client.show_default_quota_set(
project_id)['quota_set']
volume_default = quota_set_default['volumes']
- self.quotas_client.update_quota_set(project_id,
- volumes=(int(volume_default) + 5))
+ self.admin_quotas_client.update_quota_set(
+ project_id, volumes=(int(volume_default) + 5))
- self.quotas_client.delete_quota_set(project_id)
- quota_set_new = (self.quotas_client.show_quota_set(project_id)
+ self.admin_quotas_client.delete_quota_set(project_id)
+ quota_set_new = (self.admin_quotas_client.show_quota_set(project_id)
['quota_set'])
self.assertEqual(volume_default, quota_set_new['volumes'])
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index a43ee8e..dde8915 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -38,7 +38,7 @@
# NOTE(gfidente): no need to restore original quota set
# after the tests as they only work with dynamic credentials.
- cls.quotas_client.update_quota_set(
+ cls.admin_quotas_client.update_quota_set(
cls.demo_tenant_id,
**cls.shared_quota_set)
@@ -58,12 +58,12 @@
# NOTE(gfidente): quota set needs to be changed for this test
# or we may be limited by the volumes or snaps quota number, not by
# actual gigs usage; next line ensures shared set is restored.
- self.addCleanup(self.quotas_client.update_quota_set,
+ self.addCleanup(self.admin_quotas_client.update_quota_set,
self.demo_tenant_id,
**self.shared_quota_set)
new_quota_set = {'gigabytes': self.default_volume_size,
'volumes': 2, 'snapshots': 1}
- self.quotas_client.update_quota_set(
+ self.admin_quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
self.assertRaises(lib_exc.OverLimit,
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 b7f70ba..1565a8c 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -44,7 +44,7 @@
# NOTE(gfidente): no need to restore original quota set
# after the tests as they only work with tenant isolation.
- cls.quotas_client.update_quota_set(
+ cls.admin_quotas_client.update_quota_set(
cls.demo_tenant_id,
**cls.shared_quota_set)
@@ -63,12 +63,12 @@
@test.attr(type='negative')
@test.idempotent_id('c99a1ca9-6cdf-498d-9fdf-25832babef27')
def test_quota_volume_gigabytes_snapshots(self):
- self.addCleanup(self.quotas_client.update_quota_set,
+ self.addCleanup(self.admin_quotas_client.update_quota_set,
self.demo_tenant_id,
**self.shared_quota_set)
new_quota_set = {'gigabytes': 2 * self.default_volume_size,
'volumes': 1, 'snapshots': 2}
- self.quotas_client.update_quota_set(
+ self.admin_quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
self.assertRaises(lib_exc.OverLimit,
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 6fc6f1c..587dbd2 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -31,7 +31,8 @@
@test.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54')
def test_volume_type_list(self):
# List volume types.
- body = self.volume_types_client.list_volume_types()['volume_types']
+ body = \
+ self.admin_volume_types_client.list_volume_types()['volume_types']
self.assertIsInstance(body, list)
@test.idempotent_id('c03cc62c-f4e9-4623-91ec-64ce2f9c1260')
@@ -47,11 +48,11 @@
# Create two volume_types
for i in range(2):
vol_type_name = data_utils.rand_name("volume-type")
- vol_type = self.volume_types_client.create_volume_type(
+ vol_type = self.admin_volume_types_client.create_volume_type(
name=vol_type_name,
extra_specs=extra_specs)['volume_type']
volume_types.append(vol_type)
- self.addCleanup(self.volume_types_client.delete_volume_type,
+ self.addCleanup(self.admin_volume_types_client.delete_volume_type,
vol_type['id'])
params = {self.name_field: vol_name,
'volume_type': volume_types[0]['id']}
@@ -97,11 +98,11 @@
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
- body = self.volume_types_client.create_volume_type(
+ body = self.admin_volume_types_client.create_volume_type(
name=name,
extra_specs=extra_specs)['volume_type']
self.assertIn('id', body)
- self.addCleanup(self.volume_types_client.delete_volume_type,
+ self.addCleanup(self.admin_volume_types_client.delete_volume_type,
body['id'])
self.assertIn('name', body)
self.assertEqual(body['name'], name,
@@ -109,7 +110,7 @@
"to the requested name")
self.assertTrue(body['id'] is not None,
"Field volume_type id is empty or not found.")
- fetched_volume_type = self.volume_types_client.show_volume_type(
+ fetched_volume_type = self.admin_volume_types_client.show_volume_type(
body['id'])['volume_type']
self.assertEqual(name, fetched_volume_type['name'],
'The fetched Volume_type is different '
@@ -127,15 +128,16 @@
provider = "LuksEncryptor"
control_location = "front-end"
name = data_utils.rand_name("volume-type")
- body = self.volume_types_client.create_volume_type(
+ body = self.admin_volume_types_client.create_volume_type(
name=name)['volume_type']
- self.addCleanup(self.volume_types_client.delete_volume_type,
+ self.addCleanup(self.admin_volume_types_client.delete_volume_type,
body['id'])
# Create encryption type
- encryption_type = self.volume_types_client.create_encryption_type(
- body['id'], provider=provider,
- control_location=control_location)['encryption']
+ encryption_type = \
+ self.admin_volume_types_client.create_encryption_type(
+ body['id'], provider=provider,
+ control_location=control_location)['encryption']
self.assertIn('volume_type_id', encryption_type)
self.assertEqual(provider, encryption_type['provider'],
"The created encryption_type provider is not equal "
@@ -146,7 +148,7 @@
# Get encryption type
fetched_encryption_type = (
- self.volume_types_client.show_encryption_type(
+ self.admin_volume_types_client.show_encryption_type(
encryption_type['volume_type_id']))
self.assertEqual(provider,
fetched_encryption_type['provider'],
@@ -158,13 +160,13 @@
'different from the created encryption_type')
# Delete encryption type
- self.volume_types_client.delete_encryption_type(
+ self.admin_volume_types_client.delete_encryption_type(
encryption_type['volume_type_id'])
resource = {"id": encryption_type['volume_type_id'],
"type": "encryption-type"}
- self.volume_types_client.wait_for_resource_deletion(resource)
+ self.admin_volume_types_client.wait_for_resource_deletion(resource)
deleted_encryption_type = (
- self.volume_types_client.show_encryption_type(
+ self.admin_volume_types_client.show_encryption_type(
encryption_type['volume_type_id']))
self.assertEmpty(deleted_encryption_type)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 502cd86..a45bee0 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -24,23 +24,24 @@
def resource_setup(cls):
super(VolumeTypesExtraSpecsV2Test, cls).resource_setup()
vol_type_name = data_utils.rand_name('Volume-type')
- cls.volume_type = cls.volume_types_client.create_volume_type(
- name=vol_type_name)['volume_type']
+ cls.volume_type = \
+ cls.admin_volume_types_client.create_volume_type(
+ name=vol_type_name)['volume_type']
@classmethod
def resource_cleanup(cls):
- cls.volume_types_client.delete_volume_type(cls.volume_type['id'])
+ cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
super(VolumeTypesExtraSpecsV2Test, cls).resource_cleanup()
@test.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
def test_volume_type_extra_specs_list(self):
# List Volume types extra specs.
extra_specs = {"spec1": "val1"}
- body = self.volume_types_client.create_volume_type_extra_specs(
+ body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- body = self.volume_types_client.list_volume_types_extra_specs(
+ body = self.admin_volume_types_client.list_volume_types_extra_specs(
self.volume_type['id'])['extra_specs']
self.assertIsInstance(body, dict)
self.assertIn('spec1', body)
@@ -49,13 +50,13 @@
def test_volume_type_extra_specs_update(self):
# Update volume type extra specs
extra_specs = {"spec2": "val1"}
- body = self.volume_types_client.create_volume_type_extra_specs(
+ body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
extra_spec = {"spec2": "val2"}
- body = self.volume_types_client.update_volume_type_extra_specs(
+ body = self.admin_volume_types_client.update_volume_type_extra_specs(
self.volume_type['id'],
extra_spec.keys()[0],
extra_spec)
@@ -67,19 +68,19 @@
def test_volume_type_extra_spec_create_get_delete(self):
# Create/Get/Delete volume type extra spec.
extra_specs = {"spec3": "val1"}
- body = self.volume_types_client.create_volume_type_extra_specs(
+ body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'],
extra_specs)['extra_specs']
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- self.volume_types_client.show_volume_type_extra_specs(
+ self.admin_volume_types_client.show_volume_type_extra_specs(
self.volume_type['id'],
extra_specs.keys()[0])
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly fetched")
- self.volume_types_client.delete_volume_type_extra_specs(
+ self.admin_volume_types_client.delete_volume_type_extra_specs(
self.volume_type['id'],
extra_specs.keys()[0])
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 f3e52e9..6983974 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
@@ -26,13 +26,13 @@
super(ExtraSpecsNegativeV2Test, cls).resource_setup()
vol_type_name = data_utils.rand_name('Volume-type')
cls.extra_specs = {"spec1": "val1"}
- cls.volume_type = cls.volume_types_client.create_volume_type(
+ cls.volume_type = cls.admin_volume_types_client.create_volume_type(
name=vol_type_name,
extra_specs=cls.extra_specs)['volume_type']
@classmethod
def resource_cleanup(cls):
- cls.volume_types_client.delete_volume_type(cls.volume_type['id'])
+ cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
super(ExtraSpecsNegativeV2Test, cls).resource_cleanup()
@test.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
@@ -41,7 +41,7 @@
extra_spec = {"spec1": "val2"}
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.update_volume_type_extra_specs,
+ self.admin_volume_types_client.update_volume_type_extra_specs,
self.volume_type['id'], extra_spec.keys()[0], None)
@test.idempotent_id('25e5a0ee-89b3-4c53-8310-236f76c75365')
@@ -50,7 +50,7 @@
extra_spec = {"spec1": "val2"}
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.update_volume_type_extra_specs,
+ self.admin_volume_types_client.update_volume_type_extra_specs,
self.volume_type['id'], data_utils.rand_uuid(),
extra_spec)
@@ -60,7 +60,7 @@
extra_spec = {"spec1": "val2"}
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.update_volume_type_extra_specs,
+ self.admin_volume_types_client.update_volume_type_extra_specs,
self.volume_type['id'], None, extra_spec)
@test.idempotent_id('a77dfda2-9100-448e-9076-ed1711f4bdfc')
@@ -70,7 +70,7 @@
extra_spec = {"spec1": "val2", "spec2": "val1"}
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.update_volume_type_extra_specs,
+ self.admin_volume_types_client.update_volume_type_extra_specs,
self.volume_type['id'], extra_spec.keys()[0],
extra_spec)
@@ -81,7 +81,7 @@
extra_specs = {"spec2": "val1"}
self.assertRaises(
lib_exc.NotFound,
- self.volume_types_client.create_volume_type_extra_specs,
+ self.admin_volume_types_client.create_volume_type_extra_specs,
data_utils.rand_uuid(), extra_specs)
@test.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d')
@@ -89,7 +89,7 @@
# Should not create volume type extra spec for none POST body.
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.create_volume_type_extra_specs,
+ self.admin_volume_types_client.create_volume_type_extra_specs,
self.volume_type['id'], None)
@test.idempotent_id('bc772c71-1ed4-4716-b945-8b5ed0f15e87')
@@ -97,7 +97,7 @@
# Should not create volume type extra spec for invalid POST body.
self.assertRaises(
lib_exc.BadRequest,
- self.volume_types_client.create_volume_type_extra_specs,
+ self.admin_volume_types_client.create_volume_type_extra_specs,
self.volume_type['id'], extra_specs=['invalid'])
@test.idempotent_id('031cda8b-7d23-4246-8bf6-bbe73fd67074')
@@ -107,7 +107,7 @@
extra_specs = {"spec1": "val1"}
self.assertRaises(
lib_exc.NotFound,
- self.volume_types_client.delete_volume_type_extra_specs,
+ self.admin_volume_types_client.delete_volume_type_extra_specs,
data_utils.rand_uuid(), extra_specs.keys()[0])
@test.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb')
@@ -115,7 +115,7 @@
# Should not list volume type extra spec for nonexistent type id.
self.assertRaises(
lib_exc.NotFound,
- self.volume_types_client.list_volume_types_extra_specs,
+ self.admin_volume_types_client.list_volume_types_extra_specs,
data_utils.rand_uuid())
@test.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9')
@@ -124,7 +124,7 @@
extra_specs = {"spec1": "val1"}
self.assertRaises(
lib_exc.NotFound,
- self.volume_types_client.show_volume_type_extra_specs,
+ self.admin_volume_types_client.show_volume_type_extra_specs,
data_utils.rand_uuid(), extra_specs.keys()[0])
@test.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753')
@@ -133,7 +133,7 @@
# id.
self.assertRaises(
lib_exc.NotFound,
- self.volume_types_client.show_volume_type_extra_specs,
+ self.admin_volume_types_client.show_volume_type_extra_specs,
self.volume_type['id'], data_utils.rand_uuid())
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index aff5466..857e7d2 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -33,21 +33,22 @@
@test.idempotent_id('878b4e57-faa2-4659-b0d1-ce740a06ae81')
def test_create_with_empty_name(self):
# Should not be able to create volume type with an empty name.
- self.assertRaises(lib_exc.BadRequest,
- self.volume_types_client.create_volume_type, name='')
+ self.assertRaises(
+ lib_exc.BadRequest,
+ self.admin_volume_types_client.create_volume_type, name='')
@test.idempotent_id('994610d6-0476-4018-a644-a2602ef5d4aa')
def test_get_nonexistent_type_id(self):
# Should not be able to get volume type with nonexistent type id.
self.assertRaises(lib_exc.NotFound,
- self.volume_types_client.show_volume_type,
+ self.admin_volume_types_client.show_volume_type,
data_utils.rand_uuid())
@test.idempotent_id('6b3926d2-7d73-4896-bc3d-e42dfd11a9f6')
def test_delete_nonexistent_type_id(self):
# Should not be able to delete volume type with nonexistent type id.
self.assertRaises(lib_exc.NotFound,
- self.volume_types_client.delete_volume_type,
+ self.admin_volume_types_client.delete_volume_type,
data_utils.rand_uuid())
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index b09cd2c..66bab51 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -13,11 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import base64
+import six
+
+from oslo_serialization import jsonutils as json
+
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
@@ -38,60 +42,79 @@
cls.volume = cls.create_volume()
def _delete_backup(self, backup_id):
- self.backups_adm_client.delete_backup(backup_id)
- self.backups_adm_client.wait_for_backup_deletion(backup_id)
+ self.admin_backups_client.delete_backup(backup_id)
+ self.admin_backups_client.wait_for_backup_deletion(backup_id)
+
+ def _decode_url(self, backup_url):
+ return json.loads(base64.decodestring(backup_url))
+
+ def _encode_backup(self, backup):
+ retval = json.dumps(backup)
+ if six.PY3:
+ retval = retval.encode('utf-8')
+ return base64.encodestring(retval)
+
+ def _modify_backup_url(self, backup_url, changes):
+ backup = self._decode_url(backup_url)
+ backup.update(changes)
+ return self._encode_backup(backup)
@test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
def test_volume_backup_create_get_detailed_list_restore_delete(self):
# Create backup
backup_name = data_utils.rand_name('Backup')
- create_backup = self.backups_adm_client.create_backup
+ create_backup = self.admin_backups_client.create_backup
backup = create_backup(volume_id=self.volume['id'],
name=backup_name)['backup']
- self.addCleanup(self.backups_adm_client.delete_backup,
+ self.addCleanup(self.admin_backups_client.delete_backup,
backup['id'])
self.assertEqual(backup_name, backup['name'])
waiters.wait_for_volume_status(self.admin_volume_client,
self.volume['id'], 'available')
- self.backups_adm_client.wait_for_backup_status(backup['id'],
- 'available')
+ self.admin_backups_client.wait_for_backup_status(backup['id'],
+ 'available')
# Get a given backup
- backup = self.backups_adm_client.show_backup(backup['id'])['backup']
+ backup = self.admin_backups_client.show_backup(backup['id'])['backup']
self.assertEqual(backup_name, backup['name'])
# Get all backups with detail
- backups = self.backups_adm_client.list_backups(detail=True)['backups']
+ backups = self.admin_backups_client.list_backups(
+ detail=True)['backups']
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
# Restore backup
- restore = self.backups_adm_client.restore_backup(
+ restore = self.admin_backups_client.restore_backup(
backup['id'])['restore']
# Delete backup
self.addCleanup(self.admin_volume_client.delete_volume,
restore['volume_id'])
self.assertEqual(backup['id'], restore['backup_id'])
- self.backups_adm_client.wait_for_backup_status(backup['id'],
- 'available')
+ self.admin_backups_client.wait_for_backup_status(backup['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')
def test_volume_backup_export_import(self):
+ """Test backup export import functionality.
+
+ Cinder allows exporting DB backup information through its API so it can
+ be imported back in case of a DB loss.
+ """
# Create backup
backup_name = data_utils.rand_name('Backup')
- backup = (self.backups_adm_client.create_backup(
+ backup = (self.admin_backups_client.create_backup(
volume_id=self.volume['id'], name=backup_name)['backup'])
self.addCleanup(self._delete_backup, backup['id'])
self.assertEqual(backup_name, backup['name'])
- self.backups_adm_client.wait_for_backup_status(backup['id'],
- 'available')
+ self.admin_backups_client.wait_for_backup_status(backup['id'],
+ 'available')
# Export Backup
- export_backup = (self.backups_adm_client.export_backup(backup['id'])
+ export_backup = (self.admin_backups_client.export_backup(backup['id'])
['backup-record'])
self.assertIn('backup_service', export_backup)
self.assertIn('backup_url', export_backup)
@@ -99,33 +122,49 @@
'cinder.backup.drivers'))
self.assertIsNotNone(export_backup['backup_url'])
+ # NOTE(geguileo): Backups are imported with the same backup id
+ # (important for incremental backups among other things), so we cannot
+ # import the exported backup information as it is, because that Backup
+ # ID already exists. So we'll fake the data by changing the backup id
+ # in the exported backup DB info we have retrieved before importing it
+ # back.
+ new_id = data_utils.rand_uuid()
+ new_url = self._modify_backup_url(
+ export_backup['backup_url'], {'id': new_id})
+
# Import Backup
- import_backup = self.backups_adm_client.import_backup(
+ import_backup = self.admin_backups_client.import_backup(
backup_service=export_backup['backup_service'],
- backup_url=export_backup['backup_url'])['backup']
- self.addCleanup(self._delete_backup, import_backup['id'])
+ backup_url=new_url)['backup']
+
+ # NOTE(geguileo): We delete both backups, but only one of those
+ # deletions will delete data from the backup back-end because they
+ # were both pointing to the same backend data.
+ self.addCleanup(self._delete_backup, new_id)
self.assertIn("id", import_backup)
- self.backups_adm_client.wait_for_backup_status(import_backup['id'],
- 'available')
+ self.assertEqual(new_id, import_backup['id'])
+ self.admin_backups_client.wait_for_backup_status(import_backup['id'],
+ 'available')
# Verify Import Backup
- backups = self.backups_adm_client.list_backups(detail=True)['backups']
- self.assertIn(import_backup['id'], [b['id'] for b in backups])
+ backups = self.admin_backups_client.list_backups(
+ detail=True)['backups']
+ self.assertIn(new_id, [b['id'] for b in backups])
# Restore backup
- restore = (self.backups_adm_client.restore_backup(import_backup['id'])
- ['restore'])
+ restore = self.admin_backups_client.restore_backup(
+ backup['id'])['restore']
self.addCleanup(self.admin_volume_client.delete_volume,
restore['volume_id'])
- self.assertEqual(import_backup['id'], restore['backup_id'])
+ self.assertEqual(backup['id'], restore['backup_id'])
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']
self.assertIn(restore['volume_id'], [v['id'] for v in volumes])
- self.backups_adm_client.wait_for_backup_status(import_backup['id'],
- 'available')
+ self.admin_backups_client.wait_for_backup_status(import_backup['id'],
+ 'available')
class VolumesBackupsV1Test(VolumesBackupsV2Test):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 14819e3..cd21424 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -18,7 +18,7 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
import tempest.test
CONF = config.CONF
@@ -179,25 +179,25 @@
super(BaseVolumeAdminTest, cls).setup_clients()
if cls._api_version == 1:
- cls.volume_qos_client = cls.os_adm.volume_qos_client
+ cls.admin_volume_qos_client = cls.os_adm.volume_qos_client
cls.admin_volume_services_client = \
cls.os_adm.volume_services_client
- cls.volume_types_client = cls.os_adm.volume_types_client
+ cls.admin_volume_types_client = cls.os_adm.volume_types_client
cls.admin_volume_client = cls.os_adm.volumes_client
- cls.hosts_client = cls.os_adm.volume_hosts_client
+ cls.admin_hosts_client = cls.os_adm.volume_hosts_client
cls.admin_snapshots_client = cls.os_adm.snapshots_client
- cls.backups_adm_client = cls.os_adm.backups_client
- cls.quotas_client = cls.os_adm.volume_quotas_client
+ cls.admin_backups_client = cls.os_adm.backups_client
+ cls.admin_quotas_client = cls.os_adm.volume_quotas_client
elif cls._api_version == 2:
- cls.volume_qos_client = cls.os_adm.volume_qos_v2_client
+ cls.admin_volume_qos_client = cls.os_adm.volume_qos_v2_client
cls.admin_volume_services_client = \
cls.os_adm.volume_services_v2_client
- cls.volume_types_client = cls.os_adm.volume_types_v2_client
+ cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
cls.admin_volume_client = cls.os_adm.volumes_v2_client
- cls.hosts_client = cls.os_adm.volume_hosts_v2_client
+ cls.admin_hosts_client = cls.os_adm.volume_hosts_v2_client
cls.admin_snapshots_client = cls.os_adm.snapshots_v2_client
- cls.backups_adm_client = cls.os_adm.backups_v2_client
- cls.quotas_client = cls.os_adm.volume_quotas_v2_client
+ cls.admin_backups_client = cls.os_adm.backups_v2_client
+ cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
@classmethod
def resource_setup(cls):
@@ -215,7 +215,7 @@
"""create a test Qos-Specs."""
name = name or data_utils.rand_name(cls.__name__ + '-QoS')
consumer = consumer or 'front-end'
- qos_specs = cls.volume_qos_client.create_qos(
+ qos_specs = cls.admin_volume_qos_client.create_qos(
name=name, consumer=consumer, **kwargs)['qos_specs']
cls.qos_specs.append(qos_specs['id'])
return qos_specs
@@ -223,15 +223,9 @@
@classmethod
def clear_qos_specs(cls):
for qos_id in cls.qos_specs:
- try:
- cls.volume_qos_client.delete_qos(qos_id)
- except lib_exc.NotFound:
- # The qos_specs may have already been deleted which is OK.
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_qos_client.delete_qos, qos_id)
for qos_id in cls.qos_specs:
- try:
- cls.volume_qos_client.wait_for_resource_deletion(qos_id)
- except lib_exc.NotFound:
- # The qos_specs may have already been deleted which is OK.
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index e52216f..76cd36c 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -12,14 +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 testtools
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 exceptions
+from tempest import exceptions
+from tempest.lib.common.utils import test_utils
from tempest import test
-import testtools
CONF = config.CONF
@@ -30,7 +31,16 @@
def setup_clients(cls):
super(VolumesV2ActionsTest, cls).setup_clients()
cls.client = cls.volumes_client
- cls.image_client = cls.os.image_client
+ if CONF.service_available.glance:
+ # Check if glance v1 is available to determine which client to use.
+ if CONF.image_feature_enabled.api_v1:
+ cls.image_client = cls.os.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ cls.image_client = cls.os.image_client_v2
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
@classmethod
def resource_setup(cls):
@@ -124,19 +134,13 @@
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._cleanup_image, image_id)
- self.image_client.wait_for_image_status(image_id, 'active')
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.image_client.delete_image,
+ image_id)
+ waiters.wait_for_image_status(self.image_client, image_id, 'active')
waiters.wait_for_volume_status(self.client,
self.volume['id'], 'available')
- def _cleanup_image(self, image_id):
- # Ignores the image deletion
- # in the case that image wasn't created in the first place
- try:
- self.image_client.delete_image(image_id)
- except exceptions.NotFound:
- pass
-
@test.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
diff --git a/tempest/clients.py b/tempest/clients.py
index 2ad1733..7ec5c7e 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,6 +75,12 @@
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.image.v2.image_members_client import \
+ ImageMembersClient as ImageMembersClientV2
+from tempest.lib.services.image.v2.namespaces_client import NamespacesClient
+from tempest.lib.services.image.v2.resource_types_client import \
+ ResourceTypesClient
+from tempest.lib.services.image.v2.schemas_client import SchemasClient
from tempest.lib.services.network.agents_client import AgentsClient \
as NetworkAgentsClient
from tempest.lib.services.network.extensions_client import \
@@ -88,6 +94,7 @@
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.routers_client import RoutersClient
from tempest.lib.services.network.security_group_rules_client import \
SecurityGroupRulesClient
from tempest.lib.services.network.security_groups_client import \
@@ -130,9 +137,11 @@
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.image_members_client import \
+ ImageMembersClient
from tempest.services.image.v1.json.images_client import ImagesClient
-from tempest.services.image.v2.json.images_client import ImagesClientV2
-from tempest.services.network.json.routers_client import RoutersClient
+from tempest.services.image.v2.json.images_client import \
+ ImagesClient as ImagesV2Client
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
@@ -197,19 +206,22 @@
}
default_params_with_timeout_values.update(default_params)
- def __init__(self, credentials, service=None):
+ def __init__(self, credentials, service=None, scope='project'):
"""Initialization of Manager class.
Setup all services clients and make them available for tests cases.
:param credentials: type Credentials or TestResources
:param service: Service name
+ :param scope: default scope for tokens produced by the auth provider
"""
- super(Manager, self).__init__(credentials=credentials)
+ super(Manager, self).__init__(credentials=credentials, scope=scope)
self._set_compute_clients()
self._set_database_clients()
self._set_identity_clients()
self._set_volume_clients()
self._set_object_storage_clients()
+ self._set_image_clients()
+ self._set_network_clients()
self.baremetal_client = BaremetalClient(
self.auth_provider,
@@ -217,127 +229,6 @@
CONF.identity.region,
endpoint_type=CONF.baremetal.endpoint_type,
**self.default_params_with_timeout_values)
- self.network_agents_client = NetworkAgentsClient(
- 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.network_extensions_client = NetworkExtensionsClient(
- 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,
- 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.subnetpools_client = SubnetpoolsClient(
- 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.subnets_client = SubnetsClient(
- 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.ports_client = PortsClient(
- 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.network_quotas_client = NetworkQuotasClient(
- 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.floating_ips_client = FloatingIPsClient(
- 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.metering_labels_client = MeteringLabelsClient(
- 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.metering_label_rules_client = MeteringLabelRulesClient(
- 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.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,
- 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_groups_client = SecurityGroupsClient(
- 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)
- if CONF.service_available.glance:
- self.image_client = ImagesClient(
- self.auth_provider,
- CONF.image.catalog_type,
- CONF.image.region or CONF.identity.region,
- endpoint_type=CONF.image.endpoint_type,
- build_interval=CONF.image.build_interval,
- build_timeout=CONF.image.build_timeout,
- **self.default_params)
- self.image_client_v2 = ImagesClientV2(
- self.auth_provider,
- CONF.image.catalog_type,
- CONF.image.region or CONF.identity.region,
- endpoint_type=CONF.image.endpoint_type,
- build_interval=CONF.image.build_interval,
- build_timeout=CONF.image.build_timeout,
- **self.default_params)
self.orchestration_client = OrchestrationClient(
self.auth_provider,
CONF.orchestration.catalog_type,
@@ -355,6 +246,68 @@
self.negative_client = negative_rest_client.NegativeRestClient(
self.auth_provider, service, **self.default_params)
+ def _set_network_clients(self):
+ params = {
+ 'service': CONF.network.catalog_type,
+ 'region': 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
+ }
+ params.update(self.default_params)
+ self.network_agents_client = NetworkAgentsClient(
+ self.auth_provider, **params)
+ self.network_extensions_client = NetworkExtensionsClient(
+ self.auth_provider, **params)
+ self.networks_client = NetworksClient(
+ self.auth_provider, **params)
+ self.subnetpools_client = SubnetpoolsClient(
+ self.auth_provider, **params)
+ self.subnets_client = SubnetsClient(
+ self.auth_provider, **params)
+ self.ports_client = PortsClient(
+ self.auth_provider, **params)
+ self.network_quotas_client = NetworkQuotasClient(
+ self.auth_provider, **params)
+ self.floating_ips_client = FloatingIPsClient(
+ self.auth_provider, **params)
+ self.metering_labels_client = MeteringLabelsClient(
+ self.auth_provider, **params)
+ self.metering_label_rules_client = MeteringLabelRulesClient(
+ self.auth_provider, **params)
+ self.routers_client = RoutersClient(
+ self.auth_provider, **params)
+ self.security_group_rules_client = SecurityGroupRulesClient(
+ self.auth_provider, **params)
+ self.security_groups_client = SecurityGroupsClient(
+ self.auth_provider, **params)
+
+ def _set_image_clients(self):
+ params = {
+ 'service': CONF.image.catalog_type,
+ 'region': CONF.image.region or CONF.identity.region,
+ 'endpoint_type': CONF.image.endpoint_type,
+ 'build_interval': CONF.image.build_interval,
+ 'build_timeout': CONF.image.build_timeout
+ }
+ params.update(self.default_params)
+
+ if CONF.service_available.glance:
+ self.image_client = ImagesClient(
+ self.auth_provider, **params)
+ self.image_member_client = ImageMembersClient(
+ self.auth_provider, **params)
+ self.image_client_v2 = ImagesV2Client(
+ self.auth_provider, **params)
+ self.image_member_client_v2 = ImageMembersClientV2(
+ self.auth_provider, **params)
+ self.namespaces_client = NamespacesClient(
+ self.auth_provider, **params)
+ self.resource_types_client = ResourceTypesClient(
+ self.auth_provider, **params)
+ self.schemas_client = SchemasClient(
+ self.auth_provider, **params)
+
def _set_compute_clients(self):
params = {
'service': CONF.compute.catalog_type,
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 5fab961..9e1d391 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -40,13 +40,15 @@
You're probably familiar with these, but just to remind::
- +----------+------------------+----------------------+
- | Param | CLI | Environment Variable |
- +----------+------------------+----------------------+
- | Username | --os-username | OS_USERNAME |
- | Password | --os-password | OS_PASSWORD |
- | Tenant | --os-tenant-name | OS_TENANT_NAME |
- +----------+------------------+----------------------+
+ +----------+---------------------------+----------------------+
+ | Param | CLI | Environment Variable |
+ +----------+---------------------------+----------------------+
+ | Username | --os-username | OS_USERNAME |
+ | Password | --os-password | OS_PASSWORD |
+ | Project | --os-project-name | OS_PROJECT_NAME |
+ | Tenant | --os-tenant-name (depr.) | OS_TENANT_NAME |
+ | Domain | --os-domain-name | OS_DOMAIN_NAME |
+ +----------+---------------------------+----------------------+
Optional Arguments
------------------
@@ -63,8 +65,14 @@
**--os-password <auth-password>** (Optional) Password used for authentication
with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
-**--os-tenant-name <auth-tenant-name>** (Optional) Tenant to request
-authorization on. Defaults to env[OS_TENANT_NAME].
+**--os-project-name <auth-project-name>** (Optional) Project to request
+authorization on. Defaults to env[OS_PROJECT_NAME].
+
+**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
+request authorization on. Defaults to env[OS_TENANT_NAME].
+
+**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
+belong to. Defaults to env[OS_DOMAIN_NAME].
**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
will have the prefix with the given TAG in its name. Using tag is recommended
@@ -79,11 +87,13 @@
**--with-admin** (Optional) Creates admin for each concurrent group
(default: False).
+**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
+using the specified version of the identity API. (default: '3').
+
To see help on specific argument, please do: ``tempest-account-generator
[OPTIONS] <accounts_file.yaml> -h``.
"""
import argparse
-import netaddr
import os
import traceback
@@ -91,19 +101,10 @@
from oslo_log import log as logging
import yaml
-from tempest.common import identity
+from tempest.common import credentials_factory
+from tempest.common import dynamic_creds
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 routers_client
+
LOG = None
CONF = config.CONF
@@ -120,290 +121,83 @@
LOG = logging.getLogger(__name__)
-def get_admin_clients(opts):
- _creds = tempest.lib.auth.KeystoneV2Credentials(
- username=opts.os_username,
- password=opts.os_password,
- tenant_name=opts.os_tenant_name)
- auth_params = {
- 'disable_ssl_certificate_validation':
- CONF.identity.disable_ssl_certificate_validation,
- 'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests
- }
- _auth = tempest.lib.auth.KeystoneV2AuthProvider(
- _creds, CONF.identity.uri, **auth_params)
- params = {
- 'disable_ssl_certificate_validation':
- CONF.identity.disable_ssl_certificate_validation,
- 'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests,
- 'build_interval': CONF.compute.build_interval,
- 'build_timeout': CONF.compute.build_timeout
- }
- identity_admin = identity_client.IdentityClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- tenants_admin = tenants_client.TenantsClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- roles_admin = roles_client.RolesClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- users_admin = users_client.UsersClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- 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
- networks_admin = networks_client.NetworksClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- routers_admin = routers_client.RoutersClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- subnets_admin = subnets_client.SubnetsClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- return (identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, networks_admin, routers_admin,
- subnets_admin)
+def get_credential_provider(opts):
+ identity_version = "".join(['v', str(opts.identity_version)])
+ # NOTE(andreaf) For now tempest.conf controls whether resources will
+ # actually be created. Once we remove the dependency from tempest.conf
+ # we will need extra CLI option(s) to control this.
+ network_resources = {'router': True,
+ 'network': True,
+ 'subnet': True,
+ 'dhcp': True}
+ admin_creds_dict = {'username': opts.os_username,
+ 'password': opts.os_password}
+ _project_name = opts.os_project_name or opts.os_tenant_name
+ if opts.identity_version == 3:
+ admin_creds_dict['project_name'] = _project_name
+ admin_creds_dict['domain_name'] = opts.os_domain_name or 'Default'
+ elif opts.identity_version == 2:
+ admin_creds_dict['tenant_name'] = _project_name
+ admin_creds = credentials_factory.get_credentials(
+ fill_in=False, identity_version=identity_version, **admin_creds_dict)
+ return dynamic_creds.DynamicCredentialProvider(
+ identity_version=identity_version,
+ name=opts.tag,
+ network_resources=network_resources,
+ admin_creds=admin_creds,
+ **credentials_factory.get_dynamic_provider_params())
-def create_resources(opts, resources):
- (identity_admin, tenants_admin, roles_admin, users_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']:
- u['role_ids'] = []
- for r in u.get('roles', ()):
- try:
- role = filter(lambda r_: r_['name'] == r, roles)[0]
- except IndexError:
- msg = "Role: %s doesn't exist" % r
- raise exc.InvalidConfiguration(msg)
- u['role_ids'] += [role['id']]
- existing = [x['name'] for x in tenants_admin.list_tenants()['tenants']]
- for tenant in resources['tenants']:
- if tenant not in existing:
- tenants_admin.create_tenant(tenant)
- else:
- LOG.warning("Tenant '%s' already exists in this environment"
- % tenant)
- LOG.info('Tenants created')
- for u in resources['users']:
- try:
- tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- 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:
- users_admin.create_user(
- u['name'], u['pass'], tenant['id'],
- "%s@%s" % (u['name'], tenant['id']),
- enabled=True)
- break
- else:
- LOG.warning("User '%s' already exists in this environment. "
- "New name generated" % u['name'])
- u['name'] = random_user_name(opts.tag, u['prefix'])
-
- LOG.info('Users created')
- if neutron_iso_networks:
- for u in resources['users']:
- tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- network_name, router_name = create_network_resources(
- 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:
- LOG.error("Tenant: %s - not found" % u['tenant'])
- continue
- try:
- user = identity.get_user_by_username(tenants_admin,
- tenant['id'], u['name'])
- except tempest.lib.exceptions.NotFound:
- LOG.error("User: %s - not found" % u['name'])
- continue
- for r in u['role_ids']:
- try:
- roles_admin.assign_user_role(tenant['id'], user['id'], r)
- 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(networks_admin_client,
- routers_admin_client, subnets_admin_client,
- tenant_id, name):
-
- def _create_network(name):
- resp_body = networks_admin_client.create_network(
- name=name, tenant_id=tenant_id)
- return resp_body['network']
-
- def _create_subnet(subnet_name, network_id):
- 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.\
- create_subnet(
- network_id=network_id, cidr=str(subnet_cidr),
- name=subnet_name,
- tenant_id=tenant_id,
- enable_dhcp=True,
- ip_version=4)
- break
- except tempest.lib.exceptions.BadRequest as e:
- if 'overlaps with another subnet' not in str(e):
- raise
- else:
- message = 'Available CIDR for subnet creation could not be found'
- raise Exception(message)
- return resp_body['subnet']
-
- def _create_router(router_name):
- external_net_id = dict(
- network_id=CONF.network.public_network_id)
- 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):
- routers_admin_client.add_router_interface(router_id,
- subnet_id=subnet_id)
-
- network_name = name + "-network"
- network = _create_network(network_name)
- subnet_name = name + "-subnet"
- subnet = _create_subnet(subnet_name, network['id'])
- router_name = name + "-router"
- router = _create_router(router_name)
- _add_router_interface(router['id'], subnet['id'])
- return network_name, router_name
-
-
-def random_user_name(tag, prefix):
- if tag:
- return data_utils.rand_name('-'.join((tag, prefix)))
- else:
- return data_utils.rand_name(prefix)
-
-
-def generate_resources(opts):
- spec = [{'number': 1,
- 'prefix': 'primary',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])},
- {'number': 1,
- 'prefix': 'alt',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])}]
+def generate_resources(cred_provider, admin):
+ # Create the list of resources to be provisioned for each process
+ # NOTE(andreaf) get_credentials expects a string for types or a list for
+ # roles. Adding all required inputs to the spec list.
+ spec = ['primary', 'alt']
if CONF.service_available.swift:
- spec.append({'number': 1,
- 'prefix': 'swift_operator',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])})
- spec.append({'number': 1,
- 'prefix': 'swift_reseller_admin',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.reseller_admin_role])})
+ spec.append([CONF.object_storage.operator_role])
+ spec.append([CONF.object_storage.reseller_admin_role])
if CONF.service_available.heat:
- spec.append({'number': 1,
- 'prefix': 'stack_owner',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.orchestration.stack_owner_role])})
- if opts.admin:
- spec.append({
- 'number': 1,
- 'prefix': 'admin',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.identity.admin_role])
- })
- resources = {'tenants': [],
- 'users': []}
- for count in range(opts.concurrency):
- for user_group in spec:
- users = [random_user_name(opts.tag, user_group['prefix'])
- for _ in range(user_group['number'])]
- for user in users:
- tenant = '-'.join((user, 'tenant'))
- resources['tenants'].append(tenant)
- resources['users'].append({
- 'tenant': tenant,
- 'name': user,
- 'pass': data_utils.rand_password(),
- 'prefix': user_group['prefix'],
- 'roles': user_group['roles']
- })
+ spec.append([CONF.orchestration.stack_owner_role])
+ if admin:
+ spec.append('admin')
+ resources = []
+ for cred_type in spec:
+ resources.append((cred_type, cred_provider.get_credentials(
+ credential_type=cred_type)))
return resources
-def dump_accounts(opts, resources):
+def dump_accounts(resources, identity_version, account_file):
accounts = []
- for user in resources['users']:
+ for resource in resources:
+ cred_type, test_resource = resource
account = {
- 'username': user['name'],
- 'tenant_name': user['tenant'],
- 'password': user['pass'],
- 'roles': user['roles']
+ 'username': test_resource.username,
+ 'password': test_resource.password
}
- if 'network' in user or 'router' in user:
+ if identity_version == 3:
+ account['project_name'] = test_resource.project_name
+ account['domain_name'] = test_resource.domain_name
+ else:
+ account['project_name'] = test_resource.tenant_name
+
+ # If the spec includes 'admin' credentials are defined via type,
+ # else they are defined via list of roles.
+ if cred_type == 'admin':
+ account['types'] = [cred_type]
+ elif cred_type not in ['primary', 'alt']:
+ account['roles'] = cred_type
+
+ if test_resource.network:
account['resources'] = {}
- if 'network' in user:
- account['resources']['network'] = user['network']
- if 'router' in user:
- account['resources']['router'] = user['router']
+ if test_resource.network:
+ account['resources']['network'] = test_resource.network['name']
accounts.append(account)
- if os.path.exists(opts.accounts):
- os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
- with open(opts.accounts, 'w') as f:
- yaml.dump(accounts, f, default_flow_style=False)
- LOG.info('%s generated successfully!' % opts.accounts)
+ if os.path.exists(account_file):
+ os.rename(account_file, '.'.join((account_file, 'bak')))
+ with open(account_file, 'w') as f:
+ yaml.safe_dump(accounts, f, default_flow_style=False)
+ LOG.info('%s generated successfully!' % account_file)
def _parser_add_args(parser):
@@ -420,10 +214,18 @@
metavar='<auth-password>',
default=os.environ.get('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
+ parser.add_argument('--os-project-name',
+ metavar='<auth-project-name>',
+ default=os.environ.get('OS_PROJECT_NAME'),
+ help='Defaults to env[OS_PROJECT_NAME].')
parser.add_argument('--os-tenant-name',
metavar='<auth-tenant-name>',
default=os.environ.get('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME].')
+ parser.add_argument('--os-domain-name',
+ metavar='<auth-domain-name>',
+ default=os.environ.get('OS_DOMAIN_NAME'),
+ help='Defaults to env[OS_DOMAIN_NAME].')
parser.add_argument('--tag',
default='',
required=False,
@@ -439,6 +241,13 @@
action='store_true',
dest='admin',
help='Creates admin for each concurrent group')
+ parser.add_argument('-i', '--identity-version',
+ default=3,
+ choices=[2, 3],
+ type=int,
+ required=False,
+ dest='identity_version',
+ help='Version of the Identity API to use')
parser.add_argument('accounts',
metavar='accounts_file.yaml',
help='Output accounts yaml file')
@@ -468,12 +277,11 @@
def take_action(self, parsed_args):
try:
- return main(parsed_args)
+ main(parsed_args)
except Exception:
LOG.exception("Failure generating test accounts.")
traceback.print_exc()
raise
- return 0
def get_description(self):
return DESCRIPTION
@@ -487,9 +295,16 @@
opts = get_options()
if opts.config_file:
config.CONF.set_config_path(opts.config_file)
- resources = generate_resources(opts)
- create_resources(opts, resources)
- dump_accounts(opts, resources)
+ if opts.os_tenant_name:
+ LOG.warning("'os-tenant-name' and 'OS_TENANT_NAME' are both "
+ "deprecated, please use 'os-project-name' or "
+ "'OS_PROJECT_NAME' instead")
+ resources = []
+ for count in range(opts.concurrency):
+ # Use N different cred_providers to obtain different sets of creds
+ cred_provider = get_credential_provider(opts)
+ resources.extend(generate_resources(cred_provider, opts.admin))
+ dump_accounts(resources, opts.identity_version, opts.accounts)
if __name__ == "__main__":
main()
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index caba4b5..e8e691e 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -83,7 +83,6 @@
LOG.exception("Failure during cleanup")
traceback.print_exc()
raise
- return 0
def init(self, parsed_args):
cleanup_service.init_conf()
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 633b9e9..77d62d3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -21,6 +21,8 @@
from oslo_log import log as logging
from six import moves
+from tempest.cmd.workspace import WorkspaceManager
+
LOG = logging.getLogger(__name__)
TESTR_CONF = """[DEFAULT]
@@ -89,6 +91,10 @@
action='store_true', dest='show_global_dir',
help="Print the global config dir location, "
"then exit")
+ parser.add_argument('--name', help="The workspace name", default=None)
+ parser.add_argument('--workspace-path', default=None,
+ help="The path to the workspace file, the default "
+ "is ~/.tempest/workspace")
return parser
def generate_testr_conf(self, local_path):
@@ -114,15 +120,22 @@
config_parse.write(conf_file)
def copy_config(self, etc_dir, config_dir):
- shutil.copytree(config_dir, etc_dir)
+ if os.path.isdir(config_dir):
+ shutil.copytree(config_dir, etc_dir)
+ else:
+ LOG.warning("Global config dir %s can't be found" % config_dir)
def generate_sample_config(self, local_dir, config_dir):
- conf_generator = os.path.join(config_dir,
- 'config-generator.tempest.conf')
+ if os.path.isdir(config_dir):
+ conf_generator = os.path.join(config_dir,
+ 'config-generator.tempest.conf')
- subprocess.call(['oslo-config-generator', '--config-file',
- conf_generator],
- cwd=local_dir)
+ subprocess.call(['oslo-config-generator', '--config-file',
+ conf_generator],
+ cwd=local_dir)
+ else:
+ LOG.warning("Skipping sample config generation because global "
+ "config dir %s can't be found" % config_dir)
def create_working_dir(self, local_dir, config_dir):
# Create local dir if missing
@@ -159,6 +172,10 @@
subprocess.call(['testr', 'init'], cwd=local_dir)
def take_action(self, parsed_args):
+ workspace_manager = WorkspaceManager(parsed_args.workspace_path)
+ name = parsed_args.name or parsed_args.dir.split(os.path.sep)[-1]
+ workspace_manager.register_new_workspace(
+ name, parsed_args.dir, init=True)
config_dir = parsed_args.config_dir or get_tempest_default_config_dir()
if parsed_args.show_global_dir:
print("Global config dir is located at: %s" % config_dir)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 9b3cac7..6a65fcb 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -128,13 +128,13 @@
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 routers_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 routers_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
from tempest.services.volume.v1.json import volumes_client
@@ -233,7 +233,7 @@
**object_storage_params)
self.containers = container_client.ContainerClient(
_auth, **object_storage_params)
- self.images = images_client.ImagesClientV2(
+ self.images = images_client.ImagesClient(
_auth,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
@@ -823,7 +823,7 @@
if router['gateway']:
if CONF.network.public_network_id:
ext_net = CONF.network.public_network_id
- client.routers._update_router(
+ client.routers.update_router(
router_id, set_enable_snat=True,
external_gateway_info={"network_id": ext_net})
else:
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
index 1f1ff1a..5f6b3e6 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -30,7 +30,6 @@
class TempestListPlugins(command.Command):
def take_action(self, parsed_args):
self._list_plugins()
- return 0
def get_description(self):
return 'List all tempest plugins'
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
index 9c8552f..7502c23 100755
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -97,7 +97,6 @@
LOG.exception("Failure in the stress test framework")
traceback.print_exc()
raise
- return 0
def get_description(self):
return 'Run tempest stress tests'
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 72ea894..10f753c 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -418,12 +418,11 @@
def take_action(self, parsed_args):
try:
- return main(parsed_args)
+ main(parsed_args)
except Exception:
LOG.exception("Failure verifying configuration.")
traceback.print_exc()
raise
- return 0
if __name__ == "__main__":
main()
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
new file mode 100644
index 0000000..cc82284
--- /dev/null
+++ b/tempest/cmd/workspace.py
@@ -0,0 +1,218 @@
+# Copyright 2016 Rackspace
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Manages Tempest workspaces
+
+This command is used for managing tempest workspaces
+
+Commands
+========
+
+list
+----
+Outputs the name and path of all known tempest workspaces
+
+register
+--------
+Registers a new tempest workspace via a given --name and --path
+
+rename
+------
+Renames a tempest workspace from --old-name to --new-name
+
+move
+----
+Changes the path of a given tempest workspace --name to --path
+
+remove
+------
+Deletes the entry for a given tempest workspace --name
+
+General Options
+===============
+
+ **--workspace_path**: Allows the user to specify a different location for the
+ workspace.yaml file containing the workspace definitions
+ instead of ~/.tempest/workspace.yaml
+"""
+
+import os
+import sys
+
+from cliff import command
+from oslo_concurrency import lockutils
+from oslo_log import log as logging
+import prettytable
+import yaml
+
+from tempest import config
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class WorkspaceManager(object):
+ def __init__(self, path=None):
+ lockutils.get_lock_path(CONF)
+ self.path = path or os.path.join(
+ os.path.expanduser("~"), ".tempest", "workspace.yaml")
+ if not os.path.isdir(os.path.dirname(self.path)):
+ os.makedirs(self.path.rsplit(os.path.sep, 1)[0])
+ self.workspaces = {}
+
+ @lockutils.synchronized('workspaces', external=True)
+ def get_workspace(self, name):
+ """Returns the workspace that has the given name"""
+ self._populate()
+ return self.workspaces.get(name)
+
+ @lockutils.synchronized('workspaces', external=True)
+ def rename_workspace(self, old_name, new_name):
+ self._populate()
+ self._name_exists(old_name)
+ self._workspace_name_exists(new_name)
+ self.workspaces[new_name] = self.workspaces.pop(old_name)
+ self._write_file()
+
+ @lockutils.synchronized('workspaces', external=True)
+ def move_workspace(self, name, path):
+ self._populate()
+ path = os.path.abspath(os.path.expanduser(path))
+ self._name_exists(name)
+ self._validate_path(path)
+ self.workspaces[name] = path
+ self._write_file()
+
+ def _name_exists(self, name):
+ if name not in self.workspaces:
+ print("A workspace was not found with name: {0}".format(name))
+ sys.exit(1)
+
+ @lockutils.synchronized('workspaces', external=True)
+ def remove_workspace(self, name):
+ self._populate()
+ self._name_exists(name)
+ self.workspaces.pop(name)
+ self._write_file()
+
+ @lockutils.synchronized('workspaces', external=True)
+ def list_workspaces(self):
+ self._populate()
+ self._validate_workspaces()
+ return self.workspaces
+
+ def _workspace_name_exists(self, name):
+ if name in self.workspaces:
+ print("A workspace already exists with name: {0}.".format(
+ name))
+ sys.exit(1)
+
+ def _validate_path(self, path):
+ if not os.path.exists(path):
+ print("Path does not exist.")
+ sys.exit(1)
+
+ @lockutils.synchronized('workspaces', external=True)
+ def register_new_workspace(self, name, path, init=False):
+ """Adds the new workspace and writes out the new workspace config"""
+ self._populate()
+ path = os.path.abspath(os.path.expanduser(path))
+ # This only happens when register is called from outside of init
+ if not init:
+ self._validate_path(path)
+ self._workspace_name_exists(name)
+ self.workspaces[name] = path
+ self._write_file()
+
+ def _validate_workspaces(self):
+ if self.workspaces is not None:
+ self.workspaces = {n: p for n, p in self.workspaces.items()
+ if os.path.exists(p)}
+ self._write_file()
+
+ def _write_file(self):
+ with open(self.path, 'w') as f:
+ f.write(yaml.dump(self.workspaces))
+
+ def _populate(self):
+ if not os.path.isfile(self.path):
+ return
+ with open(self.path, 'r') as f:
+ self.workspaces = yaml.load(f) or {}
+
+
+class TempestWorkspace(command.Command):
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ if getattr(parsed_args, 'register', None):
+ self.manager.register_new_workspace(
+ parsed_args.name, parsed_args.path)
+ elif getattr(parsed_args, 'rename', None):
+ self.manager.rename_workspace(
+ parsed_args.old_name, parsed_args.new_name)
+ elif getattr(parsed_args, 'move', None):
+ self.manager.move_workspace(
+ parsed_args.name, parsed_args.path)
+ elif getattr(parsed_args, 'remove', None):
+ self.manager.remove_workspace(
+ parsed_args.name)
+ else:
+ self._print_workspaces()
+ sys.exit(0)
+
+ def get_description(self):
+ return 'Tempest workspace actions'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspace, self).get_parser(prog_name)
+
+ parser.add_argument(
+ '--workspace-path', required=False, default=None,
+ help="The path to the workspace file, the default is "
+ "~/.tempest/workspace.yaml")
+
+ subparsers = parser.add_subparsers()
+
+ list_parser = subparsers.add_parser('list')
+ list_parser.set_defaults(list=True)
+
+ register_parser = subparsers.add_parser('register')
+ register_parser.add_argument('--name', required=True)
+ register_parser.add_argument('--path', required=True)
+ register_parser.set_defaults(register=True)
+
+ update_parser = subparsers.add_parser('rename')
+ update_parser.add_argument('--old-name', required=True)
+ update_parser.add_argument('--new-name', required=True)
+ update_parser.set_defaults(rename=True)
+
+ move_parser = subparsers.add_parser('move')
+ move_parser.add_argument('--name', required=True)
+ move_parser.add_argument('--path', required=True)
+ move_parser.set_defaults(move=True)
+
+ remove_parser = subparsers.add_parser('remove')
+ remove_parser.add_argument('--name', required=True)
+ remove_parser.set_defaults(remove=True)
+
+ return parser
+
+ def _print_workspaces(self):
+ output = prettytable.PrettyTable(["Name", "Path"])
+ if self.manager.list_workspaces() is not None:
+ for name, path in self.manager.list_workspaces().items():
+ output.add_row([name, path])
+
+ print(output)
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 37c9727..aac036b 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -155,6 +155,9 @@
def get_credentials(self, user, project, password):
# User, project and domain already include both ID and name here,
# so there's no need to use the fill_in mode.
+ # NOTE(andreaf) We need to set all fields in the returned credentials.
+ # Scope is then used to pick only those relevant for the type of
+ # token needed by each service client.
return auth.get_credentials(
auth_url=None,
fill_in=False,
@@ -163,13 +166,38 @@
project_name=project['name'], project_id=project['id'],
password=password,
project_domain_id=self.creds_domain['id'],
- project_domain_name=self.creds_domain['name'])
+ project_domain_name=self.creds_domain['name'],
+ domain_id=self.creds_domain['id'],
+ domain_name=self.creds_domain['name'])
def _assign_user_role(self, project, user, role):
self.roles_client.assign_user_role_on_project(project['id'],
user['id'],
role['id'])
+ def assign_user_role_on_domain(self, user, role_name, domain=None):
+ """Assign the specified role on a domain
+
+ :param user: a user dict
+ :param role_name: name of the role to be assigned
+ :param domain: (optional) The domain to assign the role on. If not
+ specified the default domain of cred_client
+ """
+ # NOTE(andreaf) This method is very specific to the v3 case, and
+ # because of that it's not defined in the parent class.
+ if domain is None:
+ domain = self.creds_domain
+ role = self._check_role_exists(role_name)
+ if not role:
+ msg = 'No "%s" role found' % role_name
+ raise lib_exc.NotFound(msg)
+ try:
+ self.roles_client.assign_user_role_on_domain(
+ domain['id'], user['id'], role['id'])
+ except lib_exc.Conflict:
+ LOG.debug("Role %s already assigned on domain %s for user %s",
+ role['id'], domain['id'], user['id'])
+
def get_creds_client(identity_client,
projects_client,
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 6cb43f3..4873fcf 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -14,7 +14,6 @@
from oslo_concurrency import lockutils
from tempest import clients
-from tempest.common import cred_provider
from tempest.common import dynamic_creds
from tempest.common import preprov_creds
from tempest import config
@@ -40,19 +39,19 @@
# Subset of the parameters of credential providers that depend on configuration
-def _get_common_provider_params():
+def get_common_provider_params():
return {
'credentials_domain': CONF.auth.default_credentials_domain_name,
'admin_role': CONF.identity.admin_role
}
-def _get_dynamic_provider_params():
- return _get_common_provider_params()
+def get_dynamic_provider_params():
+ return get_common_provider_params()
-def _get_preprov_provider_params():
- _common_params = _get_common_provider_params()
+def get_preprov_provider_params():
+ _common_params = get_common_provider_params()
reseller_admin_role = CONF.object_storage.reseller_admin_role
return dict(_common_params, **dict([
('accounts_lock_dir', lockutils.get_lock_path(CONF)),
@@ -62,89 +61,6 @@
]))
-class LegacyCredentialProvider(cred_provider.CredentialProvider):
-
- def __init__(self, identity_version):
- """Credentials provider which returns credentials from tempest.conf
-
- Credentials provider which always returns the first and second
- configured accounts as primary and alt users.
- Credentials from tempest.conf are deprecated, and this credential
- provider is also accordingly.
-
- This credential provider can be used in case of serial test execution
- to preserve the current behaviour of the serial tempest run.
-
- :param identity_version: Version of the identity API
- :return: CredentialProvider
- """
- super(LegacyCredentialProvider, self).__init__(
- identity_version=identity_version)
- self._creds = {}
-
- def _unique_creds(self, cred_arg=None):
- """Verify that the configured credentials are valid and distinct """
- try:
- user = self.get_primary_creds()
- alt_user = self.get_alt_creds()
- return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
- except exceptions.InvalidCredentials as ic:
- msg = "At least one of the configured credentials is " \
- "not valid: %s" % ic.message
- raise exceptions.InvalidConfiguration(msg)
-
- def is_multi_user(self):
- return self._unique_creds('username')
-
- def is_multi_tenant(self):
- return self._unique_creds('tenant_id')
-
- def get_primary_creds(self):
- if self._creds.get('primary'):
- return self._creds.get('primary')
- primary_credential = get_configured_credentials(
- credential_type='user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['primary'] = cred_provider.TestResources(
- primary_credential)
- return self._creds['primary']
-
- def get_alt_creds(self):
- if self._creds.get('alt'):
- return self._creds.get('alt')
- alt_credential = get_configured_credentials(
- credential_type='alt_user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['alt'] = cred_provider.TestResources(
- alt_credential)
- return self._creds['alt']
-
- def clear_creds(self):
- self._creds = {}
-
- def get_admin_creds(self):
- if self._creds.get('admin'):
- return self._creds.get('admin')
- creds = get_configured_credentials(
- "identity_admin", fill_in=False)
- self._creds['admin'] = cred_provider.TestResources(creds)
- return self._creds['admin']
-
- def get_creds_by_roles(self, roles, force_new=False):
- msg = "Credentials being specified through the config file can not be"\
- " used with tests that specify using credentials by roles. "\
- "Either exclude/skip the tests doing this or use either a "\
- "test_accounts_file or dynamic credentials."
- raise exceptions.InvalidConfiguration(msg)
-
- def is_role_available(self, role):
- # NOTE(andreaf) LegacyCredentialProvider does not support credentials
- # by role, so returning always False.
- # Test that rely on credentials by role should use this to skip
- # when this is credential provider is used
- return False
-
-
# Return the right implementation of CredentialProvider based on config
# Dropping interface and password, as they are never used anyways
# TODO(andreaf) Drop them from the CredentialsProvider interface completely
@@ -157,24 +73,23 @@
# the test should be skipped else it would fail.
identity_version = identity_version or CONF.identity.auth_version
if CONF.auth.use_dynamic_credentials or force_tenant_isolation:
- admin_creds = get_configured_credentials(
- 'identity_admin', fill_in=True, identity_version=identity_version)
+ admin_creds = get_configured_admin_credentials(
+ fill_in=True, identity_version=identity_version)
return dynamic_creds.DynamicCredentialProvider(
name=name,
network_resources=network_resources,
identity_version=identity_version,
admin_creds=admin_creds,
- **_get_dynamic_provider_params())
+ **get_dynamic_provider_params())
else:
if CONF.auth.test_accounts_file:
# Most params are not relevant for pre-created accounts
return preprov_creds.PreProvisionedCredentialProvider(
name=name, identity_version=identity_version,
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
else:
- # Dynamic credentials are disabled, and the account file is not
- # defined - we fall back on credentials configured in tempest.conf
- return LegacyCredentialProvider(identity_version=identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
# We want a helper function here to check and see if admin credentials
@@ -191,13 +106,13 @@
elif CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
identity_version=identity_version, name='check_admin',
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
if not check_accounts.admin_available():
is_admin = False
else:
try:
- get_configured_credentials('identity_admin', fill_in=False,
- identity_version=identity_version)
+ get_configured_admin_credentials(fill_in=False,
+ identity_version=identity_version)
except exceptions.InvalidConfiguration:
is_admin = False
return is_admin
@@ -216,9 +131,11 @@
if CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
identity_version=identity_version, name='check_alt',
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
else:
- check_accounts = LegacyCredentialProvider(identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
+
try:
if not check_accounts.is_multi_user():
return False
@@ -246,44 +163,32 @@
# Read credentials from configuration, builds a Credentials object
# based on the specified or configured version
-def get_configured_credentials(credential_type, fill_in=True,
- identity_version=None):
+def get_configured_admin_credentials(fill_in=True, identity_version=None):
identity_version = identity_version or CONF.identity.auth_version
if identity_version not in ('v2', 'v3'):
raise exceptions.InvalidConfiguration(
'Unsupported auth version: %s' % identity_version)
- if credential_type not in CREDENTIAL_TYPES:
- raise exceptions.InvalidCredentials()
- conf_attributes = ['username', 'password', 'project_name']
+ conf_attributes = ['username', 'password',
+ 'project_name']
if identity_version == 'v3':
conf_attributes.append('domain_name')
# Read the parts of credentials from config
params = DEFAULT_PARAMS.copy()
- section, prefix = CREDENTIAL_TYPES[credential_type]
for attr in conf_attributes:
- _section = getattr(CONF, section)
- if prefix is None:
- params[attr] = getattr(_section, attr)
- else:
- params[attr] = getattr(_section, prefix + "_" + attr)
- # 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)
+ params[attr] = getattr(CONF.auth, 'admin_' + attr)
# Build and validate credentials. We are reading configured credentials,
# so validate them even if fill_in is False
credentials = get_credentials(fill_in=fill_in,
identity_version=identity_version, **params)
if not fill_in:
if not credentials.is_valid():
- msg = ("The %s credentials are incorrectly set in the config file."
- " Double check that all required values are assigned" %
- credential_type)
- raise exceptions.InvalidConfiguration(msg)
+ msg = ("The admin credentials are incorrectly set in the config "
+ "file for identity version %s. Double check that all "
+ "required values are assigned.")
+ raise exceptions.InvalidConfiguration(msg % identity_version)
return credentials
@@ -311,19 +216,10 @@
# === Credential / client managers
-class ConfiguredUserManager(clients.Manager):
- """Manager that uses user credentials for its managed client objects"""
-
- def __init__(self, service=None):
- super(ConfiguredUserManager, self).__init__(
- credentials=get_configured_credentials('user'),
- service=service)
-
-
class AdminManager(clients.Manager):
"""Manager that uses admin credentials for its managed client objects"""
def __init__(self, service=None):
super(AdminManager, self).__init__(
- credentials=get_configured_credentials('identity_admin'),
+ credentials=get_configured_admin_credentials(),
service=service)
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index a63af38..3a3e3c2 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -122,7 +122,9 @@
project = self.creds_client.create_project(
name=project_name, description=project_desc)
- username = data_utils.rand_name(root) + suffix
+ # NOTE(andreaf) User and project can be distinguished from the context,
+ # having the same ID in both makes it easier to match them and debug.
+ username = project_name
user_password = data_utils.rand_password()
email = data_utils.rand_name(root) + suffix + "@example.com"
user = self.creds_client.create_user(
@@ -134,6 +136,9 @@
self.creds_client.assign_user_role(user, project,
self.admin_role)
role_assigned = True
+ if self.identity_version == 'v3':
+ self.creds_client.assign_user_role_on_domain(
+ user, CONF.identity.admin_role)
# Add roles specified in config file
for conf_role in CONF.auth.tempest_roles:
self.creds_client.assign_user_role(user, project, conf_role)
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
deleted file mode 100644
index 00062de..0000000
--- a/tempest/common/glance_http.py
+++ /dev/null
@@ -1,361 +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.
-
-# Originally copied from python-glanceclient
-
-import copy
-import hashlib
-import posixpath
-import re
-import socket
-import struct
-
-import OpenSSL
-from oslo_log import log as logging
-import six
-from six import moves
-from six.moves import http_client as httplib
-from six.moves.urllib import parse as urlparse
-
-from tempest import exceptions as exc
-
-LOG = logging.getLogger(__name__)
-USER_AGENT = 'tempest'
-CHUNKSIZE = 1024 * 64 # 64kB
-TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$')
-
-
-class HTTPClient(object):
-
- def __init__(self, auth_provider, filters, **kwargs):
- self.auth_provider = auth_provider
- self.filters = filters
- self.endpoint = auth_provider.base_url(filters)
- endpoint_parts = urlparse.urlparse(self.endpoint)
- self.endpoint_scheme = endpoint_parts.scheme
- self.endpoint_hostname = endpoint_parts.hostname
- self.endpoint_port = endpoint_parts.port
-
- self.connection_class = self._get_connection_class(
- self.endpoint_scheme)
- self.connection_kwargs = self._get_connection_kwargs(
- self.endpoint_scheme, **kwargs)
-
- @staticmethod
- def _get_connection_class(scheme):
- if scheme == 'https':
- return VerifiedHTTPSConnection
- else:
- return httplib.HTTPConnection
-
- @staticmethod
- def _get_connection_kwargs(scheme, **kwargs):
- _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
-
- if scheme == 'https':
- _kwargs['ca_certs'] = kwargs.get('ca_certs', None)
- _kwargs['cert_file'] = kwargs.get('cert_file', None)
- _kwargs['key_file'] = kwargs.get('key_file', None)
- _kwargs['insecure'] = kwargs.get('insecure', False)
- _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
-
- return _kwargs
-
- def _get_connection(self):
- _class = self.connection_class
- try:
- return _class(self.endpoint_hostname, self.endpoint_port,
- **self.connection_kwargs)
- except httplib.InvalidURL:
- raise exc.EndpointNotFound
-
- def _http_request(self, url, method, **kwargs):
- """Send an http request with the specified characteristics.
-
- Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
- as setting headers and error handling.
- """
- # Copy the kwargs so we can reuse the original in case of redirects
- kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
- kwargs['headers'].setdefault('User-Agent', USER_AGENT)
-
- self._log_request(method, url, kwargs['headers'])
-
- conn = self._get_connection()
-
- try:
- url_parts = urlparse.urlparse(url)
- conn_url = posixpath.normpath(url_parts.path)
- LOG.debug('Actual Path: {path}'.format(path=conn_url))
- if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
- conn.putrequest(method, conn_url)
- for header, value in kwargs['headers'].items():
- conn.putheader(header, value)
- conn.endheaders()
- chunk = kwargs['body'].read(CHUNKSIZE)
- # Chunk it, baby...
- while chunk:
- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- chunk = kwargs['body'].read(CHUNKSIZE)
- conn.send('0\r\n\r\n')
- else:
- conn.request(method, conn_url, **kwargs)
- resp = conn.getresponse()
- except socket.gaierror as e:
- message = ("Error finding address for %(url)s: %(e)s" %
- {'url': url, 'e': e})
- raise exc.EndpointNotFound(message)
- except (socket.error, socket.timeout) as e:
- message = ("Error communicating with %(endpoint)s %(e)s" %
- {'endpoint': self.endpoint, 'e': e})
- raise exc.TimeoutException(message)
-
- body_iter = ResponseBodyIterator(resp)
- # Read body into string if it isn't obviously image data
- if resp.getheader('content-type', None) != 'application/octet-stream':
- body_str = ''.join([body_chunk for body_chunk in body_iter])
- body_iter = six.StringIO(body_str)
- self._log_response(resp, None)
- else:
- self._log_response(resp, body_iter)
-
- return resp, body_iter
-
- def _log_request(self, method, url, headers):
- LOG.info('Request: ' + method + ' ' + url)
- if headers:
- headers_out = headers
- if 'X-Auth-Token' in headers and headers['X-Auth-Token']:
- token = headers['X-Auth-Token']
- if len(token) > 64 and TOKEN_CHARS_RE.match(token):
- headers_out = headers.copy()
- headers_out['X-Auth-Token'] = "<Token omitted>"
- LOG.info('Request Headers: ' + str(headers_out))
-
- def _log_response(self, resp, body):
- status = str(resp.status)
- LOG.info("Response Status: " + status)
- if resp.getheaders():
- LOG.info('Response Headers: ' + str(resp.getheaders()))
- if body:
- str_body = str(body)
- length = len(body)
- LOG.info('Response Body: ' + str_body[:2048])
- if length >= 2048:
- self.LOG.debug("Large body (%d) md5 summary: %s", length,
- hashlib.md5(str_body).hexdigest())
-
- def raw_request(self, method, url, **kwargs):
- kwargs.setdefault('headers', {})
- kwargs['headers'].setdefault('Content-Type',
- 'application/octet-stream')
- if 'body' in kwargs:
- if (hasattr(kwargs['body'], 'read')
- and method.lower() in ('post', 'put')):
- # We use 'Transfer-Encoding: chunked' because
- # body size may not always be known in advance.
- kwargs['headers']['Transfer-Encoding'] = 'chunked'
-
- # Decorate the request with auth
- req_url, kwargs['headers'], kwargs['body'] = \
- self.auth_provider.auth_request(
- method=method, url=url, headers=kwargs['headers'],
- body=kwargs.get('body', None), filters=self.filters)
- return self._http_request(req_url, method, **kwargs)
-
-
-class OpenSSLConnectionDelegator(object):
- """An OpenSSL.SSL.Connection delegator.
-
- Supplies an additional 'makefile' method which httplib requires
- and is not present in OpenSSL.SSL.Connection.
-
- Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
- a delegator must be used.
- """
- def __init__(self, *args, **kwargs):
- self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
-
- def __getattr__(self, name):
- return getattr(self.connection, name)
-
- def makefile(self, *args, **kwargs):
- # Ensure the socket is closed when this file is closed
- kwargs['close'] = True
- return socket._fileobject(self.connection, *args, **kwargs)
-
-
-class VerifiedHTTPSConnection(httplib.HTTPSConnection):
- """Extended HTTPSConnection which uses OpenSSL library for enhanced SSL
-
- Note: Much of this functionality can eventually be replaced
- with native Python 3.3 code.
- """
- def __init__(self, host, port=None, key_file=None, cert_file=None,
- ca_certs=None, timeout=None, insecure=False,
- ssl_compression=True):
- httplib.HTTPSConnection.__init__(self, host, port,
- key_file=key_file,
- cert_file=cert_file)
- self.key_file = key_file
- self.cert_file = cert_file
- self.timeout = timeout
- self.insecure = insecure
- self.ssl_compression = ssl_compression
- self.ca_certs = ca_certs
- self.setcontext()
-
- @staticmethod
- def host_matches_cert(host, x509):
- """Verify that the x509 certificate we have received from 'host'
-
- Identifies the server we are connecting to, ie that the certificate's
- Common Name or a Subject Alternative Name matches 'host'.
- """
- # First see if we can match the CN
- if x509.get_subject().commonName == host:
- return True
-
- # Also try Subject Alternative Names for a match
- san_list = None
- for i in moves.xrange(x509.get_extension_count()):
- ext = x509.get_extension(i)
- if ext.get_short_name() == 'subjectAltName':
- san_list = str(ext)
- for san in ''.join(san_list.split()).split(','):
- if san == "DNS:%s" % host:
- return True
-
- # Server certificate does not match host
- msg = ('Host "%s" does not match x509 certificate contents: '
- 'CommonName "%s"' % (host, x509.get_subject().commonName))
- if san_list is not None:
- msg = msg + ', subjectAltName "%s"' % san_list
- raise exc.SSLCertificateError(msg)
-
- def verify_callback(self, connection, x509, errnum,
- depth, preverify_ok):
- if x509.has_expired():
- msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
- raise exc.SSLCertificateError(msg)
-
- if depth == 0 and preverify_ok is True:
- # We verify that the host matches against the last
- # certificate in the chain
- return self.host_matches_cert(self.host, x509)
- else:
- # Pass through OpenSSL's default result
- return preverify_ok
-
- def setcontext(self):
- """Set up the OpenSSL context."""
- self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
-
- if self.ssl_compression is False:
- self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
-
- if self.insecure is not True:
- self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
- self.verify_callback)
- else:
- self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
- self.verify_callback)
-
- if self.cert_file:
- try:
- self.context.use_certificate_file(self.cert_file)
- except Exception as e:
- msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
- raise exc.SSLConfigurationError(msg)
- if self.key_file is None:
- # We support having key and cert in same file
- try:
- self.context.use_privatekey_file(self.cert_file)
- except Exception as e:
- msg = ('No key file specified and unable to load key '
- 'from "%s" %s' % (self.cert_file, e))
- raise exc.SSLConfigurationError(msg)
-
- if self.key_file:
- try:
- self.context.use_privatekey_file(self.key_file)
- except Exception as e:
- msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
- raise exc.SSLConfigurationError(msg)
-
- if self.ca_certs:
- try:
- self.context.load_verify_locations(self.ca_certs)
- except Exception as e:
- msg = 'Unable to load CA from "%s" %s' % (self.ca_certs, e)
- raise exc.SSLConfigurationError(msg)
- else:
- self.context.set_default_verify_paths()
-
- def connect(self):
- """Connect to SSL port and apply per-connection parameters."""
- 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:
- # Remove the reference to the socket but don't close it yet.
- # Response close will close both socket and associated
- # file. Closing socket too soon will cause response
- # reads to fail with socket IO error 'Bad file descriptor'.
- self.sock = None
- httplib.HTTPSConnection.close(self)
-
-
-class ResponseBodyIterator(object):
- """A class that acts as an iterator over an HTTP response."""
-
- def __init__(self, resp):
- self.resp = resp
-
- def __iter__(self):
- while True:
- yield self.next()
-
- def next(self):
- chunk = self.resp.read(CHUNKSIZE)
- if chunk:
- return chunk
- else:
- raise StopIteration()
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index 51f723b..5992d24 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -43,6 +43,11 @@
class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
+ # Exclude from the hash fields specific to v2 or v3 identity API
+ # i.e. only include user*, project*, tenant* and password
+ HASH_CRED_FIELDS = (set(auth.KeystoneV2Credentials.ATTRIBUTES) &
+ set(auth.KeystoneV3Credentials.ATTRIBUTES))
+
def __init__(self, identity_version, test_accounts_file,
accounts_lock_dir, name=None, credentials_domain=None,
admin_role=None, object_storage_operator_role=None,
@@ -81,10 +86,8 @@
self.test_accounts_file = test_accounts_file
if test_accounts_file:
accounts = read_accounts_yaml(self.test_accounts_file)
- self.use_default_creds = False
else:
- accounts = {}
- self.use_default_creds = True
+ raise lib_exc.InvalidCredentials("No accounts file specified")
self.hash_dict = self.get_hash_dict(
accounts, admin_role, object_storage_operator_role,
object_storage_reseller_admin_role)
@@ -104,6 +107,7 @@
object_storage_operator_role=None,
object_storage_reseller_admin_role=None):
hash_dict = {'roles': {}, 'creds': {}, 'networks': {}}
+
# Loop over the accounts read from the yaml file
for account in accounts:
roles = []
@@ -116,7 +120,9 @@
if 'resources' in account:
resources = account.pop('resources')
temp_hash = hashlib.md5()
- temp_hash.update(six.text_type(account).encode('utf-8'))
+ account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
+ if k in cls.HASH_CRED_FIELDS)
+ temp_hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash_key = temp_hash.hexdigest()
hash_dict['creds'][temp_hash_key] = account
for role in roles:
@@ -157,12 +163,7 @@
return hash_dict
def is_multi_user(self):
- # Default credentials is not a valid option with locking Account
- if self.use_default_creds:
- raise lib_exc.InvalidCredentials(
- "Account file %s doesn't exist" % self.test_accounts_file)
- else:
- return len(self.hash_dict['creds']) > 1
+ return len(self.hash_dict['creds']) > 1
def is_multi_tenant(self):
return self.is_multi_user()
@@ -237,10 +238,10 @@
return temp_creds
def _get_creds(self, roles=None):
- if self.use_default_creds:
- raise lib_exc.InvalidCredentials(
- "Account file %s doesn't exist" % self.test_accounts_file)
useable_hashes = self._get_match_hash_list(roles)
+ if len(useable_hashes) == 0:
+ msg = 'No users configured for type/roles %s' % roles
+ raise lib_exc.InvalidCredentials(msg)
free_hash = self._get_free_hash(useable_hashes)
clean_creds = self._sanitize_creds(
self.hash_dict['creds'][free_hash])
@@ -262,13 +263,13 @@
for _hash in self.hash_dict['creds']:
# Comparing on the attributes that are expected in the YAML
init_attributes = creds.get_init_attributes()
+ # Only use the attributes initially used to calculate the hash
+ init_attributes = [x for x in init_attributes if
+ x in self.HASH_CRED_FIELDS]
hash_attributes = self.hash_dict['creds'][_hash].copy()
- if ('user_domain_name' in init_attributes and 'user_domain_name'
- not in hash_attributes):
- # Allow for the case of domain_name populated from config
- domain_name = self.credentials_domain
- hash_attributes['user_domain_name'] = domain_name
- if all([getattr(creds, k) == hash_attributes[k] for
+ # NOTE(andreaf) Not all fields may be available on all credentials
+ # so defaulting to None for that case.
+ if all([getattr(creds, k, None) == hash_attributes.get(k, None) for
k in init_attributes]):
return _hash
raise AttributeError('Invalid credentials %s' % creds)
@@ -318,12 +319,9 @@
return self.get_creds_by_roles([self.admin_role])
def is_role_available(self, role):
- if self.use_default_creds:
- return False
- else:
- if self.hash_dict['roles'].get(role):
- return True
- return False
+ if self.hash_dict['roles'].get(role):
+ return True
+ return False
def admin_available(self):
return self.is_role_available(self.admin_role)
@@ -351,23 +349,20 @@
return net_creds
def _extend_credentials(self, creds_dict):
- # In case of v3, adds a user_domain_name field to the creds
- # dict if not defined
+ # Add or remove credential domain fields to fit the identity version
+ domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
+ if 'domain' in x)
+ msg = 'Assuming they are valid in the default domain.'
if self.identity_version == 'v3':
- user_domain_fields = set(['user_domain_name', 'user_domain_id'])
- if not user_domain_fields.intersection(set(creds_dict.keys())):
- creds_dict['user_domain_name'] = self.credentials_domain
- # NOTE(andreaf) In case of v2, replace project with tenant if project
- # is provided and tenant is not
+ if not domain_fields.intersection(set(creds_dict.keys())):
+ msg = 'Using credentials %s for v3 API calls. ' + msg
+ LOG.warning(msg, self._sanitize_creds(creds_dict))
+ creds_dict['domain_name'] = self.credentials_domain
if self.identity_version == 'v2':
- if ('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')
+ if domain_fields.intersection(set(creds_dict.keys())):
+ msg = 'Using credentials %s for v2 API calls. ' + msg
+ LOG.warning(msg, self._sanitize_creds(creds_dict))
+ # Remove all valid domain attributes
+ for attr in domain_fields.intersection(set(creds_dict.keys())):
+ creds_dict.pop(attr)
return creds_dict
diff --git a/tempest/common/utils/file_utils.py b/tempest/common/utils/file_utils.py
deleted file mode 100644
index 43083f4..0000000
--- a/tempest/common/utils/file_utils.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-def have_effective_read_access(path):
- try:
- fh = open(path, "rb")
- except IOError:
- return False
- fh.close()
- return True
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 3f573b7..0a3e8d3 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -21,7 +21,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common import ssh
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
import tempest.lib.exceptions
CONF = config.CONF
@@ -37,7 +37,7 @@
except tempest.lib.exceptions.SSHTimeout:
try:
original_exception = sys.exc_info()
- caller = misc_utils.find_test_caller() or "not found"
+ caller = test_utils.find_test_caller() or "not found"
if self.server:
msg = 'Caller: %s. Timeout trying to ssh to server %s'
LOG.debug(msg, caller, self.server)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 95305f3..23d7f88 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -19,6 +19,7 @@
from tempest import exceptions
from tempest.lib.common.utils import misc as misc_utils
from tempest.lib import exceptions as lib_exc
+from tempest.services.image.v1.json import images_client as images_v1_client
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -122,42 +123,44 @@
The client should have a show_image(image_id) method to get the image.
The client should also have build_interval and build_timeout attributes.
"""
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
- if 'image' in image:
- image = image['image']
- start = int(time.time())
+ if isinstance(client, images_v1_client.ImagesClient):
+ # The 'check_image' method is used here because the show_image method
+ # returns image details plus the image itself which is very expensive.
+ # The 'check_image' method returns just image details.
+ show_image = client.check_image
+ else:
+ show_image = client.show_image
- while image['status'] != status:
- time.sleep(client.build_interval)
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
+ current_status = 'An unknown status'
+ start = int(time.time())
+ while int(time.time()) - start < client.build_timeout:
+ image = show_image(image_id)
+ # Compute image client returns response wrapped in 'image' element
+ # which is not case with Glance image client.
if 'image' in image:
image = image['image']
- status_curr = image['status']
- if status_curr == 'ERROR':
+
+ current_status = image['status']
+ if current_status == status:
+ return
+ if current_status.lower() == 'killed':
+ raise exceptions.ImageKilledException(image_id=image_id,
+ status=status)
+ if current_status.lower() == 'error':
raise exceptions.AddImageException(image_id=image_id)
- # check the status again to avoid a false negative where we hit
- # the timeout at the same time that the image reached the expected
- # status
- if status_curr == status:
- return
+ time.sleep(client.build_interval)
- if int(time.time()) - start >= client.build_timeout:
- message = ('Image %(image_id)s failed to reach %(status)s state'
- '(current state %(status_curr)s) '
- 'within the required time (%(timeout)s s).' %
- {'image_id': image_id,
- 'status': status,
- 'status_curr': status_curr,
- 'timeout': client.build_timeout})
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
+ message = ('Image %(image_id)s failed to reach %(status)s state '
+ '(current state %(current_status)s) within the required '
+ 'time (%(timeout)s s).' % {'image_id': image_id,
+ 'status': status,
+ 'current_status': current_status,
+ 'timeout': client.build_timeout})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
def wait_for_volume_status(client, volume_id, status):
diff --git a/tempest/config.py b/tempest/config.py
index 6360c3e..3810ceb 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -160,41 +160,9 @@
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for OpenStack Identity "
"(Keystone) API v3"),
- cfg.StrOpt('username',
- help="Username to use for Nova API requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('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',
help="Role required to administrate keystone."),
- cfg.StrOpt('password',
- help="API key to use when authenticating.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('domain_name',
- help="Domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_username',
- help="Username of alternate user to use for Nova API "
- "requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_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',
- help="API key to use when authenticating as alternate user.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('alt_domain_name',
- help="Alternate domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
cfg.StrOpt('default_domain_id',
default='default',
help="ID of the default domain"),
@@ -402,14 +370,6 @@
'encrypted volume to a running server instance? This may '
'depend on the combination of compute_driver in nova and '
'the volume_driver(s) in cinder.'),
- # TODO(mriedem): Remove allow_duplicate_networks once kilo-eol happens
- # since the option was removed from nova in Liberty and is the default
- # behavior starting in Liberty.
- cfg.BoolOpt('allow_duplicate_networks',
- default=False,
- help='Does the test environment support creating instances '
- 'with multiple ports on the same network? This is only '
- 'valid when using Neutron.'),
cfg.BoolOpt('config_drive',
default=True,
help='Enable special configuration drive with metadata.'),
@@ -881,21 +841,6 @@
help="Value must match heat configuration of the same name."),
]
-
-dashboard_group = cfg.OptGroup(name="dashboard",
- title="Dashboard options")
-
-DashboardGroup = [
- cfg.StrOpt('dashboard_url',
- default='http://localhost/',
- help="Where the dashboard can be found"),
- cfg.StrOpt('login_url',
- default='http://localhost/auth/login/',
- help="Login page for the dashboard",
- deprecated_for_removal=True),
-]
-
-
data_processing_group = cfg.OptGroup(name="data-processing",
title="Data Processing options")
@@ -1026,9 +971,6 @@
cfg.BoolOpt('heat',
default=False,
help="Whether or not Heat is expected to be available"),
- cfg.BoolOpt('horizon',
- default=True,
- help="Whether or not Horizon is expected to be available"),
cfg.BoolOpt('sahara',
default=False,
help="Whether or not Sahara is expected to be available"),
@@ -1171,7 +1113,6 @@
(object_storage_feature_group, ObjectStoreFeaturesGroup),
(database_group, DatabaseGroup),
(orchestration_group, OrchestrationGroup),
- (dashboard_group, DashboardGroup),
(data_processing_group, DataProcessingGroup),
(data_processing_feature_group, DataProcessingFeaturesGroup),
(stress_group, StressGroup),
@@ -1239,7 +1180,6 @@
'object-storage-feature-enabled']
self.database = _CONF.database
self.orchestration = _CONF.orchestration
- self.dashboard = _CONF.dashboard
self.data_processing = _CONF['data-processing']
self.data_processing_feature_enabled = _CONF[
'data-processing-feature-enabled']
@@ -1250,12 +1190,6 @@
self.baremetal = _CONF.baremetal
self.input_scenario = _CONF['input-scenario']
self.negative = _CONF.negative
- _CONF.set_default('domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
- _CONF.set_default('alt_domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
logging.tempest_set_log_file('tempest.log')
def __init__(self, parse_conf=True, config_path=None):
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index aff9dee..09106d1 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -256,6 +256,23 @@
yield (0, msg)
+def dont_use_config_in_tempest_lib(logical_line, filename):
+ """Check that tempest.lib doesn't use tempest config
+
+ T114
+ """
+
+ if 'tempest/lib/' not in filename:
+ return
+
+ if ('tempest.config' in logical_line
+ or 'from tempest import config' in logical_line
+ or 'oslo_config' in logical_line):
+ msg = ('T114: tempest.lib can not have any dependency on tempest '
+ 'config.')
+ yield(0, msg)
+
+
def factory(register):
register(import_no_clients_in_api_and_scenario_tests)
register(scenario_tests_need_service_tags)
@@ -268,4 +285,5 @@
register(get_resources_on_service_clients)
register(delete_resources_on_service_clients)
register(dont_import_local_tempest_into_lib)
+ register(dont_use_config_in_tempest_lib)
register(use_rand_uuid_instead_of_uuid4)
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
index 4ef9012..be875ee 100644
--- a/tempest/hacking/ignored_list_T110.txt
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -2,4 +2,3 @@
./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/routers_client.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/images.py b/tempest/lib/api_schema/response/compute/v2_1/images.py
index daab898..b0f1934 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/images.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/images.py
@@ -77,7 +77,7 @@
'properties': {
'id': {'type': 'string'},
'links': image_links,
- 'name': {'type': 'string'}
+ 'name': {'type': ['string', 'null']}
},
'additionalProperties': False,
'required': ['id', 'links', 'name']
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index a6833be..9c5ad8e 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -1,4 +1,5 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# Copyright 2016 Rackspace Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -68,10 +69,16 @@
class AuthProvider(object):
"""Provide authentication"""
- def __init__(self, credentials):
+ SCOPES = set(['project'])
+
+ def __init__(self, credentials, scope='project'):
"""Auth provider __init__
:param credentials: credentials for authentication
+ :param scope: the default scope to be used by the credential providers
+ when requesting a token. Valid values depend on the
+ AuthProvider class implementation, and are defined in
+ the set SCOPES. Default value is 'project'.
"""
if self.check_credentials(credentials):
self.credentials = credentials
@@ -88,6 +95,8 @@
raise TypeError("credentials object is of type %s, which is"
" not a valid Credentials object type." %
credentials.__class__.__name__)
+ self._scope = None
+ self.scope = scope
self.cache = None
self.alt_auth_data = None
self.alt_part = None
@@ -123,8 +132,14 @@
@property
def auth_data(self):
+ """Auth data for set scope"""
return self.get_auth()
+ @property
+ def scope(self):
+ """Scope used in auth requests"""
+ return self._scope
+
@auth_data.deleter
def auth_data(self):
self.clear_auth()
@@ -139,7 +154,7 @@
"""Forces setting auth.
Forces setting auth, ignores cache if it exists.
- Refills credentials
+ Refills credentials.
"""
self.cache = self._get_auth()
self._fill_credentials(self.cache[1])
@@ -222,6 +237,19 @@
"""Extracts the base_url based on provided filters"""
return
+ @scope.setter
+ def scope(self, value):
+ """Set the scope to be used in token requests
+
+ :param scope: scope to be used. If the scope is different, clear caches
+ """
+ if value not in self.SCOPES:
+ raise exceptions.InvalidScope(
+ scope=value, auth_provider=self.__class__.__name__)
+ if value != self.scope:
+ self.clear_auth()
+ self._scope = value
+
class KeystoneAuthProvider(AuthProvider):
@@ -231,17 +259,18 @@
def __init__(self, credentials, auth_url,
disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
- super(KeystoneAuthProvider, self).__init__(credentials)
+ ca_certs=None, trace_requests=None, scope='project'):
+ super(KeystoneAuthProvider, self).__init__(credentials, scope)
self.dsvm = disable_ssl_certificate_validation
self.ca_certs = ca_certs
self.trace_requests = trace_requests
+ self.auth_url = auth_url
self.auth_client = self._auth_client(auth_url)
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _ = auth_data
base_url = self.base_url(filters=filters, auth_data=auth_data)
# build authenticated request
@@ -265,6 +294,11 @@
@abc.abstractmethod
def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ By default all fields available in Credentials are passed to the
+ token request. Scope may affect this.
+ """
return
def _get_auth(self):
@@ -292,10 +326,17 @@
return expiry
def get_token(self):
- return self.auth_data[0]
+ return self.get_auth()[0]
class KeystoneV2AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V2 API
+
+ The Keystone Identity V2 API defines both unscoped and project scoped
+ tokens. This auth provider only implements 'project'.
+ """
+
+ SCOPES = set(['project'])
def _auth_client(self, auth_url):
return json_v2id.TokenClient(
@@ -303,6 +344,10 @@
ca_certs=self.ca_certs, trace_requests=self.trace_requests)
def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ All fields available in Credentials are passed to the token request.
+ """
return dict(
user=self.credentials.username,
password=self.credentials.password,
@@ -324,18 +369,24 @@
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
+ :param filters: Used to filter results
+ Filters can be:
+ - service: service type name such as compute, image, etc.
+ - region: service region name
+ - name: service name, only if service exists
+ - endpoint_type: type of endpoint such as
+ adminURL, publicURL, internalURL
+ - api_version: the version of api used to replace catalog version
+ - skip_path: skips the suffix path of the url and uses base URL
+ :rtype string
+ :return url with filters applied
"""
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _auth_data = auth_data
service = filters.get('service')
region = filters.get('region')
+ name = filters.get('name')
endpoint_type = filters.get('endpoint_type', 'publicURL')
if service is None:
@@ -344,17 +395,19 @@
_base_url = None
for ep in _auth_data['serviceCatalog']:
if ep["type"] == service:
+ if name is not None and ep["name"] != name:
+ continue
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
+ # No region or name 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))
+ "service: %s, region: %s, endpoint_type: %s, name: %s" %
+ (service, region, endpoint_type, name))
return apply_url_filters(_base_url, filters)
def is_expired(self, auth_data):
@@ -365,6 +418,9 @@
class KeystoneV3AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V3 API"""
+
+ SCOPES = set(['project', 'domain', 'unscoped', None])
def _auth_client(self, auth_url):
return json_v3id.V3TokenClient(
@@ -372,20 +428,36 @@
ca_certs=self.ca_certs, trace_requests=self.trace_requests)
def _auth_params(self):
- return dict(
+ """Auth parameters to be passed to the token request
+
+ Fields available in Credentials are passed to the token request,
+ depending on the value of scope. Valid values for scope are: "project",
+ "domain". Any other string (e.g. "unscoped") or None will lead to an
+ unscoped token request.
+ """
+
+ auth_params = dict(
user_id=self.credentials.user_id,
username=self.credentials.username,
- 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,
+ password=self.credentials.password,
auth_data=True)
+ if self.scope == 'project':
+ auth_params.update(
+ project_domain_id=self.credentials.project_domain_id,
+ project_domain_name=self.credentials.project_domain_name,
+ project_id=self.credentials.project_id,
+ project_name=self.credentials.project_name)
+
+ if self.scope == 'domain':
+ auth_params.update(
+ domain_id=self.credentials.domain_id,
+ domain_name=self.credentials.domain_name)
+
+ return auth_params
+
def _fill_credentials(self, auth_data_body):
# project or domain, depending on the scope
project = auth_data_body.get('project', None)
@@ -422,18 +494,28 @@
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 scope is not 'project', it may be that there is not catalog in
+ the auth_data. In such case, as long as the requested service is
+ 'identity', we can use the original auth URL to build the base_url.
+
+ :param filters: Used to filter results
+ Filters can be:
+ - service: service type name such as compute, image, etc.
+ - region: service region name
+ - name: service name, only if service exists
+ - endpoint_type: type of endpoint such as
+ adminURL, publicURL, internalURL
+ - api_version: the version of api used to replace catalog version
+ - skip_path: skips the suffix path of the url and uses base URL
+ :rtype string
+ :return url with filters applied
"""
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _auth_data = auth_data
service = filters.get('service')
region = filters.get('region')
+ name = filters.get('name')
endpoint_type = filters.get('endpoint_type', 'public')
if service is None:
@@ -442,14 +524,28 @@
if 'URL' in endpoint_type:
endpoint_type = endpoint_type.replace('URL', '')
_base_url = None
- catalog = _auth_data['catalog']
+ catalog = _auth_data.get('catalog', [])
# Select entries with matching service type
service_catalog = [ep for ep in catalog if ep['type'] == service]
if len(service_catalog) > 0:
- service_catalog = service_catalog[0]['endpoints']
+ if name is not None:
+ service_catalog = (
+ [ep for ep in service_catalog if ep['name'] == name])
+ if len(service_catalog) > 0:
+ service_catalog = service_catalog[0]['endpoints']
+ else:
+ raise exceptions.EndpointNotFound(name)
+ else:
+ service_catalog = service_catalog[0]['endpoints']
else:
- # No matching service
- raise exceptions.EndpointNotFound(service)
+ if len(catalog) == 0 and service == 'identity':
+ # NOTE(andreaf) If there's no catalog at all and the service
+ # is identity, it's a valid use case. Having a non-empty
+ # catalog with no identity in it is not valid instead.
+ return apply_url_filters(self.auth_url, filters)
+ else:
+ # No matching service
+ raise exceptions.EndpointNotFound(service)
# Filter by endpoint type (interface)
filtered_catalog = [ep for ep in service_catalog if
ep['interface'] == endpoint_type]
@@ -460,12 +556,12 @@
filtered_catalog = [ep for ep in filtered_catalog if
ep['region'] == region]
if len(filtered_catalog) == 0:
- # No matching region, take the first endpoint
+ # No matching region (or name), 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)
+ raise exceptions.EndpointNotFound(service)
return apply_url_filters(_base_url, filters)
def is_expired(self, auth_data):
@@ -532,6 +628,7 @@
"""
ATTRIBUTES = []
+ COLLISIONS = []
def __init__(self, **kwargs):
"""Enforce the available attributes at init time (only).
@@ -543,6 +640,13 @@
self._apply_credentials(kwargs)
def _apply_credentials(self, attr):
+ for (key1, key2) in self.COLLISIONS:
+ val1 = attr.get(key1)
+ val2 = attr.get(key2)
+ if val1 and val2 and val1 != val2:
+ msg = ('Cannot have conflicting values for %s and %s' %
+ (key1, key2))
+ raise exceptions.InvalidCredentials(msg)
for key in attr.keys():
if key in self.ATTRIBUTES:
setattr(self, key, attr[key])
@@ -600,7 +704,33 @@
class KeystoneV2Credentials(Credentials):
ATTRIBUTES = ['username', 'password', 'tenant_name', 'user_id',
- 'tenant_id']
+ 'tenant_id', 'project_id', 'project_name']
+ COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
+
+ def __str__(self):
+ """Represent only attributes included in self.ATTRIBUTES"""
+ attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ _repr = dict((k, getattr(self, k)) for k in attrs)
+ return str(_repr)
+
+ def __setattr__(self, key, value):
+ # NOTE(andreaf) In order to ease the migration towards 'project' we
+ # support v2 credentials configured with 'project' and translate it
+ # to tenant on the fly. The original kwargs are stored for clients
+ # that may rely on them. We also set project when tenant is defined
+ # so clients can rely on project being part of credentials.
+ parent = super(KeystoneV2Credentials, self)
+ # for project_* set tenant only
+ if key == 'project_id':
+ parent.__setattr__('tenant_id', value)
+ elif key == 'project_name':
+ parent.__setattr__('tenant_name', value)
+ if key == 'tenant_id':
+ parent.__setattr__('project_id', value)
+ elif key == 'tenant_name':
+ parent.__setattr__('project_name', value)
+ # trigger default behaviour for all attributes
+ parent.__setattr__(key, value)
def is_valid(self):
"""Check of credentials (no API call)
@@ -611,9 +741,6 @@
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"""
@@ -621,16 +748,7 @@
'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)
+ COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
def __setattr__(self, key, value):
parent = super(KeystoneV3Credentials, self)
@@ -669,7 +787,7 @@
def is_valid(self):
"""Check of credentials (no API call)
- Valid combinations of v3 credentials (excluding token, scope)
+ Valid combinations of v3 credentials (excluding token)
- User id, password (optional domain)
- User name, password and its domain id/name
For the scope, valid combinations are:
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index 54f35f4..a43d002 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -119,7 +119,7 @@
:param merge_stderr: if True the stderr buffer is merged into stdout
:type merge_stderr: boolean
"""
- flags += ' --endpoint-type %s' % endpoint_type
+ flags += ' --os-endpoint-type %s' % endpoint_type
return self.cmd_with_auth(
'nova', action, flags, params, fail_ok, merge_stderr)
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index fafb303..1b0f53a 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -15,6 +15,7 @@
# under the License.
import collections
+import email.utils
import logging as real_logging
import re
import time
@@ -25,7 +26,7 @@
import six
from tempest.lib.common import http
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions
# redrive rate limited calls at most twice
@@ -53,6 +54,8 @@
: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 name: The endpoint name to use for the catalog lookup; this
+ returns only if the service exists
: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
@@ -75,10 +78,11 @@
endpoint_type='publicURL',
build_interval=1, build_timeout=60,
disable_ssl_certificate_validation=False, ca_certs=None,
- trace_requests=''):
+ trace_requests='', name=None):
self.auth_provider = auth_provider
self.service = service
self.region = region
+ self.name = name
self.endpoint_type = endpoint_type
self.build_interval = build_interval
self.build_timeout = build_timeout
@@ -190,7 +194,8 @@
_filters = dict(
service=self.service,
endpoint_type=self.endpoint_type,
- region=self.region
+ region=self.region,
+ name=self.name
)
if self.api_version is not None:
_filters['api_version'] = self.api_version
@@ -220,6 +225,10 @@
:raises exceptions.InvalidHttpSuccessCode: if the read code isn't an
expected http success code
"""
+ if not isinstance(read_code, int):
+ raise TypeError("'read_code' must be an int instead of (%s)"
+ % type(read_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!"
@@ -242,7 +251,8 @@
details = pattern.format(read_code, expected_code)
raise exceptions.InvalidHttpSuccessCode(details)
- def post(self, url, body, headers=None, extra_headers=False):
+ def post(self, url, body, headers=None, extra_headers=False,
+ chunked=False):
"""Send a HTTP POST request using keystone auth
:param str url: the relative url to send the post request to
@@ -252,11 +262,12 @@
returned by the get_headers() method are to
be used but additional headers are needed in
the request pass them in as a dict.
+ :param bool chunked: sends the body with chunked encoding
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
- return self.request('POST', url, extra_headers, headers, body)
+ return self.request('POST', url, extra_headers, headers, body, chunked)
def get(self, url, headers=None, extra_headers=False):
"""Send a HTTP GET request using keystone service catalog and auth
@@ -305,7 +316,7 @@
"""
return self.request('PATCH', url, extra_headers, headers, body)
- def put(self, url, body, headers=None, extra_headers=False):
+ def put(self, url, body, headers=None, extra_headers=False, chunked=False):
"""Send a HTTP PUT request using keystone service catalog and auth
:param str url: the relative url to send the post request to
@@ -315,11 +326,12 @@
returned by the get_headers() method are to
be used but additional headers are needed in
the request pass them in as a dict.
+ :param bool chunked: sends the body with chunked encoding
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
- return self.request('PUT', url, extra_headers, headers, body)
+ return self.request('PUT', url, extra_headers, headers, body, chunked)
def head(self, url, headers=None, extra_headers=False):
"""Send a HTTP HEAD request using keystone service catalog and auth
@@ -389,7 +401,7 @@
req_body=None):
if req_headers is None:
req_headers = {}
- caller_name = misc_utils.find_test_caller()
+ caller_name = test_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))
@@ -428,7 +440,7 @@
# 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()
+ caller_name = test_utils.find_test_caller()
if secs:
secs = " %.3fs" % secs
self.LOG.info(
@@ -519,7 +531,7 @@
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):
+ def _request(self, method, url, headers=None, body=None, chunked=False):
"""A simple HTTP request interface."""
# Authenticate the request with the auth provider
req_url, req_headers, req_body = self.auth_provider.auth_request(
@@ -529,7 +541,9 @@
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)
+ req_url, method, headers=req_headers, body=req_body,
+ chunked=chunked
+ )
end = time.time()
self._log_request(method, req_url, resp, secs=(end - start),
req_headers=req_headers, req_body=req_body,
@@ -540,7 +554,7 @@
return resp, resp_body
- def raw_request(self, url, method, headers=None, body=None):
+ def raw_request(self, url, method, headers=None, body=None, chunked=False):
"""Send a raw HTTP request without the keystone catalog or auth
This method sends a HTTP request in the same manner as the request()
@@ -553,17 +567,18 @@
:param str headers: Headers to use for the request if none are specifed
the headers
:param str body: Body to send with the request
+ :param bool chunked: sends the body with chunked encoding
:rtype: tuple
:return: a tuple with the first entry containing the response headers
and the second the response body
"""
if headers is None:
headers = self.get_headers()
- return self.http_obj.request(url, method,
- headers=headers, body=body)
+ return self.http_obj.request(url, method, headers=headers,
+ body=body, chunked=chunked)
def request(self, method, url, extra_headers=False, headers=None,
- body=None):
+ body=None, chunked=False):
"""Send a HTTP request with keystone auth and using the catalog
This method will send an HTTP request using keystone auth in the
@@ -589,6 +604,7 @@
get_headers() method are used. If the request
explicitly requires no headers use an empty dict.
:param str body: Body to send with the request
+ :param bool chunked: sends the body with chunked encoding
:rtype: tuple
:return: a tuple with the first entry containing the response headers
and the second the response body
@@ -628,8 +644,8 @@
except (ValueError, TypeError):
headers = self.get_headers()
- resp, resp_body = self._request(method, url,
- headers=headers, body=body)
+ resp, resp_body = self._request(method, url, headers=headers,
+ body=body, chunked=chunked)
while (resp.status == 413 and
'retry-after' in resp and
@@ -637,7 +653,10 @@
resp, self._parse_resp(resp_body)) and
retry < MAX_RECURSION_DEPTH):
retry += 1
- delay = int(resp['retry-after'])
+ delay = self._get_retry_after_delay(resp)
+ self.LOG.debug(
+ "Sleeping %s seconds based on retry-after header", delay
+ )
time.sleep(delay)
resp, resp_body = self._request(method, url,
headers=headers, body=body)
@@ -645,6 +664,51 @@
resp, resp_body)
return resp, resp_body
+ def _get_retry_after_delay(self, resp):
+ """Extract the delay from the retry-after header.
+
+ This supports both integer and HTTP date formatted retry-after headers
+ per RFC 2616.
+
+ :param resp: The response containing the retry-after headers
+ :rtype: int
+ :return: The delay in seconds, clamped to be at least 1 second
+ :raises ValueError: On failing to parse the delay
+ """
+ delay = None
+ try:
+ delay = int(resp['retry-after'])
+ except (ValueError, KeyError):
+ pass
+
+ try:
+ retry_timestamp = self._parse_http_date(resp['retry-after'])
+ date_timestamp = self._parse_http_date(resp['date'])
+ delay = int(retry_timestamp - date_timestamp)
+ except (ValueError, OverflowError, KeyError):
+ pass
+
+ if delay is None:
+ raise ValueError(
+ "Failed to parse retry-after header %r as either int or "
+ "HTTP-date." % resp.get('retry-after')
+ )
+
+ # Retry-after headers do not have sub-second precision. Clients may
+ # receive a delay of 0. After sleeping 0 seconds, we would (likely) hit
+ # another 413. To avoid this, always sleep at least 1 second.
+ return max(1, delay)
+
+ def _parse_http_date(self, val):
+ """Parse an HTTP date, like 'Fri, 31 Dec 1999 23:59:59 GMT'.
+
+ Return an epoch timestamp (float), as returned by time.mktime().
+ """
+ parts = email.utils.parsedate(val)
+ if not parts:
+ raise ValueError("Failed to parse date %s" % val)
+ return time.mktime(parts)
+
def _error_checker(self, method, url,
headers, body, resp, resp_body):
@@ -771,10 +835,7 @@
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')
+ return 'exceed' in resp_body.get('message', 'blabla')
def wait_for_resource_deletion(self, id):
"""Waits for a resource to be deleted
@@ -796,7 +857,7 @@
'the required time (%(timeout)s s).' %
{'resource_type': self.resource_type, 'id': id,
'timeout': self.build_timeout})
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 9605479..45e5067 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -19,6 +19,8 @@
import string
import uuid
+import six.moves
+
def rand_uuid():
"""Generate a random UUID string
@@ -196,3 +198,10 @@
except TypeError:
raise TypeError('Bad prefix type for generate IPv6 address by '
'EUI-64: %s' % cidr)
+
+
+# Courtesy of http://stackoverflow.com/a/312464
+def chunkify(sequence, chunksize):
+ """Yield successive chunks from `sequence`."""
+ for i in six.moves.xrange(0, len(sequence), chunksize):
+ yield sequence[i:i + chunksize]
diff --git a/tempest/lib/common/utils/misc.py b/tempest/lib/common/utils/misc.py
index b97dd86..f13b4c8 100644
--- a/tempest/lib/common/utils/misc.py
+++ b/tempest/lib/common/utils/misc.py
@@ -12,12 +12,10 @@
# 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
+from tempest.lib.common.utils import test_utils
+
LOG = logging.getLogger(__name__)
@@ -32,56 +30,8 @@
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
+def find_test_caller(*args, **kwargs):
+ LOG.warning("tempest.lib.common.utils.misc.find_test_caller is deprecated "
+ "in favor of tempest.lib.common.utils.test_utils."
+ "find_test_caller")
+ test_utils.find_test_caller(*args, **kwargs)
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
new file mode 100644
index 0000000..50a1a7d
--- /dev/null
+++ b/tempest/lib/common/utils/test_utils.py
@@ -0,0 +1,85 @@
+# 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 inspect
+import re
+
+from oslo_log import log as logging
+
+from tempest.lib import exceptions
+
+LOG = logging.getLogger(__name__)
+
+
+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
+
+
+def call_and_ignore_notfound_exc(func, *args, **kwargs):
+ """Call the given function and pass if a `NotFound` exception is raised."""
+ try:
+ return func(*args, **kwargs)
+ except exceptions.NotFound:
+ pass
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index e78e624..6ed99b4 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -69,12 +69,11 @@
"or False") % attr
def __call__(self, func):
+ @functools.wraps(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
index b9b2ae9..259bbbb 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -207,6 +207,10 @@
message = "Invalid Credentials"
+class InvalidScope(TempestException):
+ message = "Invalid Scope %(scope)s for %(auth_provider)s"
+
+
class SSHTimeout(TempestException):
message = ("Connection to the %(host)s via SSH timed out.\n"
"User: %(user)s, Password: %(password)s")
diff --git a/tempest/lib/services/compute/base_compute_client.py b/tempest/lib/services/compute/base_compute_client.py
index 9161abb..433c94c 100644
--- a/tempest/lib/services/compute/base_compute_client.py
+++ b/tempest/lib/services/compute/base_compute_client.py
@@ -48,9 +48,9 @@
return headers
def request(self, method, url, extra_headers=False, headers=None,
- body=None):
+ body=None, chunked=False):
resp, resp_body = super(BaseComputeClient, self).request(
- method, url, extra_headers, headers, body)
+ method, url, extra_headers, headers, body, chunked)
if (COMPUTE_MICROVERSION and
COMPUTE_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
api_version_utils.assert_version_header_matches_request(
@@ -67,11 +67,13 @@
: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}]
+
+ 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)
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
index 0350175..5716027 100644
--- a/tempest/lib/services/identity/v2/token_client.py
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -75,8 +75,12 @@
return rest_client.ResponseBody(resp, body['access'])
def request(self, method, url, extra_headers=False, headers=None,
- body=None):
- """A simple HTTP request interface."""
+ body=None, chunked=False):
+ """A simple HTTP request interface.
+
+ Note: this overloads the `request` method from the parent class and
+ thus must implement the same method signature.
+ """
if headers is None:
headers = self.get_headers(accept_type="json")
elif extra_headers:
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index f342a49..964d43f 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -122,8 +122,12 @@
return rest_client.ResponseBody(resp, body)
def request(self, method, url, extra_headers=False, headers=None,
- body=None):
- """A simple HTTP request interface."""
+ body=None, chunked=False):
+ """A simple HTTP request interface.
+
+ Note: this overloads the `request` method from the parent class and
+ thus must implement the same method signature.
+ """
if headers is None:
# Always accept 'json', for xml token client too.
# Because XML response is not easily
diff --git a/tempest/services/network/__init__.py b/tempest/lib/services/image/__init__.py
similarity index 100%
copy from tempest/services/network/__init__.py
copy to tempest/lib/services/image/__init__.py
diff --git a/tempest/services/network/__init__.py b/tempest/lib/services/image/v2/__init__.py
similarity index 100%
copy from tempest/services/network/__init__.py
copy to tempest/lib/services/image/v2/__init__.py
diff --git a/tempest/lib/services/image/v2/image_members_client.py b/tempest/lib/services/image/v2/image_members_client.py
new file mode 100644
index 0000000..2ae7516
--- /dev/null
+++ b/tempest/lib/services/image/v2/image_members_client.py
@@ -0,0 +1,64 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 ImageMembersClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def list_image_members(self, image_id):
+ url = 'images/%s/members' % image_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_image_member(self, image_id, **kwargs):
+ """Create an image member.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#createImageMember-v2
+ """
+ url = 'images/%s/members' % image_id
+ data = json.dumps(kwargs)
+ resp, body = self.post(url, data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_image_member(self, image_id, member_id, **kwargs):
+ """Update an image member.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#updateImageMember-v2
+ """
+ url = 'images/%s/members/%s' % (image_id, member_id)
+ data = json.dumps(kwargs)
+ resp, body = self.put(url, data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_image_member(self, image_id, member_id):
+ url = 'images/%s/members/%s' % (image_id, member_id)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, json.loads(body))
+
+ def delete_image_member(self, image_id, member_id):
+ url = 'images/%s/members/%s' % (image_id, member_id)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v2/namespaces_client.py b/tempest/lib/services/image/v2/namespaces_client.py
new file mode 100644
index 0000000..97400e1
--- /dev/null
+++ b/tempest/lib/services/image/v2/namespaces_client.py
@@ -0,0 +1,64 @@
+# 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.common import rest_client
+
+
+class NamespacesClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def create_namespace(self, **kwargs):
+ """Create a namespace.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#createNamespace-v2
+ """
+ data = json.dumps(kwargs)
+ resp, body = self.post('metadefs/namespaces', data)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_namespace(self, namespace):
+ url = 'metadefs/namespaces/%s' % namespace
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_namespace(self, namespace, **kwargs):
+ """Update a namespace.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#updateNamespace-v2
+ """
+ # NOTE: On Glance API, we need to pass namespace on both URI
+ # and a request body.
+ params = {'namespace': namespace}
+ params.update(kwargs)
+ data = json.dumps(params)
+ url = 'metadefs/namespaces/%s' % namespace
+ resp, body = self.put(url, body=data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_namespace(self, namespace):
+ url = 'metadefs/namespaces/%s' % namespace
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v2/resource_types_client.py b/tempest/lib/services/image/v2/resource_types_client.py
new file mode 100644
index 0000000..1349c63
--- /dev/null
+++ b/tempest/lib/services/image/v2/resource_types_client.py
@@ -0,0 +1,29 @@
+# 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.common import rest_client
+
+
+class ResourceTypesClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def list_resource_types(self):
+ url = 'metadefs/resource_types'
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/image/v2/schemas_client.py b/tempest/lib/services/image/v2/schemas_client.py
new file mode 100644
index 0000000..0c9db40
--- /dev/null
+++ b/tempest/lib/services/image/v2/schemas_client.py
@@ -0,0 +1,29 @@
+# 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.common import rest_client
+
+
+class SchemasClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def show_schema(self, schema):
+ url = 'schemas/%s' % schema
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/routers_client.py b/tempest/lib/services/network/routers_client.py
new file mode 100644
index 0000000..78ffa77
--- /dev/null
+++ b/tempest/lib/services/network/routers_client.py
@@ -0,0 +1,65 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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, **kwargs):
+ uri = '/routers/%s' % router_id
+ update_body = {'router': kwargs}
+ return self.update_resource(uri, update_body)
+
+ 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 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/manager.py b/tempest/manager.py
index c97e0d1..cafa5b9 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -28,13 +28,14 @@
and a client object for a test case to use in performing actions.
"""
- def __init__(self, credentials):
+ def __init__(self, credentials, scope='project'):
"""Initialization of base manager class
Credentials to be used within the various client classes managed by the
Manager object must be defined.
:param credentials: type Credentials or TestResources
+ :param scope: default scope for tokens produced by the auth provider
"""
self.credentials = credentials
# Check if passed or default credentials are valid
@@ -48,7 +49,8 @@
else:
creds = self.credentials
# Creates an auth provider for the credentials
- self.auth_provider = get_auth_provider(creds, pre_auth=True)
+ self.auth_provider = get_auth_provider(creds, pre_auth=True,
+ scope=scope)
def get_auth_provider_class(credentials):
@@ -58,7 +60,7 @@
return auth.KeystoneV2AuthProvider, CONF.identity.uri
-def get_auth_provider(credentials, pre_auth=False):
+def get_auth_provider(credentials, pre_auth=False, scope='project'):
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
@@ -71,6 +73,7 @@
auth_provider_class, auth_url = get_auth_provider_class(
credentials)
_auth_provider = auth_provider_class(credentials, auth_url,
+ scope=scope,
**default_params)
if pre_auth:
_auth_provider.set_auth()
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 956fe88..1dfa86d 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -27,7 +27,7 @@
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.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest.scenario import network_resources
import tempest.test
@@ -50,8 +50,15 @@
cls.compute_floating_ips_client = (
cls.manager.compute_floating_ips_client)
if CONF.service_available.glance:
- # Glance image client v1
- cls.image_client = cls.manager.image_client
+ # Check if glance v1 is available to determine which client to use.
+ if CONF.image_feature_enabled.api_v1:
+ cls.image_client = cls.manager.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ cls.image_client = cls.manager.image_client_v2
+ else:
+ raise exceptions.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
# Compute image client
cls.compute_images_client = cls.manager.compute_images_client
cls.keypairs_client = cls.manager.keypairs_client
@@ -95,21 +102,6 @@
# not at the end of the class
self.addCleanup(self._wait_for_cleanups)
- def delete_wrapper(self, delete_thing, *args, **kwargs):
- """Ignores NotFound exceptions for delete operations.
-
- @param delete_thing: delete method of a resource. method will be
- executed as delete_thing(*args, **kwargs)
-
- """
- try:
- # Tempest clients return dicts, so there is no common delete
- # method available. Using a callable instead
- delete_thing(*args, **kwargs)
- except lib_exc.NotFound:
- # If the resource is already missing, mission accomplished.
- pass
-
def addCleanup_with_wait(self, waiter_callable, thing_id, thing_id_param,
cleanup_callable, cleanup_args=None,
cleanup_kwargs=None, waiter_client=None):
@@ -254,7 +246,7 @@
self.addCleanup_with_wait(
waiter_callable=waiters.wait_for_server_termination,
thing_id=body['id'], thing_id_param='server_id',
- cleanup_callable=self.delete_wrapper,
+ cleanup_callable=test_utils.call_and_ignore_notfound_exc,
cleanup_args=[clients.servers_client.delete_server, body['id']],
waiter_client=clients.servers_client)
server = clients.servers_client.show_server(body['id'])['server']
@@ -274,7 +266,7 @@
self.addCleanup(self.volumes_client.wait_for_resource_deletion,
volume['id'])
- self.addCleanup(self.delete_wrapper,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.volumes_client.delete_volume, volume['id'])
# NOTE(e0ne): Cinder API v2 uses name instead of display_name
@@ -334,7 +326,7 @@
self.assertEqual(secgroup['name'], sg_name)
self.assertEqual(secgroup['description'], sg_desc)
self.addCleanup(
- self.delete_wrapper,
+ test_utils.call_and_ignore_notfound_exc,
self.compute_security_groups_client.delete_security_group,
secgroup['id'])
@@ -373,7 +365,7 @@
message = ('Initializing SSH connection to %(ip)s failed. '
'Error: %(error)s' % {'ip': ip_address,
'error': e})
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
LOG.exception(message)
@@ -391,14 +383,23 @@
'name': name,
'container_format': fmt,
'disk_format': disk_format or fmt,
- 'is_public': 'False',
}
- params['properties'] = properties
- image = self.image_client.create_image(**params)['image']
+ if CONF.image_feature_enabled.api_v1:
+ params['is_public'] = 'False'
+ params['properties'] = properties
+ else:
+ params['visibility'] = 'private'
+ # Additional properties are flattened out in the v2 API.
+ params.update(properties)
+ body = self.image_client.create_image(**params)
+ image = body['image'] if 'image' in body else body
self.addCleanup(self.image_client.delete_image, image['id'])
self.assertEqual("queued", image['status'])
with open(path, 'rb') as image_file:
- self.image_client.update_image(image['id'], data=image_file)
+ if CONF.image_feature_enabled.api_v1:
+ self.image_client.update_image(image['id'], data=image_file)
+ else:
+ self.image_client.store_image_file(image['id'], image_file)
return image['id']
def glance_image_create(self):
@@ -409,7 +410,7 @@
img_container_format = CONF.scenario.img_container_format
img_disk_format = CONF.scenario.img_disk_format
img_properties = CONF.scenario.img_properties
- LOG.debug("paths: img: %s, container_fomat: %s, disk_format: %s, "
+ LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
"properties: %s, ami: %s, ari: %s, aki: %s" %
(img_path, img_container_format, img_disk_format,
img_properties, ami_img_path, ari_img_path, aki_img_path))
@@ -459,15 +460,22 @@
LOG.debug("Creating a snapshot image for server: %s", server['name'])
image = _images_client.create_image(server['id'], name=name)
image_id = image.response['location'].split('images/')[1]
- _image_client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(_image_client, image_id, 'active')
self.addCleanup_with_wait(
waiter_callable=_image_client.wait_for_resource_deletion,
thing_id=image_id, thing_id_param='id',
- cleanup_callable=self.delete_wrapper,
+ cleanup_callable=test_utils.call_and_ignore_notfound_exc,
cleanup_args=[_image_client.delete_image, image_id])
- snapshot_image = _image_client.get_image_meta(image_id)
+ if CONF.image_feature_enabled.api_v1:
+ # In glance v1 the additional properties are stored in the headers.
+ snapshot_image = _image_client.check_image(image_id)
+ image_props = snapshot_image.get('properties', {})
+ else:
+ # In glance v2 the additional properties are flattened.
+ snapshot_image = _image_client.show_image(image_id)
+ image_props = snapshot_image
- bdm = snapshot_image.get('properties', {}).get('block_device_mapping')
+ bdm = image_props.get('block_device_mapping')
if bdm:
bdm = json.loads(bdm)
if bdm and 'snapshot_id' in bdm[0]:
@@ -475,12 +483,11 @@
self.addCleanup(
self.snapshots_client.wait_for_resource_deletion,
snapshot_id)
- self.addCleanup(
- self.delete_wrapper, self.snapshots_client.delete_snapshot,
- snapshot_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.snapshots_client.delete_snapshot,
+ snapshot_id)
waiters.wait_for_snapshot_status(self.snapshots_client,
snapshot_id, 'available')
-
image_name = snapshot_image['name']
self.assertEqual(name, image_name)
LOG.debug("Created snapshot image %s for server %s",
@@ -537,7 +544,7 @@
return (proc.returncode == 0) == should_succeed
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
' expected result is %(should_succeed)s' % {
'caller': caller, 'ip': ip_address, 'timeout': timeout,
@@ -606,7 +613,7 @@
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,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.compute_floating_ips_client.delete_floating_ip,
floating_ip['id'])
self.compute_floating_ips_client.associate_floating_ip_to_server(
@@ -701,7 +708,8 @@
networks_client=networks_client, routers_client=routers_client,
**result['network'])
self.assertEqual(network.name, name)
- self.addCleanup(self.delete_wrapper, network.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ network.delete)
return network
def _list_networks(self, *args, **kwargs):
@@ -794,7 +802,7 @@
subnets_client=subnets_client,
routers_client=routers_client, **result['subnet'])
self.assertEqual(subnet.cidr, str_cidr)
- self.addCleanup(self.delete_wrapper, subnet.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc, subnet.delete)
return subnet
def _create_port(self, network_id, client=None, namestart='port-quotatest',
@@ -809,7 +817,7 @@
self.assertIsNotNone(result, 'Unable to allocate port')
port = network_resources.DeletablePort(ports_client=client,
**result['port'])
- self.addCleanup(self.delete_wrapper, port.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc, port.delete)
return port
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
@@ -817,11 +825,17 @@
# A port can have more then one IP address in some cases.
# If the network is dual-stack (IPv4 + IPv6), this port is associated
# with 2 subnets
+ p_status = ['ACTIVE']
+ # NOTE(vsaienko) With Ironic, instances live on separate hardware
+ # servers. Neutron does not bind ports for Ironic instances, as a
+ # result the port remains in the DOWN state.
+ if CONF.service_available.ironic:
+ p_status.append('DOWN')
port_map = [(p["id"], fxip["ip_address"])
for p in ports
for fxip in p["fixed_ips"]
if netaddr.valid_ipv4(fxip["ip_address"])
- and p['status'] == 'ACTIVE']
+ and p['status'] in p_status]
inactive = [p for p in ports if p['status'] != 'ACTIVE']
if inactive:
LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
@@ -860,7 +874,8 @@
floating_ip = network_resources.DeletableFloatingIp(
client=client,
**result['floatingip'])
- self.addCleanup(self.delete_wrapper, floating_ip.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ floating_ip.delete)
return floating_ip
def _associate_floating_ip(self, floating_ip, server):
@@ -998,7 +1013,8 @@
self.assertEqual(secgroup.name, sg_name)
self.assertEqual(tenant_id, secgroup.tenant_id)
self.assertEqual(secgroup.description, sg_desc)
- self.addCleanup(self.delete_wrapper, secgroup.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ secgroup.delete)
return secgroup
def _default_security_group(self, client=None, tenant_id=None):
@@ -1069,10 +1085,11 @@
security_groups_client=None):
"""Create loginable security group rule
- These rules are intended to permit inbound ssh and icmp
- traffic from all sources, so no group_id is provided.
- Setting a group_id would only permit traffic from ports
- belonging to the same security group.
+ This function will create:
+ 1. egress and ingress tcp port 22 allow rule in order to allow ssh
+ access for ipv4.
+ 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
+ 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
"""
if security_group_rules_client is None:
@@ -1157,7 +1174,7 @@
router = network_resources.DeletableRouter(routers_client=client,
**result['router'])
self.assertEqual(router.name, name)
- self.addCleanup(self.delete_wrapper, router.delete)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc, router.delete)
return router
def _update_router_admin_state(self, router, admin_state_up):
@@ -1286,11 +1303,8 @@
"""Waits for a node to be associated with instance_id."""
def _get_node():
- node = None
- try:
- node = self.get_node(instance_id=instance_id)
- except lib_exc.NotFound:
- pass
+ node = test_utils.call_and_ignore_notfound_exc(
+ self.get_node, instance_id=instance_id)
return node is not None
if not tempest.test.call_until_true(
@@ -1437,7 +1451,7 @@
# look for the container to assure it is created
self.list_and_check_container_objects(name)
LOG.debug('Container %s created' % (name))
- self.addCleanup(self.delete_wrapper,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.container_client.delete_container,
name)
return name
@@ -1450,7 +1464,7 @@
obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
obj_data = data_utils.arbitrary_string()
self.object_client.create_object(container_name, obj_name, obj_data)
- self.addCleanup(self.delete_wrapper,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.object_client.delete_object,
container_name,
obj_name)
diff --git a/tempest/scenario/test_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
deleted file mode 100644
index 5d4f7b3..0000000
--- a/tempest/scenario/test_dashboard_basic_ops.py
+++ /dev/null
@@ -1,120 +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.
-
-from six.moves import html_parser as HTMLParser
-from six.moves.urllib import parse
-from six.moves.urllib import request
-
-from tempest import config
-from tempest.scenario import manager
-from tempest import test
-
-CONF = config.CONF
-
-
-class HorizonHTMLParser(HTMLParser.HTMLParser):
- csrf_token = None
- region = None
- login = None
-
- def _find_name(self, attrs, name):
- for attrpair in attrs:
- if attrpair[0] == 'name' and attrpair[1] == name:
- return True
- return False
-
- def _find_value(self, attrs):
- for attrpair in attrs:
- if attrpair[0] == 'value':
- return attrpair[1]
- return None
-
- def _find_attr_value(self, attrs, attr_name):
- for attrpair in attrs:
- if attrpair[0] == attr_name:
- return attrpair[1]
- return None
-
- def handle_starttag(self, tag, attrs):
- if tag == 'input':
- if self._find_name(attrs, 'csrfmiddlewaretoken'):
- self.csrf_token = self._find_value(attrs)
- if self._find_name(attrs, 'region'):
- self.region = self._find_value(attrs)
- if tag == 'form':
- self.login = self._find_attr_value(attrs, 'action')
-
-
-class TestDashboardBasicOps(manager.ScenarioTest):
-
- """The test suite for dashboard basic operations
-
- This is a basic scenario test:
- * checks that the login page is available
- * logs in as a regular user
- * checks that the user home page loads without error
- """
-
- @classmethod
- def skip_checks(cls):
- super(TestDashboardBasicOps, cls).skip_checks()
- if not CONF.service_available.horizon:
- raise cls.skipException("Horizon support is required")
-
- @classmethod
- def setup_credentials(cls):
- cls.set_network_resources()
- super(TestDashboardBasicOps, cls).setup_credentials()
-
- def check_login_page(self):
- response = request.urlopen(CONF.dashboard.dashboard_url)
- self.assertIn("id_username", response.read())
-
- def user_login(self, username, password):
- self.opener = request.build_opener(request.HTTPCookieProcessor())
- response = self.opener.open(CONF.dashboard.dashboard_url).read()
-
- # Grab the CSRF token and default region
- parser = HorizonHTMLParser()
- parser.feed(response)
-
- # construct login url for dashboard, discovery accommodates non-/ web
- # root for dashboard
- login_url = parse.urljoin(CONF.dashboard.dashboard_url, parser.login)
-
- # Prepare login form request
- req = request.Request(login_url)
- req.add_header('Content-type', 'application/x-www-form-urlencoded')
- req.add_header('Referer', CONF.dashboard.dashboard_url)
-
- # Pass the default domain name regardless of the auth version in order
- # to test the scenario of when horizon is running with keystone v3
- params = {'username': username,
- 'password': password,
- 'region': parser.region,
- 'domain': CONF.auth.default_credentials_domain_name,
- 'csrfmiddlewaretoken': parser.csrf_token}
- self.opener.open(req, parse.urlencode(params))
-
- def check_home_page(self):
- response = self.opener.open(CONF.dashboard.dashboard_url)
- self.assertIn('Overview', response.read())
-
- @test.idempotent_id('4f8851b1-0e69-482b-b63b-84c6e76f6c80')
- @test.services('dashboard')
- def test_basic_scenario(self):
- creds = self.os.credentials
- self.check_login_page()
- self.user_login(creds.username, creds.password)
- self.check_home_page()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index b9fdd18..f5134e5 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -23,6 +23,7 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import test_utils
from tempest.scenario import manager
from tempest.scenario import network_resources
from tempest import test
@@ -252,7 +253,7 @@
net_id=self.new_net.id)['interfaceAttachment']
self.addCleanup(self.ports_client.wait_for_resource_deletion,
interface['port_id'])
- self.addCleanup(self.delete_wrapper,
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.interface_client.delete_interface,
server['id'], interface['port_id'])
@@ -754,10 +755,10 @@
The test steps are :
1. Create a new network.
2. Connect (hotplug) the VM to a new network.
- 3. Check the VM can ping the DHCP interface of this network.
+ 3. Check the VM can ping a server on the new network ("peer")
4. Spoof the mac address of the new VM interface.
5. Check the Security Group enforces mac spoofing and blocks pings via
- spoofed interface (VM cannot ping the DHCP interface).
+ spoofed interface (VM cannot ping the peer).
6. Disable port-security of the spoofed port- set the flag to false.
7. Retest 3rd step and check that the Security Group allows pings via
the spoofed interface.
@@ -778,18 +779,18 @@
ssh_client = self.get_remote_client(fip.floating_ip_address,
private_key=private_key)
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"]
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ name = data_utils.rand_name('peer')
+ peer = self._create_server(name, self.new_net)
+ peer_address = peer['addresses'][self.new_net.name][0]['addr']
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
ssh_client.set_mac_address(spoof_nic, spoof_mac)
new_mac = ssh_client.get_mac_address(nic=spoof_nic)
self.assertEqual(spoof_mac, new_mac)
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=False)
self.ports_client.update_port(spoof_port["id"],
port_security_enabled=False,
security_groups=[])
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
index 6e24801..2bdd092 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -111,7 +111,7 @@
uri += "?%s" % urllib.urlencode(kwargs)
resp, body = self.get(uri)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@@ -127,7 +127,7 @@
else:
uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
resp, body = self.get(uri)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@@ -145,7 +145,7 @@
uri = self._get_uri(resource)
resp, body = self.post(uri, body=body)
- self.expected_success(201, resp['status'])
+ self.expected_success(201, resp.status)
return resp, self.deserialize(body)
@@ -160,7 +160,7 @@
uri = self._get_uri(resource, uuid)
resp, body = self.delete(uri)
- self.expected_success(204, resp['status'])
+ self.expected_success(204, resp.status)
return resp, body
def _patch_request(self, resource, uuid, patch_object):
@@ -176,7 +176,7 @@
patch_body = json.dumps(patch_object)
resp, body = self.patch(uri, body=patch_body)
- self.expected_success(200, resp['status'])
+ self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@handle_errors
@@ -201,5 +201,5 @@
put_body = json.dumps(put_object)
resp, body = self.put(uri, body=put_body)
- self.expected_success(202, resp['status'])
+ self.expected_success([202, 204], resp.status)
return resp, body
diff --git a/tempest/services/identity/v3/json/projects_client.py b/tempest/services/identity/v3/json/projects_client.py
index dc553d0..97e43df 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/services/identity/v3/json/projects_client.py
@@ -23,17 +23,15 @@
api_version = "v3"
def create_project(self, name, **kwargs):
- """Creates a project."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- domain_id = kwargs.get('domain_id', 'default')
- post_body = {
- 'description': description,
- 'domain_id': domain_id,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'project': post_body})
+ """Create a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#createProject
+
+ """
+ # Include the project name to the kwargs parameters
+ kwargs['name'] = name
+ post_body = json.dumps({'project': kwargs})
resp, body = self.post('projects', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
@@ -49,19 +47,13 @@
return rest_client.ResponseBody(resp, body)
def update_project(self, project_id, **kwargs):
- body = self.show_project(project_id)['project']
- name = kwargs.get('name', body['name'])
- desc = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'id': project_id,
- 'name': name,
- 'description': desc,
- 'enabled': en,
- 'domain_id': domain_id,
- }
- post_body = json.dumps({'project': post_body})
+ """Update a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#updateProject
+
+ """
+ post_body = json.dumps({'project': kwargs})
resp, body = self.patch('projects/%s' % project_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
diff --git a/tempest/services/image/v1/json/image_members_client.py b/tempest/services/image/v1/json/image_members_client.py
new file mode 100644
index 0000000..df16d2fd
--- /dev/null
+++ b/tempest/services/image/v1/json/image_members_client.py
@@ -0,0 +1,63 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 ImageMembersClient(rest_client.RestClient):
+ api_version = "v1"
+
+ def list_image_members(self, image_id):
+ """List all members of an image."""
+ url = 'images/%s/members' % image_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_shared_images(self, tenant_id):
+ """List image memberships for the given tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v1.html#listSharedImages-v1
+ """
+
+ url = 'shared-images/%s' % tenant_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_member(self, member_id, image_id, **kwargs):
+ """Add a member to an image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v1.html#addMember-v1
+ """
+ url = 'images/%s/members/%s' % (image_id, member_id)
+ body = json.dumps({'member': kwargs})
+ resp, __ = self.put(url, body)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def delete_member(self, member_id, image_id):
+ """Removes a membership from the image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v1.html#removeMember-v1
+ """
+ url = 'images/%s/members/%s' % (image_id, member_id)
+ resp, __ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index e29ff89..4ffaf3b 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -14,32 +14,22 @@
# under the License.
import copy
-import errno
-import os
-import time
+import functools
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
-from tempest.common import glance_http
-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__)
+CHUNKSIZE = 1024 * 64 # 64kB
class ImagesClient(rest_client.RestClient):
-
- def __init__(self, auth_provider, catalog_type, region, **kwargs):
- super(ImagesClient, self).__init__(
- auth_provider, catalog_type, region, **kwargs)
- self._http = None
- self.dscv = kwargs.get("disable_ssl_certificate_validation")
- self.ca_certs = kwargs.get("ca_certs")
+ api_version = "v1"
def _image_meta_from_headers(self, headers):
meta = {'properties': {}}
@@ -77,56 +67,29 @@
headers['x-image-meta-%s' % key] = str(value)
return headers
- def _get_file_size(self, obj):
- """Analyze file-like object and attempt to determine its size.
-
- :param obj: file-like object, typically redirected from stdin.
- :retval The file's size or None if it cannot be determined.
- """
- # For large images, we need to supply the size of the
- # image file. See LP Bugs #827660 and #845788.
- if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
- try:
- obj.seek(0, os.SEEK_END)
- obj_size = obj.tell()
- obj.seek(0)
- return obj_size
- except IOError as e:
- if e.errno == errno.ESPIPE:
- # Illegal seek. This means the user is trying
- # to pipe image data to the client, e.g.
- # echo testdata | bin/glance add blah..., or
- # that stdin is empty, or that a file-like
- # object which doesn't support 'seek/tell' has
- # been supplied.
- return None
- else:
- raise
- else:
- # Cannot determine size of input image
- return None
-
- def _get_http(self):
- return glance_http.HTTPClient(auth_provider=self.auth_provider,
- filters=self.filters,
- insecure=self.dscv,
- ca_certs=self.ca_certs)
-
def _create_with_data(self, headers, data):
- resp, body_iter = self.http.raw_request('POST', '/v1/images',
- headers=headers, body=data)
- self._error_checker('POST', '/v1/images', headers, data, resp,
- body_iter)
- body = json.loads(''.join([c for c in body_iter]))
+ # We are going to do chunked transfert, so split the input data
+ # info fixed-sized chunks.
+ headers['Content-Type'] = 'application/octet-stream'
+ data = iter(functools.partial(data.read, CHUNKSIZE), b'')
+ resp, body = self.request('POST', 'images',
+ headers=headers, body=data, chunked=True)
+ self._error_checker('POST', 'images', headers, data, resp,
+ body)
+ body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def _update_with_data(self, image_id, headers, data):
- url = '/v1/images/%s' % image_id
- resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
- body=data)
+ # We are going to do chunked transfert, so split the input data
+ # info fixed-sized chunks.
+ headers['Content-Type'] = 'application/octet-stream'
+ data = iter(functools.partial(data.read, CHUNKSIZE), b'')
+ url = 'images/%s' % image_id
+ resp, body = self.request('PUT', url, headers=headers,
+ body=data, chunked=True)
self._error_checker('PUT', url, headers, data,
- resp, body_iter)
- body = json.loads(''.join([c for c in body_iter]))
+ resp, body)
+ body = json.loads(body)
return rest_client.ResponseBody(resp, body)
@property
@@ -143,7 +106,7 @@
if data is not None:
return self._create_with_data(headers, data)
- resp, body = self.post('v1/images', None, headers)
+ resp, body = self.post('images', None, headers)
self.expected_success(201, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
@@ -156,14 +119,14 @@
if data is not None:
return self._update_with_data(image_id, headers, data)
- url = 'v1/images/%s' % image_id
+ url = 'images/%s' % image_id
resp, body = self.put(url, None, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
- url = 'v1/images/%s' % image_id
+ url = 'images/%s' % image_id
resp, body = self.delete(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -178,7 +141,7 @@
any changes.
:param changes_since: The name is changed to changes-since
"""
- url = 'v1/images'
+ url = 'images'
if detail:
url += '/detail'
@@ -198,22 +161,24 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
- def get_image_meta(self, image_id):
- url = 'v1/images/%s' % image_id
+ def check_image(self, image_id):
+ """Check image metadata."""
+ url = 'images/%s' % image_id
resp, __ = self.head(url)
self.expected_success(200, resp.status)
body = self._image_meta_from_headers(resp)
return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
- url = 'v1/images/%s' % image_id
+ """Get image details plus the image itself."""
+ url = 'images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBodyData(resp, body)
def is_resource_deleted(self, id):
try:
- if self.get_image_meta(id)['status'] == 'deleted':
+ if self.check_image(id)['status'] == 'deleted':
return True
except lib_exc.NotFound:
return True
@@ -223,73 +188,3 @@
def resource_type(self):
"""Returns the primary type of resource this client works with."""
return 'image_meta'
-
- def list_image_members(self, image_id):
- url = 'v1/images/%s/members' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_shared_images(self, tenant_id):
- """List shared images with the specified tenant"""
- url = 'v1/shared-images/%s' % tenant_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def add_member(self, member_id, image_id, **kwargs):
- """Add a member to an image.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v1.html#addMember-v1
- """
- url = 'v1/images/%s/members/%s' % (image_id, member_id)
- body = json.dumps({'member': kwargs})
- resp, __ = self.put(url, body)
- self.expected_success(204, resp.status)
- 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 rest_client.ResponseBody(resp)
-
- # NOTE(afazekas): just for the wait function
- def _get_image_status(self, image_id):
- meta = self.get_image_meta(image_id)
- status = meta['status']
- return status
-
- # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
- def wait_for_image_status(self, image_id, status):
- """Waits for a Image to reach a given status."""
- start_time = time.time()
- old_value = value = self._get_image_status(image_id)
- while True:
- dtime = time.time() - start_time
- time.sleep(self.build_interval)
- if value != old_value:
- LOG.info('Value transition from "%s" to "%s"'
- 'in %d second(s).', old_value,
- value, dtime)
- if value == status:
- return value
-
- if value == 'killed':
- raise exceptions.ImageKilledException(image_id=image_id,
- status=status)
- if dtime > self.build_timeout:
- message = ('Time Limit Exceeded! (%ds)'
- 'while waiting for %s, '
- 'but we got %s.' %
- (self.build_timeout, status, value))
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
- time.sleep(self.build_interval)
- old_value = value
- value = self._get_image_status(image_id)
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 4e037af..71e7c6b 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -13,34 +13,19 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
+
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import glance_http
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+CHUNKSIZE = 1024 * 64 # 64kB
-class ImagesClientV2(rest_client.RestClient):
- def __init__(self, auth_provider, catalog_type, region, **kwargs):
- super(ImagesClientV2, self).__init__(
- auth_provider, catalog_type, region, **kwargs)
- self._http = None
- 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,
- filters=self.filters,
- insecure=self.dscv,
- ca_certs=self.ca_certs)
-
- @property
- def http(self):
- if self._http is None:
- self._http = self._get_http()
- return self._http
+class ImagesClient(rest_client.RestClient):
+ api_version = "v2"
def update_image(self, image_id, patch):
"""Update an image.
@@ -51,7 +36,7 @@
data = json.dumps(patch)
headers = {"Content-Type": "application/openstack-images-v2.0"
"-json-patch"}
- resp, body = self.patch('v2/images/%s' % image_id, data, headers)
+ resp, body = self.patch('images/%s' % image_id, data, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
@@ -63,31 +48,31 @@
api-ref-image-v2.html#createImage-v2
"""
data = json.dumps(kwargs)
- resp, body = self.post('v2/images', data)
+ resp, body = self.post('images', data)
self.expected_success(201, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def deactivate_image(self, image_id):
- url = 'v2/images/%s/actions/deactivate' % image_id
+ url = 'images/%s/actions/deactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
def reactivate_image(self, image_id):
- url = 'v2/images/%s/actions/reactivate' % image_id
+ url = 'images/%s/actions/reactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
- url = 'v2/images/%s' % image_id
+ url = 'images/%s' % image_id
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
def list_images(self, params=None):
- url = 'v2/images'
+ url = 'images'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -98,7 +83,7 @@
return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
- url = 'v2/images/%s' % image_id
+ url = 'images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
@@ -117,128 +102,32 @@
return 'image'
def store_image_file(self, image_id, data):
- url = 'v2/images/%s/file' % image_id
+ url = 'images/%s/file' % image_id
+
+ # We are going to do chunked transfert, so split the input data
+ # info fixed-sized chunks.
headers = {'Content-Type': 'application/octet-stream'}
- resp, body = self.http.raw_request('PUT', url, headers=headers,
- body=data)
+ data = iter(functools.partial(data.read, CHUNKSIZE), b'')
+
+ resp, body = self.request('PUT', url, headers=headers,
+ body=data, chunked=True)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
- url = 'v2/images/%s/file' % image_id
+ url = 'images/%s/file' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
- url = 'v2/images/%s/tags/%s' % (image_id, tag)
+ url = 'images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
self.expected_success(204, resp.status)
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 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 rest_client.ResponseBody(resp, body)
-
- def create_image_member(self, image_id, **kwargs):
- """Create an image member.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createImageMember-v2
- """
- url = 'v2/images/%s/members' % image_id
- data = json.dumps(kwargs)
- resp, body = self.post(url, data)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_image_member(self, image_id, member_id, **kwargs):
- """Update an image member.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateImageMember-v2
- """
- url = 'v2/images/%s/members/%s' % (image_id, member_id)
- data = json.dumps(kwargs)
- resp, body = self.put(url, data)
- self.expected_success(200, resp.status)
- body = json.loads(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 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 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 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 rest_client.ResponseBody(resp, body)
-
- def create_namespace(self, **kwargs):
- """Create a namespace.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createNamespace-v2
- """
- data = json.dumps(kwargs)
- resp, body = self.post('/v2/metadefs/namespaces', data)
- self.expected_success(201, resp.status)
- body = json.loads(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 rest_client.ResponseBody(resp, body)
-
- def update_namespace(self, namespace, **kwargs):
- """Update a namespace.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateNamespace-v2
- """
- # NOTE: On Glance API, we need to pass namespace on both URI
- # and a request body.
- params = {'namespace': namespace}
- params.update(kwargs)
- data = json.dumps(params)
- url = '/v2/metadefs/namespaces/%s' % namespace
- resp, body = self.put(url, body=data)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_namespace(self, namespace):
- url = '/v2/metadefs/namespaces/%s' % namespace
+ url = 'images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
diff --git a/tempest/services/network/json/__init__.py b/tempest/services/network/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/network/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/network/json/routers_client.py b/tempest/services/network/json/routers_client.py
deleted file mode 100644
index 725dd76..0000000
--- a/tempest/services/network/json/routers_client.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.lib.services.network import base
-
-
-class RoutersClient(base.BaseNetworkClient):
-
- def create_router(self, **kwargs):
- """Create a router.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#createRouter
- """
- post_body = {'router': kwargs}
- uri = '/routers'
- return self.create_resource(uri, post_body)
-
- def _update_router(self, router_id, set_enable_snat, **kwargs):
- uri = '/routers/%s' % router_id
- body = self.show_resource(uri)
- update_body = {}
- update_body['name'] = kwargs.get('name', body['router']['name'])
- update_body['admin_state_up'] = kwargs.get(
- 'admin_state_up', body['router']['admin_state_up'])
- cur_gw_info = body['router']['external_gateway_info']
- if cur_gw_info:
- # TODO(kevinbenton): setting the external gateway info is not
- # allowed for a regular tenant. If the ability to update is also
- # merged, a test case for this will need to be added similar to
- # the SNAT case.
- cur_gw_info.pop('external_fixed_ips', None)
- if not set_enable_snat:
- cur_gw_info.pop('enable_snat', None)
- update_body['external_gateway_info'] = kwargs.get(
- 'external_gateway_info', body['router']['external_gateway_info'])
- if 'distributed' in kwargs:
- update_body['distributed'] = kwargs['distributed']
- update_body = dict(router=update_body)
- return self.update_resource(uri, update_body)
-
- def update_router(self, router_id, **kwargs):
- """Update a router leaving enable_snat to its default value."""
- # If external_gateway_info contains enable_snat the request will fail
- # with 404 unless executed with admin client, and therefore we instruct
- # _update_router to not set this attribute
- # NOTE(salv-orlando): The above applies as long as Neutron's default
- # policy is to restrict enable_snat usage to admins only.
- return self._update_router(router_id, set_enable_snat=False, **kwargs)
-
- def show_router(self, router_id, **fields):
- uri = '/routers/%s' % router_id
- return self.show_resource(uri, **fields)
-
- def delete_router(self, router_id):
- uri = '/routers/%s' % router_id
- return self.delete_resource(uri)
-
- def list_routers(self, **filters):
- uri = '/routers'
- return self.list_resources(uri, **filters)
-
- def update_extra_routes(self, router_id, **kwargs):
- """Update Extra routes.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#updateExtraRoutes
- """
- uri = '/routers/%s' % router_id
- put_body = {'router': kwargs}
- return self.update_resource(uri, put_body)
-
- def delete_extra_routes(self, router_id):
- uri = '/routers/%s' % router_id
- put_body = {
- 'router': {
- 'routes': None
- }
- }
- return self.update_resource(uri, put_body)
-
- def update_router_with_snat_gw_info(self, router_id, **kwargs):
- """Update a router passing also the enable_snat attribute.
-
- This method must be execute with admin credentials, otherwise the API
- call will return a 404 error.
- """
- return self._update_router(router_id, set_enable_snat=True, **kwargs)
-
- def add_router_interface(self, router_id, **kwargs):
- """Add router interface.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#addRouterInterface
- """
- uri = '/routers/%s/add_router_interface' % router_id
- return self.update_resource(uri, kwargs)
-
- def remove_router_interface(self, router_id, **kwargs):
- """Remove router interface.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#removeRouterInterface
- """
- uri = '/routers/%s/remove_router_interface' % router_id
- return self.update_resource(uri, kwargs)
-
- def list_l3_agents_hosting_router(self, router_id):
- uri = '/routers/%s/l3-agents' % router_id
- return self.list_resources(uri)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index fa43d94..33dba6e 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -149,25 +149,30 @@
self.expected_success(201, resp.status)
return resp, body
- def put_object_with_chunk(self, container, name, contents, chunk_size):
- """Put an object with Transfer-Encoding header"""
+ def put_object_with_chunk(self, container, name, contents):
+ """Put an object with Transfer-Encoding header
+
+ :param container: name of the container
+ :type container: string
+ :param name: name of the object
+ :type name: string
+ :param contents: object data
+ :type contents: iterable
+ """
headers = {'Transfer-Encoding': 'chunked'}
if self.token:
headers['X-Auth-Token'] = self.token
- conn = put_object_connection(self.base_url, container, name, contents,
- chunk_size, headers)
-
- resp = conn.getresponse()
- body = resp.read()
-
- resp_headers = {}
- for header, value in resp.getheaders():
- resp_headers[header.lower()] = value
+ url = "%s/%s" % (container, name)
+ resp, body = self.put(
+ url, headers=headers,
+ body=contents,
+ chunked=True
+ )
self._error_checker('PUT', None, headers, contents, resp, body)
self.expected_success(201, resp.status)
- return resp.status, resp.reason, resp_headers
+ return resp.status, resp.reason, resp
def create_object_continue(self, container, object_name,
data, metadata=None):
@@ -262,30 +267,7 @@
headers = dict(headers)
else:
headers = {}
- if hasattr(contents, 'read'):
- conn.putrequest('PUT', path)
- for header, value in six.iteritems(headers):
- conn.putheader(header, value)
- if 'Content-Length' not in headers:
- if 'Transfer-Encoding' not in headers:
- conn.putheader('Transfer-Encoding', 'chunked')
- conn.endheaders()
- chunk = contents.read(chunk_size)
- while chunk:
- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- chunk = contents.read(chunk_size)
- conn.send('0\r\n\r\n')
- else:
- conn.endheaders()
- left = headers['Content-Length']
- while left > 0:
- size = chunk_size
- if size > left:
- size = left
- chunk = contents.read(size)
- conn.send(chunk)
- left -= len(chunk)
- else:
- conn.request('PUT', path, contents, headers)
+
+ conn.request('PUT', path, contents, headers)
return conn
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 382b851..2beaaa9 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -135,10 +135,13 @@
break
if skip:
break
+ # TODO(andreaf) This has to be reworked to use the credential
+ # provider interface. For now only tests marked as 'use_admin' will
+ # work.
if test.get('use_admin', False):
manager = admin_manager
else:
- manager = credentials.ConfiguredUserManager()
+ raise NotImplemented('Non admin tests are not supported')
for p_number in moves.xrange(test.get('threads', default_thread_num)):
if test.get('use_isolated_tenants', False):
username = data_utils.rand_name("stress_user")
diff --git a/tempest/test.py b/tempest/test.py
index aefe1a9..d31c509 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -77,7 +77,6 @@
'network': True,
'identity': True,
'object_storage': CONF.service_available.swift,
- 'dashboard': CONF.service_available.horizon,
'data_processing': CONF.service_available.sahara,
'database': CONF.service_available.trove
}
@@ -92,8 +91,8 @@
"""
def decorator(f):
services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
- 'network', 'identity', 'object_storage', 'dashboard',
- 'data_processing', 'database']
+ 'network', 'identity', 'object_storage', 'data_processing',
+ 'database']
for service in args:
if service not in services:
raise exceptions.InvalidServiceTag('%s is not a valid '
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
new file mode 100644
index 0000000..c4bd7b2
--- /dev/null
+++ b/tempest/tests/cmd/test_workspace.py
@@ -0,0 +1,124 @@
+# Copyright 2016 Rackspace
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import shutil
+import subprocess
+import tempfile
+
+from tempest.cmd.workspace import WorkspaceManager
+from tempest.lib.common.utils import data_utils
+from tempest.tests import base
+
+
+class TestTempestWorkspaceBase(base.TestCase):
+ def setUp(self):
+ super(TestTempestWorkspaceBase, self).setUp()
+ self.name = data_utils.rand_uuid()
+ self.path = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+ store_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+ self.store_file = os.path.join(store_dir, 'workspace.yaml')
+ self.workspace_manager = WorkspaceManager(path=self.store_file)
+ self.workspace_manager.register_new_workspace(self.name, self.path)
+
+
+class TestTempestWorkspace(TestTempestWorkspaceBase):
+ def _run_cmd_gets_return_code(self, cmd, expected):
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = process.communicate()
+ return_code = process.returncode
+ msg = ("%s failled with:\nstdout: %s\nstderr: %s" % (' '.join(cmd),
+ stdout, stderr))
+ self.assertEqual(return_code, expected, msg)
+
+ def test_run_workspace_list(self):
+ cmd = ['tempest', 'workspace', '--workspace-path',
+ self.store_file, 'list']
+ self._run_cmd_gets_return_code(cmd, 0)
+
+ def test_run_workspace_register(self):
+ name = data_utils.rand_uuid()
+ path = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, path, ignore_errors=True)
+ cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+ 'register', '--name', name, '--path', path]
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertIsNotNone(self.workspace_manager.get_workspace(name))
+
+ def test_run_workspace_rename(self):
+ new_name = data_utils.rand_uuid()
+ cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+ 'rename', "--old-name", self.name, '--new-name', new_name]
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+ self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
+ def test_run_workspace_move(self):
+ new_path = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+ cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+ 'move', '--name', self.name, '--path', new_path]
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertEqual(
+ self.workspace_manager.get_workspace(self.name), new_path)
+
+ def test_run_workspace_remove(self):
+ cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+ 'remove', '--name', self.name]
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+
+class TestTempestWorkspaceManager(TestTempestWorkspaceBase):
+ def setUp(self):
+ super(TestTempestWorkspaceManager, self).setUp()
+ self.name = data_utils.rand_uuid()
+ self.path = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+ store_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+ self.store_file = os.path.join(store_dir, 'workspace.yaml')
+ self.workspace_manager = WorkspaceManager(path=self.store_file)
+ self.workspace_manager.register_new_workspace(self.name, self.path)
+
+ def test_workspace_manager_get(self):
+ self.assertIsNotNone(self.workspace_manager.get_workspace(self.name))
+
+ def test_workspace_manager_rename(self):
+ new_name = data_utils.rand_uuid()
+ self.workspace_manager.rename_workspace(self.name, new_name)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+ self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
+ def test_workspace_manager_move(self):
+ new_path = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+ self.workspace_manager.move_workspace(self.name, new_path)
+ self.assertEqual(
+ self.workspace_manager.get_workspace(self.name), new_path)
+
+ def test_workspace_manager_remove(self):
+ self.workspace_manager.remove_workspace(self.name)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+ def test_path_expansion(self):
+ name = data_utils.rand_uuid()
+ path = os.path.join("~", name)
+ os.makedirs(os.path.expanduser(path))
+ self.addCleanup(shutil.rmtree, path, ignore_errors=True)
+ self.workspace_manager.register_new_workspace(name, path)
+ self.assertIsNotNone(self.workspace_manager.get_workspace(name))
diff --git a/tempest/tests/common/test_alt_available.py b/tempest/tests/common/test_alt_available.py
index d4cfab6..27db95c 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -49,28 +49,6 @@
else:
self.useFixture(mockpatch.Patch('os.path.isfile',
return_value=False))
- cred_prefix = ['', 'alt_']
- for ii in range(0, 2):
- if len(creds) > ii:
- username = 'u%s' % creds[ii]
- project = 't%s' % creds[ii]
- password = 'p'
- domain = 'd'
- else:
- username = None
- project = None
- password = None
- domain = None
-
- cfg.CONF.set_default('%susername' % cred_prefix[ii], username,
- 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,
- group='identity')
-
expected = len(set(creds)) > 1 or dynamic_creds
observed = credentials.is_alt_available(
identity_version=self.identity_version)
@@ -97,21 +75,6 @@
use_accounts_file=True,
creds=['1', '1'])
- def test__no_dynamic_creds__no_accounts_file__one_user(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '2'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users_identical(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '1'])
-
class TestAltAvailableV3(TestAltAvailable):
diff --git a/tempest/tests/common/test_configured_creds.py b/tempest/tests/common/test_configured_creds.py
deleted file mode 100644
index 3c242b3..0000000
--- a/tempest/tests/common/test_configured_creds.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_config import cfg
-
-from tempest.common import credentials_factory as common_creds
-from tempest.common import tempest_fixtures as fixtures
-from tempest import config
-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 base
-from tempest.tests import fake_config
-from tempest.tests.lib import fake_identity
-
-
-class ConfiguredV2CredentialsTests(base.TestCase):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'tenant_name': 'fake_tenant_name'
- }
-
- identity_response = fake_identity._fake_v2_response
- credentials_class = auth.KeystoneV2Credentials
- tokenclient_class = v2_client.TokenClient
- identity_version = 'v2'
-
- def setUp(self):
- super(ConfiguredV2CredentialsTests, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.patchobject(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
- self.patchobject(self.tokenclient_class, 'raw_request',
- self.identity_response)
-
- def _get_credentials(self, attributes=None):
- if attributes is None:
- attributes = self.attributes
- return self.credentials_class(**attributes)
-
- def _check(self, credentials, credentials_class, filled):
- # Check the right version of credentials has been returned
- self.assertIsInstance(credentials, credentials_class)
- # Check the id attributes are filled in
- attributes = [x for x in credentials.ATTRIBUTES if (
- '_id' in x and x != 'domain_id')]
- for attr in attributes:
- if filled:
- self.assertIsNotNone(getattr(credentials, attr))
- else:
- self.assertIsNone(getattr(credentials, attr))
-
- def _verify_credentials(self, credentials_class, filled=True,
- identity_version=None):
- for ctype in common_creds.CREDENTIAL_TYPES:
- if identity_version is None:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled)
- else:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled,
- identity_version=identity_version)
- self._check(creds, credentials_class, filled)
-
- def test_create(self):
- creds = self._get_credentials()
- self.assertEqual(self.attributes, creds._initial)
-
- def test_create_invalid_attr(self):
- self.assertRaises(lib_exc.InvalidCredentials,
- self._get_credentials,
- attributes=dict(invalid='fake'))
-
- def test_get_configured_credentials(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class)
-
- def test_get_configured_credentials_unfilled(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- filled=False)
-
- def test_get_configured_credentials_version(self):
- # version specified and not loaded from config
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- identity_version=self.identity_version)
-
- def test_is_valid(self):
- creds = self._get_credentials()
- self.assertTrue(creds.is_valid())
-
-
-class ConfiguredV3CredentialsTests(ConfiguredV2CredentialsTests):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'project_name': 'fake_project_name',
- 'user_domain_name': 'fake_domain_name'
- }
-
- credentials_class = auth.KeystoneV3Credentials
- identity_response = fake_identity._fake_v3_response
- tokenclient_class = v3_client.V3TokenClient
- identity_version = 'v3'
-
- def setUp(self):
- super(ConfiguredV3CredentialsTests, self).setUp()
- # Additional config items reset by cfg fixture after each test
- cfg.CONF.set_default('auth_version', 'v3', group='identity')
- # Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- cfg.CONF.set_default(prefix + 'domain_name', 'fake_domain_name',
- group=group)
diff --git a/tempest/tests/common/test_credentials.py b/tempest/tests/common/test_credentials.py
deleted file mode 100644
index 00f2d39..0000000
--- a/tempest/tests/common/test_credentials.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.common import credentials_factory as credentials
-from tempest import config
-from tempest import exceptions
-from tempest.tests import base
-from tempest.tests import fake_config
-
-
-class TestLegacyCredentialsProvider(base.TestCase):
-
- fixed_params = {'identity_version': 'v2'}
-
- def setUp(self):
- super(TestLegacyCredentialsProvider, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.patchobject(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
-
- def test_get_creds_roles_legacy_invalid(self):
- test_accounts_class = credentials.LegacyCredentialProvider(
- **self.fixed_params)
- self.assertRaises(exceptions.InvalidConfiguration,
- test_accounts_class.get_creds_by_roles,
- ['fake_role'])
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 8d4f33b..a7a3a22 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -21,16 +21,20 @@
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 \
- json_roles_client
+from tempest.lib.services.identity.v2 import token_client as v2_token_client
+from tempest.lib.services.identity.v3 import token_client as v3_token_client
+from tempest.lib.services.network import routers_client
+from tempest.services.identity.v2.json import identity_client as v2_iden_client
+from tempest.services.identity.v2.json import roles_client as v2_roles_client
from tempest.services.identity.v2.json import tenants_client as \
- json_tenants_client
-from tempest.services.identity.v2.json import users_client as \
- json_users_client
-from tempest.services.network.json import routers_client
+ v2_tenants_client
+from tempest.services.identity.v2.json import users_client as v2_users_client
+from tempest.services.identity.v3.json import domains_client
+from tempest.services.identity.v3.json import identity_client as v3_iden_client
+from tempest.services.identity.v3.json import projects_client as \
+ v3_projects_client
+from tempest.services.identity.v3.json import roles_client as v3_roles_client
+from tempest.services.identity.v3.json import users_clients as v3_users_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests.lib import fake_http
@@ -43,13 +47,24 @@
'identity_version': 'v2',
'admin_role': 'admin'}
+ token_client = v2_token_client
+ iden_client = v2_iden_client
+ roles_client = v2_roles_client
+ tenants_client = v2_tenants_client
+ users_client = v2_users_client
+ token_client_class = token_client.TokenClient
+ fake_response = fake_identity._fake_v2_response
+ assign_role_on_project = 'assign_user_role'
+ tenants_client_class = tenants_client.TenantsClient
+ delete_tenant = 'delete_tenant'
+
def setUp(self):
super(TestDynamicCredentialProvider, self).setUp()
self.useFixture(fake_config.ConfigFixture())
self.patchobject(config, 'TempestConfigPrivate',
fake_config.FakePrivate)
- self.patchobject(json_token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
+ self.patchobject(self.token_client_class, 'raw_request',
+ self.fake_response)
cfg.CONF.set_default('operator_role', 'FakeRole',
group='object-storage')
self._mock_list_ec2_credentials('fake_user_id', 'fake_tenant_id')
@@ -59,7 +74,7 @@
def test_tempest_client(self):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self.assertIsInstance(creds.identity_admin_client,
- json_iden_client.IdentityClient)
+ self.iden_client.IdentityClient)
def _get_fake_admin_creds(self):
return credentials.get_credentials(
@@ -70,7 +85,7 @@
def _mock_user_create(self, id, name):
user_fix = self.useFixture(mockpatch.PatchObject(
- json_users_client.UsersClient,
+ self.users_client.UsersClient,
'create_user',
return_value=(rest_client.ResponseBody
(200, {'user': {'id': id, 'name': name}}))))
@@ -78,7 +93,7 @@
def _mock_tenant_create(self, id, name):
tenant_fix = self.useFixture(mockpatch.PatchObject(
- json_tenants_client.TenantsClient,
+ self.tenants_client.TenantsClient,
'create_tenant',
return_value=(rest_client.ResponseBody
(200, {'tenant': {'id': id, 'name': name}}))))
@@ -86,7 +101,7 @@
def _mock_list_roles(self, id, name):
roles_fix = self.useFixture(mockpatch.PatchObject(
- json_roles_client.RolesClient,
+ self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
(200,
@@ -97,7 +112,7 @@
def _mock_list_2_roles(self):
roles_fix = self.useFixture(mockpatch.PatchObject(
- json_roles_client.RolesClient,
+ self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
(200,
@@ -108,24 +123,25 @@
def _mock_assign_user_role(self):
tenant_fix = self.useFixture(mockpatch.PatchObject(
- json_roles_client.RolesClient,
- 'assign_user_role',
+ self.roles_client.RolesClient,
+ self.assign_role_on_project,
return_value=(rest_client.ResponseBody
(200, {}))))
return tenant_fix
def _mock_list_role(self):
roles_fix = self.useFixture(mockpatch.PatchObject(
- json_roles_client.RolesClient,
+ self.roles_client.RolesClient,
'list_roles',
return_value=(rest_client.ResponseBody
- (200, {'roles': [{'id': '1',
- 'name': 'FakeRole'}]}))))
+ (200, {'roles': [
+ {'id': '1', 'name': 'FakeRole'},
+ {'id': '2', 'name': 'Member'}]}))))
return roles_fix
def _mock_list_ec2_credentials(self, user_id, tenant_id):
ec2_creds_fix = self.useFixture(mockpatch.PatchObject(
- json_users_client.UsersClient,
+ self.users_client.UsersClient,
'list_user_ec2_credentials',
return_value=(rest_client.ResponseBody
(200, {'credentials': [{
@@ -180,12 +196,12 @@
self._mock_user_create('1234', 'fake_admin_user')
self._mock_tenant_create('1234', 'fake_admin_tenant')
- user_mock = mock.patch.object(json_roles_client.RolesClient,
- 'assign_user_role')
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ self.assign_role_on_project)
user_mock.start()
self.addCleanup(user_mock.stop)
- with mock.patch.object(json_roles_client.RolesClient,
- 'assign_user_role') as user_mock:
+ with mock.patch.object(self.roles_client.RolesClient,
+ self.assign_role_on_project) as user_mock:
admin_creds = creds.get_admin_creds()
user_mock.assert_has_calls([
mock.call('1234', '1234', '1234')])
@@ -203,12 +219,12 @@
self._mock_user_create('1234', 'fake_role_user')
self._mock_tenant_create('1234', 'fake_role_tenant')
- user_mock = mock.patch.object(json_roles_client.RolesClient,
- 'assign_user_role')
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ self.assign_role_on_project)
user_mock.start()
self.addCleanup(user_mock.stop)
- with mock.patch.object(json_roles_client.RolesClient,
- 'assign_user_role') as user_mock:
+ with mock.patch.object(self.roles_client.RolesClient,
+ self.assign_role_on_project) as user_mock:
role_creds = creds.get_creds_by_roles(
roles=['role1', 'role2'])
calls = user_mock.mock_calls
@@ -240,12 +256,10 @@
self._mock_user_create('123456', 'fake_admin_user')
self._mock_list_roles('123456', 'admin')
creds.get_admin_creds()
- user_mock = self.patch(
- 'tempest.services.identity.v2.json.users_client.'
- 'UsersClient.delete_user')
- tenant_mock = self.patch(
- 'tempest.services.identity.v2.json.tenants_client.'
- 'TenantsClient.delete_tenant')
+ user_mock = self.patchobject(self.users_client.UsersClient,
+ 'delete_user')
+ tenant_mock = self.patchobject(self.tenants_client_class,
+ self.delete_tenant)
creds.clear_creds()
# Verify user delete calls
calls = user_mock.mock_calls
@@ -319,7 +333,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.routers_client.RoutersClient.'
+ 'tempest.lib.services.network.routers_client.RoutersClient.'
'add_router_interface')
primary_creds = creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -351,7 +365,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.routers_client.RoutersClient.'
+ 'tempest.lib.services.network.routers_client.RoutersClient.'
'add_router_interface')
creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -374,21 +388,16 @@
self._mock_router_create('123456', 'fake_admin_router')
self._mock_list_roles('123456', 'admin')
creds.get_admin_creds()
- self.patch('tempest.services.identity.v2.json.users_client.'
- 'UsersClient.delete_user')
- self.patch('tempest.services.identity.v2.json.tenants_client.'
- 'TenantsClient.delete_tenant')
- net = mock.patch.object(creds.networks_admin_client,
- 'delete_network')
+ self.patchobject(self.users_client.UsersClient, 'delete_user')
+ self.patchobject(self.tenants_client_class, self.delete_tenant)
+ net = mock.patch.object(creds.networks_admin_client, 'delete_network')
net_mock = net.start()
- subnet = mock.patch.object(creds.subnets_admin_client,
- 'delete_subnet')
+ subnet = mock.patch.object(creds.subnets_admin_client, 'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.routers_admin_client,
- 'delete_router')
+ router = mock.patch.object(creds.routers_admin_client, 'delete_router')
router_mock = router.start()
remove_router_interface_mock = self.patch(
- 'tempest.services.network.json.routers_client.RoutersClient.'
+ 'tempest.lib.services.network.routers_client.RoutersClient.'
'remove_router_interface')
return_values = ({'status': 200}, {'ports': []})
port_list_mock = mock.patch.object(creds.ports_admin_client,
@@ -459,7 +468,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.routers_client.RoutersClient.'
+ 'tempest.lib.services.network.routers_client.RoutersClient.'
'add_router_interface')
alt_creds = creds.get_alt_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -483,7 +492,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.routers_client.RoutersClient.'
+ 'tempest.lib.services.network.routers_client.RoutersClient.'
'add_router_interface')
self._mock_list_roles('123456', 'admin')
admin_creds = creds.get_admin_creds()
@@ -587,3 +596,42 @@
self._mock_tenant_create('1234', 'fake_prim_tenant')
self.assertRaises(exceptions.InvalidConfiguration,
creds.get_primary_creds)
+
+
+class TestDynamicCredentialProviderV3(TestDynamicCredentialProvider):
+
+ fixed_params = {'name': 'test class',
+ 'identity_version': 'v3',
+ 'admin_role': 'admin'}
+
+ token_client = v3_token_client
+ iden_client = v3_iden_client
+ roles_client = v3_roles_client
+ tenants_client = v3_projects_client
+ users_client = v3_users_client
+ token_client_class = token_client.V3TokenClient
+ fake_response = fake_identity._fake_v3_response
+ assign_role_on_project = 'assign_user_role_on_project'
+ tenants_client_class = tenants_client.ProjectsClient
+ delete_tenant = 'delete_project'
+
+ def setUp(self):
+ super(TestDynamicCredentialProviderV3, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.useFixture(mockpatch.PatchObject(
+ domains_client.DomainsClient, 'list_domains',
+ return_value=dict(domains=[dict(id='default',
+ name='Default')])))
+ self.patchobject(self.roles_client.RolesClient,
+ 'assign_user_role_on_domain')
+
+ def _mock_list_ec2_credentials(self, user_id, tenant_id):
+ pass
+
+ def _mock_tenant_create(self, id, name):
+ project_fix = self.useFixture(mockpatch.PatchObject(
+ self.tenants_client.ProjectsClient,
+ 'create_project',
+ return_value=(rest_client.ResponseBody
+ (200, {'project': {'id': id, 'name': name}}))))
+ return project_fix
diff --git a/tempest/tests/common/test_preprov_creds.py b/tempest/tests/common/test_preprov_creds.py
index b595c88..13d4713 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/common/test_preprov_creds.py
@@ -14,6 +14,7 @@
import hashlib
import os
+import testtools
import mock
from oslo_concurrency.fixture import lockutils as lockutils_fixtures
@@ -27,7 +28,6 @@
from tempest import config
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 base
from tempest.tests import fake_config
from tempest.tests.lib import fake_identity
@@ -43,40 +43,48 @@
'object_storage_operator_role': 'operator',
'object_storage_reseller_admin_role': 'reseller'}
+ identity_response = fake_identity._fake_v2_response
+ token_client = ('tempest.lib.services.identity.v2.token_client'
+ '.TokenClient.raw_request')
+
+ @classmethod
+ def _fake_accounts(cls, admin_role):
+ return [
+ {'username': 'test_user1', 'tenant_name': 'test_tenant1',
+ 'password': 'p'},
+ {'username': 'test_user2', 'project_name': 'test_tenant2',
+ 'password': 'p'},
+ {'username': 'test_user3', 'tenant_name': 'test_tenant3',
+ 'password': 'p'},
+ {'username': 'test_user4', 'project_name': 'test_tenant4',
+ 'password': 'p'},
+ {'username': 'test_user5', 'tenant_name': 'test_tenant5',
+ 'password': 'p'},
+ {'username': 'test_user6', 'project_name': 'test_tenant6',
+ 'password': 'p', 'roles': ['role1', 'role2']},
+ {'username': 'test_user7', 'tenant_name': 'test_tenant7',
+ 'password': 'p', 'roles': ['role2', 'role3']},
+ {'username': 'test_user8', 'project_name': 'test_tenant8',
+ 'password': 'p', 'roles': ['role4', 'role1']},
+ {'username': 'test_user9', 'tenant_name': 'test_tenant9',
+ 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_user10', 'project_name': 'test_tenant10',
+ 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_admin1', 'tenant_name': 'test_tenant11',
+ 'password': 'p', 'roles': [admin_role]},
+ {'username': 'test_admin2', 'project_name': 'test_tenant12',
+ 'password': 'p', 'roles': [admin_role]},
+ {'username': 'test_admin3', 'project_name': 'test_tenant13',
+ 'password': 'p', 'types': ['admin']}]
+
def setUp(self):
super(TestPreProvisionedCredentials, self).setUp()
self.useFixture(fake_config.ConfigFixture())
self.patchobject(config, 'TempestConfigPrivate',
fake_config.FakePrivate)
- self.patchobject(token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
+ self.patch(self.token_client, side_effect=self.identity_response)
self.useFixture(lockutils_fixtures.ExternalLockFixture())
- self.test_accounts = [
- {'username': 'test_user1', 'tenant_name': 'test_tenant1',
- 'password': 'p'},
- {'username': 'test_user2', 'tenant_name': 'test_tenant2',
- 'password': 'p'},
- {'username': 'test_user3', 'tenant_name': 'test_tenant3',
- 'password': 'p'},
- {'username': 'test_user4', 'tenant_name': 'test_tenant4',
- 'password': 'p'},
- {'username': 'test_user5', 'tenant_name': 'test_tenant5',
- 'password': 'p'},
- {'username': 'test_user6', 'tenant_name': 'test_tenant6',
- 'password': 'p', 'roles': ['role1', 'role2']},
- {'username': 'test_user7', 'tenant_name': 'test_tenant7',
- 'password': 'p', 'roles': ['role2', 'role3']},
- {'username': 'test_user8', 'tenant_name': 'test_tenant8',
- 'password': 'p', 'roles': ['role4', 'role1']},
- {'username': 'test_user9', 'tenant_name': 'test_tenant9',
- 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
- {'username': 'test_user10', 'tenant_name': 'test_tenant10',
- 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
- {'username': 'test_user11', 'tenant_name': 'test_tenant11',
- 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
- {'username': 'test_user12', 'tenant_name': 'test_tenant12',
- 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
- ]
+ self.test_accounts = self._fake_accounts(cfg.CONF.identity.admin_role)
self.accounts_mock = self.useFixture(mockpatch.Patch(
'tempest.common.preprov_creds.read_accounts_yaml',
return_value=self.test_accounts))
@@ -89,24 +97,33 @@
def _get_hash_list(self, accounts_list):
hash_list = []
+ hash_fields = (
+ preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
for account in accounts_list:
hash = hashlib.md5()
- hash.update(six.text_type(account).encode('utf-8'))
+ account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
+ if k in hash_fields)
+ hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash = hash.hexdigest()
hash_list.append(temp_hash)
return hash_list
def test_get_hash(self):
- self.patchobject(token_client.TokenClient, 'raw_request',
- fake_identity._fake_v2_response)
- test_account_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- hash_list = self._get_hash_list(self.test_accounts)
- test_cred_dict = self.test_accounts[3]
- test_creds = auth.get_credentials(fake_identity.FAKE_AUTH_URL,
- **test_cred_dict)
- results = test_account_class.get_hash(test_creds)
- self.assertEqual(hash_list[3], results)
+ # Test with all accounts to make sure we try all combinations
+ # and hide no race conditions
+ hash_index = 0
+ for test_cred_dict in self.test_accounts:
+ test_account_class = (
+ preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params))
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_creds = auth.get_credentials(
+ fake_identity.FAKE_AUTH_URL,
+ identity_version=self.fixed_params['identity_version'],
+ **test_cred_dict)
+ results = test_account_class.get_hash(test_creds)
+ self.assertEqual(hash_list[hash_index], results)
+ hash_index += 1
def test_get_hash_dict(self):
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
@@ -248,9 +265,6 @@
self.assertFalse(test_accounts_class.is_multi_user())
def test__get_creds_by_roles_one_role(self):
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
- return_value=self.test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
hashes = test_accounts_class.hash_dict['roles']['role4']
@@ -266,9 +280,6 @@
self.assertIn(i, args)
def test__get_creds_by_roles_list_role(self):
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
- return_value=self.test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
hashes = test_accounts_class.hash_dict['roles']['role4']
@@ -286,9 +297,6 @@
self.assertIn(i, args)
def test__get_creds_by_roles_no_admin(self):
- self.useFixture(mockpatch.Patch(
- 'tempest.common.preprov_creds.read_accounts_yaml',
- return_value=self.test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
hashes = list(test_accounts_class.hash_dict['creds'].keys())
@@ -331,3 +339,135 @@
self.assertIn('id', network)
self.assertEqual('fake-id', network['id'])
self.assertEqual('network-2', network['name'])
+
+ def test_get_primary_creds(self):
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ primary_creds = test_accounts_class.get_primary_creds()
+ self.assertNotIn('test_admin', primary_creds.username)
+
+ def test_get_primary_creds_none_available(self):
+ admin_accounts = [x for x in self.test_accounts if 'test_admin'
+ in x['username']]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.preprov_creds.read_accounts_yaml',
+ return_value=admin_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ with testtools.ExpectedException(lib_exc.InvalidCredentials):
+ # Get one more
+ test_accounts_class.get_primary_creds()
+
+ def test_get_alt_creds(self):
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ alt_creds = test_accounts_class.get_alt_creds()
+ self.assertNotIn('test_admin', alt_creds.username)
+
+ def test_get_alt_creds_none_available(self):
+ admin_accounts = [x for x in self.test_accounts if 'test_admin'
+ in x['username']]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.preprov_creds.read_accounts_yaml',
+ return_value=admin_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ with testtools.ExpectedException(lib_exc.InvalidCredentials):
+ # Get one more
+ test_accounts_class.get_alt_creds()
+
+ def test_get_admin_creds(self):
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ admin_creds = test_accounts_class.get_admin_creds()
+ self.assertIn('test_admin', admin_creds.username)
+
+ def test_get_admin_creds_by_type(self):
+ test_accounts = [
+ {'username': 'test_user10', 'project_name': 'test_tenant10',
+ 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_admin1', 'tenant_name': 'test_tenant11',
+ 'password': 'p', 'types': ['admin']}]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ admin_creds = test_accounts_class.get_admin_creds()
+ self.assertIn('test_admin', admin_creds.username)
+
+ def test_get_admin_creds_by_role(self):
+ test_accounts = [
+ {'username': 'test_user10', 'project_name': 'test_tenant10',
+ 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_admin1', 'tenant_name': 'test_tenant11',
+ 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]}]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ admin_creds = test_accounts_class.get_admin_creds()
+ self.assertIn('test_admin', admin_creds.username)
+
+ def test_get_admin_creds_none_available(self):
+ non_admin_accounts = [x for x in self.test_accounts if 'test_admin'
+ not in x['username']]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.preprov_creds.read_accounts_yaml',
+ return_value=non_admin_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ with testtools.ExpectedException(lib_exc.InvalidCredentials):
+ # Get one more
+ test_accounts_class.get_admin_creds()
+
+
+class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials):
+
+ fixed_params = {'name': 'test class',
+ 'identity_version': 'v3',
+ 'test_accounts_file': 'fake_accounts_file',
+ 'accounts_lock_dir': 'fake_locks_dir_v3',
+ 'admin_role': 'admin',
+ 'object_storage_operator_role': 'operator',
+ 'object_storage_reseller_admin_role': 'reseller'}
+
+ identity_response = fake_identity._fake_v3_response
+ token_client = ('tempest.lib.services.identity.v3.token_client'
+ '.V3TokenClient.raw_request')
+
+ @classmethod
+ def _fake_accounts(cls, admin_role):
+ return [
+ {'username': 'test_user1', 'project_name': 'test_project1',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user2', 'project_name': 'test_project2',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user3', 'project_name': 'test_project3',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user4', 'project_name': 'test_project4',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user5', 'project_name': 'test_project5',
+ 'domain_name': 'domain', 'password': 'p'},
+ {'username': 'test_user6', 'project_name': 'test_project6',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2']},
+ {'username': 'test_user7', 'project_name': 'test_project7',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role2', 'role3']},
+ {'username': 'test_user8', 'project_name': 'test_project8',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role4', 'role1']},
+ {'username': 'test_user9', 'project_name': 'test_project9',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_user10', 'project_name': 'test_project10',
+ 'domain_name': 'domain', 'password': 'p',
+ 'roles': ['role1', 'role2', 'role3', 'role4']},
+ {'username': 'test_admin1', 'project_name': 'test_project11',
+ 'domain_name': 'domain', 'password': 'p', 'roles': [admin_role]},
+ {'username': 'test_admin2', 'project_name': 'test_project12',
+ 'domain_name': 'domain', 'password': 'p', 'roles': [admin_role]},
+ {'username': 'test_admin3', 'project_name': 'test_tenant13',
+ 'domain_name': 'domain', 'password': 'p', 'types': ['admin']}]
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 492bdca..a56f837 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -38,8 +38,7 @@
# Ensure waiter returns before build_timeout
self.assertTrue((end_time - start_time) < 10)
- @mock.patch('time.sleep')
- def test_wait_for_image_status_timeout(self, mock_sleep):
+ def test_wait_for_image_status_timeout(self):
time_mock = self.patch('time.time')
time_mock.side_effect = utils.generate_timeout_series(1)
@@ -47,15 +46,12 @@
self.assertRaises(exceptions.TimeoutException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
- mock_sleep.assert_called_once_with(1)
- @mock.patch('time.sleep')
- def test_wait_for_image_status_error_on_image_create(self, mock_sleep):
+ def test_wait_for_image_status_error_on_image_create(self):
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/test_file_utils.py b/tempest/tests/common/utils/test_file_utils.py
deleted file mode 100644
index 937aefa..0000000
--- a/tempest/tests/common/utils/test_file_utils.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2014 IBM Corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import mock
-
-from tempest.common.utils import file_utils
-from tempest.tests import base
-
-
-class TestFileUtils(base.TestCase):
-
- def test_have_effective_read_path(self):
- with mock.patch('six.moves.builtins.open', mock.mock_open(),
- create=True):
- result = file_utils.have_effective_read_access('fake_path')
- self.assertTrue(result)
-
- def test_not_effective_read_path(self):
- result = file_utils.have_effective_read_access('fake_path')
- self.assertFalse(result)
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index edd7186..65164a0 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -48,14 +48,9 @@
self.conf.set_default('auth_version', 'v2', group='identity')
for config_option in ['username', 'password', 'project_name']:
# Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- self.conf.set_default(prefix + config_option,
- 'fake_' + config_option,
- group=group)
+ self.conf.set_default('admin_' + config_option,
+ 'fake_' + config_option,
+ group='auth')
class FakePrivate(config.TempestConfigPrivate):
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index b846c46..cc9c94c 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -12,26 +12,65 @@
# under the License.
+import mock
+import subprocess
+
from tempest.lib.cli import base as cli_base
from tempest.lib import exceptions
from tempest.tests import base
class TestExecute(base.TestCase):
- def test_execute_success(self):
+
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_success(self, mock_popen):
+ mock_popen.return_value.returncode = 0
+ mock_popen.return_value.communicate.return_value = (
+ "__init__.py", "")
result = cli_base.execute("/bin/ls", action="tempest",
flags="-l -a")
+ args, kwargs = mock_popen.call_args
+ # Check merge_stderr == False
+ self.assertEqual(subprocess.PIPE, kwargs['stderr'])
+ # Check action and flags are passed
+ args = args[0]
+ # We just tests that all pieces are passed through, we cannot make
+ # assumptions about the order
+ self.assertIn("/bin/ls", args)
+ self.assertIn("-l", args)
+ self.assertIn("-a", args)
+ self.assertIn("tempest", args)
+ # The result is mocked - checking that the mock was invoked correctly
self.assertIsInstance(result, str)
self.assertIn("__init__.py", result)
- def test_execute_failure(self):
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_failure(self, mock_popen):
+ mock_popen.return_value.returncode = 1
+ mock_popen.return_value.communicate.return_value = (
+ "No such option --foobar", "")
result = cli_base.execute("/bin/ls", action="tempest.lib",
flags="--foobar", merge_stderr=True,
fail_ok=True)
+ args, kwargs = mock_popen.call_args
+ # Check the merge_stderr
+ self.assertEqual(subprocess.STDOUT, kwargs['stderr'])
+ # Check action and flags are passed
+ args = args[0]
+ # We just tests that all pieces are passed through, we cannot make
+ # assumptions about the order
+ self.assertIn("/bin/ls", args)
+ self.assertIn("--foobar", args)
+ self.assertIn("tempest.lib", args)
+ # The result is mocked - checking that the mock was invoked correctly
self.assertIsInstance(result, str)
self.assertIn("--foobar", result)
- def test_execute_failure_raise_exception(self):
+ @mock.patch('subprocess.Popen', autospec=True)
+ def test_execute_failure_raise_exception(self, mock_popen):
+ mock_popen.return_value.returncode = 1
+ mock_popen.return_value.communicate.return_value = (
+ "No such option --foobar", "")
self.assertRaises(exceptions.CommandFailed, cli_base.execute,
"/bin/ls", action="tempest", flags="--foobar",
merge_stderr=True)
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index f9e1f44..399c4af 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -169,3 +169,10 @@
bad_mac = 99999999999999999999
self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
cidr, bad_mac)
+
+ def test_chunkify(self):
+ data = "aaa"
+ chunks = data_utils.chunkify(data, 2)
+ self.assertEqual("aa", next(chunks))
+ self.assertEqual("a", next(chunks))
+ self.assertRaises(StopIteration, next, chunks)
diff --git a/tempest/tests/lib/common/utils/test_misc.py b/tempest/tests/lib/common/utils/test_misc.py
index 9597f5b..47e81d1 100644
--- a/tempest/tests/lib/common/utils/test_misc.py
+++ b/tempest/tests/lib/common/utils/test_misc.py
@@ -50,39 +50,3 @@
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/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
new file mode 100644
index 0000000..919e219
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -0,0 +1,78 @@
+# 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 mock
+
+from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
+from tempest.tests import base
+
+
+class TestTestUtils(base.TestCase):
+
+ def test_find_test_caller_test_case(self):
+ # Calling it from here should give us the method we're in.
+ self.assertEqual('TestTestUtils:test_find_test_caller_test_case',
+ test_utils.find_test_caller())
+
+ def test_find_test_caller_setup_self(self):
+ def setUp(self):
+ return test_utils.find_test_caller()
+ self.assertEqual('TestTestUtils:setUp', setUp(self))
+
+ def test_find_test_caller_setup_no_self(self):
+ def setUp():
+ return test_utils.find_test_caller()
+ self.assertEqual(':setUp', setUp())
+
+ def test_find_test_caller_setupclass_cls(self):
+ def setUpClass(cls): # noqa
+ return test_utils.find_test_caller()
+ self.assertEqual('TestTestUtils:setUpClass',
+ setUpClass(self.__class__))
+
+ def test_find_test_caller_teardown_self(self):
+ def tearDown(self):
+ return test_utils.find_test_caller()
+ self.assertEqual('TestTestUtils:tearDown', tearDown(self))
+
+ def test_find_test_caller_teardown_no_self(self):
+ def tearDown():
+ return test_utils.find_test_caller()
+ self.assertEqual(':tearDown', tearDown())
+
+ def test_find_test_caller_teardown_class(self):
+ def tearDownClass(cls): # noqa
+ return test_utils.find_test_caller()
+ self.assertEqual('TestTestUtils:tearDownClass',
+ tearDownClass(self.__class__))
+
+ def test_call_and_ignore_notfound_exc_when_notfound_raised(self):
+ def raise_not_found():
+ raise exceptions.NotFound()
+ self.assertIsNone(
+ test_utils.call_and_ignore_notfound_exc(raise_not_found))
+
+ def test_call_and_ignore_notfound_exc_when_value_error_raised(self):
+ def raise_value_error():
+ raise ValueError()
+ self.assertRaises(ValueError, test_utils.call_and_ignore_notfound_exc,
+ raise_value_error)
+
+ def test_call_and_ignore_notfound_exc(self):
+ m = mock.Mock(return_value=42)
+ args, kwargs = (1,), {'1': None}
+ self.assertEqual(
+ 42, test_utils.call_and_ignore_notfound_exc(m, *args, **kwargs))
+ m.assert_called_once_with(*args, **kwargs)
diff --git a/tempest/tests/lib/fake_credentials.py b/tempest/tests/lib/fake_credentials.py
index fb81bd6..eac4ada 100644
--- a/tempest/tests/lib/fake_credentials.py
+++ b/tempest/tests/lib/fake_credentials.py
@@ -57,3 +57,18 @@
user_domain_name='fake_domain_name'
)
super(FakeKeystoneV3DomainCredentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3AllCredentials(auth.KeystoneV3Credentials):
+ """Fake credentials for the Keystone Identity V3 API, with no scope"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name',
+ project_name='fake_tenant_name',
+ project_domain_name='fake_domain_name',
+ domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3AllCredentials, self).__init__(**creds)
diff --git a/tempest/tests/lib/fake_http.py b/tempest/tests/lib/fake_http.py
index 397c856..cfa4b93 100644
--- a/tempest/tests/lib/fake_http.py
+++ b/tempest/tests/lib/fake_http.py
@@ -21,7 +21,7 @@
self.return_type = return_type
def request(self, uri, method="GET", body=None, headers=None,
- redirections=5, connection_type=None):
+ redirections=5, connection_type=None, chunked=False):
if not self.return_type:
fake_headers = fake_http_response(headers)
return_obj = {
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index 5732065..831f8b5 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -100,7 +100,8 @@
],
"type": "compute",
- "id": "fake_compute_endpoint"
+ "id": "fake_compute_endpoint",
+ "name": "nova"
}
CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ]
@@ -133,6 +134,49 @@
}
}
+IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ "catalog": CATALOG_V3
+ }
+}
+
+IDENTITY_V3_RESPONSE_NO_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ }
+}
+
ALT_IDENTITY_V3 = IDENTITY_V3_RESPONSE
@@ -145,6 +189,28 @@
json.dumps(IDENTITY_V3_RESPONSE))
+def _fake_v3_response_domain_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_DOMAIN_SCOPE))
+
+
+def _fake_v3_response_no_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_NO_SCOPE))
+
+
def _fake_v2_response(self, uri, method="GET", body=None, headers=None,
redirections=5, connection_type=None):
return (fake_http.fake_http_response({}, status=200),
diff --git a/tempest/tests/lib/services/compute/base.py b/tempest/tests/lib/services/base.py
similarity index 97%
rename from tempest/tests/lib/services/compute/base.py
rename to tempest/tests/lib/services/base.py
index e77b436..a244aa2 100644
--- a/tempest/tests/lib/services/compute/base.py
+++ b/tempest/tests/lib/services/base.py
@@ -19,7 +19,7 @@
from tempest.tests.lib import fake_http
-class BaseComputeServiceTest(base.TestCase):
+class BaseServiceTest(base.TestCase):
def create_response(self, body, to_utf=False, status=200, headers=None):
json_body = {}
if body:
diff --git a/tempest/tests/lib/services/compute/test_agents_client.py b/tempest/tests/lib/services/compute/test_agents_client.py
index 3c5043d..880220e 100644
--- a/tempest/tests/lib/services/compute/test_agents_client.py
+++ b/tempest/tests/lib/services/compute/test_agents_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import agents_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestAgentsClient(base.BaseComputeServiceTest):
+class TestAgentsClient(base.BaseServiceTest):
FAKE_CREATE_AGENT = {
"agent": {
"url": "http://foo.com",
diff --git a/tempest/tests/lib/services/compute/test_aggregates_client.py b/tempest/tests/lib/services/compute/test_aggregates_client.py
index a63380e..674d92a 100644
--- a/tempest/tests/lib/services/compute/test_aggregates_client.py
+++ b/tempest/tests/lib/services/compute/test_aggregates_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import aggregates_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestAggregatesClient(base.BaseComputeServiceTest):
+class TestAggregatesClient(base.BaseServiceTest):
FAKE_SHOW_AGGREGATE = {
"aggregate":
{
diff --git a/tempest/tests/lib/services/compute/test_availability_zone_client.py b/tempest/tests/lib/services/compute/test_availability_zone_client.py
index d16cf0a..6608592 100644
--- a/tempest/tests/lib/services/compute/test_availability_zone_client.py
+++ b/tempest/tests/lib/services/compute/test_availability_zone_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestAvailabilityZoneClient(base.BaseComputeServiceTest):
+class TestAvailabilityZoneClient(base.BaseServiceTest):
FAKE_AVAILABIRITY_ZONE_INFO = {
"availabilityZoneInfo":
diff --git a/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
index a867c06..81c54b5 100644
--- a/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
+++ b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
@@ -16,10 +16,10 @@
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
+from tempest.tests.lib.services import base
-class TestBareMetalNodesClient(base.BaseComputeServiceTest):
+class TestBareMetalNodesClient(base.BaseServiceTest):
FAKE_NODE_INFO = {'cpus': '8',
'disk_gb': '64',
diff --git a/tempest/tests/lib/services/compute/test_base_compute_client.py b/tempest/tests/lib/services/compute/test_base_compute_client.py
index 49d29b3..69e8542 100644
--- a/tempest/tests/lib/services/compute/test_base_compute_client.py
+++ b/tempest/tests/lib/services/compute/test_base_compute_client.py
@@ -19,10 +19,10 @@
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
+from tempest.tests.lib.services import base
-class TestMicroversionHeaderCheck(base.BaseComputeServiceTest):
+class TestMicroversionHeaderCheck(base.BaseServiceTest):
def setUp(self):
super(TestMicroversionHeaderCheck, self).setUp()
@@ -72,7 +72,7 @@
return self.get_schema(self.schema_versions_info)
-class TestSchemaVersionsNone(base.BaseComputeServiceTest):
+class TestSchemaVersionsNone(base.BaseServiceTest):
api_microversion = None
expected_schema = 'schemav21'
@@ -130,7 +130,7 @@
return self.get_schema(self.schema_versions_info)
-class TestSchemaVersionsNotFound(base.BaseComputeServiceTest):
+class TestSchemaVersionsNotFound(base.BaseServiceTest):
api_microversion = '2.10'
expected_schema = 'schemav210'
@@ -149,7 +149,7 @@
self.client.return_selected_schema)
-class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
+class TestClientWithoutMicroversionHeader(base.BaseServiceTest):
def setUp(self):
super(TestClientWithoutMicroversionHeader, self).setUp()
@@ -172,7 +172,7 @@
self.client.get('fake_url')
-class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
+class TestClientWithMicroversionHeader(base.BaseServiceTest):
def setUp(self):
super(TestClientWithMicroversionHeader, self).setUp()
diff --git a/tempest/tests/lib/services/compute/test_certificates_client.py b/tempest/tests/lib/services/compute/test_certificates_client.py
index e8123bb..9faef6f 100644
--- a/tempest/tests/lib/services/compute/test_certificates_client.py
+++ b/tempest/tests/lib/services/compute/test_certificates_client.py
@@ -16,10 +16,10 @@
from tempest.lib.services.compute import certificates_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestCertificatesClient(base.BaseComputeServiceTest):
+class TestCertificatesClient(base.BaseServiceTest):
FAKE_CERTIFICATE = {
"certificate": {
diff --git a/tempest/tests/lib/services/compute/test_extensions_client.py b/tempest/tests/lib/services/compute/test_extensions_client.py
index 7415988..d7e217e 100644
--- a/tempest/tests/lib/services/compute/test_extensions_client.py
+++ b/tempest/tests/lib/services/compute/test_extensions_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import extensions_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestExtensionsClient(base.BaseComputeServiceTest):
+class TestExtensionsClient(base.BaseServiceTest):
FAKE_SHOW_EXTENSION = {
"extension": {
diff --git a/tempest/tests/lib/services/compute/test_fixedIPs_client.py b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
index 6999f24..65bda45 100644
--- a/tempest/tests/lib/services/compute/test_fixedIPs_client.py
+++ b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestFixedIPsClient(base.BaseComputeServiceTest):
+class TestFixedIPsClient(base.BaseServiceTest):
FIXED_IP_INFO = {"fixed_ip": {"address": "10.0.0.1",
"cidr": "10.11.12.0/24",
"host": "localhost",
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
index e22b4fe..445ee22 100644
--- a/tempest/tests/lib/services/compute/test_flavors_client.py
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -20,10 +20,10 @@
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
+from tempest.tests.lib.services import base
-class TestFlavorsClient(base.BaseComputeServiceTest):
+class TestFlavorsClient(base.BaseServiceTest):
FAKE_FLAVOR = {
"disk": 1,
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
index f30719e..b0c00f0 100644
--- a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
+++ b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestFloatingIPPoolsClient(base.BaseComputeServiceTest):
+class TestFloatingIPPoolsClient(base.BaseServiceTest):
FAKE_FLOATING_IP_POOLS = {
"floating_ip_pools":
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
index c16c985..ace76f8 100644
--- a/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
+++ b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
@@ -15,10 +15,10 @@
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
+from tempest.tests.lib.services import base
-class TestFloatingIPsBulkClient(base.BaseComputeServiceTest):
+class TestFloatingIPsBulkClient(base.BaseServiceTest):
FAKE_FIP_BULK_LIST = {"floating_ip_info": [{
"address": "10.10.10.1",
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_client.py b/tempest/tests/lib/services/compute/test_floating_ips_client.py
index 3844ba8..92737f2 100644
--- a/tempest/tests/lib/services/compute/test_floating_ips_client.py
+++ b/tempest/tests/lib/services/compute/test_floating_ips_client.py
@@ -17,10 +17,10 @@
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
+from tempest.tests.lib.services import base
-class TestFloatingIpsClient(base.BaseComputeServiceTest):
+class TestFloatingIpsClient(base.BaseServiceTest):
floating_ip = {"fixed_ip": None,
"id": "46d61064-13ba-4bf0-9557-69de824c3d6f",
diff --git a/tempest/tests/lib/services/compute/test_hosts_client.py b/tempest/tests/lib/services/compute/test_hosts_client.py
index d9ff513..78537c2 100644
--- a/tempest/tests/lib/services/compute/test_hosts_client.py
+++ b/tempest/tests/lib/services/compute/test_hosts_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import hosts_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestHostsClient(base.BaseComputeServiceTest):
+class TestHostsClient(base.BaseServiceTest):
FAKE_HOST_DATA = {
"host": {
"resource": {
diff --git a/tempest/tests/lib/services/compute/test_hypervisor_client.py b/tempest/tests/lib/services/compute/test_hypervisor_client.py
index fab34da..89eb698 100644
--- a/tempest/tests/lib/services/compute/test_hypervisor_client.py
+++ b/tempest/tests/lib/services/compute/test_hypervisor_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import hypervisor_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestHypervisorClient(base.BaseComputeServiceTest):
+class TestHypervisorClient(base.BaseServiceTest):
hypervisor_id = "1"
hypervisor_name = "hyper.hostname.com"
diff --git a/tempest/tests/lib/services/compute/test_images_client.py b/tempest/tests/lib/services/compute/test_images_client.py
index 3ebc27f..a9a570d 100644
--- a/tempest/tests/lib/services/compute/test_images_client.py
+++ b/tempest/tests/lib/services/compute/test_images_client.py
@@ -19,10 +19,10 @@
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
+from tempest.tests.lib.services import base
-class TestImagesClient(base.BaseComputeServiceTest):
+class TestImagesClient(base.BaseServiceTest):
# Data Dictionaries used for testing #
FAKE_IMAGE_METADATA = {
"list":
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
index e8c22f1..21764ff 100644
--- 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
@@ -16,10 +16,10 @@
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
+from tempest.tests.lib.services import base
-class TestInstanceUsagesAuditLogClient(base.BaseComputeServiceTest):
+class TestInstanceUsagesAuditLogClient(base.BaseServiceTest):
FAKE_AUDIT_LOG = {
"hosts_not_run": [
diff --git a/tempest/tests/lib/services/compute/test_interfaces_client.py b/tempest/tests/lib/services/compute/test_interfaces_client.py
index de8e268..4d78103 100644
--- a/tempest/tests/lib/services/compute/test_interfaces_client.py
+++ b/tempest/tests/lib/services/compute/test_interfaces_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import interfaces_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestInterfacesClient(base.BaseComputeServiceTest):
+class TestInterfacesClient(base.BaseServiceTest):
# Data Values to be used for testing #
FAKE_INTERFACE_DATA = {
"fixed_ips": [{
diff --git a/tempest/tests/lib/services/compute/test_keypairs_client.py b/tempest/tests/lib/services/compute/test_keypairs_client.py
index 7c595ca..ed3b9dd 100644
--- a/tempest/tests/lib/services/compute/test_keypairs_client.py
+++ b/tempest/tests/lib/services/compute/test_keypairs_client.py
@@ -16,10 +16,10 @@
from tempest.lib.services.compute import keypairs_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestKeyPairsClient(base.BaseComputeServiceTest):
+class TestKeyPairsClient(base.BaseServiceTest):
FAKE_KEYPAIR = {"keypair": {
"public_key": "ssh-rsa foo Generated-by-Nova",
diff --git a/tempest/tests/lib/services/compute/test_limits_client.py b/tempest/tests/lib/services/compute/test_limits_client.py
index d3f0aee..5f3fa5a 100644
--- a/tempest/tests/lib/services/compute/test_limits_client.py
+++ b/tempest/tests/lib/services/compute/test_limits_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import limits_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestLimitsClient(base.BaseComputeServiceTest):
+class TestLimitsClient(base.BaseServiceTest):
def setUp(self):
super(TestLimitsClient, self).setUp()
diff --git a/tempest/tests/lib/services/compute/test_migrations_client.py b/tempest/tests/lib/services/compute/test_migrations_client.py
index 5b1578d..be62c0c 100644
--- a/tempest/tests/lib/services/compute/test_migrations_client.py
+++ b/tempest/tests/lib/services/compute/test_migrations_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import migrations_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestMigrationsClient(base.BaseComputeServiceTest):
+class TestMigrationsClient(base.BaseServiceTest):
FAKE_MIGRATION_INFO = {"migrations": [{
"created_at": "2012-10-29T13:42:02",
"dest_compute": "compute2",
diff --git a/tempest/tests/lib/services/compute/test_networks_client.py b/tempest/tests/lib/services/compute/test_networks_client.py
index 4f5c8b9..1908b57 100644
--- a/tempest/tests/lib/services/compute/test_networks_client.py
+++ b/tempest/tests/lib/services/compute/test_networks_client.py
@@ -14,10 +14,10 @@
from tempest.lib.services.compute import networks_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestNetworksClient(base.BaseComputeServiceTest):
+class TestNetworksClient(base.BaseServiceTest):
FAKE_NETWORK = {
"bridge": None,
diff --git a/tempest/tests/lib/services/compute/test_quota_classes_client.py b/tempest/tests/lib/services/compute/test_quota_classes_client.py
index 4b67576..22d8b91 100644
--- a/tempest/tests/lib/services/compute/test_quota_classes_client.py
+++ b/tempest/tests/lib/services/compute/test_quota_classes_client.py
@@ -16,10 +16,10 @@
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
+from tempest.tests.lib.services import base
-class TestQuotaClassesClient(base.BaseComputeServiceTest):
+class TestQuotaClassesClient(base.BaseServiceTest):
FAKE_QUOTA_CLASS_SET = {
"injected_file_content_bytes": 10240,
diff --git a/tempest/tests/lib/services/compute/test_quotas_client.py b/tempest/tests/lib/services/compute/test_quotas_client.py
index 9f5d1f6..4c49e8d 100644
--- a/tempest/tests/lib/services/compute/test_quotas_client.py
+++ b/tempest/tests/lib/services/compute/test_quotas_client.py
@@ -16,10 +16,10 @@
from tempest.lib.services.compute import quotas_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestQuotasClient(base.BaseComputeServiceTest):
+class TestQuotasClient(base.BaseServiceTest):
FAKE_QUOTA_SET = {
"quota_set": {
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
index 581f7b1..86eee02 100644
--- 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
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestSecurityGroupDefaultRulesClient(base.BaseComputeServiceTest):
+class TestSecurityGroupDefaultRulesClient(base.BaseServiceTest):
FAKE_RULE = {
"from_port": 80,
"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
index 9a7c04d..2b0a94d 100644
--- a/tempest/tests/lib/services/compute/test_security_group_rules_client.py
+++ b/tempest/tests/lib/services/compute/test_security_group_rules_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestSecurityGroupRulesClient(base.BaseComputeServiceTest):
+class TestSecurityGroupRulesClient(base.BaseServiceTest):
FAKE_SECURITY_GROUP_RULE = {
"security_group_rule": {
diff --git a/tempest/tests/lib/services/compute/test_security_groups_client.py b/tempest/tests/lib/services/compute/test_security_groups_client.py
index 6a11c29..d293a08 100644
--- a/tempest/tests/lib/services/compute/test_security_groups_client.py
+++ b/tempest/tests/lib/services/compute/test_security_groups_client.py
@@ -17,10 +17,10 @@
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
+from tempest.tests.lib.services import base
-class TestSecurityGroupsClient(base.BaseComputeServiceTest):
+class TestSecurityGroupsClient(base.BaseServiceTest):
FAKE_SECURITY_GROUP_INFO = [{
"description": "default",
diff --git a/tempest/tests/lib/services/compute/test_server_groups_client.py b/tempest/tests/lib/services/compute/test_server_groups_client.py
index cb163a8..bf03b84 100644
--- a/tempest/tests/lib/services/compute/test_server_groups_client.py
+++ b/tempest/tests/lib/services/compute/test_server_groups_client.py
@@ -17,10 +17,10 @@
from tempest.lib.services.compute import server_groups_client
from tempest.tests.lib import fake_http
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestServerGroupsClient(base.BaseComputeServiceTest):
+class TestServerGroupsClient(base.BaseServiceTest):
server_group = {
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index 0078497..93550fd 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -16,10 +16,10 @@
from tempest.lib.services.compute import servers_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestServersClient(base.BaseComputeServiceTest):
+class TestServersClient(base.BaseServiceTest):
FAKE_SERVERS = {'servers': [{
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 7add187..41da39c 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -16,10 +16,10 @@
from tempest.lib.services.compute import services_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestServicesClient(base.BaseComputeServiceTest):
+class TestServicesClient(base.BaseServiceTest):
FAKE_SERVICES = {
"services":
diff --git a/tempest/tests/lib/services/compute/test_snapshots_client.py b/tempest/tests/lib/services/compute/test_snapshots_client.py
index b1d8ade..1629943 100644
--- a/tempest/tests/lib/services/compute/test_snapshots_client.py
+++ b/tempest/tests/lib/services/compute/test_snapshots_client.py
@@ -17,10 +17,10 @@
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
+from tempest.tests.lib.services import base
-class TestSnapshotsClient(base.BaseComputeServiceTest):
+class TestSnapshotsClient(base.BaseServiceTest):
FAKE_SNAPSHOT = {
"createdAt": "2015-10-02T16:27:54.724209",
diff --git a/tempest/tests/lib/services/compute/test_tenant_networks_client.py b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
index cfb68cc..f71aad9 100644
--- a/tempest/tests/lib/services/compute/test_tenant_networks_client.py
+++ b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestTenantNetworksClient(base.BaseComputeServiceTest):
+class TestTenantNetworksClient(base.BaseServiceTest):
FAKE_NETWORK = {
"cidr": "None",
diff --git a/tempest/tests/lib/services/compute/test_tenant_usages_client.py b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
index 88d093d..49eae08 100644
--- a/tempest/tests/lib/services/compute/test_tenant_usages_client.py
+++ b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
@@ -14,10 +14,10 @@
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
+from tempest.tests.lib.services import base
-class TestTenantUsagesClient(base.BaseComputeServiceTest):
+class TestTenantUsagesClient(base.BaseServiceTest):
FAKE_SERVER_USAGES = [{
"ended_at": None,
diff --git a/tempest/tests/lib/services/compute/test_versions_client.py b/tempest/tests/lib/services/compute/test_versions_client.py
index fc6c1d2..06ecdc3 100644
--- a/tempest/tests/lib/services/compute/test_versions_client.py
+++ b/tempest/tests/lib/services/compute/test_versions_client.py
@@ -17,10 +17,10 @@
from tempest.lib.services.compute import versions_client
from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services.compute import base
+from tempest.tests.lib.services import base
-class TestVersionsClient(base.BaseComputeServiceTest):
+class TestVersionsClient(base.BaseServiceTest):
FAKE_INIT_VERSION = {
"version": {
diff --git a/tempest/tests/lib/services/compute/test_volumes_client.py b/tempest/tests/lib/services/compute/test_volumes_client.py
index d16f5b0..b81cdbb 100644
--- a/tempest/tests/lib/services/compute/test_volumes_client.py
+++ b/tempest/tests/lib/services/compute/test_volumes_client.py
@@ -19,10 +19,10 @@
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
+from tempest.tests.lib.services import base
-class TestVolumesClient(base.BaseComputeServiceTest):
+class TestVolumesClient(base.BaseServiceTest):
FAKE_VOLUME = {
"id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
diff --git a/tempest/tests/services/compute/__init__.py b/tempest/tests/lib/services/image/__init__.py
similarity index 100%
copy from tempest/tests/services/compute/__init__.py
copy to tempest/tests/lib/services/image/__init__.py
diff --git a/tempest/tests/services/compute/__init__.py b/tempest/tests/lib/services/image/v2/__init__.py
similarity index 100%
copy from tempest/tests/services/compute/__init__.py
copy to tempest/tests/lib/services/image/v2/__init__.py
diff --git a/tempest/tests/lib/services/image/v2/test_image_members_client.py b/tempest/tests/lib/services/image/v2/test_image_members_client.py
new file mode 100644
index 0000000..703b6e1
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_image_members_client.py
@@ -0,0 +1,90 @@
+# 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.services.image.v2 import image_members_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestImageMembersClient(base.BaseServiceTest):
+ FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER = {
+ "status": "pending",
+ "created_at": "2013-11-26T07:21:21Z",
+ "updated_at": "2013-11-26T07:21:21Z",
+ "image_id": "0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+ "member_id": "8989447062e04a818baf9e073fd04fa7",
+ "schema": "/v2/schemas/member"
+ }
+
+ def setUp(self):
+ super(TestImageMembersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = image_members_client.ImageMembersClient(fake_auth,
+ 'image',
+ 'regionOne')
+
+ def _test_show_image_member(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_image_member,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+ bytes_body,
+ image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+ member_id="8989447062e04a818baf9e073fd04fa7")
+
+ def _test_create_image_member(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_image_member,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+ bytes_body,
+ image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+ member_id="8989447062e04a818baf9e073fd04fa7")
+
+ def _test_update_image_member(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_image_member,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+ bytes_body,
+ image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+ member_id="8989447062e04a818baf9e073fd04fa7",
+ schema="/v2/schemas/member2")
+
+ def test_show_image_member_with_str_body(self):
+ self._test_show_image_member()
+
+ def test_show_image_member_with_bytes_body(self):
+ self._test_show_image_member(bytes_body=True)
+
+ def test_create_image_member_with_str_body(self):
+ self._test_create_image_member()
+
+ def test_create_image_member_with_bytes_body(self):
+ self._test_create_image_member(bytes_body=True)
+
+ def test_delete_image_member(self):
+ self.check_service_client_function(
+ self.client.delete_image_member,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+ member_id="8989447062e04a818baf9e073fd04fa7",
+ status=204)
+
+ def test_update_image_member_with_str_body(self):
+ self._test_update_image_member()
+
+ def test_update_image_member_with_bytes_body(self):
+ self._test_update_image_member(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_namespaces_client.py b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
new file mode 100644
index 0000000..4cb9d01
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
@@ -0,0 +1,93 @@
+# 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.services.image.v2 import namespaces_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNamespacesClient(base.BaseServiceTest):
+ FAKE_CREATE_SHOW_NAMESPACE = {
+ "namespace": "OS::Compute::Hypervisor",
+ "visibility": "public",
+ "description": "Tempest",
+ "display_name": u"\u2740(*\xb4\u25e1`*)\u2740",
+ "protected": True
+ }
+
+ FAKE_UPDATE_NAMESPACE = {
+ "namespace": "OS::Compute::Hypervisor",
+ "visibility": "public",
+ "description": "Tempest",
+ "display_name": u"\u2740(*\xb4\u25e2`*)\u2740",
+ "protected": True
+ }
+
+ def setUp(self):
+ super(TestNamespacesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = namespaces_client.NamespacesClient(fake_auth,
+ 'image', 'regionOne')
+
+ def _test_show_namespace(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_namespace,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CREATE_SHOW_NAMESPACE,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor")
+
+ def _test_create_namespace(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_namespace,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_SHOW_NAMESPACE,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor",
+ visibility="public", description="Tempest",
+ display_name=u"\u2740(*\xb4\u25e1`*)\u2740", protected=True,
+ status=201)
+
+ def _test_update_namespace(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_namespace,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_NAMESPACE,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor",
+ display_name=u"\u2740(*\xb4\u25e2`*)\u2740", protected=True)
+
+ def test_show_namespace_with_str_body(self):
+ self._test_show_namespace()
+
+ def test_show_namespace_with_bytes_body(self):
+ self._test_show_namespace(bytes_body=True)
+
+ def test_create_namespace_with_str_body(self):
+ self._test_create_namespace()
+
+ def test_create_namespace_with_bytes_body(self):
+ self._test_create_namespace(bytes_body=True)
+
+ def test_delete_namespace(self):
+ self.check_service_client_function(
+ self.client.delete_namespace,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, namespace="OS::Compute::Hypervisor", status=204)
+
+ def test_update_namespace_with_str_body(self):
+ self._test_update_namespace()
+
+ def test_update_namespace_with_bytes_body(self):
+ self._test_update_namespace(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_resource_types_client.py b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
new file mode 100644
index 0000000..2e3b117
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
@@ -0,0 +1,69 @@
+# 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.services.image.v2 import resource_types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestResouceTypesClient(base.BaseServiceTest):
+ FAKE_LIST_RESOURCETYPES = {
+ "resource_types": [
+ {
+ "created_at": "2014-08-28T18:13:04Z",
+ "name": "OS::Glance::Image",
+ "updated_at": "2014-08-28T18:13:04Z"
+ },
+ {
+ "created_at": "2014-08-28T18:13:04Z",
+ "name": "OS::Cinder::Volume",
+ "updated_at": "2014-08-28T18:13:04Z"
+ },
+ {
+ "created_at": "2014-08-28T18:13:04Z",
+ "name": "OS::Nova::Flavor",
+ "updated_at": "2014-08-28T18:13:04Z"
+ },
+ {
+ "created_at": "2014-08-28T18:13:04Z",
+ "name": "OS::Nova::Aggregate",
+ "updated_at": "2014-08-28T18:13:04Z"
+ },
+ {
+ "created_at": "2014-08-28T18:13:04Z",
+ "name": u"\u2740(*\xb4\u25e1`*)\u2740",
+ "updated_at": "2014-08-28T18:13:04Z"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestResouceTypesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = resource_types_client.ResourceTypesClient(fake_auth,
+ 'image',
+ 'regionOne')
+
+ def _test_list_resouce_types(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_resource_types,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_RESOURCETYPES,
+ bytes_body)
+
+ def test_list_resouce_types_with_str_body(self):
+ self._test_list_resouce_types()
+
+ def test_list_resouce_types_with_bytes_body(self):
+ self._test_list_resouce_types(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_schemas_client.py b/tempest/tests/lib/services/image/v2/test_schemas_client.py
new file mode 100644
index 0000000..4c4b86a
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_schemas_client.py
@@ -0,0 +1,96 @@
+# 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.services.image.v2 import schemas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSchemasClient(base.BaseServiceTest):
+ FAKE_SHOW_SCHEMA = {
+ "links": [
+ {
+ "href": "{schema}",
+ "rel": "describedby"
+ }
+ ],
+ "name": "members",
+ "properties": {
+ "members": {
+ "items": {
+ "name": "member",
+ "properties": {
+ "created_at": {
+ "description": ("Date and time of image member"
+ " creation"),
+ "type": "string"
+ },
+ "image_id": {
+ "description": "An identifier for the image",
+ "pattern": ("^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}"
+ "-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}"
+ "-([0-9a-fA-F]){12}$"),
+ "type": "string"
+ },
+ "member_id": {
+ "description": ("An identifier for the image"
+ " member (tenantId)"),
+ "type": "string"
+ },
+ "schema": {
+ "type": "string"
+ },
+ "status": {
+ "description": "The status of this image member",
+ "enum": [
+ "pending",
+ "accepted",
+ "rejected"
+ ],
+ "type": "string"
+ },
+ "updated_at": {
+ "description": ("Date and time of last"
+ " modification of image member"),
+ "type": "string"
+ }
+ }
+ },
+ "type": "array"
+ },
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+
+ def setUp(self):
+ super(TestSchemasClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = schemas_client.SchemasClient(fake_auth,
+ 'image', 'regionOne')
+
+ def _test_show_schema(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_schema,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_SCHEMA,
+ bytes_body,
+ schema="member")
+
+ def test_show_schema_with_str_body(self):
+ self._test_show_schema()
+
+ def test_show_schema_with_bytes_body(self):
+ self._test_show_schema(bytes_body=True)
diff --git a/tempest/services/network/__init__.py b/tempest/tests/lib/services/network/__init__.py
similarity index 100%
rename from tempest/services/network/__init__.py
rename to tempest/tests/lib/services/network/__init__.py
diff --git a/tempest/tests/lib/services/network/test_routers_client.py b/tempest/tests/lib/services/network/test_routers_client.py
new file mode 100644
index 0000000..2fa5993
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_routers_client.py
@@ -0,0 +1,109 @@
+# 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.services.network import routers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestRoutersClient(base.BaseServiceTest):
+ FAKE_CREATE_ROUTER = {
+ "router": {
+ "name": u'\u2740(*\xb4\u25e1`*)\u2740',
+ "external_gateway_info": {
+ "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b",
+ "enable_snat": True,
+ "external_fixed_ips": [
+ {
+ "subnet_id": "255.255.255.0",
+ "ip": "192.168.10.1"
+ }
+ ]
+ },
+ "admin_state_up": True,
+ "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
+ }
+ }
+
+ FAKE_UPDATE_ROUTER = {
+ "router": {
+ "name": u'\u2740(*\xb4\u25e1`*)\u2740',
+ "external_gateway_info": {
+ "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b",
+ "enable_snat": True,
+ "external_fixed_ips": [
+ {
+ "subnet_id": "255.255.255.0",
+ "ip": "192.168.10.1"
+ }
+ ]
+ },
+ "admin_state_up": False,
+ "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
+ }
+ }
+
+ def setUp(self):
+ super(TestRoutersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = routers_client.RoutersClient(fake_auth,
+ 'network', 'regionOne')
+
+ def _test_list_routers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_routers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"routers": []},
+ bytes_body)
+
+ def _test_create_router(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_router,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_ROUTER,
+ bytes_body,
+ name="another_router", admin_state_up="true", status=201)
+
+ def _test_update_router(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_router,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_ROUTER,
+ bytes_body,
+ router_id="8604a0de-7f6b-409a-a47c-a1cc7bc77b2e",
+ admin_state_up=False)
+
+ def test_list_routers_with_str_body(self):
+ self._test_list_routers()
+
+ def test_list_routers_with_bytes_body(self):
+ self._test_list_routers(bytes_body=True)
+
+ def test_create_router_with_str_body(self):
+ self._test_create_router()
+
+ def test_create_router_with_bytes_body(self):
+ self._test_create_router(bytes_body=True)
+
+ def test_delete_router(self):
+ self.check_service_client_function(
+ self.client.delete_router,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, router_id="1", status=204)
+
+ def test_update_router_with_str_body(self):
+ self._test_update_router()
+
+ def test_update_router_with_bytes_body(self):
+ self._test_update_router(bytes_body=True)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index cc71c92..c08bf6a 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -15,6 +15,7 @@
import copy
import datetime
+import testtools
from oslotest import mockpatch
@@ -359,6 +360,58 @@
self.assertRaises(exceptions.EndpointNotFound,
self._test_base_url_helper, None, self.filters)
+ def test_base_url_with_known_name(self):
+ """If name and service is known, return the endpoint."""
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'name': 'nova'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_known_name_and_unknown_servce(self):
+ """Test with Known Name and Unknown service
+
+ If the name is known but the service is unknown, raise an exception.
+ """
+ self.filters = {
+ 'service': 'AintNoBodyKnowThatService',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'name': 'AintNoBodyKnowThatName'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_with_unknown_name_and_known_service(self):
+ """Test with Unknown Name and Known Service
+
+ If the name is unknown, raise an exception. Note that filtering by
+ name is only successful service exists.
+ """
+
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'name': 'AintNoBodyKnowThatName'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_without_name(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_with_api_version_filter(self):
self.filters = {
'service': 'compute',
@@ -425,6 +478,16 @@
self.assertEqual(self.auth_provider.is_expired(auth_data),
should_be_expired)
+ def test_set_scope_all_valid(self):
+ for scope in self.auth_provider.SCOPES:
+ self.auth_provider.scope = scope
+ self.assertEqual(scope, self.auth_provider.scope)
+
+ def test_set_scope_invalid(self):
+ with testtools.ExpectedException(exceptions.InvalidScope,
+ '.* invalid_scope .*'):
+ self.auth_provider.scope = 'invalid_scope'
+
class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
_endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
@@ -529,6 +592,98 @@
expected = 'http://fake_url/v3'
self._test_base_url_helper(expected, filters, ('token', auth_data))
+ # Base URL test with scope only for V3
+ def test_base_url_scope_project(self):
+ self.auth_provider.scope = 'project'
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_identity(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = fake_identity.FAKE_AUTH_URL
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_other(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.base_url,
+ auth_data=self.auth_provider.auth_data,
+ filters=self.filters)
+
+ def test_auth_parameters_with_scope_unset(self):
+ # No scope defaults to 'project'
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_project_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'project'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_domain_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'domain'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_unscoped(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'unscoped'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_') or attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
class TestKeystoneV3Credentials(base.TestCase):
def testSetAttrUserDomain(self):
@@ -630,3 +785,20 @@
self.assertEqual(
'http://localhost/identity/v2.0/uuid/',
auth.replace_version('http://localhost/identity/v3/uuid/', 'v2.0'))
+
+
+class TestKeystoneV3AuthProvider_DomainScope(BaseAuthTestsSetUp):
+ _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
+ _auth_provider_class = auth.KeystoneV3AuthProvider
+ credentials = fake_credentials.FakeKeystoneV3Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV3AuthProvider_DomainScope, self).setUp()
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_domain_scope)
+
+ def test_get_auth_with_domain_scope(self):
+ self.auth_provider.scope = 'domain'
+ _, auth_data = self.auth_provider.get_auth()
+ self.assertIn('domain', auth_data)
+ self.assertNotIn('project', auth_data)
diff --git a/tempest/tests/lib/test_credentials.py b/tempest/tests/lib/test_credentials.py
index ca3baa1..b6f2cf6 100644
--- a/tempest/tests/lib/test_credentials.py
+++ b/tempest/tests/lib/test_credentials.py
@@ -36,8 +36,10 @@
# Check the right version of credentials has been returned
self.assertIsInstance(credentials, credentials_class)
# Check the id attributes are filled in
+ # NOTE(andreaf) project_* attributes are accepted as input but
+ # never set on the credentials object
attributes = [x for x in credentials.ATTRIBUTES if (
- '_id' in x and x != 'domain_id')]
+ '_id' in x and x != 'domain_id' and x != 'project_id')]
for attr in attributes:
if filled:
self.assertIsNotNone(getattr(credentials, attr))
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
index 2959294..057f57b 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/test_rest_client.py
@@ -547,6 +547,65 @@
self.assertIsNotNone(str(self.rest_client))
+class TestRateLimiting(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRateLimiting, self).setUp()
+
+ def test__get_retry_after_delay_with_integer(self):
+ resp = {'retry-after': '123'}
+ self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_with_http_date(self):
+ resp = {
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
+ }
+ self.assertEqual(123, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_of_zero_with_integer(self):
+ resp = {'retry-after': '0'}
+ self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_of_zero_with_http_date(self):
+ resp = {
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ }
+ self.assertEqual(1, self.rest_client._get_retry_after_delay(resp))
+
+ def test__get_retry_after_delay_with_missing_date_header(self):
+ resp = {
+ 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT',
+ }
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ resp)
+
+ def test__get_retry_after_delay_with_invalid_http_date(self):
+ resp = {
+ 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT',
+ 'date': 'Mon, 4 Apr 2016 21:56:23 GMT',
+ }
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ resp)
+
+ def test__get_retry_after_delay_with_missing_retry_after_header(self):
+ self.assertRaises(ValueError, self.rest_client._get_retry_after_delay,
+ {})
+
+ def test_is_absolute_limit_gives_false_with_retry_after(self):
+ resp = {'retry-after': 123}
+
+ # is_absolute_limit() requires the overLimit body to be unwrapped
+ resp_body = self.rest_client._parse_resp("""{
+ "overLimit": {
+ "message": ""
+ }
+ }""")
+ self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body))
+
+
class TestProperties(BaseRestClientTestClass):
def setUp(self):
@@ -574,6 +633,7 @@
expected = {'api_version': 'v1',
'endpoint_type': 'publicURL',
'region': None,
+ 'name': None,
'service': None,
'skip_path': True}
self.rest_client.skip_path()
@@ -584,6 +644,7 @@
expected = {'api_version': 'v1',
'endpoint_type': 'publicURL',
'region': None,
+ 'name': None,
'service': None}
self.assertEqual(expected, self.rest_client.filters)
@@ -634,6 +695,24 @@
self.assertRaises(AssertionError, self.rest_client.expected_success,
expected_code, read_code)
+ def test_non_success_read_code_as_string(self):
+ expected_code = 202
+ read_code = '202'
+ self.assertRaises(TypeError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_read_code_as_list(self):
+ expected_code = 202
+ read_code = [202]
+ self.assertRaises(TypeError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_code_as_non_int(self):
+ expected_code = ['201', 202]
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
class TestResponseBody(base.TestCase):
diff --git a/tempest/tests/services/compute/__init__.py b/tempest/tests/services/image/__init__.py
similarity index 100%
rename from tempest/tests/services/compute/__init__.py
rename to tempest/tests/services/image/__init__.py
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
deleted file mode 100644
index 768cd05..0000000
--- a/tempest/tests/test_glance_http.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# Copyright 2014 IBM Corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import socket
-
-import mock
-from oslotest import mockpatch
-import six
-from six.moves import http_client as httplib
-
-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.lib import fake_http
-
-
-class TestGlanceHTTPClient(base.TestCase):
-
- def setUp(self):
- super(TestGlanceHTTPClient, self).setUp()
- self.endpoint = 'http://fake_url.com'
- self.fake_auth = fake_auth_provider.FakeAuthProvider()
-
- self.fake_auth.base_url = mock.MagicMock(return_value=self.endpoint)
-
- self.useFixture(mockpatch.PatchObject(
- httplib.HTTPConnection,
- 'request',
- 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_http_response(header, status=status,
- body=six.StringIO(resp_body))
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'getresponse', return_value=resp))
- return resp
-
- def test_raw_request(self):
- self._set_response_fixture({}, 200, 'fake_response_body')
- resp, body = self.client.raw_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertEqual('fake_response_body', body.read())
-
- def test_raw_request_with_response_chunked(self):
- self._set_response_fixture({}, 200, 'fake_response_body')
- self.useFixture(mockpatch.PatchObject(glance_http,
- 'CHUNKSIZE', 1))
- resp, body = self.client.raw_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertEqual('fake_response_body', body.read())
-
- def test_raw_request_chunked(self):
- self.useFixture(mockpatch.PatchObject(glance_http,
- 'CHUNKSIZE', 1))
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'endheaders'))
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'send'))
-
- self._set_response_fixture({}, 200, 'fake_response_body')
- req_body = six.StringIO('fake_request_body')
- resp, body = self.client.raw_request('PUT', '/images', body=req_body)
- self.assertEqual(200, resp.status)
- self.assertEqual('fake_response_body', body.read())
- call_count = httplib.HTTPConnection.send.call_count
- self.assertEqual(call_count - 1, req_body.tell())
-
- def test_get_connection_class_for_https(self):
- conn_class = self.client._get_connection_class('https')
- self.assertEqual(glance_http.VerifiedHTTPSConnection, conn_class)
-
- def test_get_connection_class_for_http(self):
- conn_class = (self.client._get_connection_class('http'))
- self.assertEqual(httplib.HTTPConnection, conn_class)
-
- def test_get_connection_http(self):
- 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.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',
- side_effect=httplib.InvalidURL()
- ))
- self.assertRaises(exceptions.EndpointNotFound,
- self.client._get_connection)
-
- def test_get_connection_kwargs_default_for_http(self):
- kwargs = self.client._get_connection_kwargs('http')
- self.assertEqual(600, kwargs['timeout'])
- self.assertEqual(1, len(kwargs.keys()))
-
- def test_get_connection_kwargs_set_timeout_for_http(self):
- kwargs = self.client._get_connection_kwargs('http', timeout=10,
- ca_certs='foo')
- self.assertEqual(10, kwargs['timeout'])
- # nothing more than timeout is evaluated for http connections
- self.assertEqual(1, len(kwargs.keys()))
-
- def test_get_connection_kwargs_default_for_https(self):
- kwargs = self.client._get_connection_kwargs('https')
- self.assertEqual(600, kwargs['timeout'])
- self.assertIsNone(kwargs['ca_certs'])
- self.assertIsNone(kwargs['cert_file'])
- self.assertIsNone(kwargs['key_file'])
- self.assertEqual(False, kwargs['insecure'])
- self.assertEqual(True, kwargs['ssl_compression'])
- self.assertEqual(6, len(kwargs.keys()))
-
- def test_get_connection_kwargs_set_params_for_https(self):
- kwargs = self.client._get_connection_kwargs('https', timeout=10,
- ca_certs='foo',
- cert_file='/foo/bar.cert',
- key_file='/foo/key.pem',
- insecure=True,
- ssl_compression=False)
- self.assertEqual(10, kwargs['timeout'])
- self.assertEqual('foo', kwargs['ca_certs'])
- self.assertEqual('/foo/bar.cert', kwargs['cert_file'])
- self.assertEqual('/foo/key.pem', kwargs['key_file'])
- self.assertEqual(True, kwargs['insecure'])
- self.assertEqual(False, kwargs['ssl_compression'])
- 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_http_response({}, six.StringIO(
- 'X' * (glance_http.CHUNKSIZE + 1)))
- iterator = glance_http.ResponseBodyIterator(resp)
- chunks = list(iterator)
- self.assertEqual(chunks, ['X' * glance_http.CHUNKSIZE, 'X'])
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index aba2aab..f005c21 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -167,3 +167,16 @@
self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib(
"import tempest.exception",
'./tempest/lib/common/compute.py'))))
+
+ def test_dont_use_config_in_tempest_lib(self):
+ self.assertFalse(list(checks.dont_use_config_in_tempest_lib(
+ 'from tempest import config', './tempest/common/compute.py')))
+ self.assertFalse(list(checks.dont_use_config_in_tempest_lib(
+ 'from oslo_concurrency import lockutils',
+ './tempest/lib/auth.py')))
+ self.assertTrue(list(checks.dont_use_config_in_tempest_lib(
+ 'from tempest import config', './tempest/lib/auth.py')))
+ self.assertTrue(list(checks.dont_use_config_in_tempest_lib(
+ 'from oslo_config import cfg', './tempest/lib/decorators.py')))
+ self.assertTrue(list(checks.dont_use_config_in_tempest_lib(
+ 'import tempest.config', './tempest/lib/common/rest_client.py')))
diff --git a/test-requirements.txt b/test-requirements.txt
index 9ef956a..763f0ba 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,6 +7,6 @@
python-subunit>=0.0.18 # Apache-2.0/BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
reno>=1.6.2 # Apache2
-mock>=1.2 # BSD
+mock>=2.0 # BSD
coverage>=3.6 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
diff --git a/tools/check_logs.py b/tools/check_logs.py
index fa7129d..caad85c 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -20,8 +20,8 @@
import os
import re
import six
+import six.moves.urllib.request as urlreq
import sys
-import urllib2
import yaml
@@ -54,7 +54,6 @@
'q-meta',
'q-metering',
'q-svc',
- 'q-vpn',
's-proxy'])
@@ -68,9 +67,9 @@
logs_with_errors.append(name)
for (name, url) in url_specs:
whitelist = whitelists.get(name, [])
- req = urllib2.Request(url)
+ req = urlreq.Request(url)
req.add_header('Accept-Encoding', 'gzip')
- page = urllib2.urlopen(req)
+ page = urlreq.urlopen(req)
buf = six.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
if scan_content(name, f.read().splitlines(), regexp, whitelist):
@@ -96,7 +95,7 @@
def collect_url_logs(url):
- page = urllib2.urlopen(url)
+ page = urlreq.urlopen(url)
content = page.read()
logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
return logs
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index 49a42fe..f2da27a 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -19,8 +19,8 @@
import pprint
import re
import six
+import six.moves.urllib.request as urlreq
import sys
-import urllib2
pp = pprint.PrettyPrinter()
@@ -65,9 +65,9 @@
def hunt_for_stacktrace(url):
"""Return TRACE or ERROR lines out of logs."""
- req = urllib2.Request(url)
+ req = urlreq.Request(url)
req.add_header('Accept-Encoding', 'gzip')
- page = urllib2.urlopen(req)
+ page = urlreq.urlopen(req)
buf = six.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
content = f.read()
@@ -105,7 +105,7 @@
def collect_logs(url):
- page = urllib2.urlopen(url)
+ page = urlreq.urlopen(url)
content = page.read()
logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
return logs