Merge "boot into a network that has "port_security_enabled=False""
diff --git a/.gitignore b/.gitignore
index e96deb1..9292dbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
AUTHORS
ChangeLog
*.pyc
+__pycache__/
etc/tempest.conf
etc/tempest.conf.sample
etc/logging.conf
diff --git a/README.rst b/README.rst
index 53c7de5..3c0463b 100644
--- a/README.rst
+++ b/README.rst
@@ -124,6 +124,9 @@
Release Versioning
------------------
+`Tempest Release Notes <http://docs.openstack.org/releasenotes/tempest>`_
+shows what changes have been released on each version.
+
Tempest's released versions are broken into 2 sets of information. Depending on
how you intend to consume tempest you might need
@@ -178,9 +181,8 @@
is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the
Tempest suite.
-Alternatively, you can use the run_tests.sh script which will create a venv and
-run the unit tests. There are also the py27 and py34 tox jobs which will run
-the unit tests with the corresponding version of python.
+Alternatively, there are the py27 and py34 tox jobs which will run the unit
+tests with the corresponding version of python.
Python 2.6
----------
@@ -224,7 +226,7 @@
$ cd $TEMPEST_ROOT_DIR
$ oslo-config-generator --config-file \
- etc/config-generator.tempest.conf \
+ tempest/cmd/config-generator.tempest.conf \
--output-file etc/tempest.conf
After that, open up the ``etc/tempest.conf`` file and edit the
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 127613d..2edaddb 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -14,6 +14,7 @@
import sys
import os
import subprocess
+import warnings
# Build the plugin registry
def build_plugin_registry(app):
@@ -140,9 +141,13 @@
# using the given strftime format.
git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
"-n1"]
-html_last_updated_fmt = subprocess.Popen(git_cmd,
- stdout=subprocess.PIPE).\
- communicate()[0]
+try:
+ html_last_updated_fmt = subprocess.Popen(git_cmd,
+ stdout=subprocess.PIPE).\
+ communicate()[0]
+except Exception:
+ warnings.warn('Cannot get last updated time from git repository. '
+ 'Not setting "html_last_updated_fmt".')
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index e4b104f..fd9ad05 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -61,10 +61,9 @@
Credential Provider Mechanisms
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Tempest currently also has three different internal methods for providing
-authentication to tests: dynamic credentials, locking test accounts, and
-non-locking test accounts. Depending on which one is in use the configuration
-of Tempest is slightly different.
+Tempest currently has two different internal methods for providing authentication
+to tests: dynamic credentials and pre-provisioned credentials.
+Depending on which one is in use the configuration of Tempest is slightly different.
Dynamic Credentials
"""""""""""""""""""
@@ -135,7 +134,10 @@
It is worth pointing out that each set of credentials in the accounts.yaml
should have a unique project. This is required to provide proper isolation
to the tests using the credentials, and failure to do this will likely cause
-unexpected failures in some tests.
+unexpected failures in some tests. Also, ensure that these projects and users
+used do not have any pre-existing resources created. Tempest assumes all
+tenants it's using are empty and may sporadically fail if there are unexpected
+resources present.
When the keystone in the target cloud requires domain scoped tokens to
perform admin actions, all pre-provisioned admin users must have a role
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f1ede06..6abe9dc 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -50,7 +50,6 @@
account_generator
cleanup
- javelin
subunit_describe_calls
workspace
run
diff --git a/doc/source/javelin.rst b/doc/source/javelin.rst
deleted file mode 100644
index 01090ca..0000000
--- a/doc/source/javelin.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-----------------------------------------------------------
-Javelin2 - How to check that resources survived an upgrade
-----------------------------------------------------------
-
-.. automodule:: tempest.cmd.javelin
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 6a2fb83..29248d1 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -67,3 +67,4 @@
library/utils
library/api_microversion_testing
library/auth
+ library/clients
diff --git a/doc/source/library/clients.rst b/doc/source/library/clients.rst
new file mode 100644
index 0000000..086cfc9
--- /dev/null
+++ b/doc/source/library/clients.rst
@@ -0,0 +1,24 @@
+.. _clients:
+
+Service Clients Usage
+=====================
+
+Tests make requests against APIs using service clients. Service clients are
+specializations of the ``RestClient`` class. The service clients that cover the
+APIs exposed by a service should be grouped in a service clients module.
+A service clients module is python module where all service clients are
+defined. If major API versions are available, submodules should be defined,
+one for each version.
+
+The ``ClientsFactory`` class helps initializing all clients of a specific
+service client module from a set of shared parameters.
+
+The ``ServiceClients`` class provides a convenient way to get access to all
+available service clients initialized with a provided set of credentials.
+
+------------------
+The clients module
+------------------
+
+.. automodule:: tempest.lib.services.clients
+ :members:
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 17059e4..bff18f8 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -217,3 +217,11 @@
* `2.20`_
.. _2.20: http://docs.openstack.org/developer/nova/api_microversion_history.html#id18
+
+ * `2.25`_
+
+ .. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka
+
+ * `2.37`_
+
+ .. _2.37: http://docs.openstack.org/developer/nova/api_microversion_history.html#id34
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 9640469..d34023f 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -111,8 +111,9 @@
class MyPlugin(plugins.TempestPlugin):
-Then you need to ensure you locally define all of the methods in the abstract
-class, you can refer to the api doc below for a reference of what that entails.
+Then you need to ensure you locally define all of the mandatory methods in the
+abstract class, you can refer to the api doc below for a reference of what that
+entails.
Abstract Plugin Class
---------------------
@@ -164,6 +165,142 @@
CONF object from tempest. This enables the plugin to add options to both
existing sections and also create new configuration sections for new options.
+Service Clients
+---------------
+
+If a plugin defines a service client, it is beneficial for it to implement the
+``get_service_clients`` method in the plugin class. All service clients which
+are exposed via this interface will be automatically configured and be
+available in any instance of the service clients class, defined in
+``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are
+installed, all service clients from all plugins will be registered, making it
+easy to write tests which rely on multiple APIs whose service clients are in
+different plugins.
+
+Example implementation of ``get_service_clients``::
+
+ def get_service_clients(self):
+ # Example implementation with two service clients
+ my_service1_config = config.service_client_config('my_service')
+ params_my_service1 = {
+ 'name': 'my_service_v1',
+ 'service_version': 'my_service.v1',
+ 'module_path': 'plugin_tempest_tests.services.my_service.v1',
+ 'client_names': ['API1Client', 'API2Client'],
+ }
+ params_my_service1.update(my_service_config)
+ my_service2_config = config.service_client_config('my_service')
+ params_my_service2 = {
+ 'name': 'my_service_v2',
+ 'service_version': 'my_service.v2',
+ 'module_path': 'plugin_tempest_tests.services.my_service.v2',
+ 'client_names': ['API1Client', 'API2Client'],
+ }
+ params_my_service2.update(my_service2_config)
+ return [params_my_service1, params_my_service2]
+
+Parameters:
+
+* **name**: Name of the attribute used to access the ``ClientsFactory`` from
+ the ``ServiceClients`` instance. See example below.
+* **service_version**: Tempest enforces a single implementation for each
+ service client. Available service clients are held in a ``ClientsRegistry``
+ singleton, and registered with ``service_version``, which means that
+ ``service_version`` must be unique and it should represent the service API
+ and version implemented by the service client.
+* **module_path**: Relative to the service client module, from the root of the
+ plugin.
+* **client_names**: Name of the classes that implement service clients in the
+ service clients module.
+
+Example usage of the the service clients in tests::
+
+ # my_creds is instance of tempest.lib.auth.Credentials
+ # identity_uri is v2 or v3 depending on the configuration
+ from tempest.lib.services import clients
+
+ my_clients = clients.ServiceClients(my_creds, identity_uri)
+ my_service1_api1_client = my_clients.my_service_v1.API1Client()
+ my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any')
+
+Automatic configuration and registration of service clients imposes some extra
+constraints on the structure of the configuration options exposed by the
+plugin.
+
+First ``service_version`` should be in the format `service_config[.version]`.
+The `.version` part is optional, and should only be used if there are multiple
+versions of the same API available. The `service_config` must match the name of
+a configuration options group defined by the plugin. Different versions of one
+API must share the same configuration group.
+
+Second the configuration options group `service_config` must contain the
+following options:
+
+* `catalog_type`: corresponds to `service` in the catalog
+* `endpoint_type`
+
+The following options will be honoured if defined, but they are not mandatory,
+as they do not necessarily apply to all service clients.
+
+* `region`: default to identity.region
+* `build_timeout` : default to compute.build_timeout
+* `build_interval`: default to compute.build_interval
+
+Third the service client classes should inherit from ``RestClient``, should
+accept generic keyword arguments, and should pass those arguments to the
+``__init__`` method of ``RestClient``. Extra arguments can be added. For
+instance::
+
+ class MyAPIClient(rest_client.RestClient):
+
+ def __init__(self, auth_provider, service, region,
+ my_arg, my_arg2=True, **kwargs):
+ super(MyAPIClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+ self.my_arg = my_arg
+ self.my_args2 = my_arg
+
+Finally the service client should be structured in a python module, so that all
+service client classes are importable from it. Each major API version should
+have its own module.
+
+The following folder and module structure is recommended for a single major
+API version::
+
+ plugin_dir/
+ services/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+
+The content of __init__.py module should be::
+
+ from client_api_1.py import API1Client
+ from client_api_2.py import API2Client
+
+ __all__ = ['API1Client', 'API2Client']
+
+The following folder and module structure is recommended for multiple major
+API version::
+
+ plugin_dir/
+ services/
+ v1/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+ v2/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+
+The content each of __init__.py module under vN should be::
+
+ from client_api_1.py import API1Client
+ from client_api_2.py import API2Client
+
+ __all__ = ['API1Client', 'API2Client']
+
Using Plugins
=============
diff --git a/doc/source/test-removal.rst b/doc/source/test-removal.rst
index 6570bb7..79a5846 100644
--- a/doc/source/test-removal.rst
+++ b/doc/source/test-removal.rst
@@ -31,7 +31,7 @@
#. The tests proposed for removal must have equiv. coverage in a different
project's test suite (whether this is another gating test project, or an in
- tree funcitonal test suite) For API tests preferably the other project will
+ tree functional test suite). For API tests preferably the other project will
have a similar source of friction in place to prevent breaking api changes
so that we don't regress and let breaking api changes slip through the
gate.
@@ -62,7 +62,7 @@
SELECT * from tests where test_id like "%test_id%";
(where $test_id is the full test_id, but truncated to the class because of
-setupclass or teardownclass failures)
+setupClass or tearDownClass failures)
You can access the infra mysql subunit2sql db w/ read-only permissions with:
@@ -113,7 +113,7 @@
well ahead of the scheduled meeting. Since the meeting time will be well known
ahead of time anyone who depends on the tests will have ample time beforehand
to outline any concerns on the before the meeting. To give ample time for
-people to respond to removal proposals please add things to the agend by the
+people to respond to removal proposals please add things to the agenda by the
Monday before the meeting.
The other option is to raise the removal on the openstack-dev mailing list.
@@ -163,6 +163,6 @@
anything that lives in tempest which doesn't test one of these projects can be
removed assuming there is equivalent testing elsewhere. Preferably using the
`tempest plugin mechanism`_
-to mantain continuity after migrating the tests out of tempest
+to maintain continuity after migrating the tests out of tempest.
.. _tempest plugin mechanism: http://docs.openstack.org/developer/tempest/plugin.html
diff --git a/etc/javelin-resources.yaml.sample b/etc/javelin-resources.yaml.sample
deleted file mode 100644
index 1565686..0000000
--- a/etc/javelin-resources.yaml.sample
+++ /dev/null
@@ -1,63 +0,0 @@
-tenants:
- - javelin
- - discuss
-
-users:
- - name: javelin
- pass: gungnir
- tenant: javelin
- - name: javelin2
- pass: gungnir2
- tenant: discuss
-
-secgroups:
- - name: secgroup1
- owner: javelin
- description: SecurityGroup1
- rules:
- - 'icmp -1 -1 0.0.0.0/0'
- - 'tcp 22 22 0.0.0.0/0'
- - name: secgroup2
- owner: javelin2
- description: SecurityGroup2
- rules:
- - 'tcp 80 80 0.0.0.0/0'
-
-images:
- - name: cirros1
- owner: javelin
- imgdir: images
- file: cirros.img
- container_format: bare
- disk_format: qcow2
- - name: cirros2
- owner: javelin2
- imgdir: files/images/cirros-0.3.2-x86_64-uec
- file: cirros-0.3.2-x86_64-blank.img
- container_format: ami
- disk_format: ami
- aki: cirros-0.3.2-x86_64-vmlinuz
- ari: cirros-0.3.2-x86_64-initrd
-
-networks:
- - name: network1
- owner: javelin
- - name: network2
- owner: javelin2
-
-subnets:
- - name: net1-subnet1
- range: 10.1.0.0/24
- network: network1
- owner: javelin
- - name: net2-subnet2
- range: 192.168.1.0/24
- network: network2
- owner: javelin2
-
-objects:
- - container: container1
- name: object1
- owner: javelin
- file: /etc/hosts
- swift_role: Member
diff --git a/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml b/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
new file mode 100644
index 0000000..a360f8e
--- /dev/null
+++ b/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - RestClient now supports setting timeout in urllib3.poolmanager.
+ Clients will use CONF.service_clients.http_timeout for timeout
+ value to wait for http request to response.
+ - KeystoneAuthProvider will accept http_timeout and will use it in
+ get_credentials.
diff --git a/releasenotes/notes/add-new-identity-clients-3c3afd674a395bde.yaml b/releasenotes/notes/add-new-identity-clients-3c3afd674a395bde.yaml
new file mode 100644
index 0000000..3ec8b56
--- /dev/null
+++ b/releasenotes/notes/add-new-identity-clients-3c3afd674a395bde.yaml
@@ -0,0 +1,13 @@
+---
+features:
+ - |
+ Define identity service clients as libraries.
+ The following identity service clients are defined as library interface,
+ so the other projects can use these modules as stable libraries without
+ any maintenance changes.
+
+ * endpoints_client(v3)
+ * policies_client (v3)
+ * regions_client(v3)
+ * services_client(v3)
+ * projects_client(v3)
diff --git a/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml b/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
new file mode 100644
index 0000000..9e828f6
--- /dev/null
+++ b/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
@@ -0,0 +1,15 @@
+---
+features:
+ - |
+ Define identity service clients as libraries.
+ Add new service clients to the library interface so the other projects can use these modules as stable libraries without
+ any maintenance changes.
+
+ * identity_client(v2)
+ * groups_client(v3)
+ * trusts_client(v3)
+ * users_client(v3)
+ * identity_client(v3)
+ * roles_client(v3)
+ * inherited_roles_client(v3)
+ * credentials_client(v3)
diff --git a/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml b/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
index b457ddd..092014e 100644
--- a/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
+++ b/releasenotes/notes/add_subunit_describe_calls-5498a37e6cd66c4b.yaml
@@ -1,4 +1,8 @@
---
features:
- - Adds subunit-describe-calls. A parser for subunit streams to determine what
+ - |
+ Adds subunit-describe-calls. A parser for subunit streams to determine what
REST API calls are made inside of a test and in what order they are called.
+
+ * Input can be piped in or a file can be specified
+ * Output is shortened for stdout, the output file has more information
diff --git a/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml b/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml
new file mode 100644
index 0000000..53741da
--- /dev/null
+++ b/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml
@@ -0,0 +1,18 @@
+---
+features:
+ - The Tempest plugin interface contains a new optional method, which allows
+ plugins to declare and automatically register any service client defined
+ in the plugin.
+ - tempest.lib exposes a new stable interface, the clients module and
+ ServiceClients class, which provides a convinient way for plugin tests to
+ access service clients defined in Tempest as well as service clients
+ defined in all loaded plugins.
+ The new ServiceClients class only exposes for now the service clients
+ which are in tempest.lib, i.e. compute, network and image. The remaing
+ service clients (identity, volume and object-storage) will be added in
+ future updates.
+deprecations:
+ - The new clients module provides a stable alternative to tempest classes
+ manager.Manager and clients.Manager. manager.Manager only exists now
+ to smoothen the transition of plugins to the new interface, but it will
+ be removed shortly without further notice.
diff --git a/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml b/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml
new file mode 100644
index 0000000..543cf7b
--- /dev/null
+++ b/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml
@@ -0,0 +1,5 @@
+---
+deprecations:
+ - The ``call_until_true`` function is moved from the ``tempest.test`` module
+ to the ``tempest.lib.common.utils.test_utils`` module. Backward
+ compatibilty is preserved until Ocata.
diff --git a/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml b/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml
new file mode 100644
index 0000000..cfe97c5
--- /dev/null
+++ b/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+
+ - The ``nova_cert`` option default is changed to ``False``. The nova
+ certification management APIs were a hold over from ec2, and are
+ not used by any other parts of nova. They are deprecated for
+ removal in nova after the newton release. This makes false a more
+ sensible default going forward.
\ No newline at end of file
diff --git a/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml b/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml
new file mode 100644
index 0000000..64f729a
--- /dev/null
+++ b/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - A new optional interface `TempestPlugin.get_service_clients`
+ is available to plugins. It allows them to declare
+ any service client they implement. For now this is used by
+ tempest only, for auto-registration of service clients
+ in the new class `ServiceClients`.
+ - A new singleton class `clients.ClientsRegistry` is
+ available. It holds the service clients registration data
+ from all plugins. It is used by `ServiceClients` for
+ auto-registration of the service clients implemented
+ in plugins.
diff --git a/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml b/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml
new file mode 100644
index 0000000..8e893b8
--- /dev/null
+++ b/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - The previously deprecated Javelin utility has been removed from Tempest.
+ As an alternative Ansible can be used to construct similar yaml workflows
+ to what Javelin used to provide.
diff --git a/releasenotes/notes/volume-clients-as-library-9a3444dd63c134b3.yaml b/releasenotes/notes/volume-clients-as-library-9a3444dd63c134b3.yaml
new file mode 100644
index 0000000..cf504ad
--- /dev/null
+++ b/releasenotes/notes/volume-clients-as-library-9a3444dd63c134b3.yaml
@@ -0,0 +1,18 @@
+---
+features:
+ - |
+ Define volume service clients as libraries
+ The following volume service clients are defined as library interface,
+ so the other projects can use these modules as stable libraries
+ without any maintenance changes.
+
+ * availability_zone_client(v1)
+ * availability_zone_client(v2)
+ * extensions_client(v1)
+ * extensions_client(v2)
+ * hosts_client(v1)
+ * hosts_client(v2)
+ * quotas_client(v1)
+ * quotas_client(v2)
+ * services_client(v1)
+ * services_client(v2)
diff --git a/requirements.txt b/requirements.txt
index 84be219..a773d16 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,18 +8,18 @@
paramiko>=2.0 # LGPLv2.1+
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.8.0 # Apache-2.0
-oslo.config>=3.10.0 # Apache-2.0
+oslo.config>=3.14.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.14.0 # Apache-2.0
+oslo.utils>=3.16.0 # Apache-2.0
six>=1.9.0 # MIT
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
+stevedore>=1.16.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
os-testr>=0.7.0 # Apache-2.0
urllib3>=1.15.1 # MIT
+debtcollector>=1.2.0 # Apache-2.0
diff --git a/run_tests.sh b/run_tests.sh
index 22314b6..a856bb4 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -17,6 +17,44 @@
echo " -- [TESTROPTIONS] After the first '--' you can pass arbitrary arguments to testr "
}
+function deprecation_warning {
+ cat <<EOF
+-------------------------------------------------------------------------
+WARNING: run_tests.sh is deprecated and this script will be removed after
+the Newton release. All tests should be run through testr/ostestr or tox.
+
+To run style checks:
+
+ tox -e pep8
+
+To run python 2.7 unit tests
+
+ tox -e py27
+
+To run unit tests and generate coverage report
+
+ tox -e cover
+
+To run a subset of any of these tests:
+
+ tox -e py27 someregex
+
+ i.e.: tox -e py27 test_servers
+
+Additional tox targets are available in tox.ini. For more information
+see:
+http://docs.openstack.org/project-team-guide/project-setup/python.html
+
+NOTE: if you want to use testr to run tests, you can instead use:
+
+ OS_TEST_PATH=./tempest/tests testr run
+
+Documentation on using testr directly can be found at
+http://testrepository.readthedocs.org/en/latest/MANUAL.html
+-------------------------------------------------------------------------
+EOF
+}
+
testrargs=""
just_pep8=0
venv=${VENV:-.venv}
@@ -32,6 +70,8 @@
config_file=""
update=0
+deprecation_warning
+
if ! options=$(getopt -o VNnfuctphd -l virtual-env,no-virtual-env,no-site-packages,force,update,serial,coverage,pep8,help,debug -- "$@")
then
# parse error
diff --git a/setup.cfg b/setup.cfg
index 2a3000d..50bf891 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -17,6 +17,7 @@
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
+ Programming Language :: Python :: 3.5
[files]
packages =
@@ -27,7 +28,6 @@
[entry_points]
console_scripts =
verify-tempest-config = tempest.cmd.verify_tempest_config:main
- javelin2 = tempest.cmd.javelin:main
run-tempest-stress = tempest.cmd.run_stress:main
tempest-cleanup = tempest.cmd.cleanup:main
tempest-account-generator = tempest.cmd.account_generator:main
diff --git a/tempest/README.rst b/tempest/README.rst
index 113b191..c9a0491 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -26,10 +26,9 @@
API tests are validation tests for the OpenStack API. They should not
use the existing python clients for OpenStack, but should instead use
-the tempest implementations of clients. This allows us to test both
-XML and JSON. Having raw clients also lets us pass invalid JSON and
-XML to the APIs and see the results, something we could not get with
-the native clients.
+the tempest implementations of clients. Having raw clients let us
+pass invalid JSON to the APIs and see the results, something we could
+not get with the native clients.
When it makes sense, API testing should be moved closer to the
projects themselves, possibly as functional tests in their unit test
diff --git a/tempest/api/baremetal/admin/base.py b/tempest/api/baremetal/admin/base.py
index f7891dd..ac5986c 100644
--- a/tempest/api/baremetal/admin/base.py
+++ b/tempest/api/baremetal/admin/base.py
@@ -96,10 +96,10 @@
@classmethod
@creates('chassis')
- def create_chassis(cls, description=None, expect_errors=False):
+ def create_chassis(cls, description=None):
"""Wrapper utility for creating test chassis.
- :param description: A description of the chassis. if not supplied,
+ :param description: A description of the chassis. If not supplied,
a random value will be generated.
:return: Created chassis.
@@ -114,6 +114,7 @@
memory_mb=4096):
"""Wrapper utility for creating test baremetal nodes.
+ :param chassis_id: The unique identifier of the chassis.
:param cpu_arch: CPU architecture of the node. Default: x86.
:param cpus: Number of CPUs. Default: 8.
:param local_gb: Disk size. Default: 10.
@@ -133,6 +134,7 @@
def create_port(cls, node_id, address, extra=None, uuid=None):
"""Wrapper utility for creating test ports.
+ :param node_id: The unique identifier of the node.
:param address: MAC address of the port.
:param extra: Meta data of the port. If not supplied, an empty
dictionary will be created.
@@ -150,7 +152,7 @@
def delete_chassis(cls, chassis_id):
"""Deletes a chassis having the specified UUID.
- :param uuid: The unique identifier of the chassis.
+ :param chassis_id: The unique identifier of the chassis.
:return: Server response.
"""
@@ -166,7 +168,7 @@
def delete_node(cls, node_id):
"""Deletes a node having the specified UUID.
- :param uuid: The unique identifier of the node.
+ :param node_id: The unique identifier of the node.
:return: Server response.
"""
@@ -182,7 +184,7 @@
def delete_port(cls, port_id):
"""Deletes a port having the specified UUID.
- :param uuid: The unique identifier of the port.
+ :param port_id: The unique identifier of the port.
:return: Server response.
"""
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 84b00a7..fbcc1d1 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -25,8 +25,6 @@
class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Aggregates API that require admin privileges"""
- _host_key = 'OS-EXT-SRV-ATTR:host'
-
@classmethod
def setup_clients(cls):
super(AggregatesAdminTestJSON, cls).setup_clients()
@@ -181,7 +179,7 @@
host=self.host)
aggregates = self.client.list_aggregates()['aggregates']
- aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates)
+ aggs = [agg for agg in aggregates if agg['id'] == aggregate['id']]
self.assertEqual(1, len(aggs))
agg = aggs[0]
self.assertEqual(aggregate_name, agg['name'])
@@ -223,4 +221,4 @@
availability_zone=az_name,
wait_until='ACTIVE')
body = admin_servers_client.show_server(server['id'])['server']
- self.assertEqual(self.host, body[self._host_key])
+ self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 6b75aee..3c4e313 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -36,8 +36,8 @@
cls.az_name_prefix = 'test_az'
hosts_all = cls.os_adm.hosts_client.list_hosts()['hosts']
- hosts = map(lambda x: x['host_name'],
- filter(lambda y: y['service'] == 'compute', hosts_all))
+ hosts = ([host['host_name']
+ for host in hosts_all if host['service'] == 'compute'])
cls.host = hosts[0]
@test.attr(type=['negative'])
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
new file mode 100644
index 0000000..ee8ed14
--- /dev/null
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -0,0 +1,211 @@
+# Copyright 2016 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log
+
+from tempest.api.compute import base
+from tempest.common import compute
+from tempest.common import credentials_factory as credentials
+from tempest.common import waiters
+from tempest import config
+from tempest import exceptions
+from tempest.lib.common.utils import test_utils
+from tempest import test
+
+CONF = config.CONF
+LOG = log.getLogger(__name__)
+
+
+# NOTE(mriedem): This is in the admin directory only because it requires
+# force_tenant_isolation=True, but doesn't extend BaseV2ComputeAdminTest
+# because it doesn't actually use any admin credentials in the tests.
+class AutoAllocateNetworkTest(base.BaseV2ComputeTest):
+ """Tests auto-allocating networks with the v2.37 microversion.
+
+ These tests rely on Neutron being enabled. Also, the tenant must not have
+ any network resources available to it so we can make sure that Nova
+ calls to Neutron to automatically allocate the network topology.
+ """
+
+ force_tenant_isolation = True
+
+ min_microversion = '2.37'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(AutoAllocateNetworkTest, cls).skip_checks()
+ identity_version = cls.get_identity_version()
+ if not credentials.is_admin_available(
+ identity_version=identity_version):
+ msg = "Missing Identity Admin API credentials in configuration."
+ raise cls.skipException(msg)
+ if not CONF.service_available.neutron:
+ raise cls.skipException('Neutron is required')
+ if not test.is_extension_enabled('auto-allocated-topology', 'network'):
+ raise cls.skipException(
+ 'auto-allocated-topology extension is not available')
+
+ @classmethod
+ def setup_credentials(cls):
+ # Do not create network resources for these tests.
+ cls.set_network_resources()
+ super(AutoAllocateNetworkTest, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(AutoAllocateNetworkTest, cls).setup_clients()
+ cls.servers_client = cls.servers_client
+ cls.networks_client = cls.os.networks_client
+ cls.routers_client = cls.os.routers_client
+ cls.subnets_client = cls.os.subnets_client
+ cls.ports_client = cls.os.ports_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(AutoAllocateNetworkTest, cls).resource_setup()
+ # Sanity check that there are no networks available to the tenant.
+ # This is essentially what Nova does for getting available networks.
+ tenant_id = cls.networks_client.tenant_id
+ # (1) Retrieve non-public network list owned by the tenant.
+ search_opts = {'tenant_id': tenant_id, 'shared': False}
+ nets = cls.networks_client.list_networks(
+ **search_opts).get('networks', [])
+ if nets:
+ raise exceptions.TempestException(
+ 'Found tenant networks: %s' % nets)
+ # (2) Retrieve shared network list.
+ search_opts = {'shared': True}
+ nets = cls.networks_client.list_networks(
+ **search_opts).get('networks', [])
+ if nets:
+ raise exceptions.TempestException(
+ 'Found shared networks: %s' % nets)
+
+ @classmethod
+ def resource_cleanup(cls):
+ """Deletes any auto_allocated_network and it's associated resources."""
+
+ # Find the auto-allocated router for the tenant.
+ # This is a bit hacky since we don't have a great way to find the
+ # auto-allocated router given the private tenant network we have.
+ routers = cls.routers_client.list_routers().get('routers', [])
+ if len(routers) > 1:
+ # This indicates a race where nova is concurrently calling the
+ # neutron auto-allocated-topology API for multiple server builds
+ # at the same time (it's called from nova-compute when setting up
+ # networking for a server). Neutron will detect duplicates and
+ # automatically clean them up, but there is a window where the API
+ # can return multiple and we don't have a good way to filter those
+ # out right now, so we'll just handle them.
+ LOG.info('(%s) Found more than one router for tenant.',
+ test_utils.find_test_caller())
+
+ # Let's just blindly remove any networks, duplicate or otherwise, that
+ # the test might have created even though Neutron will cleanup
+ # duplicate resources automatically (so ignore 404s).
+ networks = cls.networks_client.list_networks().get('networks', [])
+
+ for router in routers:
+ # Disassociate the subnets from the router. Because of the race
+ # mentioned above the subnets might not be associated with the
+ # router so ignore any 404.
+ for network in networks:
+ for subnet_id in network['subnets']:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.routers_client.remove_router_interface,
+ router['id'], subnet_id=subnet_id)
+
+ # Delete the router.
+ cls.routers_client.delete_router(router['id'])
+
+ for network in networks:
+ # Get and delete the ports for the given network.
+ ports = cls.ports_client.list_ports(
+ network_id=network['id']).get('ports', [])
+ for port in ports:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.ports_client.delete_port, port['id'])
+
+ # Delete the subnets.
+ for subnet_id in network['subnets']:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.subnets_client.delete_subnet, subnet_id)
+
+ # Delete the network.
+ test_utils.call_and_ignore_notfound_exc(
+ cls.networks_client.delete_network, network['id'])
+
+ @test.idempotent_id('5eb7b8fa-9c23-47a2-9d7d-02ed5809dd34')
+ def test_server_create_no_allocate(self):
+ """Tests that no networking is allocated for the server."""
+ # create the server with no networking
+ server, _ = compute.create_test_server(
+ self.os, networks='none', wait_until='ACTIVE')
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.servers_client, server['id'])
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ # get the server ips
+ addresses = self.servers_client.list_addresses(
+ server['id'])['addresses']
+ # assert that there is no networking
+ self.assertEqual({}, addresses)
+
+ @test.idempotent_id('2e6cf129-9e28-4e8a-aaaa-045ea826b2a6')
+ def test_server_multi_create_auto_allocate(self):
+ """Tests that networking is auto-allocated for multiple servers."""
+
+ # Create multiple servers with auto networking to make sure the
+ # automatic network allocation is atomic. Using a minimum of three
+ # servers is essential for this scenario because:
+ #
+ # - First request sees no networks for the tenant so it auto-allocates
+ # one from Neutron, let's call that net1.
+ # - Second request sees no networks for the tenant so it auto-allocates
+ # one from Neutron. Neutron creates net2 but sees it's a duplicate
+ # so it queues net2 for deletion and returns net1 from the API and
+ # Nova uses that for the second server request.
+ # - Third request sees net1 and net2 for the tenant and fails with a
+ # NetworkAmbiguous 400 error.
+ _, servers = compute.create_test_server(
+ self.os, networks='auto', wait_until='ACTIVE',
+ min_count=3)
+ server_nets = set()
+ for server in servers:
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.servers_client, server['id'])
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ # get the server ips
+ addresses = self.servers_client.list_addresses(
+ server['id'])['addresses']
+ # assert that there is networking (should only be one)
+ self.assertEqual(1, len(addresses))
+ server_nets.add(list(addresses.keys())[0])
+ # all servers should be on the same network
+ self.assertEqual(1, len(server_nets))
+
+ # List the networks for the tenant; we filter on admin_state_up=True
+ # because the auto-allocated-topology code in Neutron won't set that
+ # to True until the network is ready and is returned from the API.
+ # Duplicate networks created from a race should have
+ # admin_state_up=False.
+ search_opts = {'tenant_id': self.networks_client.tenant_id,
+ 'shared': False,
+ 'admin_state_up': True}
+ nets = self.networks_client.list_networks(
+ **search_opts).get('networks', [])
+ self.assertEqual(1, len(nets))
+ # verify the single private tenant network is the one that the servers
+ # are using also
+ self.assertIn(nets[0]['name'], server_nets)
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 94635ff..18a6afc 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -26,7 +26,8 @@
class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
- _host_key = 'OS-EXT-SRV-ATTR:host'
+ max_microversion = '2.24'
+ block_migration = None
@classmethod
def skip_checks(cls):
@@ -61,15 +62,19 @@
return body
def _get_host_for_server(self, server_id):
- return self._get_server_details(server_id)[self._host_key]
+ return self._get_server_details(server_id)['OS-EXT-SRV-ATTR:host']
def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
- block_migration = (CONF.compute_feature_enabled.
- block_migration_for_live_migration and
- not volume_backed)
+ kwargs = dict()
+ block_migration = getattr(self, 'block_migration', None)
+ if self.block_migration is None:
+ kwargs['disk_over_commit'] = False
+ block_migration = (CONF.compute_feature_enabled.
+ block_migration_for_live_migration and
+ not volume_backed)
body = self.admin_servers_client.live_migrate_server(
server_id, host=dest_host, block_migration=block_migration,
- disk_over_commit=False)
+ **kwargs)
return body
def _get_host_other_than(self, host):
@@ -151,7 +156,7 @@
target_host = self._get_host_other_than(actual_host)
volume = self.volumes_client.create_volume(
- display_name='test')['volume']
+ size=CONF.volume.volume_size, display_name='test')['volume']
waiters.wait_for_volume_status(self.volumes_client,
volume['id'], 'available')
@@ -167,3 +172,9 @@
waiters.wait_for_server_status(self.servers_client,
server_id, 'ACTIVE')
self.assertEqual(target_host, self._get_host_for_server(server_id))
+
+
+class LiveAutoBlockMigrationV225TestJSON(LiveBlockMigrationTestJSON):
+ min_microversion = '2.25'
+ max_microversion = 'latest'
+ block_migration = 'auto'
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index b1f0755..7d97ce2 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -117,8 +117,6 @@
password=password,
project=project,
email=email)
- if 'user' in user:
- user = user['user']
user_id = user['id']
self.addCleanup(self.identity_utils.delete_user, user_id)
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index 1494745..e329869 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -15,11 +15,8 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest):
@@ -65,7 +62,7 @@
# Fetch all security groups based on 'all_tenants' search filter
fetched_list = self.adm_client.list_security_groups(
all_tenants='true')['security_groups']
- sec_group_id_list = map(lambda sg: sg['id'], fetched_list)
+ sec_group_id_list = [sg['id'] for sg in fetched_list]
# Now check if all created Security Groups are present in fetched list
for sec_group in security_group_list:
self.assertIn(sec_group['id'], sec_group_id_list)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
old mode 100644
new mode 100755
index 3eb6d94..aabb40c
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -24,8 +24,6 @@
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Servers API using admin privileges"""
- _host_key = 'OS-EXT-SRV-ATTR:host'
-
@classmethod
def setup_clients(cls):
super(ServersAdminTestJSON, cls).setup_clients()
@@ -37,23 +35,16 @@
def resource_setup(cls):
super(ServersAdminTestJSON, cls).resource_setup()
- cls.s1_name = data_utils.rand_name('server')
+ cls.s1_name = data_utils.rand_name(cls.__name__ + '-server')
server = cls.create_test_server(name=cls.s1_name,
wait_until='ACTIVE')
cls.s1_id = server['id']
- cls.s2_name = data_utils.rand_name('server')
+ cls.s2_name = data_utils.rand_name(cls.__name__ + '-server')
server = cls.create_test_server(name=cls.s2_name,
wait_until='ACTIVE')
cls.s2_id = server['id']
- @test.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
- def test_list_servers_by_admin(self):
- # Listing servers by admin user returns empty list by default
- body = self.client.list_servers(detail=True)
- servers = body['servers']
- self.assertEqual([], servers)
-
@test.idempotent_id('06f960bb-15bb-48dc-873d-f96e89be7870')
def test_list_servers_filter_by_error_status(self):
# Filter the list of servers by server error status
@@ -70,6 +61,26 @@
self.assertIn(self.s1_id, map(lambda x: x['id'], servers))
self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers))
+ @test.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb')
+ def test_list_servers_detailed_filter_by_invalid_status(self):
+ params = {'status': 'invalid_status'}
+ body = self.client.list_servers(detail=True, **params)
+ servers = body['servers']
+ self.assertEqual([], servers)
+
+ @test.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
+ def test_list_servers_by_admin(self):
+ # Listing servers by admin user returns a list which doesn't
+ # contain the other tenants' server by default
+ body = self.client.list_servers(detail=True)
+ servers = body['servers']
+
+ # This case is for the test environments which contain
+ # the existing servers before testing
+ servers_name = [server['name'] for server in servers]
+ self.assertNotIn(self.s1_name, servers_name)
+ self.assertNotIn(self.s2_name, servers_name)
+
@test.idempotent_id('9f5579ae-19b4-4985-a091-2a5d56106580')
def test_list_servers_by_admin_with_all_tenants(self):
# Listing servers by admin user with all tenants parameter
@@ -77,7 +88,7 @@
params = {'all_tenants': ''}
body = self.client.list_servers(detail=True, **params)
servers = body['servers']
- servers_name = map(lambda x: x['name'], servers)
+ servers_name = [server['name'] for server in servers]
self.assertIn(self.s1_name, servers_name)
self.assertIn(self.s2_name, servers_name)
@@ -103,7 +114,7 @@
@test.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f')
def test_list_servers_filter_by_exist_host(self):
# Filter the list of servers by existent host
- name = data_utils.rand_name('server')
+ name = data_utils.rand_name(self.__class__.__name__ + '-server')
network = self.get_tenant_network()
network_kwargs = fixed_network.set_networks_kwarg(network)
# We need to create the server as an admin, so we can't use
@@ -114,7 +125,7 @@
self.addCleanup(self.client.delete_server, test_server['id'])
server = self.client.show_server(test_server['id'])['server']
self.assertEqual(server['status'], 'ACTIVE')
- hostname = server[self._host_key]
+ hostname = server['OS-EXT-SRV-ATTR:host']
params = {'host': hostname}
body = self.client.list_servers(**params)
servers = body['servers']
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
old mode 100644
new mode 100755
index 7437c14..23b16e7
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -34,13 +34,14 @@
cls.client = cls.os_adm.servers_client
cls.non_adm_client = cls.servers_client
cls.flavors_client = cls.os_adm.flavors_client
+ cls.quotas_client = cls.os_adm.quotas_client
@classmethod
def resource_setup(cls):
super(ServersAdminNegativeTestJSON, cls).resource_setup()
cls.tenant_id = cls.client.tenant_id
- cls.s1_name = data_utils.rand_name('server')
+ cls.s1_name = data_utils.rand_name(cls.__name__ + '-server')
server = cls.create_test_server(name=cls.s1_name,
wait_until='ACTIVE')
cls.s1_id = server['id']
@@ -64,11 +65,11 @@
self.useFixture(fixtures.LockFixture('compute_quotas'))
flavor_name = data_utils.rand_name("flavor")
flavor_id = self._get_unused_flavor_id()
- quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
- ['quota_set'])
+ quota_set = self.quotas_client.show_quota_set(
+ self.tenant_id)['quota_set']
ram = int(quota_set['ram'])
if ram == -1:
- raise self.skipException("default ram quota set is -1,"
+ raise self.skipException("ram quota set is -1,"
" cannot test overlimit")
ram += 1
vcpus = 8
@@ -93,11 +94,11 @@
flavor_name = data_utils.rand_name("flavor")
flavor_id = self._get_unused_flavor_id()
ram = 512
- quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
- ['quota_set'])
+ quota_set = self.quotas_client.show_quota_set(
+ self.tenant_id)['quota_set']
vcpus = int(quota_set['cores'])
if vcpus == -1:
- raise self.skipException("default cores quota set is -1,"
+ raise self.skipException("cores quota set is -1,"
" cannot test overlimit")
vcpus += 1
disk = 10
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index a4ed8dc..dbc22e0 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -16,6 +16,7 @@
import datetime
from tempest.api.compute import base
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as e
from tempest import test
@@ -59,8 +60,8 @@
return True
except e.InvalidHTTPResponseBody:
return False
- self.assertEqual(test.call_until_true(is_valid, duration, 1), True,
- "%s not return valid response in %s secs" % (
+ self.assertEqual(test_utils.call_until_true(is_valid, duration, 1),
+ True, "%s not return valid response in %s secs" % (
func.__name__, duration))
return self.resp
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 37aa5ac..5e75493 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -359,7 +359,7 @@
for address in addresses:
if address['version'] == CONF.validation.ip_version_for_ssh:
return address['addr']
- raise exceptions.ServerUnreachable()
+ raise exceptions.ServerUnreachable(server_id=server['id'])
else:
raise exceptions.InvalidConfiguration()
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 6f80730..999233d 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -71,7 +71,7 @@
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))
+ image_file = six.BytesIO((b'*' * 1024))
if CONF.image_feature_enabled.api_v1:
cls.glance_client.update_image(cls.image_id, data=image_file)
else:
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 150e8af..3754637 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -49,6 +49,9 @@
name=snapshot_name,
wait_until='SAVING')
self.client.delete_image(image['id'])
+ msg = ('The image with ID {image_id} failed to be deleted'
+ .format(image_id=image['id']))
+ self.assertTrue(self.client.is_resource_deleted(image['id']), msg)
@test.idempotent_id('aaacd1d0-55a2-4ce8-818a-b5439df8adc9')
def test_create_image_from_stopped_server(self):
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
old mode 100644
new mode 100755
index 9017461..f340658
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -60,7 +60,7 @@
def _create_image():
params = {
- 'name': data_utils.rand_name('image'),
+ 'name': data_utils.rand_name(cls.__name__ + '-image'),
'container_format': 'bare',
'disk_format': 'raw'
}
@@ -78,7 +78,7 @@
# 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))
+ image_file = six.BytesIO((b'*' * 1024))
if CONF.image_feature_enabled.api_v1:
cls.glance_client.update_image(image_id, data=image_file)
else:
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 853ef31..4f53663 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -23,7 +23,8 @@
def not_existing_id():
- if CONF.service_available.neutron:
+ if (CONF.service_available.neutron and
+ test.is_extension_enabled('security-group', 'network')):
return data_utils.rand_uuid()
else:
return data_utils.rand_int_id(start=999)
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 5125e2b..e6abf28 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -44,9 +44,11 @@
security_group_id.append(body[i]['id'])
# Generate a non-existent security group id
while True:
- non_exist_id = data_utils.rand_int_id(start=999)
- if self.neutron_available:
+ if (self.neutron_available and
+ test.is_extension_enabled('security-group', 'network')):
non_exist_id = data_utils.rand_uuid()
+ else:
+ non_exist_id = data_utils.rand_int_id(start=999)
if non_exist_id not in security_group_id:
break
return non_exist_id
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index fdf55e5..7c12bf9 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -16,9 +16,12 @@
import time
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common.utils import net_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -48,6 +51,7 @@
cls.networks_client = cls.os.networks_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
+ cls.servers_client = cls.servers_client
def wait_for_interface_status(self, server, port_id, status):
"""Waits for an interface to reach a given status."""
@@ -73,6 +77,34 @@
return body
+ # TODO(mriedem): move this into a common waiters utility module
+ def wait_for_port_detach(self, port_id):
+ """Waits for the port's device_id to be unset.
+
+ :param port_id: The id of the port being detached.
+ :returns: The final port dict from the show_port response.
+ """
+ port = self.ports_client.show_port(port_id)['port']
+ device_id = port['device_id']
+ start = int(time.time())
+
+ # NOTE(mriedem): Nova updates the port's device_id to '' rather than
+ # None, but it's not contractual so handle Falsey either way.
+ while device_id:
+ time.sleep(self.build_interval)
+ port = self.ports_client.show_port(port_id)['port']
+ device_id = port['device_id']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if device_id and timed_out:
+ message = ('Port %s failed to detach (device_id %s) within '
+ 'the required time (%s s).' %
+ (port_id, device_id, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ return port
+
def _check_interface(self, iface, port_id=None, network_id=None,
fixed_ip=None, mac_addr=None):
self.assertIn('port_state', iface)
@@ -240,3 +272,41 @@
if fixed_ip is not None:
break
self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
+
+ @decorators.skip_because(bug='1607714')
+ @test.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
+ def test_reassign_port_between_servers(self):
+ """Tests the following:
+
+ 1. Create a port in Neutron.
+ 2. Create two servers in Nova.
+ 3. Attach the port to the first server.
+ 4. Detach the port from the first server.
+ 5. Attach the port to the second server.
+ 6. Detach the port from the second server.
+ """
+ network = self.get_tenant_network()
+ network_id = network['id']
+ port = self.ports_client.create_port(network_id=network_id)
+ port_id = port['port']['id']
+ self.addCleanup(self.ports_client.delete_port, port_id)
+
+ # create two servers
+ _, servers = compute.create_test_server(
+ self.os, tenant_network=network, wait_until='ACTIVE', min_count=2)
+ # add our cleanups for the servers since we bypassed the base class
+ for server in servers:
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.servers_client, server['id'])
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+
+ for server in servers:
+ # attach the port to the server
+ iface = self.client.create_interface(
+ server['id'], port_id=port_id)['interfaceAttachment']
+ self._check_interface(iface, port_id=port_id)
+
+ # detach the port from the server; this is a cast in the compute
+ # API so we have to poll the port until the device_id is unset.
+ self.client.delete_interface(server['id'], port_id)
+ self.wait_for_port_detach(port_id)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
old mode 100644
new mode 100755
index da9d548..78f0db4
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -48,7 +48,7 @@
cls.meta = {'hello': 'world'}
cls.accessIPv4 = '1.1.1.1'
cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
- cls.name = data_utils.rand_name('server')
+ cls.name = data_utils.rand_name(cls.__name__ + '-server')
cls.password = data_utils.rand_password()
disk_config = cls.disk_config
cls.server_initial = cls.create_test_server(
@@ -139,7 +139,7 @@
hostname = linux_client.get_hostname()
msg = ('Failed while verifying servername equals hostname. Expected '
'hostname "%s" but got "%s".' % (self.name, hostname))
- self.assertEqual(self.name, hostname, msg)
+ self.assertEqual(self.name.lower(), hostname, msg)
@test.idempotent_id('ed20d3fb-9d1f-4329-b160-543fbd5d9811')
def test_create_server_with_scheduler_hint_group(self):
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index c1fbb12..26cbb090 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -17,13 +17,10 @@
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
-from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest import test
-CONF = config.CONF
-
class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
@@ -274,14 +271,9 @@
msg = 'fixed_network_name needs to be configured to run this test'
raise self.skipException(msg)
self.s1 = self.client.show_server(self.s1['id'])['server']
- for addr_spec in self.s1['addresses'][self.fixed_network_name]:
- ip = addr_spec['addr']
- if addr_spec['version'] == 4:
- params = {'ip': ip}
- break
- else:
- msg = "Skipped until bug 1450859 is resolved"
- raise self.skipException(msg)
+ # Get first ip address inspite of v4 or v6
+ addr_spec = self.s1['addresses'][self.fixed_network_name][0]
+ params = {'ip': addr_spec['addr']}
body = self.client.list_servers(**params)
servers = body['servers']
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 357c907..fcd5a24 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.compute import base
from tempest.common import waiters
from tempest.lib import exceptions as lib_exc
@@ -38,7 +36,7 @@
# tearDownClass method of the super-class.
cls.existing_fixtures = []
cls.deleted_fixtures = []
- for x in moves.xrange(2):
+ for x in range(2):
srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
old mode 100644
new mode 100755
index 0d06119..062e920
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -110,6 +110,10 @@
servers_client=self.client)
boot_time = linux_client.get_boot_time()
+ # NOTE: This sync is for avoiding the loss of pub key data
+ # in a server
+ linux_client.exec_command("sync")
+
self.client.reboot_server(self.server_id, type=reboot_type)
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
@@ -151,7 +155,7 @@
def test_rebuild_server(self):
# The server should be rebuilt using the provided image and data
meta = {'rebuild': 'server'}
- new_name = data_utils.rand_name('server')
+ new_name = data_utils.rand_name(self.__class__.__name__ + '-server')
password = 'rebuildPassw0rd'
rebuilt_server = self.client.rebuild_server(
self.server_id,
@@ -266,6 +270,9 @@
'SHUTOFF')
self.client.resize_server(self.server_id, self.flavor_ref_alt)
+ # NOTE(jlk): Explicitly delete the server to get a new one for later
+ # tests. Avoids resize down race issues.
+ self.addCleanup(self.delete_server, self.server_id)
waiters.wait_for_server_status(self.client, self.server_id,
'VERIFY_RESIZE')
@@ -281,10 +288,6 @@
# NOTE(mriedem): tearDown requires the server to be started.
self.client.start_server(self.server_id)
- # NOTE(jlk): Explicitly delete the server to get a new one for later
- # tests. Avoids resize down race issues.
- self.addCleanup(self.delete_server, self.server_id)
-
@test.idempotent_id('1499262a-9328-4eda-9068-db1ac57498d2')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@@ -305,6 +308,9 @@
# values after a resize is reverted
self.client.resize_server(self.server_id, self.flavor_ref_alt)
+ # NOTE(zhufl): Explicitly delete the server to get a new one for later
+ # tests. Avoids resize down race issues.
+ self.addCleanup(self.delete_server, self.server_id)
waiters.wait_for_server_status(self.client, self.server_id,
'VERIFY_RESIZE')
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index baa4f9a..cd90473 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import base64
+from oslo_serialization import base64
from tempest.api.compute import base
from tempest.common.utils.linux import remote_client
@@ -55,7 +55,7 @@
file_contents = 'This is a test file.'
file_path = '/test.txt'
personality = [{'path': file_path,
- 'contents': base64.b64encode(file_contents)}]
+ 'contents': base64.encode_as_text(file_contents)}]
password = data_utils.rand_password()
created_server = self.create_test_server(personality=personality,
adminPass=password,
@@ -79,7 +79,7 @@
server_id = server['id']
file_contents = 'Test server rebuild.'
personality = [{'path': 'rebuild.txt',
- 'contents': base64.b64encode(file_contents)}]
+ 'contents': base64.encode_as_text(file_contents)}]
rebuilt_server = self.client.rebuild_server(server_id,
self.image_ref_alt,
personality=personality)
@@ -100,7 +100,8 @@
for i in range(0, int(max_file_limit) + 1):
path = 'etc/test' + str(i) + '.txt'
personality.append({'path': path,
- 'contents': base64.b64encode(file_contents)})
+ 'contents': base64.encode_as_text(
+ file_contents)})
# A 403 Forbidden or 413 Overlimit (old behaviour) exception
# will be raised when out of quota
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
@@ -120,7 +121,7 @@
path = '/etc/test' + str(i) + '.txt'
person.append({
'path': path,
- 'contents': base64.b64encode(file_contents),
+ 'contents': base64.encode_as_text(file_contents),
})
password = data_utils.rand_password()
created_server = self.create_test_server(personality=person,
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
old mode 100644
new mode 100755
index e91857a..5aeba4e
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -52,7 +52,8 @@
# Creating a server with a name that already exists is allowed
# TODO(sdague): clear out try, we do cleanup one layer up
- server_name = data_utils.rand_name('server')
+ server_name = data_utils.rand_name(
+ self.__class__.__name__ + '-server')
server = self.create_test_server(name=server_name,
wait_until='ACTIVE')
id1 = server['id']
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
old mode 100644
new mode 100755
index 10ea31d..89be3f3
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -253,7 +253,8 @@
# Update name of a non-existent server
nonexistent_server = data_utils.rand_uuid()
- new_name = data_utils.rand_name('server') + '_updated'
+ new_name = data_utils.rand_name(
+ self.__class__.__name__ + '-server') + '_updated'
self.assertRaises(lib_exc.NotFound, self.client.update_server,
nonexistent_server, name=new_name)
@@ -301,7 +302,7 @@
# Pass a server ID that exceeds length limit to delete server
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
- sys.maxint + 1)
+ sys.maxsize + 1)
@test.attr(type=['negative'])
@test.idempotent_id('c5fa6041-80cd-483b-aa6d-4e45f19d093c')
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/test_live_block_migration_negative.py
index dc57396..ffd274f 100644
--- a/tempest/api/compute/test_live_block_migration_negative.py
+++ b/tempest/api/compute/test_live_block_migration_negative.py
@@ -24,8 +24,6 @@
class LiveBlockMigrationNegativeTestJSON(base.BaseV2ComputeAdminTest):
- _host_key = 'OS-EXT-SRV-ATTR:host'
-
@classmethod
def skip_checks(cls):
super(LiveBlockMigrationNegativeTestJSON, cls).skip_checks()
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 05c23ee..ff5dc49 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -57,122 +57,105 @@
waiters.wait_for_volume_status(self.volumes_client,
volume_id, 'available')
- def _delete_volume(self):
- # Delete the created Volumes
- if self.volume:
- self.volumes_client.delete_volume(self.volume['id'])
- self.volumes_client.wait_for_resource_deletion(self.volume['id'])
- self.volume = None
-
- def _create_and_attach(self, shelve_server=False):
+ def _create_server(self):
# Start a server and wait for it to become ready
- self.admin_pass = self.image_ssh_password
- self.server = self.create_test_server(
+ server = self.create_test_server(
validatable=True,
wait_until='ACTIVE',
- adminPass=self.admin_pass)
+ adminPass=self.image_ssh_password)
# Record addresses so that we can ssh later
- self.server['addresses'] = self.servers_client.list_addresses(
- self.server['id'])['addresses']
+ server['addresses'] = self.servers_client.list_addresses(
+ server['id'])['addresses']
+ return server
+ def _create_and_attach_volume(self, server):
# Create a volume and wait for it to become ready
- self.volume = self.volumes_client.create_volume(
+ volume = self.volumes_client.create_volume(
size=CONF.volume.volume_size, display_name='test')['volume']
- self.addCleanup(self._delete_volume)
+ self.addCleanup(self.delete_volume, volume['id'])
waiters.wait_for_volume_status(self.volumes_client,
- self.volume['id'], 'available')
-
- if shelve_server:
- # NOTE(andreaf) If we are going to shelve a server, we should
- # check first whether the server is ssh-able. Otherwise we won't
- # be able to distinguish failures introduced by shelve from
- # pre-existing ones. Also it's good to wait for cloud-init to be
- # done and sshd server to be running before shelving to avoid
- # breaking the VM
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(self.server),
- self.image_ssh_user,
- self.admin_pass,
- self.validation_resources['keypair']['private_key'])
- linux_client.validate_authentication()
- # If validation went ok, shelve the server
- compute.shelve_server(self.servers_client, self.server['id'])
+ volume['id'], 'available')
# Attach the volume to the server
self.attachment = self.servers_client.attach_volume(
- self.server['id'],
- volumeId=self.volume['id'],
+ server['id'],
+ volumeId=volume['id'],
device='/dev/%s' % self.device)['volumeAttachment']
waiters.wait_for_volume_status(self.volumes_client,
- self.volume['id'], 'in-use')
+ volume['id'], 'in-use')
- self.addCleanup(self._detach, self.server['id'], self.volume['id'])
+ self.addCleanup(self._detach, server['id'], volume['id'])
+ return volume
@test.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
- @testtools.skipUnless(CONF.validation.run_validation,
- 'SSH required for this test')
def test_attach_detach_volume(self):
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
- self._create_and_attach()
+ server = self._create_server()
+ volume = self._create_and_attach_volume(server)
- self.servers_client.stop_server(self.server['id'])
- waiters.wait_for_server_status(self.servers_client, self.server['id'],
+ self.servers_client.stop_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'SHUTOFF')
- self.servers_client.start_server(self.server['id'])
- waiters.wait_for_server_status(self.servers_client, self.server['id'],
+ self.servers_client.start_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(self.server),
- self.image_ssh_user,
- self.admin_pass,
- self.validation_resources['keypair']['private_key'],
- server=self.server,
- servers_client=self.servers_client)
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ self.image_ssh_user,
+ self.image_ssh_password,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- self.assertIn(self.device, partitions)
+ partitions = linux_client.get_partitions()
+ device_name_to_match = ' ' + self.device + '\n'
+ self.assertIn(device_name_to_match, partitions)
- self._detach(self.server['id'], self.volume['id'])
+ self._detach(server['id'], volume['id'])
self.attachment = None
- self.servers_client.stop_server(self.server['id'])
- waiters.wait_for_server_status(self.servers_client, self.server['id'],
+ self.servers_client.stop_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'SHUTOFF')
- self.servers_client.start_server(self.server['id'])
- waiters.wait_for_server_status(self.servers_client, self.server['id'],
+ self.servers_client.start_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(self.server),
- self.image_ssh_user,
- self.admin_pass,
- self.validation_resources['keypair']['private_key'],
- server=self.server,
- servers_client=self.servers_client)
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ self.image_ssh_user,
+ self.image_ssh_password,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- self.assertNotIn(self.device, partitions)
+ partitions = linux_client.get_partitions()
+ self.assertNotIn(device_name_to_match, partitions)
@test.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
def test_list_get_volume_attachments(self):
# Create Server, Volume and attach that Volume to Server
- self._create_and_attach()
+ server = self._create_server()
+ volume = self._create_and_attach_volume(server)
+
# List Volume attachment of the server
body = self.servers_client.list_volume_attachments(
- self.server['id'])['volumeAttachments']
+ server['id'])['volumeAttachments']
self.assertEqual(1, len(body))
self.assertIn(self.attachment, body)
# Get Volume attachment of the server
body = self.servers_client.show_volume_attachment(
- self.server['id'],
+ server['id'],
self.attachment['id'])['volumeAttachment']
- self.assertEqual(self.server['id'], body['serverId'])
- self.assertEqual(self.volume['id'], body['volumeId'])
+ self.assertEqual(server['id'], body['serverId'])
+ self.assertEqual(volume['id'], body['volumeId'])
self.assertEqual(self.attachment['id'], body['id'])
@@ -186,40 +169,71 @@
min_microversion = '2.20'
max_microversion = 'latest'
- def _unshelve_server_and_check_volumes(self, number_of_partition):
- # Unshelve the instance and check that there are expected volumes
- self.servers_client.unshelve_server(self.server['id'])
- waiters.wait_for_server_status(self.servers_client,
- self.server['id'],
- 'ACTIVE')
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(self.server['id']),
- self.image_ssh_user,
- self.admin_pass,
- self.validation_resources['keypair']['private_key'],
- server=self.server,
- servers_client=self.servers_client)
+ def _count_volumes(self, server):
+ # Count number of volumes on an instance
+ volumes = 0
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ self.image_ssh_user,
+ self.image_ssh_password,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
- command = 'grep [vs]d /proc/partitions | wc -l'
- nb_partitions = linux_client.exec_command(command).strip()
- self.assertEqual(number_of_partition, nb_partitions)
+ command = 'grep -c -E [vs]d.$ /proc/partitions'
+ volumes = int(linux_client.exec_command(command).strip())
+ return volumes
+
+ def _shelve_server(self, server):
+ # NOTE(andreaf) If we are going to shelve a server, we should
+ # check first whether the server is ssh-able. Otherwise we
+ # won't be able to distinguish failures introduced by shelve
+ # from pre-existing ones. Also it's good to wait for cloud-init
+ # to be done and sshd server to be running before shelving to
+ # avoid breaking the VM
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ self.image_ssh_user,
+ self.image_ssh_password,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+ linux_client.validate_authentication()
+
+ # If validation went ok, or it was skipped, shelve the server
+ compute.shelve_server(self.servers_client, server['id'])
+
+ def _unshelve_server_and_check_volumes(self, server, number_of_volumes):
+ # Unshelve the instance and check that there are expected volumes
+ self.servers_client.unshelve_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'],
+ 'ACTIVE')
+ if CONF.validation.run_validation:
+ counted_volumes = self._count_volumes(server)
+ self.assertEqual(number_of_volumes, counted_volumes)
@test.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
- @testtools.skipUnless(CONF.validation.run_validation,
- 'SSH required for this test')
def test_attach_volume_shelved_or_offload_server(self):
- self._create_and_attach(shelve_server=True)
+ # Create server, count number of volumes on it, shelve
+ # server and attach pre-created volume to shelved server
+ server = self._create_server()
+ num_vol = self._count_volumes(server)
+ self._shelve_server(server)
+ self._create_and_attach_volume(server)
- # Unshelve the instance and check that there are two volumes
- self._unshelve_server_and_check_volumes('2')
+ # Unshelve the instance and check that attached volume exists
+ self._unshelve_server_and_check_volumes(server, num_vol + 1)
# Get Volume attachment of the server
volume_attachment = self.servers_client.show_volume_attachment(
- self.server['id'],
+ server['id'],
self.attachment['id'])['volumeAttachment']
- self.assertEqual(self.server['id'], volume_attachment['serverId'])
+ self.assertEqual(server['id'], volume_attachment['serverId'])
self.assertEqual(self.attachment['id'], volume_attachment['id'])
# Check the mountpoint is not None after unshelve server even in
# case of shelved_offloaded.
@@ -228,14 +242,18 @@
@test.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
- @testtools.skipUnless(CONF.validation.run_validation,
- 'SSH required for this test')
def test_detach_volume_shelved_or_offload_server(self):
- self._create_and_attach(shelve_server=True)
+ # Create server, count number of volumes on it, shelve
+ # server and attach pre-created volume to shelved server
+ server = self._create_server()
+ num_vol = self._count_volumes(server)
+ self._shelve_server(server)
+ volume = self._create_and_attach_volume(server)
# Detach the volume
- self._detach(self.server['id'], self.volume['id'])
+ self._detach(server['id'], volume['id'])
self.attachment = None
- # Unshelve the instance and check that there is only one volume
- self._unshelve_server_and_check_volumes('1')
+ # Unshelve the instance and check that we have the expected number of
+ # volume(s)
+ self._unshelve_server_and_check_volumes(server, num_vol)
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
old mode 100644
new mode 100755
index f42d153..e96982d
--- a/tempest/api/compute/volumes/test_volume_snapshots.py
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -40,14 +40,14 @@
@test.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
def test_volume_snapshot_create_get_list_delete(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
volume = self.volumes_client.create_volume(
size=CONF.volume.volume_size,
display_name=v_name)['volume']
self.addCleanup(self.delete_volume, volume['id'])
waiters.wait_for_volume_status(self.volumes_client, volume['id'],
'available')
- s_name = data_utils.rand_name('Snapshot')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
# Create snapshot
snapshot = self.snapshots_client.create_snapshot(
volume_id=volume['id'],
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
old mode 100644
new mode 100755
index 6074054..d599431
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -42,15 +42,14 @@
@test.idempotent_id('f10f25eb-9775-4d9d-9cbe-1cf54dae9d5f')
def test_volume_create_get_delete(self):
# CREATE, GET, DELETE Volume
- volume = None
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
# Create volume
volume = self.client.create_volume(size=CONF.volume.volume_size,
display_name=v_name,
metadata=metadata)['volume']
- self.addCleanup(self.delete_volume, volume['id'])
self.assertIn('id', volume)
+ self.addCleanup(self.delete_volume, volume['id'])
self.assertIn('displayName', volume)
self.assertEqual(volume['displayName'], v_name,
"The created volume name is not equal "
@@ -66,6 +65,10 @@
fetched_volume['displayName'],
'The fetched Volume is different '
'from the created Volume')
+ self.assertEqual(CONF.volume.volume_size,
+ fetched_volume['size'],
+ 'The fetched volume size is different '
+ 'from the created Volume')
self.assertEqual(volume['id'],
fetched_volume['id'],
'The fetched Volume is different '
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
old mode 100644
new mode 100755
index f709c91..c60fcca
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -48,7 +48,7 @@
cls.volume_list = []
cls.volume_id_list = []
for i in range(3):
- v_name = data_utils.rand_name('volume')
+ v_name = data_utils.rand_name(cls.__name__ + '-volume')
metadata = {'Type': 'work'}
try:
volume = cls.client.create_volume(size=CONF.volume.volume_size,
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
old mode 100644
new mode 100755
index 92f5ea8..7ecad12
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -59,7 +59,7 @@
def test_create_volume_with_invalid_size(self):
# Negative: Should not be able to create volume with invalid size
# in request
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
@@ -69,7 +69,7 @@
def test_create_volume_with_out_passing_size(self):
# Negative: Should not be able to create volume without passing size
# in request
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
@@ -78,7 +78,7 @@
@test.idempotent_id('8cce995e-0a83-479a-b94d-e1e40b8a09d1')
def test_create_volume_with_size_zero(self):
# Negative: Should not be able to create volume with size zero
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index decccf3..c8506ae 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from collections import OrderedDict
+import collections
import copy
import six
@@ -112,7 +112,7 @@
DEFAULT_TEMPLATES = {
- 'vanilla': OrderedDict([
+ 'vanilla': collections.OrderedDict([
('2.6.0', copy.deepcopy(BASE_VANILLA_DESC)),
('2.7.1', copy.deepcopy(BASE_VANILLA_DESC)),
('1.2.1', {
@@ -148,7 +148,7 @@
}
})
]),
- 'hdp': OrderedDict([
+ 'hdp': collections.OrderedDict([
('2.0.6', {
'NODES': {
'master1': {
@@ -174,11 +174,11 @@
}
})
]),
- 'spark': OrderedDict([
+ 'spark': collections.OrderedDict([
('1.0.0', copy.deepcopy(BASE_SPARK_DESC)),
('1.3.1', copy.deepcopy(BASE_SPARK_DESC))
]),
- 'cdh': OrderedDict([
+ 'cdh': collections.OrderedDict([
('5.4.0', copy.deepcopy(BASE_CDH_DESC)),
('5.3.0', copy.deepcopy(BASE_CDH_DESC)),
('5', copy.deepcopy(BASE_CDH_DESC))
@@ -349,7 +349,7 @@
return None
for plugin in CONF.data_processing_feature_enabled.plugins:
- if plugin in DEFAULT_TEMPLATES.keys():
+ if plugin in DEFAULT_TEMPLATES:
break
else:
plugin = ''
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 0924619..d284aac 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -25,17 +24,22 @@
@classmethod
def resource_setup(cls):
super(RolesTestJSON, cls).resource_setup()
- for _ in moves.xrange(5):
+ cls.roles = list()
+ for _ in range(5):
role_name = data_utils.rand_name(name='role')
role = cls.roles_client.create_role(name=role_name)['role']
- cls.data.roles.append(role)
+ cls.roles.append(role)
+
+ @classmethod
+ def resource_cleanup(cls):
+ super(RolesTestJSON, cls).resource_cleanup()
+ for role in cls.roles:
+ cls.roles_client.delete_role(role['id'])
def _get_role_params(self):
- self.data.setup_test_user()
- self.data.setup_test_role()
- user = self.get_user_by_name(self.data.user['name'])
- tenant = self.get_tenant_by_name(self.data.tenant['name'])
- role = self.get_role_by_name(self.data.role['name'])
+ user = self.setup_test_user()
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ role = self.setup_test_role()
return (user, tenant, role)
def assert_role_in_role_list(self, role, roles):
@@ -49,15 +53,17 @@
def test_list_roles(self):
"""Return a list of all roles."""
body = self.roles_client.list_roles()['roles']
- found = [role for role in body if role in self.data.roles]
+ found = [role for role in body if role in self.roles]
self.assertTrue(any(found))
- self.assertEqual(len(found), len(self.data.roles))
+ self.assertEqual(len(found), len(self.roles))
@test.idempotent_id('c62d909d-6c21-48c0-ae40-0a0760e6db5e')
def test_role_create_delete(self):
"""Role should be created, verified, and deleted."""
role_name = data_utils.rand_name(name='role-test')
body = self.roles_client.create_role(name=role_name)['role']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.roles_client.delete_role, body['id'])
self.assertEqual(role_name, body['name'])
body = self.roles_client.list_roles()['roles']
@@ -73,9 +79,9 @@
@test.idempotent_id('db6870bd-a6ed-43be-a9b1-2f10a5c9994f')
def test_get_role_by_id(self):
"""Get a role by its id."""
- self.data.setup_test_role()
- role_id = self.data.role['id']
- role_name = self.data.role['name']
+ role = self.setup_test_role()
+ role_id = role['id']
+ role_name = role['name']
body = self.roles_client.show_role(role_id)['role']
self.assertEqual(role_id, body['id'])
self.assertEqual(role_name, body['name'])
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index 770bb14..7116913 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -22,11 +22,9 @@
class RolesNegativeTestJSON(base.BaseIdentityV2AdminTest):
def _get_role_params(self):
- self.data.setup_test_user()
- self.data.setup_test_role()
- user = self.get_user_by_name(self.data.user['name'])
- tenant = self.get_tenant_by_name(self.data.tenant['name'])
- role = self.get_role_by_name(self.data.role['name'])
+ user = self.setup_test_user()
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ role = self.setup_test_role()
return (user, tenant, role)
@test.attr(type=['negative'])
@@ -89,7 +87,7 @@
# Non-administrator user should not be able to delete role
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
- self.data.roles.append(body)
+ self.addCleanup(self.roles_client.delete_role, body['id'])
role_id = body.get('id')
self.assertRaises(lib_exc.Forbidden,
self.non_admin_roles_client.delete_role, role_id)
@@ -100,7 +98,7 @@
# Request to delete role without a valid token should fail
role_name = data_utils.rand_name(name='role')
body = self.roles_client.create_role(name=role_name)['role']
- self.data.roles.append(body)
+ self.addCleanup(self.roles_client.delete_role, body['id'])
role_id = body.get('id')
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index 6c9b564..3ed51f0 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
@@ -84,7 +82,7 @@
def test_list_services(self):
# Create, List, Verify and Delete Services
services = []
- for _ in moves.xrange(3):
+ for _ in range(3):
name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
description = data_utils.rand_name('description')
@@ -93,7 +91,7 @@
name=name, type=s_type,
description=description)['OS-KSADM:service']
services.append(service)
- service_ids = map(lambda x: x['id'], services)
+ service_ids = [svc['id'] for svc in services]
def delete_services():
for service_id in service_ids:
diff --git a/tempest/api/identity/admin/v2/test_tenant_negative.py b/tempest/api/identity/admin/v2/test_tenant_negative.py
index d1e0217..baa78e9 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -44,7 +44,7 @@
# Non-administrator user should not be able to delete a tenant
tenant_name = data_utils.rand_name(name='tenant')
tenant = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.delete_tenant,
tenant['id'])
@@ -55,7 +55,7 @@
# Request to delete a tenant without a valid token should fail
tenant_name = data_utils.rand_name(name='tenant')
tenant = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized,
@@ -76,12 +76,9 @@
# Tenant names should be unique
tenant_name = data_utils.rand_name(name='tenant')
body = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- tenant = body
- self.data.tenants.append(tenant)
tenant1_id = body.get('id')
self.addCleanup(self.tenants_client.delete_tenant, tenant1_id)
- self.addCleanup(self.data.tenants.remove, tenant)
self.assertRaises(lib_exc.Conflict, self.tenants_client.create_tenant,
name=tenant_name)
@@ -136,7 +133,7 @@
# Non-administrator user should not be able to update a tenant
tenant_name = data_utils.rand_name(name='tenant')
tenant = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
self.assertRaises(lib_exc.Forbidden,
self.non_admin_tenants_client.update_tenant,
tenant['id'])
@@ -147,7 +144,7 @@
# Request to update a tenant without a valid token should fail
tenant_name = data_utils.rand_name(name='tenant')
tenant = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
self.assertRaises(lib_exc.Unauthorized,
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index 1aa80df..f4fad53 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -26,20 +25,21 @@
def test_tenant_list_delete(self):
# Create several tenants and delete them
tenants = []
- for _ in moves.xrange(3):
+ for _ in range(3):
tenant_name = data_utils.rand_name(name='tenant-new')
tenant = self.tenants_client.create_tenant(
name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
tenants.append(tenant)
- tenant_ids = map(lambda x: x['id'], tenants)
+ tenant_ids = [tn['id'] for tn in tenants]
body = self.tenants_client.list_tenants()['tenants']
found = [t for t in body if t['id'] in tenant_ids]
self.assertEqual(len(found), len(tenants), 'Tenants not created')
for tenant in tenants:
self.tenants_client.delete_tenant(tenant['id'])
- self.data.tenants.remove(tenant)
body = self.tenants_client.list_tenants()['tenants']
found = [tenant for tenant in body if tenant['id'] in tenant_ids]
@@ -53,7 +53,9 @@
body = self.tenants_client.create_tenant(name=tenant_name,
description=tenant_desc)
tenant = body['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
tenant_id = tenant['id']
desc1 = tenant['description']
self.assertEqual(desc1, tenant_desc, 'Description should have '
@@ -63,7 +65,6 @@
self.assertEqual(desc2, tenant_desc, 'Description does not appear'
'to be set')
self.tenants_client.delete_tenant(tenant_id)
- self.data.tenants.remove(tenant)
@test.idempotent_id('670bdddc-1cd7-41c7-b8e2-751cfb67df50')
def test_tenant_create_enabled(self):
@@ -72,7 +73,9 @@
body = self.tenants_client.create_tenant(name=tenant_name,
enabled=True)
tenant = body['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
tenant_id = tenant['id']
en1 = tenant['enabled']
self.assertTrue(en1, 'Enable should be True in response')
@@ -80,7 +83,6 @@
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
self.tenants_client.delete_tenant(tenant_id)
- self.data.tenants.remove(tenant)
@test.idempotent_id('3be22093-b30f-499d-b772-38340e5e16fb')
def test_tenant_create_not_enabled(self):
@@ -89,7 +91,9 @@
body = self.tenants_client.create_tenant(name=tenant_name,
enabled=False)
tenant = body['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
tenant_id = tenant['id']
en1 = tenant['enabled']
self.assertEqual('false', str(en1).lower(),
@@ -99,7 +103,6 @@
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
self.tenants_client.delete_tenant(tenant_id)
- self.data.tenants.remove(tenant)
@test.idempotent_id('781f2266-d128-47f3-8bdb-f70970add238')
def test_tenant_update_name(self):
@@ -107,7 +110,9 @@
t_name1 = data_utils.rand_name(name='tenant')
body = self.tenants_client.create_tenant(name=t_name1)['tenant']
tenant = body
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
t_id = body['id']
resp1_name = body['name']
@@ -125,7 +130,6 @@
self.assertEqual(resp2_name, resp3_name)
self.tenants_client.delete_tenant(t_id)
- self.data.tenants.remove(tenant)
@test.idempotent_id('859fcfe1-3a03-41ef-86f9-b19a47d1cd87')
def test_tenant_update_desc(self):
@@ -135,7 +139,9 @@
body = self.tenants_client.create_tenant(name=t_name,
description=t_desc)
tenant = body['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
t_id = tenant['id']
resp1_desc = tenant['description']
@@ -154,7 +160,6 @@
self.assertEqual(resp2_desc, resp3_desc)
self.tenants_client.delete_tenant(t_id)
- self.data.tenants.remove(tenant)
@test.idempotent_id('8fc8981f-f12d-4c66-9972-2bdcf2bc2e1a')
def test_tenant_update_enable(self):
@@ -163,7 +168,9 @@
t_en = False
body = self.tenants_client.create_tenant(name=t_name, enabled=t_en)
tenant = body['tenant']
- self.data.tenants.append(tenant)
+ # Add the tenant to the cleanup list
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.tenants_client.delete_tenant, tenant['id'])
t_id = tenant['id']
resp1_en = tenant['enabled']
@@ -182,4 +189,3 @@
self.assertEqual(resp2_en, resp3_en)
self.tenants_client.delete_tenant(t_id)
- self.data.tenants.remove(tenant)
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index 5cf337b..2f7e941 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -28,13 +28,15 @@
# first:create a tenant
tenant_name = data_utils.rand_name(name='tenant')
tenant = self.tenants_client.create_tenant(name=tenant_name)['tenant']
- self.data.tenants.append(tenant)
+ # Delete the tenant at the end of the test
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
# second:create a user
user = self.users_client.create_user(name=user_name,
password=user_password,
tenantId=tenant['id'],
email='')['user']
- self.data.users.append(user)
+ # Delete the user at the end of the test
+ self.addCleanup(self.users_client.delete_user, user['id'])
# then get a token for the user
body = self.token_client.auth(user_name,
user_password,
@@ -68,23 +70,27 @@
password=user_password,
tenantId=tenant_id,
email=email)['user']
- self.data.users.append(user)
+ # Delete the user at the end of the test
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Create a couple tenants.
tenant1_name = data_utils.rand_name(name='tenant')
tenant1 = self.tenants_client.create_tenant(
name=tenant1_name)['tenant']
- self.data.tenants.append(tenant1)
+ # Delete the tenant at the end of the test
+ self.addCleanup(self.tenants_client.delete_tenant, tenant1['id'])
tenant2_name = data_utils.rand_name(name='tenant')
tenant2 = self.tenants_client.create_tenant(
name=tenant2_name)['tenant']
- self.data.tenants.append(tenant2)
+ # Delete the tenant at the end of the test
+ self.addCleanup(self.tenants_client.delete_tenant, tenant2['id'])
# Create a role
role_name = data_utils.rand_name(name='role')
role = self.roles_client.create_role(name=role_name)['role']
- self.data.roles.append(role)
+ # Delete the role at the end of the test
+ self.addCleanup(self.roles_client.delete_role, role['id'])
# Grant the user the role on the tenants.
self.roles_client.create_user_role_on_project(tenant1['id'],
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 167cbc7..8e63498 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -19,6 +19,7 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -35,25 +36,27 @@
@test.idempotent_id('2d55a71e-da1d-4b43-9c03-d269fd93d905')
def test_create_user(self):
# Create a user
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
user = self.users_client.create_user(name=self.alt_user,
password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)['user']
- self.data.users.append(user)
+ # Delete the User at the end of the test
+ self.addCleanup(self.users_client.delete_user, user['id'])
self.assertEqual(self.alt_user, user['name'])
@test.idempotent_id('89d9fdb8-15c2-4304-a429-48715d0af33d')
def test_create_user_with_enabled(self):
# Create a user with enabled : False
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
name = data_utils.rand_name('test_user')
user = self.users_client.create_user(name=name,
password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email,
enabled=False)['user']
- self.data.users.append(user)
+ # Delete the User at the end of the test
+ self.addCleanup(self.users_client.delete_user, user['id'])
self.assertEqual(name, user['name'])
self.assertEqual(False, user['enabled'])
self.assertEqual(self.alt_email, user['email'])
@@ -62,10 +65,10 @@
def test_update_user(self):
# Test case to check if updating of user attributes is successful.
test_user = data_utils.rand_name('test_user')
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
user = self.users_client.create_user(name=test_user,
password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)['user']
# Delete the User at the end of this method
self.addCleanup(self.users_client.delete_user, user['id'])
@@ -89,76 +92,85 @@
def test_delete_user(self):
# Delete a user
test_user = data_utils.rand_name('test_user')
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
user = self.users_client.create_user(name=test_user,
password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)['user']
+ # Delete the User at the end of the test
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.users_client.delete_user, user['id'])
self.users_client.delete_user(user['id'])
@test.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1')
def test_user_authentication(self):
# Valid user's token is authenticated
- self.data.setup_test_user()
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
# Get a token
- self.token_client.auth(self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ self.token_client.auth(user['name'],
+ password,
+ tenant['name'])
# Re-auth
- self.token_client.auth(self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ self.token_client.auth(user['name'],
+ password,
+ tenant['name'])
@test.idempotent_id('5d1fa498-4c2d-4732-a8fe-2b054598cfdd')
def test_authentication_request_without_token(self):
# Request for token authentication with a valid token in header
- self.data.setup_test_user()
- self.token_client.auth(self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ self.token_client.auth(user['name'],
+ password,
+ tenant['name'])
# Get the token of the current client
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
# Re-auth
- self.token_client.auth(self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ self.token_client.auth(user['name'],
+ password,
+ tenant['name'])
self.client.auth_provider.clear_auth()
@test.idempotent_id('a149c02e-e5e0-4b89-809e-7e8faf33ccda')
def test_get_users(self):
# Get a list of users and find the test user
- self.data.setup_test_user()
+ user = self.setup_test_user()
users = self.users_client.list_users()['users']
self.assertThat([u['name'] for u in users],
- matchers.Contains(self.data.user['name']),
- "Could not find %s" % self.data.user['name'])
+ matchers.Contains(user['name']),
+ "Could not find %s" % user['name'])
@test.idempotent_id('6e317209-383a-4bed-9f10-075b7c82c79a')
def test_list_users_for_tenant(self):
# Return a list of all users for a tenant
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
user_ids = list()
fetched_user_ids = list()
password1 = data_utils.rand_password()
alt_tenant_user1 = data_utils.rand_name('tenant_user1')
user1 = self.users_client.create_user(name=alt_tenant_user1,
password=password1,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email='user1@123')['user']
user_ids.append(user1['id'])
- self.data.users.append(user1)
+ # Delete the User at the end of the test
+ self.addCleanup(self.users_client.delete_user, user1['id'])
password2 = data_utils.rand_password()
alt_tenant_user2 = data_utils.rand_name('tenant_user2')
user2 = self.users_client.create_user(name=alt_tenant_user2,
password=password2,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email='user2@123')['user']
user_ids.append(user2['id'])
- self.data.users.append(user2)
+ # Delete the User at the end of the test
+ self.addCleanup(self.users_client.delete_user, user2['id'])
# List of users for the respective tenant ID
- body = (self.tenants_client.list_tenant_users(self.data.tenant['id'])
+ body = (self.tenants_client.list_tenant_users(tenant['id'])
['users'])
for i in body:
fetched_user_ids.append(i['id'])
@@ -172,11 +184,9 @@
@test.idempotent_id('a8b54974-40e1-41c0-b812-50fc90827971')
def test_list_users_with_roles_for_tenant(self):
# Return list of users on tenant when roles are assigned to users
- self.data.setup_test_user()
- self.data.setup_test_role()
- user = self.get_user_by_name(self.data.user['name'])
- tenant = self.get_tenant_by_name(self.data.tenant['name'])
- role = self.get_role_by_name(self.data.role['name'])
+ user = self.setup_test_user()
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ role = self.setup_test_role()
# Assigning roles to two users
user_ids = list()
fetched_user_ids = list()
@@ -189,15 +199,15 @@
second_user = self.users_client.create_user(
name=alt_user2,
password=alt_password2,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email='user2@123')['user']
user_ids.append(second_user['id'])
- self.data.users.append(second_user)
+ # Delete the User at the end of the test
+ self.addCleanup(self.users_client.delete_user, second_user['id'])
role = self.roles_client.create_user_role_on_project(
tenant['id'], second_user['id'], role['id'])['role']
# List of users with roles for the respective tenant ID
- body = (self.tenants_client.list_tenant_users(self.data.tenant['id'])
- ['users'])
+ body = (self.tenants_client.list_tenant_users(tenant['id'])['users'])
for i in body:
fetched_user_ids.append(i['id'])
# verifying the user Id in the list
@@ -210,17 +220,18 @@
@test.idempotent_id('1aeb25ac-6ec5-4d8b-97cb-7ac3567a989f')
def test_update_user_password(self):
# Test case to check if updating of user password is successful.
- self.data.setup_test_user()
+ user = self.setup_test_user()
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
# Updating the user with new password
new_pass = data_utils.rand_password()
update_user = self.users_client.update_user_password(
- self.data.user['id'], password=new_pass)['user']
- self.assertEqual(update_user['id'], self.data.user['id'])
+ user['id'], password=new_pass)['user']
+ self.assertEqual(update_user['id'], user['id'])
# NOTE(morganfainberg): Fernet tokens are not subsecond aware and
# Keystone should only be precise to the second. Sleep to ensure
# we are passing the second boundary.
time.sleep(1)
# Validate the updated password through getting a token.
- body = self.token_client.auth(self.data.user['name'], new_pass,
- self.data.tenant['name'])
+ body = self.token_client.auth(user['name'], new_pass,
+ tenant['name'])
self.assertTrue('id' in body['token'])
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 78b89fa..597413e 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -32,43 +32,45 @@
@test.idempotent_id('60a1f5fa-5744-4cdf-82bf-60b7de2d29a4')
def test_create_user_by_unauthorized_user(self):
# Non-administrator should not be authorized to create a user
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.create_user,
name=self.alt_user, password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)
@test.attr(type=['negative'])
@test.idempotent_id('d80d0c2f-4514-4d1e-806d-0930dfc5a187')
def test_create_user_with_empty_name(self):
# User with an empty name should not be created
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
name='', password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)
@test.attr(type=['negative'])
@test.idempotent_id('7704b4f3-3b75-4b82-87cc-931d41c8f780')
def test_create_user_with_name_length_over_255(self):
# Length of user name filed should be restricted to 255 characters
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
name='a' * 256, password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)
@test.attr(type=['negative'])
@test.idempotent_id('57ae8558-120c-4723-9308-3751474e7ecf')
def test_create_user_with_duplicate_name(self):
# Duplicate user should not be created
- self.data.setup_test_user()
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
self.assertRaises(lib_exc.Conflict, self.users_client.create_user,
- name=self.data.user['name'],
- password=self.data.user_password,
- tenantId=self.data.tenant['id'],
- email=self.data.user['email'])
+ name=user['name'],
+ password=password,
+ tenantId=tenant['id'],
+ email=user['email'])
@test.attr(type=['negative'])
@test.idempotent_id('0132cc22-7c4f-42e1-9e50-ac6aad31d59a')
@@ -84,7 +86,7 @@
@test.idempotent_id('55bbb103-d1ae-437b-989b-bcdf8175c1f4')
def test_create_user_request_without_a_token(self):
# Request to create a user without a valid token should fail
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
# Get the token of the current client
token = self.client.auth_provider.get_token()
# Delete the token from database
@@ -95,18 +97,18 @@
self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
name=self.alt_user, password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email)
@test.attr(type=['negative'])
@test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
def test_create_user_with_enabled_non_bool(self):
# Attempt to create a user with valid enabled para should fail
- self.data.setup_test_tenant()
+ tenant = self.setup_test_tenant()
name = data_utils.rand_name('test_user')
self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
name=name, password=self.alt_password,
- tenantId=self.data.tenant['id'],
+ tenantId=tenant['id'],
email=self.alt_email, enabled=3)
@test.attr(type=['negative'])
@@ -138,7 +140,6 @@
@test.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac')
def test_update_user_by_unauthorized_user(self):
# Non-administrator should not be authorized to update user
- self.data.setup_test_tenant()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.update_user,
self.alt_user)
@@ -147,10 +148,10 @@
@test.idempotent_id('d45195d5-33ed-41b9-a452-7d0d6a00f6e9')
def test_delete_users_by_unauthorized_user(self):
# Non-administrator user should not be authorized to delete a user
- self.data.setup_test_user()
+ user = self.setup_test_user()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.delete_user,
- self.data.user['id'])
+ user['id'])
@test.attr(type=['negative'])
@test.idempotent_id('7cc82f7e-9998-4f89-abae-23df36495867')
@@ -179,57 +180,62 @@
@test.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829')
def test_authentication_for_disabled_user(self):
# Disabled user's token should not get authenticated
- self.data.setup_test_user()
- self.disable_user(self.data.user['name'])
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ self.disable_user(user['name'])
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
- self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ user['name'],
+ password,
+ tenant['name'])
@test.attr(type=['negative'])
@test.idempotent_id('440a7a8d-9328-4b7b-83e0-d717010495e4')
def test_authentication_when_tenant_is_disabled(self):
# User's token for a disabled tenant should not be authenticated
- self.data.setup_test_user()
- self.disable_tenant(self.data.tenant['name'])
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
+ self.disable_tenant(tenant['name'])
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
- self.data.user['name'],
- self.data.user_password,
- self.data.tenant['name'])
+ user['name'],
+ password,
+ tenant['name'])
@test.attr(type=['negative'])
@test.idempotent_id('921f1ad6-7907-40b8-853f-637e7ee52178')
def test_authentication_with_invalid_tenant(self):
# User's token for an invalid tenant should not be authenticated
- self.data.setup_test_user()
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
- self.data.user['name'],
- self.data.user_password,
+ user['name'],
+ password,
'junktenant1234')
@test.attr(type=['negative'])
@test.idempotent_id('bde9aecd-3b1c-4079-858f-beb5deaa5b5e')
def test_authentication_with_invalid_username(self):
# Non-existent user's token should not get authenticated
- self.data.setup_test_user()
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
- 'junkuser123', self.data.user_password,
- self.data.tenant['name'])
+ 'junkuser123', password, tenant['name'])
@test.attr(type=['negative'])
@test.idempotent_id('d5308b33-3574-43c3-8d87-1c090c5e1eca')
def test_authentication_with_invalid_password(self):
# User's token with invalid password should not be authenticated
- self.data.setup_test_user()
+ user = self.setup_test_user()
+ tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
self.assertRaises(lib_exc.Unauthorized, self.token_client.auth,
- self.data.user['name'], 'junkpass1234',
- self.data.tenant['name'])
+ user['name'], 'junkpass1234', tenant['name'])
@test.attr(type=['negative'])
@test.idempotent_id('284192ce-fb7c-4909-a63b-9a502e0ddd11')
def test_get_users_by_unauthorized_user(self):
# Non-administrator user should not be authorized to get user list
- self.data.setup_test_user()
self.assertRaises(lib_exc.Forbidden,
self.non_admin_users_client.list_users)
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 7c2e8e0..12b236f 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -37,7 +37,7 @@
cls.projects.append(cls.project['id'])
cls.user_body = cls.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.projects[0])['user']
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index a540da7..59ffc19 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -54,7 +54,7 @@
# default project
user_name = data_utils.rand_name('user')
user_body = self.users_client.create_user(
- user_name,
+ name=user_name,
password=user_name,
domain_id=dom_id,
default_project_id=proj_id)['user']
@@ -69,7 +69,7 @@
admin_role_id = admin_role['id']
# grant the admin role to the user on his project
- self.roles_client.assign_user_role_on_project(proj_id, user_id,
+ self.roles_client.create_user_role_on_project(proj_id, user_id,
admin_role_id)
# create a new client with user's credentials (NOTE: unscoped token!)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 24a7a4e..cbf1439 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -44,11 +44,11 @@
super(DomainsTestJSON, cls).resource_cleanup()
@classmethod
- def _delete_domain(self, domain_id):
+ def _delete_domain(cls, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- self.domains_client.update_domain(domain_id, enabled=False)
- self.domains_client.delete_domain(domain_id)
+ cls.domains_client.update_domain(domain_id, enabled=False)
+ cls.domains_client.delete_domain(domain_id)
@test.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
def test_list_domains(self):
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index a3ada21..3cbcc1f 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -23,14 +23,21 @@
@classmethod
def resource_setup(cls):
super(GroupsV3TestJSON, cls).resource_setup()
- cls.data.setup_test_domain()
+ cls.domain = cls.create_domain()
+
+ @classmethod
+ def resource_cleanup(cls):
+ # Cleanup the domains created in the setup
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
+ super(GroupsV3TestJSON, cls).resource_cleanup()
@test.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
def test_group_create_update_get(self):
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, domain_id=self.data.domain['id'],
+ name=name, domain_id=self.domain['id'],
description=description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
self.assertEqual(group['name'], name)
@@ -53,7 +60,7 @@
name = data_utils.rand_name('Group')
old_description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, domain_id=self.data.domain['id'],
+ name=name, domain_id=self.domain['id'],
description=old_description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
@@ -69,21 +76,23 @@
def test_group_users_add_list_delete(self):
name = data_utils.rand_name('Group')
group = self.groups_client.create_group(
- name=name, domain_id=self.data.domain['id'])['group']
+ name=name, domain_id=self.domain['id'])['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
# add user into group
users = []
for i in range(3):
name = data_utils.rand_name('User')
password = data_utils.rand_password()
- user = self.users_client.create_user(name, password)['user']
+ user = self.users_client.create_user(name=name,
+ password=password)['user']
users.append(user)
self.addCleanup(self.users_client.delete_user, user['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list users in group
group_users = self.groups_client.list_group_users(group['id'])['users']
- self.assertEqual(sorted(users), sorted(group_users))
+ self.assertEqual(sorted(users, key=lambda k: k['name']),
+ sorted(group_users, key=lambda k: k['name']))
# check and delete user in group
for user in users:
self.groups_client.check_group_user_existence(
@@ -96,20 +105,22 @@
def test_list_user_groups(self):
# create a user
user = self.users_client.create_user(
- data_utils.rand_name('User'), data_utils.rand_password())['user']
+ name=data_utils.rand_name('User'),
+ password=data_utils.rand_password())['user']
self.addCleanup(self.users_client.delete_user, user['id'])
# create two groups, and add user into them
groups = []
for i in range(2):
name = data_utils.rand_name('Group')
group = self.groups_client.create_group(
- name=name, domain_id=self.data.domain['id'])['group']
+ name=name, domain_id=self.domain['id'])['group']
groups.append(group)
self.addCleanup(self.groups_client.delete_group, group['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
user_groups = self.users_client.list_user_groups(user['id'])['groups']
- self.assertEqual(sorted(groups), sorted(user_groups))
+ self.assertEqual(sorted(groups, key=lambda k: k['name']),
+ sorted(user_groups, key=lambda k: k['name']))
self.assertEqual(2, len(user_groups))
@test.idempotent_id('cc9a57a5-a9ed-4f2d-a29f-4f979a06ec71')
@@ -121,7 +132,7 @@
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
group = self.groups_client.create_group(
- name=name, domain_id=self.data.domain['id'],
+ name=name, domain_id=self.domain['id'],
description=description)['group']
self.addCleanup(self.groups_client.delete_group, group['id'])
group_ids.append(group['id'])
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index fe20349..373d44b 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -12,11 +12,8 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class BaseInheritsV3Test(base.BaseIdentityV3AdminTest):
@@ -44,7 +41,7 @@
name=data_utils.rand_name('group-'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
cls.user = cls.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
@@ -71,10 +68,10 @@
name=data_utils.rand_name('Role'))['role']
self.addCleanup(self.roles_client.delete_role, src_role['id'])
# Assign role on domains user
- self.roles_client.assign_inherited_role_on_domains_user(
+ self.inherited_roles_client.create_inherited_role_on_domains_user(
self.domain['id'], self.user['id'], src_role['id'])
# list role on domains user
- roles = self.roles_client.\
+ roles = self.inherited_roles_client.\
list_inherited_project_role_for_user_on_domain(
self.domain['id'], self.user['id'])['roles']
@@ -83,10 +80,11 @@
src_role['id'])
# Check role on domains user
- self.roles_client.check_user_inherited_project_role_on_domain(
- self.domain['id'], self.user['id'], src_role['id'])
+ (self.inherited_roles_client.
+ check_user_inherited_project_role_on_domain(
+ self.domain['id'], self.user['id'], src_role['id']))
# Revoke role from domains user.
- self.roles_client.revoke_inherited_role_from_user_on_domain(
+ self.inherited_roles_client.delete_inherited_role_from_user_on_domain(
self.domain['id'], self.user['id'], src_role['id'])
@test.idempotent_id('c7a8dda2-be50-4fb4-9a9c-e830771078b1')
@@ -96,10 +94,10 @@
name=data_utils.rand_name('Role'))['role']
self.addCleanup(self.roles_client.delete_role, src_role['id'])
# Assign role on domains group
- self.roles_client.assign_inherited_role_on_domains_group(
+ self.inherited_roles_client.create_inherited_role_on_domains_group(
self.domain['id'], self.group['id'], src_role['id'])
# List role on domains group
- roles = self.roles_client.\
+ roles = self.inherited_roles_client.\
list_inherited_project_role_for_group_on_domain(
self.domain['id'], self.group['id'])['roles']
@@ -108,10 +106,11 @@
src_role['id'])
# Check role on domains group
- self.roles_client.check_group_inherited_project_role_on_domain(
- self.domain['id'], self.group['id'], src_role['id'])
+ (self.inherited_roles_client.
+ check_group_inherited_project_role_on_domain(
+ self.domain['id'], self.group['id'], src_role['id']))
# Revoke role from domains group
- self.roles_client.revoke_inherited_role_from_group_on_domain(
+ self.inherited_roles_client.delete_inherited_role_from_group_on_domain(
self.domain['id'], self.group['id'], src_role['id'])
@test.idempotent_id('18b70e45-7687-4b72-8277-b8f1a47d7591')
@@ -121,13 +120,14 @@
name=data_utils.rand_name('Role'))['role']
self.addCleanup(self.roles_client.delete_role, src_role['id'])
# Assign role on projects user
- self.roles_client.assign_inherited_role_on_projects_user(
+ self.inherited_roles_client.create_inherited_role_on_projects_user(
self.project['id'], self.user['id'], src_role['id'])
# Check role on projects user
- self.roles_client.check_user_has_flag_on_inherited_to_project(
- self.project['id'], self.user['id'], src_role['id'])
+ (self.inherited_roles_client.
+ check_user_has_flag_on_inherited_to_project(
+ self.project['id'], self.user['id'], src_role['id']))
# Revoke role from projects user
- self.roles_client.revoke_inherited_role_from_user_on_project(
+ self.inherited_roles_client.delete_inherited_role_from_user_on_project(
self.project['id'], self.user['id'], src_role['id'])
@test.idempotent_id('26021436-d5a4-4256-943c-ded01e0d4b45')
@@ -137,11 +137,13 @@
name=data_utils.rand_name('Role'))['role']
self.addCleanup(self.roles_client.delete_role, src_role['id'])
# Assign role on projects group
- self.roles_client.assign_inherited_role_on_projects_group(
+ self.inherited_roles_client.create_inherited_role_on_projects_group(
self.project['id'], self.group['id'], src_role['id'])
# Check role on projects group
- self.roles_client.check_group_has_flag_on_inherited_to_project(
- self.project['id'], self.group['id'], src_role['id'])
+ (self.inherited_roles_client.
+ check_group_has_flag_on_inherited_to_project(
+ self.project['id'], self.group['id'], src_role['id']))
# Revoke role from projects group
- self.roles_client.revoke_inherited_role_from_group_on_project(
- self.project['id'], self.group['id'], src_role['id'])
+ (self.inherited_roles_client.
+ delete_inherited_role_from_group_on_project(
+ self.project['id'], self.group['id'], src_role['id']))
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 86f6b12..7d9e41b 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -24,26 +24,38 @@
def resource_setup(cls):
super(ListProjectsTestJSON, cls).resource_setup()
cls.project_ids = list()
- cls.data.setup_test_domain()
+ # Create a domain
+ cls.domain = cls.create_domain()
# Create project with domain
+ cls.projects = list()
cls.p1_name = data_utils.rand_name('project')
cls.p1 = cls.projects_client.create_project(
cls.p1_name, enabled=False,
- domain_id=cls.data.domain['id'])['project']
- cls.data.projects.append(cls.p1)
+ domain_id=cls.domain['id'])['project']
+ cls.projects.append(cls.p1)
cls.project_ids.append(cls.p1['id'])
# Create default project
p2_name = data_utils.rand_name('project')
cls.p2 = cls.projects_client.create_project(p2_name)['project']
- cls.data.projects.append(cls.p2)
+ cls.projects.append(cls.p2)
cls.project_ids.append(cls.p2['id'])
# Create a new project (p3) using p2 as parent project
p3_name = data_utils.rand_name('project')
cls.p3 = cls.projects_client.create_project(
p3_name, parent_id=cls.p2['id'])['project']
- cls.data.projects.append(cls.p3)
+ cls.projects.append(cls.p3)
cls.project_ids.append(cls.p3['id'])
+ @classmethod
+ def resource_cleanup(cls):
+ # Cleanup the projects created during setup in inverse order
+ for project in reversed(cls.projects):
+ cls.projects_client.delete_project(project['id'])
+ # Cleanup the domain created during setup
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
+ super(ListProjectsTestJSON, cls).resource_cleanup()
+
@test.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
def test_list_projects(self):
# List projects
@@ -57,7 +69,7 @@
def test_list_projects_with_domains(self):
# List projects with domain
self._list_projects_with_params(
- {'domain_id': self.data.domain['id']}, 'domain_id')
+ {'domain_id': self.domain['id']}, 'domain_id')
@test.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8')
def test_list_projects_with_enabled(self):
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 5b27ab1..99df559 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -25,7 +25,7 @@
# assert the response based on expected and not_expected
# expected: user expected in the list response
# not_expected: user, which should not be present in list response
- body = self.users_client.list_users(params)['users']
+ body = self.users_client.list_users(**params)['users']
self.assertIn(expected[key], map(lambda x: x[key], body))
self.assertNotIn(not_expected[key],
map(lambda x: x[key], body))
@@ -36,24 +36,36 @@
alt_user = data_utils.rand_name('test_user')
alt_password = data_utils.rand_password()
cls.alt_email = alt_user + '@testmail.tm'
- cls.data.setup_test_domain()
+ # Create a domain
+ cls.domain = cls.create_domain()
# Create user with Domain
+ cls.users = list()
u1_name = data_utils.rand_name('test_user')
cls.domain_enabled_user = cls.users_client.create_user(
- u1_name, password=alt_password,
- email=cls.alt_email, domain_id=cls.data.domain['id'])['user']
- cls.data.users.append(cls.domain_enabled_user)
+ name=u1_name, password=alt_password,
+ email=cls.alt_email, domain_id=cls.domain['id'])['user']
+ cls.users.append(cls.domain_enabled_user)
# Create default not enabled user
u2_name = data_utils.rand_name('test_user')
cls.non_domain_enabled_user = cls.users_client.create_user(
- u2_name, password=alt_password,
+ name=u2_name, password=alt_password,
email=cls.alt_email, enabled=False)['user']
- cls.data.users.append(cls.non_domain_enabled_user)
+ cls.users.append(cls.non_domain_enabled_user)
+
+ @classmethod
+ def resource_cleanup(cls):
+ # Cleanup the users created during setup
+ for user in cls.users:
+ cls.users_client.delete_user(user['id'])
+ # Cleanup the domain created during setup
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
+ super(UsersV3TestJSON, cls).resource_cleanup()
@test.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d')
def test_list_user_domains(self):
# List users with domain
- params = {'domain_id': self.data.domain['id']}
+ params = {'domain_id': self.domain['id']}
self._list_users_with_params(params, 'domain_id',
self.domain_enabled_user,
self.non_domain_enabled_user)
@@ -79,7 +91,7 @@
# List users
body = self.users_client.list_users()['users']
fetched_ids = [u['id'] for u in body]
- missing_users = [u['id'] for u in self.data.users
+ missing_users = [u['id'] for u in self.users
if u['id'] not in fetched_ids]
self.assertEqual(0, len(missing_users),
"Failed to find user %s in fetched list" %
@@ -88,8 +100,8 @@
@test.idempotent_id('b4baa3ae-ac00-4b4e-9e27-80deaad7771f')
def test_get_user(self):
# Get a user detail
- user = self.users_client.show_user(self.data.users[0]['id'])['user']
- self.assertEqual(self.data.users[0]['id'], user['id'])
- self.assertEqual(self.data.users[0]['name'], user['name'])
+ user = self.users_client.show_user(self.users[0]['id'])['user']
+ self.assertEqual(self.users[0]['id'], user['id'])
+ self.assertEqual(self.users[0]['name'], user['name'])
self.assertEqual(self.alt_email, user['email'])
- self.assertEqual(self.data.domain['id'], user['domain_id'])
+ self.assertEqual(self.domain['id'], user['domain_id'])
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index bee77df..1137191 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC
+# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -32,7 +32,7 @@
project_desc = data_utils.rand_name('desc')
project = self.projects_client.create_project(
project_name, description=project_desc)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
project_id = project['id']
desc1 = project['description']
self.assertEqual(desc1, project_desc, 'Description should have '
@@ -45,25 +45,25 @@
@test.idempotent_id('5f50fe07-8166-430b-a882-3b2ee0abe26f')
def test_project_create_with_domain(self):
# Create project with a domain
- self.data.setup_test_domain()
+ domain = self.setup_test_domain()
project_name = data_utils.rand_name('project')
project = self.projects_client.create_project(
- project_name, domain_id=self.data.domain['id'])['project']
- self.data.projects.append(project)
+ project_name, domain_id=domain['id'])['project']
+ self.addCleanup(self.projects_client.delete_project, project['id'])
project_id = project['id']
self.assertEqual(project_name, project['name'])
- self.assertEqual(self.data.domain['id'], project['domain_id'])
+ self.assertEqual(domain['id'], project['domain_id'])
body = self.projects_client.show_project(project_id)['project']
self.assertEqual(project_name, body['name'])
- self.assertEqual(self.data.domain['id'], body['domain_id'])
+ self.assertEqual(domain['id'], body['domain_id'])
@testtools.skipUnless(CONF.identity_feature_enabled.reseller,
'Reseller not available.')
@test.idempotent_id('1854f9c0-70bc-4d11-a08a-1c789d339e3d')
def test_project_create_with_parent(self):
# Create root project without providing a parent_id
- self.data.setup_test_domain()
- domain_id = self.data.domain['id']
+ domain = self.setup_test_domain()
+ domain_id = domain['id']
root_project_name = data_utils.rand_name('root_project')
root_project = self.projects_client.create_project(
@@ -94,7 +94,7 @@
project_name = data_utils.rand_name('project')
project = self.projects_client.create_project(
project_name, enabled=True)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
project_id = project['id']
en1 = project['enabled']
self.assertTrue(en1, 'Enable should be True in response')
@@ -108,7 +108,7 @@
project_name = data_utils.rand_name('project')
project = self.projects_client.create_project(
project_name, enabled=False)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
en1 = project['enabled']
self.assertEqual('false', str(en1).lower(),
'Enable should be False in response')
@@ -122,7 +122,7 @@
# Update name attribute of a project
p_name1 = data_utils.rand_name('project')
project = self.projects_client.create_project(p_name1)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
resp1_name = project['name']
@@ -146,7 +146,7 @@
p_desc = data_utils.rand_name('desc')
project = self.projects_client.create_project(
p_name, description=p_desc)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
resp1_desc = project['description']
p_desc2 = data_utils.rand_name('desc2')
@@ -169,7 +169,7 @@
p_en = False
project = self.projects_client.create_project(p_name,
enabled=p_en)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
resp1_en = project['enabled']
@@ -192,7 +192,7 @@
# Create a Project
p_name = data_utils.rand_name('project')
project = self.projects_client.create_project(p_name)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
# Create a User
u_name = data_utils.rand_name('user')
@@ -200,7 +200,7 @@
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
user = self.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email, project_id=project['id'])['user']
# Delete the User at the end of this method
self.addCleanup(self.users_client.delete_user, user['id'])
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index fb4a8cf..c76b9ee 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC
+# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -34,7 +34,7 @@
# Project names should be unique
project_name = data_utils.rand_name('project-dup')
project = self.projects_client.create_project(project_name)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
self.assertRaises(lib_exc.Conflict,
self.projects_client.create_project, project_name)
@@ -69,7 +69,7 @@
# Non-admin user should not be able to delete a project
project_name = data_utils.rand_name('project')
project = self.projects_client.create_project(project_name)['project']
- self.data.projects.append(project)
+ self.addCleanup(self.projects_client.delete_project, project['id'])
self.assertRaises(
lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
project['id'])
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 12ef369..f5bf923 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -23,10 +23,11 @@
@classmethod
def resource_setup(cls):
super(RolesV3TestJSON, cls).resource_setup()
+ cls.roles = list()
for _ in range(3):
role_name = data_utils.rand_name(name='role')
role = cls.roles_client.create_role(name=role_name)['role']
- cls.data.roles.append(role)
+ cls.roles.append(role)
cls.fetched_role_ids = list()
u_name = data_utils.rand_name('user')
u_desc = '%s description' % u_name
@@ -43,7 +44,7 @@
name=data_utils.rand_name('Group'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
cls.user_body = cls.users_client.create_user(
- u_name, description=u_desc, password=cls.u_password,
+ name=u_name, description=u_desc, password=cls.u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
cls.role = cls.roles_client.create_role(
@@ -59,6 +60,8 @@
# before deleting,or else it would result in unauthorized error
cls.domains_client.update_domain(cls.domain['id'], enabled=False)
cls.domains_client.delete_domain(cls.domain['id'])
+ for role in cls.roles:
+ cls.roles_client.delete_role(role['id'])
super(RolesV3TestJSON, cls).resource_cleanup()
def _list_assertions(self, body, fetched_role_ids, role_id):
@@ -91,7 +94,7 @@
@test.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f')
def test_grant_list_revoke_role_to_user_on_project(self):
- self.roles_client.assign_user_role_on_project(self.project['id'],
+ self.roles_client.create_user_role_on_project(self.project['id'],
self.user_body['id'],
self.role['id'])
@@ -112,7 +115,7 @@
@test.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd')
def test_grant_list_revoke_role_to_user_on_domain(self):
- self.roles_client.assign_user_role_on_domain(
+ self.roles_client.create_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
roles = self.roles_client.list_user_roles_on_domain(
@@ -133,7 +136,7 @@
@test.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
def test_grant_list_revoke_role_to_group_on_project(self):
# Grant role to group on project
- self.roles_client.assign_group_role_on_project(
+ self.roles_client.create_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
# List group roles on project
roles = self.roles_client.list_group_roles_on_project(
@@ -167,7 +170,7 @@
@test.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
def test_grant_list_revoke_role_to_group_on_domain(self):
- self.roles_client.assign_group_role_on_domain(
+ self.roles_client.create_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
roles = self.roles_client.list_group_roles_on_domain(
@@ -189,5 +192,5 @@
def test_list_roles(self):
# Return a list of all roles
body = self.roles_client.list_roles()['roles']
- found = [role for role in body if role in self.data.roles]
- self.assertEqual(len(found), len(self.data.roles))
+ found = [role for role in body if role in self.roles]
+ self.assertEqual(len(found), len(self.roles))
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 89cfd5b..8706cf7 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -32,7 +32,7 @@
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_password()
user = self.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email)['user']
self.addCleanup(self.users_client.delete_user, user['id'])
# Perform Authentication
@@ -62,7 +62,7 @@
# Create a user.
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
- user = self.users_client.create_user(user_name,
+ user = self.users_client.create_user(name=user_name,
password=user_password)['user']
self.addCleanup(self.users_client.delete_user, user['id'])
@@ -83,11 +83,11 @@
self.addCleanup(self.roles_client.delete_role, role['id'])
# Grant the user the role on both projects.
- self.roles_client.assign_user_role_on_project(project1['id'],
+ self.roles_client.create_user_role_on_project(project1['id'],
user['id'],
role['id'])
- self.roles_client.assign_user_role_on_project(project2['id'],
+ self.roles_client.create_user_role_on_project(project2['id'],
user['id'],
role['id'])
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 9c8f1f6..4e69de8 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -57,7 +57,7 @@
u_email = self.trustor_username + '@testmail.xx'
self.trustor_password = data_utils.rand_password()
user = self.users_client.create_user(
- self.trustor_username,
+ name=self.trustor_username,
description=u_desc,
password=self.trustor_password,
email=u_email,
@@ -77,11 +77,11 @@
self.not_delegated_role_id = role['id']
# Assign roles to trustor
- self.roles_client.assign_user_role_on_project(
+ self.roles_client.create_user_role_on_project(
self.trustor_project_id,
self.trustor_user_id,
self.delegated_role_id)
- self.roles_client.assign_user_role_on_project(
+ self.roles_client.create_user_role_on_project(
self.trustor_project_id,
self.trustor_user_id,
self.not_delegated_role_id)
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 371da9c..fd2683e 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -31,7 +31,7 @@
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
user = self.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email, enabled=False)['user']
# Delete the User at the end of this method
self.addCleanup(self.users_client.delete_user, user['id'])
@@ -71,7 +71,7 @@
u_name = data_utils.rand_name('user')
original_password = data_utils.rand_password()
user = self.users_client.create_user(
- u_name, password=original_password)['user']
+ name=u_name, password=original_password)['user']
# Delete the User at the end all test methods
self.addCleanup(self.users_client.delete_user, user['id'])
# Update user with new password
@@ -107,7 +107,7 @@
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
user_body = self.users_client.create_user(
- u_name, description=u_desc, password=u_password,
+ name=u_name, description=u_desc, password=u_password,
email=u_email, enabled=False, project_id=u_project['id'])['user']
# Delete the User at the end of this method
self.addCleanup(self.users_client.delete_user, user_body['id'])
@@ -130,7 +130,7 @@
self.addCleanup(
self.projects_client.delete_project, project_body['id'])
# Assigning roles to user on project
- self.roles_client.assign_user_role_on_project(project['id'],
+ self.roles_client.create_user_role_on_project(project['id'],
user['id'],
role['id'])
assigned_project_ids.append(project['id'])
@@ -149,6 +149,6 @@
@test.idempotent_id('c10dcd90-461d-4b16-8e23-4eb836c00644')
def test_get_user(self):
# Get a user detail
- self.data.setup_test_user()
- user = self.users_client.show_user(self.data.user['id'])['user']
- self.assertEqual(self.data.user['id'], user['id'])
+ user = self.setup_test_user()
+ fetched_user = self.users_client.show_user(user['id'])['user']
+ self.assertEqual(user['id'], fetched_user['id'])
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 1375db1..5b0fc97 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -29,7 +29,7 @@
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
- u_name, u_password,
+ name=u_name, password=u_password,
email=u_email,
domain_id=data_utils.rand_uuid_hex())
@@ -37,9 +37,10 @@
@test.idempotent_id('b3c9fccc-4134-46f5-b600-1da6fb0a3b1f')
def test_authentication_for_disabled_user(self):
# Attempt to authenticate for disabled user should fail
- self.data.setup_test_user()
- self.disable_user(self.data.user['name'], self.data.user['domain_id'])
+ password = data_utils.rand_password()
+ user = self.setup_test_user(password)
+ self.disable_user(user['name'], user['domain_id'])
self.assertRaises(lib_exc.Unauthorized, self.token.auth,
- username=self.data.user['name'],
- password=self.data.user_password,
+ username=user['name'],
+ password=password,
user_domain_id='default')
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index df39390..f5e4943 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -13,20 +13,22 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
-
from tempest.common.utils import data_utils
from tempest import config
-from tempest.lib.common.utils import test_utils
import tempest.test
CONF = config.CONF
-LOG = logging.getLogger(__name__)
class BaseIdentityTest(tempest.test.BaseTestCase):
@classmethod
+ def setup_credentials(cls):
+ # Create no network resources for these test.
+ cls.set_network_resources()
+ super(BaseIdentityTest, cls).setup_credentials()
+
+ @classmethod
def disable_user(cls, user_name):
user = cls.get_user_by_name(user_name)
cls.users_client.update_user_enabled(user['id'], enabled=False)
@@ -40,7 +42,7 @@
def get_user_by_name(cls, name, domain_id=None):
if domain_id:
params = {'domain_id': domain_id}
- users = cls.users_client.list_users(params)['users']
+ users = cls.users_client.list_users(**params)['users']
else:
users = cls.users_client.list_users()['users']
user = [u for u in users if u['name'] == name]
@@ -64,6 +66,23 @@
if len(role) > 0:
return role[0]
+ def _create_test_user(self, **kwargs):
+ if kwargs['password'] is None:
+ user_password = data_utils.rand_password()
+ kwargs['password'] = user_password
+ user = self.users_client.create_user(**kwargs)['user']
+ # Delete the user at the end of the test
+ self.addCleanup(self.users_client.delete_user, user['id'])
+ return user
+
+ def setup_test_role(self):
+ """Set up a test role."""
+ role = self.roles_client.create_role(
+ name=data_utils.rand_name('test_role'))['role']
+ # Delete the role at the end of the test
+ self.addCleanup(self.roles_client.delete_role, role['id'])
+ return role
+
class BaseIdentityV2Test(BaseIdentityTest):
@@ -104,13 +123,25 @@
@classmethod
def resource_setup(cls):
super(BaseIdentityV2AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV2(cls.tenants_client, cls.users_client,
- cls.roles_client)
+ cls.projects_client = cls.tenants_client
- @classmethod
- def resource_cleanup(cls):
- cls.data.teardown_all()
- super(BaseIdentityV2AdminTest, cls).resource_cleanup()
+ def setup_test_user(self, password=None):
+ """Set up a test user."""
+ tenant = self.setup_test_tenant()
+ username = data_utils.rand_name('test_user')
+ email = username + '@testmail.tm'
+ user = self._create_test_user(name=username, email=email,
+ tenantId=tenant['id'], password=password)
+ return user
+
+ def setup_test_tenant(self):
+ """Set up a test tenant."""
+ tenant = self.projects_client.create_tenant(
+ name=data_utils.rand_name('test_tenant'),
+ description=data_utils.rand_name('desc'))['tenant']
+ # Delete the tenant at the end of the test
+ self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
+ return tenant
class BaseIdentityV3Test(BaseIdentityTest):
@@ -142,6 +173,7 @@
cls.users_client = cls.os_adm.users_v3_client
cls.trusts_client = cls.os_adm.trusts_client
cls.roles_client = cls.os_adm.roles_v3_client
+ cls.inherited_roles_client = cls.os_adm.inherited_roles_client
cls.token = cls.os_adm.token_v3_client
cls.endpoints_client = cls.os_adm.endpoints_v3_client
cls.regions_client = cls.os_adm.regions_client
@@ -158,20 +190,17 @@
cls.os_adm.auth_provider.scope = 'domain'
@classmethod
- def resource_setup(cls):
- super(BaseIdentityV3AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV3(cls.projects_client, cls.users_client,
- cls.roles_client, cls.domains_client)
-
- @classmethod
- def resource_cleanup(cls):
- cls.data.teardown_all()
- super(BaseIdentityV3AdminTest, cls).resource_cleanup()
-
- @classmethod
def disable_user(cls, user_name, domain_id=None):
user = cls.get_user_by_name(user_name, domain_id)
- cls.users_client.update_user(user['id'], user_name, enabled=False)
+ cls.users_client.update_user(user['id'], name=user_name, enabled=False)
+
+ @classmethod
+ def create_domain(cls):
+ """Create a domain."""
+ domain = cls.domains_client.create_domain(
+ name=data_utils.rand_name('test_domain'),
+ description=data_utils.rand_name('desc'))['domain']
+ return domain
def delete_domain(self, domain_id):
# NOTE(mpavlase) It is necessary to disable the domain before deleting
@@ -179,100 +208,28 @@
self.domains_client.update_domain(domain_id, enabled=False)
self.domains_client.delete_domain(domain_id)
-
-class BaseDataGenerator(object):
-
- def __init__(self, projects_client, users_client, roles_client,
- domains_client=None):
- self.projects_client = projects_client
- self.users_client = users_client
- self.roles_client = roles_client
- self.domains_client = domains_client
-
- self.user_password = None
- self.user = None
- self.tenant = None
- self.project = None
- self.role = None
- self.domain = None
-
- self.users = []
- self.tenants = []
- self.projects = []
- self.roles = []
- self.domains = []
-
- def _create_test_user(self, **kwargs):
- self.user_password = data_utils.rand_password()
- self.user = self.users_client.create_user(
- password=self.user_password,
- **kwargs)['user']
- self.users.append(self.user)
-
- def setup_test_role(self):
- """Set up a test role."""
- self.role = self.roles_client.create_role(
- name=data_utils.rand_name('test_role'))['role']
- self.roles.append(self.role)
-
- def teardown_all(self):
- for user in self.users:
- test_utils.call_and_ignore_notfound_exc(
- self.users_client.delete_user, user['id'])
- for tenant in self.tenants:
- test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_tenant, tenant['id'])
- for project in reversed(self.projects):
- test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_project, project['id'])
- for role in self.roles:
- test_utils.call_and_ignore_notfound_exc(
- self.roles_client.delete_role, role['id'])
- for domain in self.domains:
- test_utils.call_and_ignore_notfound_exc(
- self.domains_client.update_domain, domain['id'], enabled=False)
- test_utils.call_and_ignore_notfound_exc(
- self.domains_client.delete_domain, domain['id'])
-
-
-class DataGeneratorV2(BaseDataGenerator):
-
- def setup_test_user(self):
+ def setup_test_user(self, password=None):
"""Set up a test user."""
- self.setup_test_tenant()
+ project = self.setup_test_project()
username = data_utils.rand_name('test_user')
email = username + '@testmail.tm'
- self._create_test_user(name=username, email=email,
- tenantId=self.tenant['id'])
-
- def setup_test_tenant(self):
- """Set up a test tenant."""
- self.tenant = self.projects_client.create_tenant(
- name=data_utils.rand_name('test_tenant'),
- description=data_utils.rand_name('desc'))['tenant']
- self.tenants.append(self.tenant)
-
-
-class DataGeneratorV3(BaseDataGenerator):
-
- def setup_test_user(self):
- """Set up a test user."""
- self.setup_test_project()
- username = data_utils.rand_name('test_user')
- email = username + '@testmail.tm'
- self._create_test_user(user_name=username, email=email,
- project_id=self.project['id'])
+ user = self._create_test_user(name=username, email=email,
+ project_id=project['id'],
+ password=password)
+ return user
def setup_test_project(self):
"""Set up a test project."""
- self.project = self.projects_client.create_project(
+ project = self.projects_client.create_project(
name=data_utils.rand_name('test_project'),
description=data_utils.rand_name('desc'))['project']
- self.projects.append(self.project)
+ # Delete the project at the end of the test
+ self.addCleanup(self.projects_client.delete_project, project['id'])
+ return project
def setup_test_domain(self):
"""Set up a test domain."""
- self.domain = self.domains_client.create_domain(
- name=data_utils.rand_name('test_domain'),
- description=data_utils.rand_name('desc'))['domain']
- self.domains.append(self.domain)
+ domain = self.create_domain()
+ # Delete the domain at the end of the test
+ self.addCleanup(self.delete_domain, domain['id'])
+ return domain
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index 3c379f0..8f493aa 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -51,7 +51,6 @@
def test_list_ec2_credentials(self):
"""Get the list of user ec2 credentials."""
created_creds = []
- fetched_creds = []
# create first ec2 credentials
creds1 = self.non_admin_users_client.create_user_ec2_credential(
self.creds.user_id,
diff --git a/tempest/api/image/admin/v2/test_images.py b/tempest/api/image/admin/v2/test_images.py
index 80da7a1..c719b7a 100644
--- a/tempest/api/image/admin/v2/test_images.py
+++ b/tempest/api/image/admin/v2/test_images.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
+import six
import testtools
from tempest.api.image import base
@@ -42,7 +42,7 @@
self.addCleanup(self.client.delete_image, image_id)
# upload an image file
content = data_utils.random_bytes()
- image_file = moves.cStringIO(content)
+ image_file = six.BytesIO(content)
self.client.store_image_file(image_id, image_file)
# deactivate image
self.admin_client.deactivate_image(image_id)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
old mode 100644
new mode 100755
index 6fd6ea6..f74f97b
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
+import six
from tempest.common import image as common_image
from tempest.common.utils import data_utils
@@ -118,7 +118,7 @@
cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
def _create_image(self):
- image_file = moves.cStringIO(data_utils.random_bytes())
+ image_file = six.BytesIO(data_utils.random_bytes())
image = self.create_image(container_format='bare',
disk_format='raw',
is_public=False,
@@ -144,6 +144,18 @@
cls.resource_types_client = cls.os.resource_types_client
cls.schemas_client = cls.os.schemas_client
+ def create_namespace(cls, namespace_name=None, visibility='public',
+ description='Tempest', protected=False,
+ **kwargs):
+ if not namespace_name:
+ namespace_name = data_utils.rand_name('test-ns')
+ kwargs.setdefault('display_name', namespace_name)
+ namespace = cls.namespaces_client.create_namespace(
+ namespace=namespace_name, visibility=visibility,
+ description=description, protected=protected, **kwargs)
+ cls.addCleanup(cls.namespaces_client.delete_namespace, namespace_name)
+ return namespace
+
class BaseV2MemberImageTest(BaseV2ImageTest):
@@ -167,7 +179,7 @@
return image_ids
def _create_image(self):
- name = data_utils.rand_name('image')
+ name = data_utils.rand_name(self.__class__.__name__ + '-image')
image = self.client.create_image(name=name,
container_format='bare',
disk_format='raw')
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 94edb6c..50f0926 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -25,7 +25,7 @@
self.image_member_client.create_image_member(image, self.alt_tenant_id)
body = self.image_member_client.list_image_members(image)
members = body['members']
- members = map(lambda x: x['member_id'], members)
+ members = [member['member_id'] for member in members]
self.assertIn(self.alt_tenant_id, members)
# get image as alt user
self.alt_img_cli.show_image(image)
@@ -40,7 +40,7 @@
body = self.image_member_client.list_shared_images(
self.alt_tenant_id)
images = body['shared_images']
- images = map(lambda x: x['image_id'], images)
+ images = [img['image_id'] for img in images]
self.assertIn(share_image, images)
self.assertIn(image, images)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index def7750..712b34b 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
+import six
from tempest.api.image import base
from tempest.common import image as common_image
@@ -63,7 +63,7 @@
self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
- image_file = moves.cStringIO(data_utils.random_bytes())
+ image_file = six.BytesIO(data_utils.random_bytes())
body = self.client.update_image(image_id, data=image_file)['image']
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -199,7 +199,7 @@
Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = moves.cStringIO(data_utils.random_bytes(size))
+ image_file = six.BytesIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
image = cls.create_image(name=name,
container_format=container_format,
@@ -212,7 +212,7 @@
def test_index_no_params(self):
# Simple test to see all fixture images returned
images_list = self.client.list_images()['images']
- image_list = map(lambda x: x['id'], images_list)
+ image_list = [image['id'] for image in images_list]
for image_id in self.created_images:
self.assertIn(image_id, image_list)
@@ -294,7 +294,7 @@
disk_format, size):
"""Create a new standard image and return newly-registered image-id"""
- image_file = moves.cStringIO(data_utils.random_bytes(size))
+ image_file = six.BytesIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
image = cls.create_image(name=name,
container_format=container_format,
@@ -322,10 +322,7 @@
metadata['properties'].update(req_metadata)
headers = common_image.image_meta_to_headers(
properties=metadata['properties'])
- metadata = self.client.update_image(self.image_id,
- headers=headers)['image']
-
+ self.client.update_image(self.image_id, headers=headers)
resp = self.client.check_image(self.image_id)
resp_metadata = common_image.get_image_meta_from_headers(resp)
- expected = {'key1': 'alt1', 'key2': 'value2'}
- self.assertEqual(expected, resp_metadata['properties'])
+ self.assertEqual(req_metadata, resp_metadata['properties'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 1fb9c52..443e332 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -16,7 +16,7 @@
import random
-from six import moves
+import six
from oslo_log import log as logging
from tempest.api.image import base
@@ -60,7 +60,7 @@
# Now try uploading an image file
file_content = data_utils.random_bytes()
- image_file = moves.cStringIO(file_content)
+ image_file = six.BytesIO(file_content)
self.client.store_image_file(image_id, image_file)
# Now try to get image details
@@ -117,7 +117,7 @@
image_id = body['id']
# Now try uploading an image file
- image_file = moves.cStringIO(data_utils.random_bytes())
+ image_file = six.BytesIO(data_utils.random_bytes())
self.client.store_image_file(image_id, image_file)
# Update Image
@@ -160,7 +160,7 @@
1024 and 4096
"""
size = random.randint(1024, 4096)
- image_file = moves.cStringIO(data_utils.random_bytes(size))
+ image_file = six.BytesIO(data_utils.random_bytes(size))
name = data_utils.rand_name('image')
body = cls.create_image(name=name,
container_format=container_format,
@@ -185,7 +185,7 @@
def test_list_no_params(self):
# Simple test to see all fixture images returned
images_list = self.client.list_images()['images']
- image_list = map(lambda x: x['id'], images_list)
+ image_list = [image['id'] for image in images_list]
for image in self.created_images:
self.assertIn(image, image_list)
diff --git a/tempest/api/image/v2/test_images_metadefs_resource_types.py b/tempest/api/image/v2/test_images_metadefs_resource_types.py
new file mode 100644
index 0000000..a5143a1
--- /dev/null
+++ b/tempest/api/image/v2/test_images_metadefs_resource_types.py
@@ -0,0 +1,54 @@
+# Copyright 2016 Ericsson India Global Services Private Limited
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest import test
+
+
+class MetadataResourceTypesTest(base.BaseV2ImageTest):
+ """Test the Metadata definition ressource types basic functionality"""
+
+ @test.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606')
+ def test_basic_meta_def_resource_type_association(self):
+ # Get the available resource types and use one resource_type
+ body = self.resource_types_client.list_resource_types()
+ resource_name = body['resource_types'][0]['name']
+ # Create a namespace
+ namespace = self.create_namespace()
+ # Create resource type association
+ body = self.resource_types_client.create_resource_type_association(
+ namespace['namespace'], name=resource_name)
+ self.assertEqual(body['name'], resource_name)
+ # NOTE(raiesmh08): Here intentionally I have not added addcleanup
+ # method for resource type dissociation because its a metadata add and
+ # being cleaned as soon as namespace is cleaned at test case level.
+ # When namespace cleans, resource type associaion will automatically
+ # clean without any error or dependency.
+
+ # List resource type associations and validate creation
+ rs_type_associations = [
+ rs_type_association['name'] for rs_type_association in
+ self.resource_types_client.list_resource_type_association(
+ namespace['namespace'])['resource_type_associations']]
+ self.assertIn(resource_name, rs_type_associations)
+ # Delete resource type association
+ self.resource_types_client.delete_resource_type_association(
+ namespace['namespace'], resource_name)
+ # List resource type associations and validate deletion
+ rs_type_associations = [
+ rs_type_association['name'] for rs_type_association in
+ self.resource_types_client.list_resource_type_association(
+ namespace['namespace'])['resource_type_associations']]
+ self.assertNotIn(resource_name, rs_type_associations)
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 14de8fd..f60fb0c 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -29,7 +29,7 @@
** get image with image_id=NULL
** get the deleted image
** delete non-existent image
- ** delete rimage with image_id=NULL
+ ** delete image with image_id=NULL
** delete the deleted image
"""
diff --git a/tempest/api/network/admin/test_floating_ips_admin_actions.py b/tempest/api/network/admin/test_floating_ips_admin_actions.py
index baeaa0c..2686af2 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -26,6 +26,13 @@
credentials = ['primary', 'alt', 'admin']
@classmethod
+ def skip_checks(cls):
+ super(FloatingIPAdminTestJSON, cls).skip_checks()
+ if not test.is_extension_enabled('router', 'network'):
+ msg = "router extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
def setup_clients(cls):
super(FloatingIPAdminTestJSON, cls).setup_clients()
cls.alt_floating_ips_client = cls.alt_manager.floating_ips_client
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 9e7c795..5c67d68 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -270,7 +270,7 @@
"""Wrapper utility that returns a test metering label."""
body = cls.admin_metering_labels_client.create_metering_label(
description=description,
- name=data_utils.rand_name("metering-label"))
+ name=name)
metering_label = body['metering_label']
cls.metering_labels.append(metering_label)
return metering_label
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index 2abbf93..c64b01e 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -54,7 +54,7 @@
# Create network, subnet, router and add interface
cls.network = cls.create_network()
- cls.subnet = cls.create_subnet(cls.network)
+ cls.subnet = cls.create_subnet(cls.network, enable_dhcp=False)
cls.router = cls.create_router(data_utils.rand_name('router-'),
external_network_id=cls.ext_net_id)
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index bf80ff5..3825f84 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -127,7 +127,7 @@
def _get_allocation_pools_from_gateway(cls, ip_version):
"""Return allocation range for subnet of given gateway"""
gateway = cls._get_gateway_from_tempest_conf(ip_version)
- return [{'start': str(gateway + 2), 'end': str(gateway + 3)}]
+ return [{'start': str(gateway + 2), 'end': str(gateway + 6)}]
def subnet_dict(self, include_keys):
# Return a subnet dict which has include_keys and their corresponding
@@ -559,7 +559,7 @@
# Verifies Subnet GW is set in IPv6
self.assertEqual(subnet1['gateway_ip'], ipv6_gateway)
# Verifies Subnet GW is None in IPv4
- self.assertEqual(subnet2['gateway_ip'], None)
+ self.assertIsNone(subnet2['gateway_ip'])
# Verifies all 2 subnets in the same network
body = self.subnets_client.list_subnets()
subnets = [sub['id'] for sub in body['subnets']
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 97d9eed..fd973c6 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 data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -57,16 +58,51 @@
cls.container_client.auth_provider.clear_auth()
cls.account_client.auth_provider.clear_auth()
+ # make sure that discoverability is enabled and that the sections
+ # have not been disallowed by Swift
+ cls.policies = None
+
+ if CONF.object_storage_feature_enabled.discoverability:
+ _, body = cls.account_client.list_extensions()
+
+ if 'swift' in body and 'policies' in body['swift']:
+ cls.policies = body['swift']['policies']
+
+ cls.containers = []
+
@classmethod
- def delete_containers(cls, containers, container_client=None,
+ def create_container(cls):
+ # wrapper that returns a test container
+ container_name = data_utils.rand_name(name='TestContainer')
+ cls.container_client.create_container(container_name)
+ cls.containers.append(container_name)
+
+ return container_name
+
+ @classmethod
+ def create_object(cls, container_name, object_name=None,
+ data=None, metadata=None):
+ # wrapper that returns a test object
+ if object_name is None:
+ object_name = data_utils.rand_name(name='TestObject')
+ if data is None:
+ data = data_utils.arbitrary_string()
+ cls.object_client.create_object(container_name,
+ object_name,
+ data,
+ metadata=metadata)
+
+ return object_name, data
+
+ @classmethod
+ def delete_containers(cls, container_client=None,
object_client=None):
- """Remove given containers and all objects in them.
+ """Remove containers and all objects in them.
The containers should be visible from the container_client given.
Will not throw any error if the containers don't exist.
Will not check that object and container deletions succeed.
- :param containers: list of container names to remove
:param container_client: if None, use cls.container_client, this means
that the default testing user will be used (see 'username' in
'etc/tempest.conf')
@@ -76,7 +112,7 @@
container_client = cls.container_client
if object_client is None:
object_client = cls.object_client
- for cont in containers:
+ for cont in cls.containers:
try:
objlist = container_client.list_all_container_objects(cont)
# delete every object in the container
@@ -91,5 +127,5 @@
"""Check the existence and the format of response headers"""
self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
- target, method))
+ target, method, self.policies))
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index da4c80c..7292ee9 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -27,7 +27,7 @@
self.containers = []
def tearDown(self):
- self.delete_containers(self.containers)
+ self.delete_containers()
super(BulkTest, self).tearDown()
def _create_archive(self):
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 0f6a330..fcbd6eb 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -34,8 +34,7 @@
@classmethod
def resource_setup(cls):
super(AccountQuotasTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name="TestContainer")
- cls.container_client.create_container(cls.container_name)
+ cls.container_name = cls.create_container()
# Retrieve a ResellerAdmin auth data and use it to set a quota
# on the client's account
@@ -73,8 +72,7 @@
@classmethod
def resource_cleanup(cls):
- if hasattr(cls, "container_name"):
- cls.delete_containers([cls.container_name])
+ cls.delete_containers()
super(AccountQuotasTest, cls).resource_cleanup()
@test.attr(type="smoke")
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index 546bb06..ae8dfcc 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -13,9 +13,7 @@
# under the License.
from tempest.api.object_storage import base
-from tempest.common.utils import data_utils
from tempest import config
-from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -36,8 +34,7 @@
@classmethod
def resource_setup(cls):
super(AccountQuotasNegativeTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name="TestContainer")
- cls.container_client.create_container(cls.container_name)
+ cls.container_name = cls.create_container()
# Retrieve a ResellerAdmin auth data and use it to set a quota
# on the client's account
@@ -74,8 +71,7 @@
@classmethod
def resource_cleanup(cls):
- if hasattr(cls, "container_name"):
- cls.delete_containers([cls.container_name])
+ cls.delete_containers()
super(AccountQuotasNegativeTest, cls).resource_cleanup()
@test.attr(type=["negative"])
@@ -93,14 +89,3 @@
self.assertRaises(lib_exc.Forbidden,
self.account_client.create_account_metadata,
{"Quota-Bytes": "100"})
-
- @test.attr(type=["negative"])
- @decorators.skip_because(bug="1310597")
- @test.idempotent_id('cf9e21f5-3aa4-41b1-9462-28ac550d8d3f')
- @test.requires_ext(extension='account_quotas', service='object')
- def test_upload_large_object(self):
- object_name = data_utils.rand_name(name="TestObject")
- data = data_utils.arbitrary_string(30)
- self.assertRaises(lib_exc.OverLimit,
- self.object_client.create_object,
- self.container_name, object_name, data)
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 5983c1f..a0c0a5f 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -15,7 +15,6 @@
import random
-from six import moves
import testtools
from tempest.api.object_storage import base
@@ -42,7 +41,7 @@
@classmethod
def resource_setup(cls):
super(AccountTest, cls).resource_setup()
- for i in moves.xrange(ord('a'), ord('f') + 1):
+ for i in range(ord('a'), ord('f') + 1):
name = data_utils.rand_name(name='%s-' % chr(i))
cls.container_client.create_container(name)
cls.containers.append(name)
@@ -50,7 +49,7 @@
@classmethod
def resource_cleanup(cls):
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(AccountTest, cls).resource_cleanup()
@test.attr(type='smoke')
@@ -78,7 +77,16 @@
# container request, the response does not contain 'accept-ranges'
# header. This is a special case, therefore the existence of response
# headers is checked without custom matcher.
- self.assertIn('content-length', resp)
+ #
+ # As the expected response is 204 No Content, Content-Length presence
+ # is not checked here intensionally. According to RFC 7230 a server
+ # MUST NOT send the header in such responses. Thus, clients should not
+ # depend on this header. However, the standard does not require them
+ # to validate the server's behavior. We leverage that to not refuse
+ # any implementation violating it like Swift [1] or some versions of
+ # Ceph RadosGW [2].
+ # [1] https://bugs.launchpad.net/swift/+bug/1537811
+ # [2] http://tracker.ceph.com/issues/13582
self.assertIn('x-timestamp', resp)
self.assertIn('x-account-bytes-used', resp)
self.assertIn('x-account-container-count', resp)
@@ -131,7 +139,8 @@
@test.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2')
def test_list_containers_with_limit(self):
# list containers one of them, half of them then all of them
- for limit in (1, self.containers_count / 2, self.containers_count):
+ for limit in (1, self.containers_count // 2,
+ self.containers_count):
params = {'limit': limit}
resp, container_list = \
self.account_client.list_account_containers(params=params)
@@ -152,12 +161,13 @@
self.assertEqual(len(container_list), 0)
- params = {'marker': self.containers[self.containers_count / 2]}
+ params = {'marker': self.containers[self.containers_count // 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
self.assertHeaders(resp, 'Account', 'GET')
- self.assertEqual(len(container_list), self.containers_count / 2 - 1)
+ self.assertEqual(len(container_list),
+ self.containers_count // 2 - 1)
@test.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786')
def test_list_containers_with_end_marker(self):
@@ -171,11 +181,11 @@
self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list), 0)
- params = {'end_marker': self.containers[self.containers_count / 2]}
+ params = {'end_marker': self.containers[self.containers_count // 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
self.assertHeaders(resp, 'Account', 'GET')
- self.assertEqual(len(container_list), self.containers_count / 2)
+ self.assertEqual(len(container_list), self.containers_count // 2)
@test.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e')
def test_list_containers_with_marker_and_end_marker(self):
@@ -206,12 +216,12 @@
# list containers combining limit and end_marker param
limit = random.randint(1, self.containers_count)
params = {'limit': limit,
- 'end_marker': self.containers[self.containers_count / 2]}
+ 'end_marker': self.containers[self.containers_count // 2]}
resp, container_list = self.account_client.list_account_containers(
params=params)
self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list),
- min(limit, self.containers_count / 2))
+ min(limit, self.containers_count // 2))
@test.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd')
def test_list_containers_with_limit_and_marker_and_end_marker(self):
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index c1b6711..ffdd1de 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -39,11 +39,10 @@
def setUp(self):
super(ObjectTestACLs, self).setUp()
- self.container_name = data_utils.rand_name(name='TestContainer')
- self.container_client.create_container(self.container_name)
+ self.container_name = self.create_container()
def tearDown(self):
- self.delete_containers([self.container_name])
+ self.delete_containers()
super(ObjectTestACLs, self).tearDown()
@test.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6')
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 01e5389..8cbe441 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -15,11 +15,9 @@
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
-CONF = config.CONF
QUOTA_BYTES = 10
QUOTA_COUNT = 3
@@ -38,8 +36,7 @@
Maximum object count of the container.
"""
super(ContainerQuotasTest, self).setUp()
- self.container_name = data_utils.rand_name(name="TestContainer")
- self.container_client.create_container(self.container_name)
+ self.container_name = self.create_container()
metadata = {"quota-bytes": str(QUOTA_BYTES),
"quota-count": str(QUOTA_COUNT), }
self.container_client.update_container_metadata(
@@ -47,7 +44,7 @@
def tearDown(self):
"""Cleans the container of any object after each test."""
- self.delete_containers([self.container_name])
+ self.delete_containers()
super(ContainerQuotasTest, self).tearDown()
@test.idempotent_id('9a0fb034-86af-4df0-86fa-f8bd7db21ae0')
@@ -71,7 +68,7 @@
@test.requires_ext(extension='container_quotas', service='object')
@test.attr(type="smoke")
def test_upload_large_object(self):
- """Attempts to upload an object lagger than the bytes quota."""
+ """Attempts to upload an object larger than the bytes quota."""
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string(QUOTA_BYTES + 1)
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 9d043e5..8522269 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -19,33 +19,10 @@
class ContainerTest(base.BaseObjectTest):
- def setUp(self):
- super(ContainerTest, self).setUp()
- self.containers = []
-
def tearDown(self):
- self.delete_containers(self.containers)
+ self.delete_containers()
super(ContainerTest, self).tearDown()
- def _create_container(self):
- # setup container
- container_name = data_utils.rand_name(name='TestContainer')
- self.container_client.create_container(container_name)
- self.containers.append(container_name)
-
- return container_name
-
- def _create_object(self, container_name, object_name=None):
- # setup object
- if object_name is None:
- object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
- self.object_client.create_object(container_name,
- object_name,
- data)
-
- return object_name
-
@test.attr(type='smoke')
@test.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
def test_create_container(self):
@@ -140,18 +117,17 @@
@test.idempotent_id('95d3a249-b702-4082-a2c4-14bb860cf06a')
def test_delete_container(self):
# create a container
- container_name = self._create_container()
+ container_name = self.create_container()
# delete container, success asserted within
resp, _ = self.container_client.delete_container(container_name)
self.assertHeaders(resp, 'Container', 'DELETE')
- self.containers.remove(container_name)
@test.attr(type='smoke')
@test.idempotent_id('312ff6bd-5290-497f-bda1-7c5fec6697ab')
def test_list_container_contents(self):
# get container contents list
- container_name = self._create_container()
- object_name = self._create_object(container_name)
+ container_name = self.create_container()
+ object_name, _ = self.create_object(container_name)
resp, object_list = self.container_client.list_container_contents(
container_name)
@@ -161,7 +137,7 @@
@test.idempotent_id('4646ac2d-9bfb-4c7d-a3c5-0f527402b3df')
def test_list_container_contents_with_no_object(self):
# get empty container contents list
- container_name = self._create_container()
+ container_name = self.create_container()
resp, object_list = self.container_client.list_container_contents(
container_name)
@@ -171,9 +147,9 @@
@test.idempotent_id('fe323a32-57b9-4704-a996-2e68f83b09bc')
def test_list_container_contents_with_delimiter(self):
# get container contents list using delimiter param
- container_name = self._create_container()
+ container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject/')
- self._create_object(container_name, object_name)
+ self.create_object(container_name, object_name)
params = {'delimiter': '/'}
resp, object_list = self.container_client.list_container_contents(
@@ -185,8 +161,8 @@
@test.idempotent_id('55b4fa5c-e12e-4ca9-8fcf-a79afe118522')
def test_list_container_contents_with_end_marker(self):
# get container contents list using end_marker param
- container_name = self._create_container()
- object_name = self._create_object(container_name)
+ container_name = self.create_container()
+ object_name, _ = self.create_object(container_name)
params = {'end_marker': 'ZzzzObject1234567890'}
resp, object_list = self.container_client.list_container_contents(
@@ -198,8 +174,8 @@
@test.idempotent_id('196f5034-6ab0-4032-9da9-a937bbb9fba9')
def test_list_container_contents_with_format_json(self):
# get container contents list using format_json param
- container_name = self._create_container()
- self._create_object(container_name)
+ container_name = self.create_container()
+ self.create_object(container_name)
params = {'format': 'json'}
resp, object_list = self.container_client.list_container_contents(
@@ -217,8 +193,8 @@
@test.idempotent_id('655a53ca-4d15-408c-a377-f4c6dbd0a1fa')
def test_list_container_contents_with_format_xml(self):
# get container contents list using format_xml param
- container_name = self._create_container()
- self._create_object(container_name)
+ container_name = self.create_container()
+ self.create_object(container_name)
params = {'format': 'xml'}
resp, object_list = self.container_client.list_container_contents(
@@ -241,8 +217,8 @@
@test.idempotent_id('297ec38b-2b61-4ff4-bcd1-7fa055e97b61')
def test_list_container_contents_with_limit(self):
# get container contents list using limit param
- container_name = self._create_container()
- object_name = self._create_object(container_name)
+ container_name = self.create_container()
+ object_name, _ = self.create_object(container_name)
params = {'limit': data_utils.rand_int_id(1, 10000)}
resp, object_list = self.container_client.list_container_contents(
@@ -254,8 +230,8 @@
@test.idempotent_id('c31ddc63-2a58-4f6b-b25c-94d2937e6867')
def test_list_container_contents_with_marker(self):
# get container contents list using marker param
- container_name = self._create_container()
- object_name = self._create_object(container_name)
+ container_name = self.create_container()
+ object_name, _ = self.create_object(container_name)
params = {'marker': 'AaaaObject1234567890'}
resp, object_list = self.container_client.list_container_contents(
@@ -267,9 +243,9 @@
@test.idempotent_id('58ca6cc9-6af0-408d-aaec-2a6a7b2f0df9')
def test_list_container_contents_with_path(self):
# get container contents list using path param
- container_name = self._create_container()
+ container_name = self.create_container()
object_name = data_utils.rand_name(name='Swift/TestObject')
- self._create_object(container_name, object_name)
+ self.create_object(container_name, object_name)
params = {'path': 'Swift'}
resp, object_list = self.container_client.list_container_contents(
@@ -281,8 +257,8 @@
@test.idempotent_id('77e742c7-caf2-4ec9-8aa4-f7d509a3344c')
def test_list_container_contents_with_prefix(self):
# get container contents list using prefix param
- container_name = self._create_container()
- object_name = self._create_object(container_name)
+ container_name = self.create_container()
+ object_name, _ = self.create_object(container_name)
prefix_key = object_name[0:8]
params = {'prefix': prefix_key}
@@ -296,7 +272,7 @@
@test.idempotent_id('96e68f0e-19ec-4aa2-86f3-adc6a45e14dd')
def test_list_container_metadata(self):
# List container metadata
- container_name = self._create_container()
+ container_name = self.create_container()
metadata = {'name': 'Pictures'}
self.container_client.update_container_metadata(
@@ -312,7 +288,7 @@
@test.idempotent_id('a2faf936-6b13-4f8d-92a2-c2278355821e')
def test_list_no_container_metadata(self):
# HEAD container without metadata
- container_name = self._create_container()
+ container_name = self.create_container()
resp, _ = self.container_client.list_container_metadata(
container_name)
@@ -345,7 +321,7 @@
@test.idempotent_id('2ae5f295-4bf1-4e04-bfad-21e54b62cec5')
def test_update_container_metadata_with_create_metadata(self):
# update container metadata using add metadata
- container_name = self._create_container()
+ container_name = self.create_container()
metadata = {'test-container-meta1': 'Meta1'}
resp, _ = self.container_client.update_container_metadata(
@@ -380,7 +356,7 @@
@test.idempotent_id('31f40a5f-6a52-4314-8794-cd89baed3040')
def test_update_container_metadata_with_create_metadata_key(self):
# update container metadata with a blank value of metadata
- container_name = self._create_container()
+ container_name = self.create_container()
metadata = {'test-container-meta1': ''}
resp, _ = self.container_client.update_container_metadata(
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 5b3ce79..47ef0d3 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -24,18 +24,14 @@
@classmethod
def resource_setup(cls):
super(StaticWebTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name="TestContainer")
# This header should be posted on the container before every test
cls.headers_public_read_acl = {'Read': '.r:*,.rlistings'}
# Create test container and create one object in it
- cls.container_client.create_container(cls.container_name)
- cls.object_name = data_utils.rand_name(name="TestObject")
- cls.object_data = data_utils.arbitrary_string()
- cls.object_client.create_object(cls.container_name,
- cls.object_name,
- cls.object_data)
+ cls.container_name = cls.create_container()
+ cls.object_name, cls.object_data = cls.create_object(
+ cls.container_name)
cls.container_client.update_container_metadata(
cls.container_name,
@@ -44,8 +40,7 @@
@classmethod
def resource_cleanup(cls):
- if hasattr(cls, "container_name"):
- cls.delete_containers([cls.container_name])
+ cls.delete_containers()
super(StaticWebTest, cls).resource_cleanup()
@test.idempotent_id('c1f055ab-621d-4a6a-831f-846fcb578b8b')
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 2a5cec6..e10b900 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -80,7 +80,7 @@
@classmethod
def resource_cleanup(cls):
for client in cls.clients.values():
- cls.delete_containers(cls.containers, client[0], client[1])
+ cls.delete_containers(client[0], client[1])
super(ContainerSyncTest, cls).resource_cleanup()
def _test_container_synchronization(self, make_headers):
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 9db8bde..11acb31 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -16,7 +16,6 @@
import time
from tempest.api.object_storage import base
-from tempest.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -25,19 +24,17 @@
@classmethod
def resource_setup(cls):
super(ObjectExpiryTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name='TestContainer')
- cls.container_client.create_container(cls.container_name)
+ cls.container_name = cls.create_container()
def setUp(self):
super(ObjectExpiryTest, self).setUp()
# create object
- self.object_name = data_utils.rand_name(name='TestObject')
- resp, _ = self.object_client.create_object(self.container_name,
- self.object_name, '')
+ self.object_name, _ = self.create_object(
+ self.container_name)
@classmethod
def resource_cleanup(cls):
- cls.delete_containers([cls.container_name])
+ cls.delete_containers()
super(ObjectExpiryTest, cls).resource_cleanup()
def _test_object_expiry(self, metadata):
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index 356f560..102ec2f 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -31,12 +31,9 @@
@classmethod
def resource_setup(cls):
super(ObjectFormPostTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name='TestContainer')
+ cls.container_name = cls.create_container()
cls.object_name = data_utils.rand_name(name='ObjectTemp')
- cls.container_client.create_container(cls.container_name)
- cls.containers = [cls.container_name]
-
cls.key = 'Meta'
cls.metadata = {'Temp-URL-Key': cls.key}
cls.account_client.create_account_metadata(metadata=cls.metadata)
@@ -56,7 +53,7 @@
@classmethod
def resource_cleanup(cls):
cls.account_client.delete_account_metadata(metadata=cls.metadata)
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ObjectFormPostTest, cls).resource_cleanup()
def get_multipart_form(self, expires=600):
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index cb13271..8ff5d82 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -32,12 +32,9 @@
@classmethod
def resource_setup(cls):
super(ObjectFormPostNegativeTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name='TestContainer')
+ cls.container_name = cls.create_container()
cls.object_name = data_utils.rand_name(name='ObjectTemp')
- cls.container_client.create_container(cls.container_name)
- cls.containers = [cls.container_name]
-
cls.key = 'Meta'
cls.metadata = {'Temp-URL-Key': cls.key}
cls.account_client.create_account_metadata(metadata=cls.metadata)
@@ -57,7 +54,7 @@
@classmethod
def resource_cleanup(cls):
cls.account_client.delete_account_metadata(metadata=cls.metadata)
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ObjectFormPostNegativeTest, cls).resource_cleanup()
def get_multipart_form(self, expires=600):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index a88e4f4..919f695 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -35,32 +35,21 @@
@classmethod
def resource_setup(cls):
super(ObjectTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name='TestContainer')
- cls.container_client.create_container(cls.container_name)
- cls.containers = [cls.container_name]
+ cls.container_name = cls.create_container()
@classmethod
def resource_cleanup(cls):
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ObjectTest, cls).resource_cleanup()
- def _create_object(self, metadata=None):
- # setup object
- object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
- self.object_client.create_object(self.container_name,
- object_name, data, metadata=metadata)
-
- return object_name, data
-
def _upload_segments(self):
# create object
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in six.moves.xrange(segments)]
+ data_segments = [data + str(i) for i in range(segments)]
# uploading segments
- for i in six.moves.xrange(segments):
+ for i in range(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
@@ -335,7 +324,7 @@
@test.idempotent_id('7a94c25d-66e6-434c-9c38-97d4e2c29945')
def test_update_object_metadata(self):
# update object metadata
- object_name, data = self._create_object()
+ object_name, _ = self.create_object(self.container_name)
metadata = {'X-Object-Meta-test-meta': 'Meta'}
resp, _ = self.object_client.update_object_metadata(
@@ -431,8 +420,8 @@
@test.idempotent_id('0dbbe89c-6811-4d84-a2df-eca2bdd40c0e')
def test_update_object_metadata_with_x_object_metakey(self):
- # update object metadata with a blenk value of metadata
- object_name, data = self._create_object()
+ # update object metadata with a blank value of metadata
+ object_name, _ = self.create_object(self.container_name)
update_metadata = {'X-Object-Meta-test-meta': ''}
resp, _ = self.object_client.update_object_metadata(
@@ -494,7 +483,7 @@
@test.idempotent_id('170fb90e-f5c3-4b1f-ae1b-a18810821172')
def test_list_no_object_metadata(self):
# get empty list of object metadata
- object_name, data = self._create_object()
+ object_name, _ = self.create_object(self.container_name)
resp, _ = self.object_client.list_object_metadata(
self.container_name,
@@ -548,7 +537,7 @@
# retrieve object's data (in response body)
# create object
- object_name, data = self._create_object()
+ object_name, data = self.create_object(self.container_name)
# get object
resp, body = self.object_client.get_object(self.container_name,
object_name)
@@ -701,7 +690,7 @@
@test.idempotent_id('0aa1201c-10aa-467a-bee7-63cbdd463152')
def test_get_object_with_if_unmodified_since(self):
# get object with if_unmodified_since
- object_name, data = self._create_object()
+ object_name, data = self.create_object(self.container_name)
time_now = time.time()
http_date = time.ctime(time_now + 86400)
@@ -716,7 +705,7 @@
@test.idempotent_id('94587078-475f-48f9-a40f-389c246e31cd')
def test_get_object_with_x_newest(self):
# get object with x_newest
- object_name, data = self._create_object()
+ object_name, data = self.create_object(self.container_name)
list_metadata = {'X-Newest': 'true'}
resp, body = self.object_client.get_object(
@@ -757,7 +746,7 @@
# change the content type of an existing object
# create object
- object_name, data = self._create_object()
+ object_name, _ = self.create_object(self.container_name)
# get the old content type
resp_tmp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
@@ -843,7 +832,8 @@
def test_copy_object_with_x_fresh_metadata(self):
# create source object
metadata = {'x-object-meta-src': 'src_value'}
- src_object_name, data = self._create_object(metadata)
+ src_object_name, data = self.create_object(self.container_name,
+ metadata=metadata)
# copy source object with x_fresh_metadata header
metadata = {'X-Fresh-Metadata': 'true'}
@@ -863,7 +853,8 @@
def test_copy_object_with_x_object_metakey(self):
# create source object
metadata = {'x-object-meta-src': 'src_value'}
- src_obj_name, data = self._create_object(metadata)
+ src_obj_name, data = self.create_object(self.container_name,
+ metadata=metadata)
# copy source object to destination with x-object-meta-key
metadata = {'x-object-meta-test': ''}
@@ -885,7 +876,8 @@
def test_copy_object_with_x_object_meta(self):
# create source object
metadata = {'x-object-meta-src': 'src_value'}
- src_obj_name, data = self._create_object(metadata)
+ src_obj_name, data = self.create_object(self.container_name,
+ metadata=metadata)
# copy source object to destination with object metadata
metadata = {'x-object-meta-test': 'value'}
@@ -909,9 +901,9 @@
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in six.moves.xrange(segments)]
+ data_segments = [data + str(i) for i in range(segments)]
# uploading segments
- for i in six.moves.xrange(segments):
+ for i in range(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
# creating a manifest file
@@ -951,7 +943,7 @@
# Make a conditional request for an object using the If-None-Match
# header, it should get downloaded only if the local file is different,
# otherwise the response code should be 304 Not Modified
- object_name, data = self._create_object()
+ object_name, data = self.create_object(self.container_name)
# local copy is identical, no download
md5 = hashlib.md5(data).hexdigest()
headers = {'If-None-Match': md5}
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 867159b..e00bbab 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -30,8 +30,7 @@
def setUp(self):
super(ObjectSloTest, self).setUp()
- self.container_name = data_utils.rand_name(name='TestContainer')
- self.container_client.create_container(self.container_name)
+ self.container_name = self.create_container()
self.objects = []
def tearDown(self):
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 3d28f6e..7287a2d 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -20,11 +20,8 @@
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class ObjectTempUrlTest(base.BaseObjectTest):
@@ -32,9 +29,7 @@
def resource_setup(cls):
super(ObjectTempUrlTest, cls).resource_setup()
# create a container
- cls.container_name = data_utils.rand_name(name='TestContainer')
- cls.container_client.create_container(cls.container_name)
- cls.containers = [cls.container_name]
+ cls.container_name = cls.create_container()
# update account metadata
cls.key = 'Meta'
@@ -44,11 +39,7 @@
cls.account_client.create_account_metadata(metadata=metadata)
# create an object
- cls.object_name = data_utils.rand_name(name='ObjectTemp')
- cls.content = data_utils.arbitrary_string(size=len(cls.object_name),
- base_text=cls.object_name)
- cls.object_client.create_object(cls.container_name,
- cls.object_name, cls.content)
+ cls.object_name, cls.content = cls.create_object(cls.container_name)
@classmethod
def resource_cleanup(cls):
@@ -56,7 +47,7 @@
cls.account_client.delete_account_metadata(
metadata=metadata)
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ObjectTempUrlTest, cls).resource_cleanup()
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index 38fe697..577f3bd 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -33,9 +33,7 @@
def resource_setup(cls):
super(ObjectTempUrlNegativeTest, cls).resource_setup()
- cls.container_name = data_utils.rand_name(name='TestContainer')
- cls.container_client.create_container(cls.container_name)
- cls.containers = [cls.container_name]
+ cls.container_name = cls.create_container()
# update account metadata
cls.key = 'Meta'
@@ -49,7 +47,7 @@
resp, _ = cls.account_client.delete_account_metadata(
metadata=cls.metadata)
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ObjectTempUrlNegativeTest, cls).resource_cleanup()
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 24ec3f5..3f6623b 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -31,7 +31,7 @@
@classmethod
def resource_cleanup(cls):
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(ContainerTest, cls).resource_cleanup()
def assertContainer(self, container, count, byte, versioned):
diff --git a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
index be33c94..ccb1b54 100644
--- a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
@@ -58,7 +58,7 @@
#!/bin/sh -v
SIGNAL_DATA='{"Status": "SUCCESS", "Reason": "SmokeServerNeutron created", "Data": "Application has completed configuration.", "UniqueId": "00000"}'
- while ! curl --fail -X PUT -H 'Content-Type:' --data-binary "$SIGNAL_DATA" \
+ while ! curl --insecure --fail -X PUT -H 'Content-Type:' --data-binary "$SIGNAL_DATA" \
'wait_handle' ; do sleep 3; done
params:
wait_handle: {get_resource: WaitHandleNeutron}
diff --git a/tempest/api/orchestration/stacks/test_environment.py b/tempest/api/orchestration/stacks/test_environment.py
index 9d2b425..f2ffbd7 100644
--- a/tempest/api/orchestration/stacks/test_environment.py
+++ b/tempest/api/orchestration/stacks/test_environment.py
@@ -12,13 +12,9 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import test
-CONF = config.CONF
-
-
class StackEnvironmentTest(base.BaseOrchestrationTest):
@test.idempotent_id('37d4346b-1abd-4442-b7b1-2a4e5749a1e3')
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 3be5bb6..4ead084 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -125,7 +125,7 @@
'resource_status_reason',
'resource_status', 'event_time')
- resource_statuses = map(lambda event: event['resource_status'], events)
+ resource_statuses = [event['resource_status'] for event in events]
self.assertIn('CREATE_IN_PROGRESS', resource_statuses)
self.assertIn('CREATE_COMPLETE', resource_statuses)
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
index 6a4e2b9..b660f6e 100644
--- a/tempest/api/orchestration/stacks/test_soft_conf.py
+++ b/tempest/api/orchestration/stacks/test_soft_conf.py
@@ -12,12 +12,9 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
-CONF = config.CONF
-
class TestSoftwareConfig(base.BaseOrchestrationTest):
@@ -45,7 +42,7 @@
def _validate_config(self, configuration, api_config):
# Assert all expected keys are present with matching data
- for k in configuration.keys():
+ for k in configuration:
self.assertEqual(configuration[k],
api_config['software_config'][k])
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
new file mode 100644
index 0000000..8a21853
--- /dev/null
+++ b/tempest/api/volume/admin/test_backends_capabilities.py
@@ -0,0 +1,79 @@
+# 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 operator
+
+from tempest.api.volume import base
+from tempest import test
+
+
+class BackendsCapabilitiesAdminV2TestsJSON(base.BaseVolumeAdminTest):
+
+ CAPABILITIES = ('namespace',
+ 'vendor_name',
+ 'volume_backend_name',
+ 'pool_name',
+ 'driver_version',
+ 'storage_protocol',
+ 'display_name',
+ 'description',
+ 'visibility',
+ 'properties')
+
+ @classmethod
+ def resource_setup(cls):
+ super(BackendsCapabilitiesAdminV2TestsJSON, cls).resource_setup()
+ # Get host list, formation: host@backend-name
+ cls.hosts = [
+ pool['name'] for pool in
+ cls.admin_volume_client.show_pools()['pools']
+ ]
+
+ @test.idempotent_id('3750af44-5ea2-4cd4-bc3e-56e7e6caf854')
+ def test_get_capabilities_backend(self):
+ # Test backend properties
+ backend = self.admin_volume_client.show_backend_capabilities(
+ self.hosts[0])
+
+ # Verify getting capabilities parameters from a backend
+ for key in self.CAPABILITIES:
+ self.assertIn(key, backend)
+
+ @test.idempotent_id('a9035743-d46a-47c5-9cb7-3c80ea16dea0')
+ def test_compare_volume_stats_values(self):
+ # Test values comparison between show_backend_capabilities
+ # to show_pools
+ VOLUME_STATS = ('vendor_name',
+ 'volume_backend_name',
+ 'storage_protocol')
+
+ # Get list backend capabilities using show_pools
+ cinder_pools = [
+ pool['capabilities'] for pool in
+ self.admin_volume_client.show_pools(detail=True)['pools']
+ ]
+
+ # Get list backends capabilities using show_backend_capabilities
+ capabilities = [
+ self.admin_volume_client.show_backend_capabilities(
+ host=host) for host in self.hosts
+ ]
+
+ # Returns a tuple of VOLUME_STATS values
+ expected_list = map(operator.itemgetter(*VOLUME_STATS),
+ cinder_pools)
+ observed_list = map(operator.itemgetter(*VOLUME_STATS),
+ capabilities)
+ self.assertEqual(expected_list, observed_list)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 5615cf3..120dbb1 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -53,30 +53,30 @@
cls._create_type_and_volume(backend_name, True)
@classmethod
- def _create_type_and_volume(self, backend_name_key, with_prefix):
+ def _create_type_and_volume(cls, backend_name_key, with_prefix):
# Volume/Type creation
- type_name = data_utils.rand_name('Type')
- vol_name = data_utils.rand_name('Volume')
+ type_name = data_utils.rand_name(cls.__name__ + '-Type')
+ vol_name = data_utils.rand_name(cls.__name__ + '-Volume')
spec_key_with_prefix = "capabilities:volume_backend_name"
spec_key_without_prefix = "volume_backend_name"
if with_prefix:
extra_specs = {spec_key_with_prefix: backend_name_key}
else:
extra_specs = {spec_key_without_prefix: backend_name_key}
- self.type = self.create_volume_type(name=type_name,
- extra_specs=extra_specs)
+ cls.type = cls.create_volume_type(name=type_name,
+ extra_specs=extra_specs)
- params = {self.name_field: vol_name, 'volume_type': type_name}
-
- self.volume = self.admin_volume_client.create_volume(
+ params = {cls.name_field: vol_name, 'volume_type': type_name,
+ 'size': CONF.volume.volume_size}
+ cls.volume = cls.admin_volume_client.create_volume(
**params)['volume']
if with_prefix:
- self.volume_id_list_with_prefix.append(self.volume['id'])
+ cls.volume_id_list_with_prefix.append(cls.volume['id'])
else:
- self.volume_id_list_without_prefix.append(
- self.volume['id'])
- waiters.wait_for_volume_status(self.admin_volume_client,
- self.volume['id'], 'available')
+ cls.volume_id_list_without_prefix.append(
+ cls.volume['id'])
+ waiters.wait_for_volume_status(cls.admin_volume_client,
+ cls.volume['id'], 'available')
@classmethod
def resource_cleanup(cls):
diff --git a/tempest/api/volume/admin/test_qos.py b/tempest/api/volume/admin/test_qos.py
old mode 100644
new mode 100755
index 9402668..98139e7
--- a/tempest/api/volume/admin/test_qos.py
+++ b/tempest/api/volume/admin/test_qos.py
@@ -37,7 +37,7 @@
read_iops_sec='2000')
def _create_delete_test_qos_with_given_consumer(self, consumer):
- name = utils.rand_name('qos')
+ name = utils.rand_name(self.__class__.__name__ + '-qos')
qos = {'name': name, 'consumer': consumer}
body = self.create_test_qos_specs(name, consumer)
for key in ['name', 'consumer']:
@@ -119,8 +119,7 @@
self.admin_volume_qos_client.unset_qos_key(self.created_qos['id'],
keys)
operation = 'qos-key-unset'
- self.admin_volume_qos_client.wait_for_qos_operations(
- self.created_qos['id'], operation, keys)
+ self.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'])
@@ -154,8 +153,8 @@
self.admin_volume_qos_client.disassociate_qos(
self.created_qos['id'], vol_type[0]['id'])
operation = 'disassociate'
- self.admin_volume_qos_client.wait_for_qos_operations(
- self.created_qos['id'], operation, vol_type[0]['id'])
+ self.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)
@@ -163,8 +162,7 @@
self.admin_volume_qos_client.disassociate_all_qos(
self.created_qos['id'])
operation = 'disassociate-all'
- self.admin_volume_qos_client.wait_for_qos_operations(
- self.created_qos['id'], operation)
+ self.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_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index a17cc69..1468e90 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -15,7 +15,6 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
-from tempest.common import waiters
from tempest import config
from tempest import test
@@ -42,28 +41,13 @@
vol_name = data_utils.rand_name(cls.__name__ + '-Volume')
cls.name_field = cls.special_fields['name_field']
params = {cls.name_field: vol_name}
- cls.volume = cls.volumes_client.create_volume(**params)['volume']
- waiters.wait_for_volume_status(cls.volumes_client,
- cls.volume['id'], 'available')
+ cls.volume = cls.create_volume(**params)
# Create a test shared snapshot for tests
snap_name = data_utils.rand_name(cls.__name__ + '-Snapshot')
params = {cls.name_field: snap_name}
- cls.snapshot = cls.client.create_snapshot(
- volume_id=cls.volume['id'], **params)['snapshot']
- waiters.wait_for_snapshot_status(cls.client,
- cls.snapshot['id'], 'available')
-
- @classmethod
- def resource_cleanup(cls):
- # Delete the test snapshot
- cls.client.delete_snapshot(cls.snapshot['id'])
- cls.client.wait_for_resource_deletion(cls.snapshot['id'])
-
- # Delete the test volume
- cls.delete_volume(cls.volumes_client, cls.volume['id'])
-
- super(SnapshotsActionsV2Test, cls).resource_cleanup()
+ cls.snapshot = cls.create_snapshot(
+ volume_id=cls.volume['id'], **params)
def tearDown(self):
# Set snapshot's status to available after test
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index ba17d9c..fe105e8 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -73,8 +73,9 @@
@test.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed')
def test_show_quota_usage(self):
- quota_usage = self.admin_quotas_client.show_quota_usage(
- self.os_adm.credentials.tenant_id)['quota_set']
+ quota_usage = self.admin_quotas_client.show_quota_set(
+ self.os_adm.credentials.tenant_id,
+ params={'usage': True})['quota_set']
for key in QUOTA_KEYS:
self.assertIn(key, quota_usage)
for usage_key in QUOTA_USAGE_KEYS:
@@ -82,15 +83,15 @@
@test.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f')
def test_quota_usage(self):
- quota_usage = self.admin_quotas_client.show_quota_usage(
- self.demo_tenant_id)['quota_set']
+ quota_usage = self.admin_quotas_client.show_quota_set(
+ self.demo_tenant_id, params={'usage': True})['quota_set']
volume = self.create_volume()
self.addCleanup(self.delete_volume,
self.admin_volume_client, volume['id'])
- new_quota_usage = self.admin_quotas_client.show_quota_usage(
- self.demo_tenant_id)['quota_set']
+ new_quota_usage = self.admin_quotas_client.show_quota_set(
+ self.demo_tenant_id, params={'usage': True})['quota_set']
self.assertEqual(quota_usage['volumes']['in_use'] + 1,
new_quota_usage['volumes']['in_use'])
@@ -128,11 +129,11 @@
self.admin_volume_client, volume['id'])
# List of tenants quota usage pre-transfer
- primary_quota = self.admin_quotas_client.show_quota_usage(
- self.demo_tenant_id)['quota_set']
+ primary_quota = self.admin_quotas_client.show_quota_set(
+ self.demo_tenant_id, params={'usage': True})['quota_set']
- alt_quota = self.admin_quotas_client.show_quota_usage(
- self.alt_client.tenant_id)['quota_set']
+ alt_quota = self.admin_quotas_client.show_quota_set(
+ self.alt_client.tenant_id, params={'usage': True})['quota_set']
# Creates a volume transfer
transfer = self.volumes_client.create_volume_transfer(
@@ -149,11 +150,11 @@
self.alt_client, volume['id'], 'available')
# List of tenants quota usage post transfer
- new_primary_quota = self.admin_quotas_client.show_quota_usage(
- self.demo_tenant_id)['quota_set']
+ new_primary_quota = self.admin_quotas_client.show_quota_set(
+ self.demo_tenant_id, params={'usage': True})['quota_set']
- new_alt_quota = self.admin_quotas_client.show_quota_usage(
- self.alt_client.tenant_id)['quota_set']
+ new_alt_quota = self.admin_quotas_client.show_quota_set(
+ self.alt_client.tenant_id, params={'usage': True})['quota_set']
# Verify tenants quota usage was updated
self.assertEqual(primary_quota['volumes']['in_use'] -
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index dde8915..c19b1c4 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -32,8 +32,7 @@
@classmethod
def resource_setup(cls):
super(BaseVolumeQuotasNegativeV2TestJSON, cls).resource_setup()
- cls.default_volume_size = cls.volumes_client.default_volume_size
- cls.shared_quota_set = {'gigabytes': 2 * cls.default_volume_size,
+ cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size,
'volumes': 1}
# NOTE(gfidente): no need to restore original quota set
@@ -50,7 +49,8 @@
@test.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6')
def test_quota_volumes(self):
self.assertRaises(lib_exc.OverLimit,
- self.volumes_client.create_volume)
+ self.volumes_client.create_volume,
+ size=CONF.volume.volume_size)
@test.attr(type='negative')
@test.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374')
@@ -61,13 +61,14 @@
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,
+ new_quota_set = {'gigabytes': CONF.volume.volume_size,
'volumes': 2, 'snapshots': 1}
self.admin_quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
self.assertRaises(lib_exc.OverLimit,
- self.volumes_client.create_volume)
+ self.volumes_client.create_volume,
+ size=CONF.volume.volume_size)
class VolumeQuotasNegativeV1TestJSON(BaseVolumeQuotasNegativeV2TestJSON):
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 755365d..165874b 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -79,7 +79,7 @@
services = (self.admin_volume_services_client.list_services(
host=self.host_name, binary=self.binary_name))['services']
- self.assertEqual(1, len(services))
+ self.assertNotEqual(0, len(services))
self.assertEqual(self.host_name, _get_host(services[0]['host']))
self.assertEqual(self.binary_name, services[0]['binary'])
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index 1565a8c..09af7fe 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -38,7 +38,7 @@
@classmethod
def resource_setup(cls):
super(VolumeSnapshotQuotasNegativeV2TestJSON, cls).resource_setup()
- cls.default_volume_size = cls.volumes_client.default_volume_size
+ cls.default_volume_size = CONF.volume.volume_size
cls.shared_quota_set = {'gigabytes': 3 * cls.default_volume_size,
'volumes': 1, 'snapshots': 1}
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index fac71a8..91ff5af 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -17,9 +17,12 @@
from tempest.api.volume import base
from tempest.common import waiters
+from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
+CONF = config.CONF
+
class VolumeTypesAccessV2Test(base.BaseVolumeAdminTest):
@@ -38,7 +41,8 @@
# Try creating a volume from volume type in primary tenant
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
- volume_type=volume_type['id'])
+ volume_type=volume_type['id'],
+ size=CONF.volume.volume_size)
# Adding volume type access for primary tenant
self.admin_volume_types_client.add_type_access(
@@ -49,7 +53,8 @@
# Creating a volume from primary tenant
volume = self.volumes_client.create_volume(
- volume_type=volume_type['id'])['volume']
+ volume_type=volume_type['id'],
+ size=CONF.volume.volume_size)['volume']
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
waiters.wait_for_volume_status(self.volumes_client, volume['id'],
'available')
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
old mode 100644
new mode 100755
index 27f6ccb..646bc68
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -35,7 +35,7 @@
def test_volume_crud_with_volume_type_and_extra_specs(self):
# Create/update/get/delete volume with volume_type and extra spec.
volume_types = list()
- vol_name = data_utils.rand_name("volume")
+ vol_name = data_utils.rand_name(self.__class__.__name__ + '-volume')
self.name_field = self.special_fields['name_field']
proto = CONF.volume.storage_protocol
vendor = CONF.volume.vendor_name
@@ -43,13 +43,15 @@
"vendor_name": vendor}
# Create two volume_types
for i in range(2):
- vol_type_name = data_utils.rand_name("volume-type")
+ vol_type_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume-type')
vol_type = self.create_volume_type(
name=vol_type_name,
extra_specs=extra_specs)
volume_types.append(vol_type)
params = {self.name_field: vol_name,
- 'volume_type': volume_types[0]['id']}
+ 'volume_type': volume_types[0]['id'],
+ 'size': CONF.volume.volume_size}
# Create volume
volume = self.volumes_client.create_volume(**params)['volume']
@@ -87,19 +89,22 @@
def test_volume_type_create_get_delete(self):
# Create/get volume type.
body = {}
- name = data_utils.rand_name("volume-type")
+ name = data_utils.rand_name(self.__class__.__name__ + '-volume-type')
+ description = data_utils.rand_name("volume-type-description")
proto = CONF.volume.storage_protocol
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
- body = self.create_volume_type(
- name=name,
- extra_specs=extra_specs)
+ body = self.create_volume_type(description=description, name=name,
+ extra_specs=extra_specs)
self.assertIn('id', body)
self.assertIn('name', body)
- self.assertEqual(body['name'], name,
+ self.assertEqual(name, body['name'],
"The created volume_type name is not equal "
"to the requested name")
+ self.assertEqual(description, body['description'],
+ "The created volume_type_description name is "
+ "not equal to the requested name")
self.assertTrue(body['id'] is not None,
"Field volume_type id is empty or not found.")
fetched_volume_type = self.admin_volume_types_client.show_volume_type(
@@ -119,11 +124,11 @@
# Create/get/delete encryption type.
provider = "LuksEncryptor"
control_location = "front-end"
- name = data_utils.rand_name("volume-type")
+ name = data_utils.rand_name(self.__class__.__name__ + '-volume-type')
body = self.create_volume_type(name=name)
# Create encryption type
encryption_type = \
- self.admin_volume_types_client.create_encryption_type(
+ self.admin_encryption_types_client.create_encryption_type(
body['id'], provider=provider,
control_location=control_location)['encryption']
self.assertIn('volume_type_id', encryption_type)
@@ -136,7 +141,7 @@
# Get encryption type
fetched_encryption_type = (
- self.admin_volume_types_client.show_encryption_type(
+ self.admin_encryption_types_client.show_encryption_type(
encryption_type['volume_type_id']))
self.assertEqual(provider,
fetched_encryption_type['provider'],
@@ -148,16 +153,35 @@
'different from the created encryption_type')
# 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.admin_volume_types_client.wait_for_resource_deletion(resource)
+ type_id = encryption_type['volume_type_id']
+ self.admin_encryption_types_client.delete_encryption_type(type_id)
+ self.admin_encryption_types_client.wait_for_resource_deletion(type_id)
deleted_encryption_type = (
- self.admin_volume_types_client.show_encryption_type(
- encryption_type['volume_type_id']))
+ self.admin_encryption_types_client.show_encryption_type(type_id))
self.assertEmpty(deleted_encryption_type)
+ @test.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8')
+ def test_volume_type_update(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # New volume type details
+ name = data_utils.rand_name("volume-type")
+ description = data_utils.rand_name("volume-type-description")
+ is_public = not volume_type['is_public']
+
+ # Update volume type details
+ kwargs = {'name': name,
+ 'description': description,
+ 'is_public': is_public}
+ updated_vol_type = self.admin_volume_types_client.update_volume_type(
+ volume_type['id'], **kwargs)['volume_type']
+
+ # Verify volume type details were updated
+ self.assertEqual(name, updated_vol_type['name'])
+ self.assertEqual(description, updated_vol_type['description'])
+ self.assertEqual(is_public, updated_vol_type['is_public'])
+
class VolumeTypesV1Test(VolumeTypesV2Test):
_api_version = 1
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
old mode 100644
new mode 100755
index 9e49b94..d50ba27
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -23,7 +23,7 @@
@classmethod
def resource_setup(cls):
super(VolumeTypesExtraSpecsV2Test, cls).resource_setup()
- vol_type_name = data_utils.rand_name('Volume-type')
+ vol_type_name = data_utils.rand_name(cls.__name__ + '-Volume-type')
cls.volume_type = cls.create_volume_type(name=vol_type_name)
@test.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
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
old mode 100644
new mode 100755
index 2193aa6..2e07457
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -24,7 +24,7 @@
@classmethod
def resource_setup(cls):
super(ExtraSpecsNegativeV2Test, cls).resource_setup()
- vol_type_name = data_utils.rand_name('Volume-type')
+ vol_type_name = data_utils.rand_name(cls.__name__ + '-Volume-type')
cls.extra_specs = {"spec1": "val1"}
cls.volume_type = cls.create_volume_type(name=vol_type_name,
extra_specs=cls.extra_specs)
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
old mode 100644
new mode 100755
index 5388f7f..9686473
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -16,8 +16,11 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils as utils
from tempest.common import waiters
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class VolumesActionsV2Test(base.BaseVolumeAdminTest):
@@ -33,7 +36,7 @@
# Create a test shared volume for tests
vol_name = utils.rand_name(cls.__name__ + '-Volume')
cls.name_field = cls.special_fields['name_field']
- params = {cls.name_field: vol_name}
+ params = {cls.name_field: vol_name, 'size': CONF.volume.volume_size}
cls.volume = cls.client.create_volume(**params)['volume']
waiters.wait_for_volume_status(cls.client,
@@ -59,8 +62,8 @@
def _create_temp_volume(self):
# Create a temp volume for force delete tests
- vol_name = utils.rand_name('Volume')
- params = {self.name_field: vol_name}
+ vol_name = utils.rand_name(self.__class__.__name__ + '-Volume')
+ params = {self.name_field: vol_name, 'size': CONF.volume.volume_size}
temp_volume = self.client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.client,
temp_volume['id'], 'available')
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
old mode 100644
new mode 100755
index b6dc488..a26052c
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import base64
-import six
-
+from oslo_serialization import base64
from oslo_serialization import jsonutils as json
from tempest.api.volume import base
@@ -43,60 +41,20 @@
def _delete_backup(self, backup_id):
self.admin_backups_client.delete_backup(backup_id)
- self.admin_backups_client.wait_for_backup_deletion(backup_id)
+ self.admin_backups_client.wait_for_resource_deletion(backup_id)
def _decode_url(self, backup_url):
- return json.loads(base64.decodestring(backup_url))
+ return json.loads(base64.decode_as_text(backup_url))
def _encode_backup(self, backup):
retval = json.dumps(backup)
- if six.PY3:
- retval = retval.encode('utf-8')
- return base64.encodestring(retval)
+ return base64.encode_as_text(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.admin_backups_client.create_backup
- backup = create_backup(volume_id=self.volume['id'],
- name=backup_name)['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.admin_backups_client.wait_for_backup_status(backup['id'],
- 'available')
-
- # Get a given backup
- backup = self.admin_backups_client.show_backup(backup['id'])['backup']
- self.assertEqual(backup_name, backup['name'])
-
- # Get all backups with detail
- 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.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.admin_backups_client.wait_for_backup_status(backup['id'],
- 'available')
- waiters.wait_for_volume_status(self.admin_volume_client,
- restore['volume_id'], 'available')
-
@test.idempotent_id('a99c54a1-dd80-4724-8a13-13bf58d4068d')
def test_volume_backup_export_import(self):
"""Test backup export import functionality.
@@ -105,7 +63,7 @@
be imported back in case of a DB loss.
"""
# Create backup
- backup_name = data_utils.rand_name('Backup')
+ backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
backup = (self.admin_backups_client.create_backup(
volume_id=self.volume['id'], name=backup_name)['backup'])
self.addCleanup(self._delete_backup, backup['id'])
@@ -166,6 +124,24 @@
self.admin_backups_client.wait_for_backup_status(import_backup['id'],
'available')
+ @test.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
+ def test_volume_backup_reset_status(self):
+ # Create a backup
+ backup_name = data_utils.rand_name(
+ self.__class__.__name__ + '-Backup')
+ backup = self.admin_backups_client.create_backup(
+ volume_id=self.volume['id'], name=backup_name)['backup']
+ self.addCleanup(self.admin_backups_client.delete_backup,
+ backup['id'])
+ self.assertEqual(backup_name, backup['name'])
+ self.admin_backups_client.wait_for_backup_status(backup['id'],
+ 'available')
+ # Reset backup status to error
+ self.admin_backups_client.reset_backup_status(backup_id=backup['id'],
+ status="error")
+ self.admin_backups_client.wait_for_backup_status(backup['id'],
+ 'error')
+
class VolumesBackupsAdminV1Test(VolumesBackupsAdminV2Test):
_api_version = 1
diff --git a/tempest/api/volume/admin/test_volumes_list.py b/tempest/api/volume/admin/test_volumes_list.py
index 64041b8..4437803 100644
--- a/tempest/api/volume/admin/test_volumes_list.py
+++ b/tempest/api/volume/admin/test_volumes_list.py
@@ -17,8 +17,11 @@
from tempest.api.volume import base
from tempest.common import waiters
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class VolumesListAdminV2TestJSON(base.BaseVolumeAdminTest):
@@ -38,7 +41,8 @@
def test_volume_list_param_tenant(self):
# Test to list volumes from single tenant
# Create a volume in admin tenant
- adm_vol = self.admin_volume_client.create_volume()['volume']
+ adm_vol = self.admin_volume_client.create_volume(
+ size=CONF.volume.volume_size)['volume']
waiters.wait_for_volume_status(self.admin_volume_client,
adm_vol['id'], 'available')
self.addCleanup(self.admin_volume_client.delete_volume, adm_vol['id'])
@@ -53,7 +57,7 @@
self.assertEqual(sorted(expected_list_ids), sorted(fetched_list_ids))
# Verifying tenant id of volumes fetched list is related to
# primary tenant
- fetched_tenant_id = map(operator.itemgetter(
- 'os-vol-tenant-attr:tenant_id'), fetched_list)
+ fetched_tenant_id = [operator.itemgetter(
+ 'os-vol-tenant-attr:tenant_id')(item) for item in fetched_list]
expected_tenant_id = [self.volumes_client.tenant_id] * 3
self.assertEqual(expected_tenant_id, fetched_tenant_id)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 087b9a8..d1549e2 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -110,7 +113,10 @@
@classmethod
def create_volume(cls, **kwargs):
"""Wrapper utility that returns a test volume."""
- name = data_utils.rand_name('Volume')
+ if 'size' not in kwargs:
+ kwargs['size'] = CONF.volume.volume_size
+
+ name = data_utils.rand_name(cls.__name__ + '-Volume')
name_field = cls.special_fields['name_field']
@@ -169,14 +175,21 @@
except Exception:
pass
- @classmethod
- def create_server(cls, name, **kwargs):
- tenant_network = cls.get_tenant_network()
+ def create_server(self, name, wait_for_deletion=False, **kwargs):
+ tenant_network = self.get_tenant_network()
body, _ = compute.create_test_server(
- cls.os,
+ self.os,
tenant_network=tenant_network,
name=name,
**kwargs)
+
+ if wait_for_deletion:
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ waiters.wait_for_server_termination,
+ self.servers_client, body['id'])
+
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.servers_client.delete_server, body['id'])
return body
@@ -198,6 +211,8 @@
cls.admin_hosts_client = cls.os_adm.volume_hosts_client
cls.admin_snapshots_client = cls.os_adm.snapshots_client
cls.admin_backups_client = cls.os_adm.backups_client
+ cls.admin_encryption_types_client = \
+ cls.os_adm.encryption_types_client
cls.admin_quotas_client = cls.os_adm.volume_quotas_client
elif cls._api_version == 2:
cls.admin_volume_qos_client = cls.os_adm.volume_qos_v2_client
@@ -208,6 +223,8 @@
cls.admin_hosts_client = cls.os_adm.volume_hosts_v2_client
cls.admin_snapshots_client = cls.os_adm.snapshots_v2_client
cls.admin_backups_client = cls.os_adm.backups_v2_client
+ cls.admin_encryption_types_client = \
+ cls.os_adm.encryption_types_v2_client
cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
@classmethod
@@ -236,7 +253,7 @@
@classmethod
def create_volume_type(cls, name=None, **kwargs):
"""Create a test volume-type"""
- name = name or data_utils.rand_name('volume-type')
+ name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
volume_type = cls.admin_volume_types_client.create_volume_type(
name=name, **kwargs)['volume_type']
cls.volume_types.append(volume_type['id'])
@@ -259,9 +276,38 @@
cls.admin_volume_types_client.delete_volume_type, vol_type)
for vol_type in cls.volume_types:
- # Resource dictionary uses for is_resource_deleted method,
- # to distinguish between volume-type to encryption-type.
- resource = {'id': vol_type, 'type': 'volume-type'}
test_utils.call_and_ignore_notfound_exc(
cls.admin_volume_types_client.wait_for_resource_deletion,
- resource)
+ vol_type)
+
+ def wait_for_qos_operations(self, qos_id, operation, args=None):
+ """Waits for a qos operations to be completed.
+
+ NOTE : operation value is required for wait_for_qos_operations()
+ operation = 'qos-key' / 'disassociate' / 'disassociate-all'
+ args = keys[] when operation = 'qos-key'
+ args = volume-type-id disassociated when operation = 'disassociate'
+ args = None when operation = 'disassociate-all'
+ """
+ start_time = int(time.time())
+ client = self.admin_volume_qos_client
+ while True:
+ if operation == 'qos-key-unset':
+ body = client.show_qos(qos_id)['qos_specs']
+ if not any(key in body['specs'] for key in args):
+ return
+ elif operation == 'disassociate':
+ body = client.show_association_qos(qos_id)['qos_associations']
+ if not any(args in body[i]['id'] for i in range(0, len(body))):
+ return
+ elif operation == 'disassociate-all':
+ body = client.show_association_qos(qos_id)['qos_associations']
+ if not body:
+ return
+ else:
+ msg = (" operation value is either not defined or incorrect.")
+ raise lib_exc.UnprocessableEntity(msg)
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+ time.sleep(self.build_interval)
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index d138490..a8889e0 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -17,11 +17,8 @@
from tempest.api.volume import base
from tempest.common import waiters
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class VolumesV2TransfersTest(base.BaseVolumeTest):
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
old mode 100644
new mode 100755
index 76cd36c..b80a4a4
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -45,34 +45,25 @@
@classmethod
def resource_setup(cls):
super(VolumesV2ActionsTest, cls).resource_setup()
- # Create a test shared instance
- srv_name = data_utils.rand_name(cls.__name__ + '-Instance')
- cls.server = cls.create_server(
- name=srv_name,
- wait_until='ACTIVE')
# Create a test shared volume for attach/detach tests
cls.volume = cls.create_volume()
waiters.wait_for_volume_status(cls.client,
cls.volume['id'], 'available')
- @classmethod
- def resource_cleanup(cls):
- # Delete the test instance
- cls.servers_client.delete_server(cls.server['id'])
- waiters.wait_for_server_termination(cls.servers_client,
- cls.server['id'])
-
- super(VolumesV2ActionsTest, cls).resource_cleanup()
-
@test.idempotent_id('fff42874-7db5-4487-a8e1-ddda5fb5288d')
@test.stresstest(class_setup_per='process')
@test.attr(type='smoke')
@test.services('compute')
def test_attach_detach_volume_to_instance(self):
+ # Create a server
+ srv_name = data_utils.rand_name(self.__class__.__name__ + '-Instance')
+ server = self.create_server(
+ name=srv_name,
+ wait_until='ACTIVE')
# Volume is attached and detached successfully from an instance
self.client.attach_volume(self.volume['id'],
- instance_uuid=self.server['id'],
+ instance_uuid=server['id'],
mountpoint='/dev/%s' %
CONF.compute.volume_device_name)
waiters.wait_for_volume_status(self.client,
@@ -99,9 +90,14 @@
@test.stresstest(class_setup_per='process')
@test.services('compute')
def test_get_volume_attachment(self):
+ # Create a server
+ srv_name = data_utils.rand_name(self.__class__.__name__ + '-Instance')
+ server = self.create_server(
+ name=srv_name,
+ wait_until='ACTIVE')
# Verify that a volume's attachment information is retrieved
self.client.attach_volume(self.volume['id'],
- instance_uuid=self.server['id'],
+ instance_uuid=server['id'],
mountpoint='/dev/%s' %
CONF.compute.volume_device_name)
waiters.wait_for_volume_status(self.client,
@@ -114,11 +110,12 @@
self.addCleanup(self.client.detach_volume, self.volume['id'])
volume = self.client.show_volume(self.volume['id'])['volume']
self.assertIn('attachments', volume)
- attachment = self.client.get_attachment_from_volume(volume)
+ attachment = volume['attachments'][0]
+
self.assertEqual('/dev/%s' %
CONF.compute.volume_device_name,
attachment['device'])
- self.assertEqual(self.server['id'], attachment['server_id'])
+ self.assertEqual(server['id'], attachment['server_id'])
self.assertEqual(self.volume['id'], attachment['id'])
self.assertEqual(self.volume['id'], attachment['volume_id'])
@@ -129,7 +126,7 @@
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
# using the Glance image_client and from Cinder via tearDownClass.
- image_name = data_utils.rand_name('Image')
+ image_name = data_utils.rand_name(self.__class__.__name__ + '-Image')
body = self.client.upload_volume(
self.volume['id'], image_name=image_name,
disk_format=CONF.volume.disk_format)['os-volume_upload_image']
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
old mode 100644
new mode 100755
index 87146db..86076b7
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -30,11 +30,47 @@
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
- @classmethod
- def resource_setup(cls):
- super(VolumesBackupsV2Test, cls).resource_setup()
+ @test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
+ def test_volume_backup_create_get_detailed_list_restore_delete(self):
+ # Create backup
+ volume = self.create_volume()
+ self.addCleanup(self.volumes_client.delete_volume,
+ volume['id'])
+ backup_name = data_utils.rand_name(
+ self.__class__.__name__ + '-Backup')
+ create_backup = self.backups_client.create_backup
+ backup = create_backup(volume_id=volume['id'],
+ name=backup_name)['backup']
+ self.addCleanup(self.backups_client.delete_backup,
+ backup['id'])
+ self.assertEqual(backup_name, backup['name'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'available')
+ self.backups_client.wait_for_backup_status(backup['id'],
+ 'available')
- cls.volume = cls.create_volume()
+ # Get a given backup
+ backup = self.backups_client.show_backup(backup['id'])['backup']
+ self.assertEqual(backup_name, backup['name'])
+
+ # Get all backups with detail
+ backups = self.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_client.restore_backup(
+ backup['id'])['restore']
+
+ # Delete backup
+ self.addCleanup(self.volumes_client.delete_volume,
+ restore['volume_id'])
+ self.assertEqual(backup['id'], restore['backup_id'])
+ self.backups_client.wait_for_backup_status(backup['id'],
+ 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ restore['volume_id'], 'available')
@test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
@test.services('compute')
@@ -45,22 +81,26 @@
is "available" or "in-use".
"""
# Create a server
- server_name = data_utils.rand_name('instance')
+ volume = self.create_volume()
+ self.addCleanup(self.volumes_client.delete_volume,
+ volume['id'])
+ server_name = data_utils.rand_name(
+ self.__class__.__name__ + '-instance')
server = self.create_server(name=server_name, wait_until='ACTIVE')
- self.addCleanup(self.servers_client.delete_server, server['id'])
# Attach volume to instance
self.servers_client.attach_volume(server['id'],
- volumeId=self.volume['id'])
+ volumeId=volume['id'])
waiters.wait_for_volume_status(self.volumes_client,
- self.volume['id'], 'in-use')
+ volume['id'], 'in-use')
self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
- self.volume['id'], 'available')
+ volume['id'], 'available')
self.addCleanup(self.servers_client.detach_volume, server['id'],
- self.volume['id'])
+ volume['id'])
# Create backup using force flag
- backup_name = data_utils.rand_name('Backup')
+ backup_name = data_utils.rand_name(
+ self.__class__.__name__ + '-Backup')
backup = self.backups_client.create_backup(
- volume_id=self.volume['id'],
+ volume_id=volume['id'],
name=backup_name, force=True)['backup']
self.addCleanup(self.backups_client.delete_backup, backup['id'])
self.backups_client.wait_for_backup_status(backup['id'],
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
index f38a068..7529dc2 100644
--- a/tempest/api/volume/test_volumes_clone.py
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -23,6 +23,12 @@
class VolumesCloneTest(base.BaseVolumeTest):
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesCloneTest, cls).skip_checks()
+ if not CONF.volume_feature_enabled.clone:
+ raise cls.skipException("Cinder volume clones are disabled")
+
@test.idempotent_id('9adae371-a257-43a5-9555-dc7c88e66e0e')
def test_create_from_volume(self):
# Creates a volume from another volume passing a size different from
diff --git a/tempest/api/volume/test_volumes_clone_negative.py b/tempest/api/volume/test_volumes_clone_negative.py
index ee51e00..d1bedb4 100644
--- a/tempest/api/volume/test_volumes_clone_negative.py
+++ b/tempest/api/volume/test_volumes_clone_negative.py
@@ -24,6 +24,12 @@
class VolumesCloneTest(base.BaseVolumeTest):
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesCloneTest, cls).skip_checks()
+ if not CONF.volume_feature_enabled.clone:
+ raise cls.skipException("Cinder volume clones are disabled")
+
@test.idempotent_id('9adae371-a257-43a5-459a-dc7c88e66e0e')
def test_create_from_volume_decreasing_size(self):
# Creates a volume from another volume passing a size different from
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 1947779..7aea1c4 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -15,11 +15,8 @@
from tempest.api.volume import base
from tempest.common import waiters
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class VolumesV2ExtendTest(base.BaseVolumeTest):
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
old mode 100644
new mode 100755
index e5fcdfe..07f799b
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -41,8 +41,7 @@
def _volume_create_get_update_delete(self, **kwargs):
# Create a volume, Get it's details and Delete the volume
- volume = {}
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'Test'}
# Create a volume
kwargs[self.name_field] = v_name
@@ -82,7 +81,8 @@
params = {self.name_field: v_name}
self.client.update_volume(volume['id'], **params)
# Test volume update when display_name is new
- new_v_name = data_utils.rand_name('new-Volume')
+ new_v_name = data_utils.rand_name(
+ self.__class__.__name__ + '-new-Volume')
new_desc = 'This is the new description of volume'
params = {self.name_field: new_v_name,
self.descrip_field: new_desc}
@@ -103,10 +103,10 @@
# Test volume create when display_name is none and display_description
# contains specific characters,
# then test volume update if display_name is duplicated
- new_volume = {}
new_v_desc = data_utils.rand_name('@#$%^* description')
params = {self.descrip_field: new_v_desc,
- 'availability_zone': volume['availability_zone']}
+ 'availability_zone': volume['availability_zone'],
+ 'size': CONF.volume.volume_size}
new_volume = self.client.create_volume(**params)['volume']
self.assertIn('id', new_volume)
self.addCleanup(self.delete_volume, self.client, new_volume['id'])
@@ -125,7 +125,7 @@
@test.attr(type='smoke')
@test.idempotent_id('27fb0e9f-fb64-41dd-8bdb-1ffa762f0d51')
def test_volume_create_get_update_delete(self):
- self._volume_create_get_update_delete()
+ self._volume_create_get_update_delete(size=CONF.volume.volume_size)
@test.attr(type='smoke')
@test.idempotent_id('54a01030-c7fc-447c-86ee-c1182beae638')
@@ -143,7 +143,8 @@
'Cinder volume clones are disabled')
def test_volume_create_get_update_delete_as_clone(self):
origin = self.create_volume()
- self._volume_create_get_update_delete(source_volid=origin['id'])
+ self._volume_create_get_update_delete(source_volid=origin['id'],
+ size=CONF.volume.volume_size)
class VolumesV1GetTest(VolumesV2GetTest):
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index a93025d..b5ef7c0 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -33,8 +33,9 @@
def assertVolumesIn(self, fetched_list, expected_list, fields=None):
if fields:
- expected_list = map(operator.itemgetter(*fields), expected_list)
- fetched_list = map(operator.itemgetter(*fields), fetched_list)
+ fieldsgetter = operator.itemgetter(*fields)
+ expected_list = map(fieldsgetter, expected_list)
+ fetched_list = [fieldsgetter(item) for item in fetched_list]
missing_vols = [v for v in expected_list if v not in fetched_list]
if len(missing_vols) == 0:
@@ -60,20 +61,11 @@
# Create 3 test volumes
cls.volume_list = []
- cls.volume_id_list = []
cls.metadata = {'Type': 'work'}
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
volume = cls.client.show_volume(volume['id'])['volume']
cls.volume_list.append(volume)
- cls.volume_id_list.append(volume['id'])
-
- @classmethod
- def resource_cleanup(cls):
- # Delete the created volumes
- for volid in cls.volume_id_list:
- cls.delete_volume(cls.client, volid)
- super(VolumesV2ListTestJSON, cls).resource_cleanup()
def _list_by_param_value_and_assert(self, params, with_detail=False):
"""list or list_details with given params and validates result"""
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
old mode 100644
new mode 100755
index 77bfaf1..5bef7f3
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -15,7 +15,6 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
-from tempest.common import waiters
from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -56,7 +55,7 @@
def test_create_volume_with_invalid_size(self):
# Should not be able to create volume with invalid size
# in request
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
@@ -66,7 +65,7 @@
def test_create_volume_with_out_passing_size(self):
# Should not be able to create volume without passing size
# in request
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
@@ -75,7 +74,7 @@
@test.idempotent_id('41331caa-eaf4-4001-869d-bc18c1869360')
def test_create_volume_with_size_zero(self):
# Should not be able to create volume with size zero
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
@@ -84,7 +83,7 @@
@test.idempotent_id('8b472729-9eba-446e-a83b-916bdb34bef7')
def test_create_volume_with_size_negative(self):
# Should not be able to create volume with size negative
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
size='-1', display_name=v_name, metadata=metadata)
@@ -93,7 +92,7 @@
@test.idempotent_id('10254ed8-3849-454e-862e-3ab8e6aa01d2')
def test_create_volume_with_nonexistent_volume_type(self):
# Should not be able to create volume with non-existent volume type
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
size='1', volume_type=data_utils.rand_uuid(),
@@ -103,7 +102,7 @@
@test.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9')
def test_create_volume_with_nonexistent_snapshot_id(self):
# Should not be able to create volume with non-existent snapshot
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
size='1', snapshot_id=data_utils.rand_uuid(),
@@ -113,7 +112,7 @@
@test.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344')
def test_create_volume_with_nonexistent_source_volid(self):
# Should not be able to create volume with non-existent source volume
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.create_volume,
size='1', source_volid=data_utils.rand_uuid(),
@@ -122,7 +121,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
def test_update_volume_with_nonexistent_volume_id(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.update_volume,
volume_id=data_utils.rand_uuid(),
@@ -132,7 +131,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
def test_update_volume_with_invalid_volume_id(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.update_volume,
volume_id='#$%%&^&^', display_name=v_name,
@@ -141,7 +140,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
def test_update_volume_with_empty_volume_id(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
self.assertRaises(lib_exc.NotFound, self.client.update_volume,
volume_id='', display_name=v_name,
@@ -177,13 +176,11 @@
@test.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@test.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
- srv_name = data_utils.rand_name('Instance')
+ srv_name = data_utils.rand_name(self.__class__.__name__ + '-Instance')
server = self.create_server(
name=srv_name,
+ wait_for_deletion=True,
wait_until='ACTIVE')
- self.addCleanup(waiters.wait_for_server_termination,
- self.servers_client, server['id'])
- self.addCleanup(self.servers_client.delete_server, server['id'])
self.assertRaises(lib_exc.NotFound,
self.client.attach_volume,
@@ -267,7 +264,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f')
def test_list_volumes_with_nonexistent_name(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {self.name_field: v_name}
fetched_volume = self.client.list_volumes(params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
@@ -275,7 +272,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359')
def test_list_volumes_detail_with_nonexistent_name(self):
- v_name = data_utils.rand_name('Volume')
+ v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {self.name_field: v_name}
fetched_volume = \
self.client.list_volumes(detail=True, params=params)['volumes']
@@ -299,4 +296,3 @@
class VolumesV1NegativeTest(VolumesV2NegativeTest):
_api_version = 1
- _name = 'display_name'
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
old mode 100644
new mode 100755
index c7f1e6e..8f7996a
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -36,7 +36,7 @@
cls.name_field = cls.special_fields['name_field']
cls.descrip_field = cls.special_fields['descrip_field']
# Create 2 snapshots
- for _ in xrange(2):
+ for _ in range(2):
cls.create_snapshot(cls.volume_origin['id'])
def _detach(self, volume_id):
@@ -75,11 +75,11 @@
def test_snapshot_create_with_volume_in_use(self):
# Create a snapshot when volume status is in-use
# Create a test instance
- server_name = data_utils.rand_name('instance')
+ server_name = data_utils.rand_name(
+ self.__class__.__name__ + '-instance')
server = self.create_server(
name=server_name,
wait_until='ACTIVE')
- self.addCleanup(self.servers_client.delete_server, server['id'])
self.servers_client.attach_volume(
server['id'], volumeId=self.volume_origin['id'],
device='/dev/%s' % CONF.compute.volume_device_name)
@@ -98,7 +98,7 @@
@test.idempotent_id('2a8abbe4-d871-46db-b049-c41f5af8216e')
def test_snapshot_create_get_list_update_delete(self):
# Create a snapshot
- s_name = data_utils.rand_name('snap')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
params = {self.name_field: s_name}
snapshot = self.create_snapshot(self.volume_origin['id'], **params)
@@ -116,7 +116,8 @@
self.assertIn(tracking_data, snaps_data)
# Updates snapshot with new values
- new_s_name = data_utils.rand_name('new-snap')
+ new_s_name = data_utils.rand_name(
+ self.__class__.__name__ + '-new-snap')
new_desc = 'This is the new description of snapshot.'
params = {self.name_field: new_s_name,
self.descrip_field: new_desc}
@@ -138,7 +139,7 @@
def test_snapshots_list_with_params(self):
"""list snapshots with params."""
# Create a snapshot
- display_name = data_utils.rand_name('snap')
+ display_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
params = {self.name_field: display_name}
snapshot = self.create_snapshot(self.volume_origin['id'], **params)
self.addCleanup(self.cleanup_snapshot, snapshot)
@@ -160,7 +161,7 @@
def test_snapshots_list_details_with_params(self):
"""list snapshot details with params."""
# Create a snapshot
- display_name = data_utils.rand_name('snap')
+ display_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
params = {self.name_field: display_name}
snapshot = self.create_snapshot(self.volume_origin['id'], **params)
self.addCleanup(self.cleanup_snapshot, snapshot)
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
old mode 100644
new mode 100755
index 2df9523..1f5bb0d
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -31,7 +31,7 @@
@test.idempotent_id('e3e466af-70ab-4f4b-a967-ab04e3532ea7')
def test_create_snapshot_with_nonexistent_volume_id(self):
# Create a snapshot with nonexistent volume id
- s_name = data_utils.rand_name('snap')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
self.assertRaises(lib_exc.NotFound,
self.snapshots_client.create_snapshot,
volume_id=data_utils.rand_uuid(),
@@ -41,7 +41,7 @@
@test.idempotent_id('bb9da53e-d335-4309-9c15-7e76fd5e4d6d')
def test_create_snapshot_without_passing_volume_id(self):
# Create a snapshot without passing volume id
- s_name = data_utils.rand_name('snap')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
self.assertRaises(lib_exc.NotFound,
self.snapshots_client.create_snapshot,
volume_id=None, display_name=s_name)
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 5117e6c..60a35b0 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -42,22 +42,15 @@
super(VolumesV2ListTestJSON, cls).resource_setup()
# Create 3 test volumes
- cls.volume_list = []
- cls.volume_id_list = []
cls.metadata = {'Type': 'work'}
+ # NOTE(zhufl): When using pre-provisioned credentials, the project
+ # may have volumes other than those created below.
+ existing_volumes = cls.client.list_volumes()['volumes']
+ cls.volume_id_list = [vol['id'] for vol in existing_volumes]
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
- volume = cls.client.show_volume(volume['id'])['volume']
- cls.volume_list.append(volume)
cls.volume_id_list.append(volume['id'])
- @classmethod
- def resource_cleanup(cls):
- # Delete the created volumes
- for volid in cls.volume_id_list:
- cls.delete_volume(cls.client, volid)
- super(VolumesV2ListTestJSON, cls).resource_cleanup()
-
@test.idempotent_id('2a7064eb-b9c3-429b-b888-33928fc5edd3')
def test_volume_list_details_with_multiple_params(self):
# List volumes detail using combined condition
@@ -174,9 +167,9 @@
# If cannot follow make sure it's because we have finished
else:
- self.assertListEqual([], remaining or [],
- 'No more pages reported, but still '
- 'missing ids %s' % remaining)
+ self.assertEqual([], remaining or [],
+ 'No more pages reported, but still '
+ 'missing ids %s' % remaining)
break
@test.idempotent_id('e9138a2c-f67b-4796-8efa-635c196d01de')
diff --git a/tempest/api/volume/v3/admin/test_user_messages.py b/tempest/api/volume/v3/admin/test_user_messages.py
old mode 100644
new mode 100755
index 9d59d1b..39a5dfa
--- a/tempest/api/volume/v3/admin/test_user_messages.py
+++ b/tempest/api/volume/v3/admin/test_user_messages.py
@@ -16,9 +16,12 @@
from tempest.api.volume.v3 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
+CONF = config.CONF
+
MESSAGE_KEYS = [
'created_at',
'event_id',
@@ -42,13 +45,15 @@
bad_vendor = data_utils.rand_name('vendor_name')
extra_specs = {'storage_protocol': bad_protocol,
'vendor_name': bad_vendor}
- vol_type_name = data_utils.rand_name('volume-type')
+ vol_type_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume-type')
bogus_type = self.admin_volume_types_client.create_volume_type(
name=vol_type_name,
extra_specs=extra_specs)['volume_type']
self.addCleanup(self.admin_volume_types_client.delete_volume_type,
bogus_type['id'])
- params = {'volume_type': bogus_type['id']}
+ params = {'volume_type': bogus_type['id'],
+ 'size': CONF.volume.volume_size}
volume = self.volumes_client.create_volume(**params)['volume']
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
try:
diff --git a/tempest/clients.py b/tempest/clients.py
index fd010f2..4c677f0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -14,18 +14,13 @@
# under the License.
import copy
-
from oslo_log import log as logging
-
from tempest.common import negative_rest_client
from tempest import config
from tempest import exceptions
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
-from tempest.lib.services import compute
-from tempest.lib.services import image
-from tempest.lib.services import network
-from tempest import service_clients
+from tempest.lib.services import clients
from tempest.services import baremetal
from tempest.services import data_processing
from tempest.services import identity
@@ -37,7 +32,7 @@
LOG = logging.getLogger(__name__)
-class Manager(service_clients.ServiceClients):
+class Manager(clients.ServiceClients):
"""Top level manager for OpenStack tempest clients"""
default_params = config.service_client_config()
@@ -102,17 +97,16 @@
This uses `config.service_client_config` for all services to collect
most configuration items needed to init the clients.
"""
- # NOTE(andreaf) Configuration items will be passed in future patches
- # into ClientFactory objects, but for now we update all the
- # _set_*_client methods to consume them so we can verify that the
- # configuration collected is correct
+ # NOTE(andreaf) Once all service clients in Tempest are migrated
+ # to tempest.lib, their configuration will be picked up from the
+ # registry, and this method will become redundant.
configuration = {}
- # Setup the parameters for all Tempest services.
+ # Setup the parameters for all Tempest services which are not in lib.
# NOTE(andreaf) Since client.py is an internal module of Tempest,
# it doesn't have to consider plugin configuration.
- for service in service_clients.tempest_modules():
+ for service in clients._tempest_internal_modules():
try:
# NOTE(andreaf) Use the unversioned service name to fetch
# the configuration since configuration is not versioned.
@@ -127,133 +121,84 @@
return configuration
def _set_network_clients(self):
- params = self.parameters['network']
- self.network_agents_client = network.AgentsClient(
- self.auth_provider, **params)
- self.network_extensions_client = network.ExtensionsClient(
- self.auth_provider, **params)
- self.networks_client = network.NetworksClient(
- self.auth_provider, **params)
- self.subnetpools_client = network.SubnetpoolsClient(
- self.auth_provider, **params)
- self.subnets_client = network.SubnetsClient(
- self.auth_provider, **params)
- self.ports_client = network.PortsClient(
- self.auth_provider, **params)
- self.network_quotas_client = network.QuotasClient(
- self.auth_provider, **params)
- self.floating_ips_client = network.FloatingIPsClient(
- self.auth_provider, **params)
- self.metering_labels_client = network.MeteringLabelsClient(
- self.auth_provider, **params)
- self.metering_label_rules_client = network.MeteringLabelRulesClient(
- self.auth_provider, **params)
- self.routers_client = network.RoutersClient(
- self.auth_provider, **params)
- self.security_group_rules_client = network.SecurityGroupRulesClient(
- self.auth_provider, **params)
- self.security_groups_client = network.SecurityGroupsClient(
- self.auth_provider, **params)
- self.network_versions_client = network.NetworkVersionsClient(
- self.auth_provider, **params)
+ self.network_agents_client = self.network.AgentsClient()
+ self.network_extensions_client = self.network.ExtensionsClient()
+ self.networks_client = self.network.NetworksClient()
+ self.subnetpools_client = self.network.SubnetpoolsClient()
+ self.subnets_client = self.network.SubnetsClient()
+ self.ports_client = self.network.PortsClient()
+ self.network_quotas_client = self.network.QuotasClient()
+ self.floating_ips_client = self.network.FloatingIPsClient()
+ self.metering_labels_client = self.network.MeteringLabelsClient()
+ self.metering_label_rules_client = (
+ self.network.MeteringLabelRulesClient())
+ self.routers_client = self.network.RoutersClient()
+ self.security_group_rules_client = (
+ self.network.SecurityGroupRulesClient())
+ self.security_groups_client = self.network.SecurityGroupsClient()
+ self.network_versions_client = self.network.NetworkVersionsClient()
def _set_image_clients(self):
if CONF.service_available.glance:
- params = self.parameters['image']
- self.image_client = image.v1.ImagesClient(
- self.auth_provider, **params)
- self.image_member_client = image.v1.ImageMembersClient(
- self.auth_provider, **params)
-
- self.image_client_v2 = image.v2.ImagesClient(
- self.auth_provider, **params)
- self.image_member_client_v2 = image.v2.ImageMembersClient(
- self.auth_provider, **params)
- self.namespaces_client = image.v2.NamespacesClient(
- self.auth_provider, **params)
- self.resource_types_client = image.v2.ResourceTypesClient(
- self.auth_provider, **params)
- self.schemas_client = image.v2.SchemasClient(
- self.auth_provider, **params)
+ self.image_client = self.image_v1.ImagesClient()
+ self.image_member_client = self.image_v1.ImageMembersClient()
+ self.image_client_v2 = self.image_v2.ImagesClient()
+ self.image_member_client_v2 = self.image_v2.ImageMembersClient()
+ self.namespaces_client = self.image_v2.NamespacesClient()
+ self.resource_types_client = self.image_v2.ResourceTypesClient()
+ self.schemas_client = self.image_v2.SchemasClient()
def _set_compute_clients(self):
- params = self.parameters['compute']
-
- self.agents_client = compute.AgentsClient(self.auth_provider, **params)
- self.compute_networks_client = compute.NetworksClient(
- self.auth_provider, **params)
- self.migrations_client = compute.MigrationsClient(self.auth_provider,
- **params)
+ self.agents_client = self.compute.AgentsClient()
+ self.compute_networks_client = self.compute.NetworksClient()
+ self.migrations_client = self.compute.MigrationsClient()
self.security_group_default_rules_client = (
- compute.SecurityGroupDefaultRulesClient(self.auth_provider,
- **params))
- self.certificates_client = compute.CertificatesClient(
- self.auth_provider, **params)
- self.servers_client = compute.ServersClient(
- self.auth_provider,
- enable_instance_password=CONF.compute_feature_enabled
- .enable_instance_password,
- **params)
- self.server_groups_client = compute.ServerGroupsClient(
- self.auth_provider, **params)
- self.limits_client = compute.LimitsClient(self.auth_provider, **params)
- self.compute_images_client = compute.ImagesClient(self.auth_provider,
- **params)
- self.keypairs_client = compute.KeyPairsClient(self.auth_provider,
- **params)
- self.quotas_client = compute.QuotasClient(self.auth_provider, **params)
- self.quota_classes_client = compute.QuotaClassesClient(
- self.auth_provider, **params)
- self.flavors_client = compute.FlavorsClient(self.auth_provider,
- **params)
- self.extensions_client = compute.ExtensionsClient(self.auth_provider,
- **params)
- self.floating_ip_pools_client = compute.FloatingIPPoolsClient(
- self.auth_provider, **params)
- self.floating_ips_bulk_client = compute.FloatingIPsBulkClient(
- self.auth_provider, **params)
- self.compute_floating_ips_client = compute.FloatingIPsClient(
- self.auth_provider, **params)
+ self.compute.SecurityGroupDefaultRulesClient())
+ self.certificates_client = self.compute.CertificatesClient()
+ eip = CONF.compute_feature_enabled.enable_instance_password
+ self.servers_client = self.compute.ServersClient(
+ enable_instance_password=eip)
+ self.server_groups_client = self.compute.ServerGroupsClient()
+ self.limits_client = self.compute.LimitsClient()
+ self.compute_images_client = self.compute.ImagesClient()
+ self.keypairs_client = self.compute.KeyPairsClient()
+ self.quotas_client = self.compute.QuotasClient()
+ self.quota_classes_client = self.compute.QuotaClassesClient()
+ self.flavors_client = self.compute.FlavorsClient()
+ self.extensions_client = self.compute.ExtensionsClient()
+ self.floating_ip_pools_client = self.compute.FloatingIPPoolsClient()
+ self.floating_ips_bulk_client = self.compute.FloatingIPsBulkClient()
+ self.compute_floating_ips_client = self.compute.FloatingIPsClient()
self.compute_security_group_rules_client = (
- compute.SecurityGroupRulesClient(self.auth_provider, **params))
- self.compute_security_groups_client = compute.SecurityGroupsClient(
- self.auth_provider, **params)
- self.interfaces_client = compute.InterfacesClient(self.auth_provider,
- **params)
- self.fixed_ips_client = compute.FixedIPsClient(self.auth_provider,
- **params)
- self.availability_zone_client = compute.AvailabilityZoneClient(
- self.auth_provider, **params)
- self.aggregates_client = compute.AggregatesClient(self.auth_provider,
- **params)
- self.services_client = compute.ServicesClient(self.auth_provider,
- **params)
- self.tenant_usages_client = compute.TenantUsagesClient(
- self.auth_provider, **params)
- self.hosts_client = compute.HostsClient(self.auth_provider, **params)
- self.hypervisor_client = compute.HypervisorClient(self.auth_provider,
- **params)
+ self.compute.SecurityGroupRulesClient())
+ self.compute_security_groups_client = (
+ self.compute.SecurityGroupsClient())
+ self.interfaces_client = self.compute.InterfacesClient()
+ self.fixed_ips_client = self.compute.FixedIPsClient()
+ self.availability_zone_client = self.compute.AvailabilityZoneClient()
+ self.aggregates_client = self.compute.AggregatesClient()
+ self.services_client = self.compute.ServicesClient()
+ self.tenant_usages_client = self.compute.TenantUsagesClient()
+ self.hosts_client = self.compute.HostsClient()
+ self.hypervisor_client = self.compute.HypervisorClient()
self.instance_usages_audit_log_client = (
- compute.InstanceUsagesAuditLogClient(self.auth_provider, **params))
- self.tenant_networks_client = compute.TenantNetworksClient(
- self.auth_provider, **params)
- self.baremetal_nodes_client = compute.BaremetalNodesClient(
- self.auth_provider, **params)
+ self.compute.InstanceUsagesAuditLogClient())
+ self.tenant_networks_client = self.compute.TenantNetworksClient()
+ self.baremetal_nodes_client = self.compute.BaremetalNodesClient()
# NOTE: The following client needs special timeout values because
# the API is a proxy for the other component.
- params_volume = copy.deepcopy(params)
- # Optional parameters
+ params_volume = {}
for _key in ('build_interval', 'build_timeout'):
_value = self.parameters['volume'].get(_key)
if _value:
params_volume[_key] = _value
- self.volumes_extensions_client = compute.VolumesClient(
- self.auth_provider, **params_volume)
- self.compute_versions_client = compute.VersionsClient(
- self.auth_provider, **params_volume)
- self.snapshots_extensions_client = compute.SnapshotsClient(
- self.auth_provider, **params_volume)
+ self.volumes_extensions_client = self.compute.VolumesClient(
+ **params_volume)
+ self.compute_versions_client = self.compute.VersionsClient(
+ **params_volume)
+ self.snapshots_extensions_client = self.compute.SnapshotsClient(
+ **params_volume)
def _set_identity_clients(self):
params = self.parameters['identity']
@@ -301,6 +246,8 @@
self.auth_provider, **params_v3)
self.roles_v3_client = identity.v3.RolesClient(self.auth_provider,
**params_v3)
+ self.inherited_roles_client = identity.v3.InheritedRolesClient(
+ self.auth_provider, **params_v3)
self.identity_services_v3_client = identity.v3.ServicesClient(
self.auth_provider, **params_v3)
self.policies_client = identity.v3.PoliciesClient(self.auth_provider,
@@ -348,16 +295,18 @@
**params)
self.backups_v2_client = volume.v2.BackupsClient(self.auth_provider,
**params)
+ self.encryption_types_client = volume.v1.EncryptionTypesClient(
+ self.auth_provider, **params)
+ self.encryption_types_v2_client = volume.v2.EncryptionTypesClient(
+ self.auth_provider, **params)
self.snapshots_client = volume.v1.SnapshotsClient(self.auth_provider,
**params)
self.snapshots_v2_client = volume.v2.SnapshotsClient(
self.auth_provider, **params)
- self.volumes_client = volume.v1.VolumesClient(
- self.auth_provider, default_volume_size=CONF.volume.volume_size,
- **params)
- self.volumes_v2_client = volume.v2.VolumesClient(
- self.auth_provider, default_volume_size=CONF.volume.volume_size,
- **params)
+ self.volumes_client = volume.v1.VolumesClient(self.auth_provider,
+ **params)
+ self.volumes_v2_client = volume.v2.VolumesClient(self.auth_provider,
+ **params)
self.volume_messages_client = volume.v3.MessagesClient(
self.auth_provider, **params)
self.volume_types_client = volume.v1.TypesClient(self.auth_provider,
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 80de6f5..af86fe3 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -106,7 +106,7 @@
self._load_json()
def _cleanup(self):
- print ("Begin cleanup")
+ print("Begin cleanup")
is_dry_run = self.options.dry_run
is_preserve = not self.options.delete_tempest_conf_objects
is_save_state = False
@@ -124,7 +124,7 @@
'is_save_state': is_save_state}
tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
tenants = tenant_service.list()
- print ("Process %s tenants" % len(tenants))
+ print("Process %s tenants" % len(tenants))
# Loop through list of tenants and clean them up.
for tenant in tenants:
@@ -155,7 +155,7 @@
self._remove_admin_role(tenant_id)
def _clean_tenant(self, tenant):
- print ("Cleaning tenant: %s " % tenant['name'])
+ print("Cleaning tenant: %s " % tenant['name'])
is_dry_run = self.options.dry_run
dry_run_data = self.dry_run_data
is_preserve = not self.options.delete_tempest_conf_objects
@@ -266,7 +266,7 @@
return False
def _init_state(self):
- print ("Initializing saved state.")
+ print("Initializing saved state.")
data = {}
admin_mgr = self.admin_mgr
kwargs = {'data': data,
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 8d2cfdc..9758061 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -67,11 +67,8 @@
CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name
CONF_PUB_NETWORK = CONF.network.public_network_id
CONF_PUB_ROUTER = CONF.network.public_router_id
- CONF_TENANTS = [CONF.auth.admin_project_name,
- CONF.identity.project_name,
- CONF.identity.alt_project_name]
- CONF_USERS = [CONF.auth.admin_username, CONF.identity.username,
- CONF.identity.alt_username]
+ CONF_TENANTS = [CONF.auth.admin_project_name]
+ CONF_USERS = [CONF.auth.admin_username]
if IS_NEUTRON:
CONF_PRIV_NETWORK = _get_network_id(CONF.compute.fixed_network_name,
@@ -352,7 +349,8 @@
LOG.exception("Delete Volume Quotas exception.")
def dry_run(self):
- quotas = self.client.show_quota_usage(self.tenant_id)['quota_set']
+ quotas = self.client.show_quota_set(
+ self.tenant_id, params={'usage': True})['quota_set']
self.data['volume_quotas'] = quotas
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index e3788ab..ba1f1fa 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -22,7 +22,7 @@
from oslo_log import log as logging
from six import moves
-from tempest.cmd.workspace import WorkspaceManager
+from tempest.cmd import workspace
LOG = logging.getLogger(__name__)
@@ -69,7 +69,10 @@
def get_parser(self, prog_name):
parser = super(TempestInit, self).get_parser(prog_name)
- parser.add_argument('dir', nargs='?', default=os.getcwd())
+ parser.add_argument('dir', nargs='?', default=os.getcwd(),
+ help="The path to the workspace directory. If you "
+ "omit this argument, the workspace directory is "
+ "your current directory")
parser.add_argument('--config-dir', '-c', default=None)
parser.add_argument('--show-global-config-dir', '-s',
action='store_true', dest='show_global_dir',
@@ -78,7 +81,7 @@
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")
+ "is ~/.tempest/workspace.yaml")
return parser
def generate_testr_conf(self, local_path):
@@ -89,18 +92,28 @@
with open(testr_conf_path, 'w+') as testr_conf_file:
testr_conf_file.write(testr_conf)
- def update_local_conf(self, conf_path, lock_dir, log_dir):
+ def get_configparser(self, conf_path):
config_parse = moves.configparser.SafeConfigParser()
config_parse.optionxform = str
- with open(conf_path, 'a+') as conf_file:
- # Set local lock_dir in tempest conf
- if not config_parse.has_section('oslo_concurrency'):
- config_parse.add_section('oslo_concurrency')
- config_parse.set('oslo_concurrency', 'lock_path', lock_dir)
- # Set local log_dir in tempest conf
- config_parse.set('DEFAULT', 'log_dir', log_dir)
- # Set default log filename to tempest.log
- config_parse.set('DEFAULT', 'log_file', 'tempest.log')
+ # get any existing values if a config file already exists
+ if os.path.isfile(conf_path):
+ # use read() for Python 2 and 3 compatibility
+ config_parse.read(conf_path)
+ return config_parse
+
+ def update_local_conf(self, conf_path, lock_dir, log_dir):
+ config_parse = self.get_configparser(conf_path)
+ # Set local lock_dir in tempest conf
+ if not config_parse.has_section('oslo_concurrency'):
+ config_parse.add_section('oslo_concurrency')
+ config_parse.set('oslo_concurrency', 'lock_path', lock_dir)
+ # Set local log_dir in tempest conf
+ config_parse.set('DEFAULT', 'log_dir', log_dir)
+ # Set default log filename to tempest.log
+ config_parse.set('DEFAULT', 'log_file', 'tempest.log')
+
+ # write out a new file with the updated configurations
+ with open(conf_path, 'w+') as conf_file:
config_parse.write(conf_file)
def copy_config(self, etc_dir, config_dir):
@@ -157,7 +170,8 @@
subprocess.call(['testr', 'init'], cwd=local_dir)
def take_action(self, parsed_args):
- workspace_manager = WorkspaceManager(parsed_args.workspace_path)
+ workspace_manager = workspace.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)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
deleted file mode 100755
index a9e5167..0000000
--- a/tempest/cmd/javelin.py
+++ /dev/null
@@ -1,1131 +0,0 @@
-#!/usr/bin/env python
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Javelin is a tool for creating, verifying, and deleting a small set of
-resources in a declarative way.
-
-Javelin is meant to be used as a way to validate quickly that resources can
-survive an upgrade process.
-
-Authentication
---------------
-
-Javelin will be creating (and removing) users and tenants so it needs the admin
-credentials of your cloud to operate properly. The corresponding info can be
-given the usual way, either through CLI options or environment variables.
-
-You're probably familiar with these, but just in case::
-
- +----------+------------------+----------------------+
- | Param | CLI | Environment Variable |
- +----------+------------------+----------------------+
- | Username | --os-username | OS_USERNAME |
- | Password | --os-password | OS_PASSWORD |
- | Tenant | --os-tenant-name | OS_TENANT_NAME |
- +----------+------------------+----------------------+
-
-
-Runtime Arguments
------------------
-
-**-m/--mode**: (Required) Has to be one of 'check', 'create' or 'destroy'. It
-indicates which actions javelin is going to perform.
-
-**-r/--resources**: (Required) The path to a YAML file describing the resources
-used by Javelin.
-
-**-d/--devstack-base**: (Required) The path to the devstack repo used to
-retrieve artefacts (like images) that will be referenced in the resource files.
-
-**-c/--config-file**: (Optional) The path to a valid Tempest config file
-describing your cloud. Javelin may use this to determine if certain services
-are enabled and modify its behavior accordingly.
-
-
-Resource file
--------------
-
-The resource file is a valid YAML file describing the resources that will be
-created, checked and destroyed by javelin. Here's a canonical example of a
-resource file::
-
- tenants:
- - javelin
- - discuss
-
- users:
- - name: javelin
- pass: gungnir
- tenant: javelin
- - name: javelin2
- pass: gungnir2
- tenant: discuss
-
- # resources that we want to create
- images:
- - name: javelin_cirros
- owner: javelin
- file: cirros-0.3.2-x86_64-blank.img
- disk_format: ami
- container_format: ami
- aki: cirros-0.3.2-x86_64-vmlinuz
- ari: cirros-0.3.2-x86_64-initrd
-
- servers:
- - name: peltast
- owner: javelin
- flavor: m1.small
- image: javelin_cirros
- floating_ip_pool: public
- - name: hoplite
- owner: javelin
- flavor: m1.medium
- image: javelin_cirros
-
-
-An important piece of the resource definition is the *owner* field, which is
-the user (that we've created) that is the owner of that resource. All
-operations on that resource will happen as that regular user to ensure that
-admin level access does not mask issues.
-
-The check phase will act like a unit test, using well known assert methods to
-verify that the correct resources exist.
-
-"""
-
-import argparse
-import collections
-import datetime
-import os
-import sys
-import unittest
-
-import netaddr
-from oslo_log import log as logging
-import six
-import yaml
-
-from tempest.common import identity
-from tempest.common import waiters
-from tempest import config
-from tempest.lib import auth
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.compute import flavors_client
-from tempest.lib.services.compute import floating_ips_client
-from tempest.lib.services.compute import security_group_rules_client
-from tempest.lib.services.compute import security_groups_client
-from tempest.lib.services.compute import servers_client
-from tempest.lib.services.identity.v2 import roles_client
-from tempest.lib.services.identity.v2 import tenants_client
-from tempest.lib.services.identity.v2 import users_client
-from tempest.lib.services.image.v2 import images_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.object_storage import container_client
-from tempest.services.object_storage import object_client
-from tempest.services.volume.v1.json import volumes_client
-
-CONF = config.CONF
-OPTS = {}
-USERS = {}
-RES = collections.defaultdict(list)
-
-LOG = None
-
-JAVELIN_START = datetime.datetime.utcnow()
-
-
-class OSClient(object):
- _creds = None
- identity = None
- servers = None
-
- def __init__(self, user, pw, tenant):
- default_params = {
- 'disable_ssl_certificate_validation':
- CONF.identity.disable_ssl_certificate_validation,
- 'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests
- }
- default_params_with_timeout_values = {
- 'build_interval': CONF.compute.build_interval,
- 'build_timeout': CONF.compute.build_timeout
- }
- default_params_with_timeout_values.update(default_params)
-
- compute_params = {
- 'service': CONF.compute.catalog_type,
- 'region': CONF.compute.region or CONF.identity.region,
- 'endpoint_type': CONF.compute.endpoint_type,
- 'build_interval': CONF.compute.build_interval,
- 'build_timeout': CONF.compute.build_timeout
- }
- compute_params.update(default_params)
-
- object_storage_params = {
- 'service': CONF.object_storage.catalog_type,
- 'region': CONF.object_storage.region or CONF.identity.region,
- 'endpoint_type': CONF.object_storage.endpoint_type
- }
- object_storage_params.update(default_params)
-
- _creds = auth.KeystoneV2Credentials(
- username=user,
- password=pw,
- tenant_name=tenant)
- auth_provider_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 = auth.KeystoneV2AuthProvider(
- _creds, CONF.identity.uri, **auth_provider_params)
- self.identity = identity_client.IdentityClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **default_params_with_timeout_values)
- self.tenants = tenants_client.TenantsClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **default_params_with_timeout_values)
- self.roles = roles_client.RolesClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **default_params_with_timeout_values)
- self.users = users_client.UsersClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **default_params_with_timeout_values)
- self.servers = servers_client.ServersClient(_auth,
- **compute_params)
- self.flavors = flavors_client.FlavorsClient(_auth,
- **compute_params)
- self.floating_ips = floating_ips_client.FloatingIPsClient(
- _auth, **compute_params)
- self.secgroups = security_groups_client.SecurityGroupsClient(
- _auth, **compute_params)
- self.secrules = security_group_rules_client.SecurityGroupRulesClient(
- _auth, **compute_params)
- self.objects = object_client.ObjectClient(_auth,
- **object_storage_params)
- self.containers = container_client.ContainerClient(
- _auth, **object_storage_params)
- self.images = images_client.ImagesClient(
- _auth,
- 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,
- **default_params)
- self.volumes = volumes_client.VolumesClient(
- _auth,
- CONF.volume.catalog_type,
- CONF.volume.region or CONF.identity.region,
- endpoint_type=CONF.volume.endpoint_type,
- build_interval=CONF.volume.build_interval,
- build_timeout=CONF.volume.build_timeout,
- **default_params)
- self.networks = networks_client.NetworksClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type=CONF.network.endpoint_type,
- build_interval=CONF.network.build_interval,
- build_timeout=CONF.network.build_timeout,
- **default_params)
- self.ports = ports_client.PortsClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type=CONF.network.endpoint_type,
- build_interval=CONF.network.build_interval,
- build_timeout=CONF.network.build_timeout,
- **default_params)
- self.routers = routers_client.RoutersClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type=CONF.network.endpoint_type,
- build_interval=CONF.network.build_interval,
- build_timeout=CONF.network.build_timeout,
- **default_params)
- self.subnets = subnets_client.SubnetsClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type=CONF.network.endpoint_type,
- build_interval=CONF.network.build_interval,
- build_timeout=CONF.network.build_timeout,
- **default_params)
-
-
-def load_resources(fname):
- """Load the expected resources from a yaml file."""
- return yaml.load(open(fname, 'r'))
-
-
-def keystone_admin():
- return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
-
-
-def client_for_user(name):
- LOG.debug("Entering client_for_user")
- if name in USERS:
- user = USERS[name]
- LOG.debug("Created client for user %s" % user)
- return OSClient(user['name'], user['pass'], user['tenant'])
- else:
- LOG.error("%s not found in USERS: %s" % (name, USERS))
-
-
-###################
-#
-# TENANTS
-#
-###################
-
-
-def create_tenants(tenants):
- """Create tenants from resource definition.
-
- Don't create the tenants if they already exist.
- """
- admin = keystone_admin()
- body = admin.tenants.list_tenants()['tenants']
- existing = [x['name'] for x in body]
- for tenant in tenants:
- if tenant not in existing:
- admin.tenants.create_tenant(name=tenant)['tenant']
- else:
- LOG.warning("Tenant '%s' already exists in this environment"
- % tenant)
-
-
-def destroy_tenants(tenants):
- admin = keystone_admin()
- for tenant in tenants:
- tenant_id = identity.get_tenant_by_name(admin.tenant, tenant)['id']
- admin.tenants.delete_tenant(tenant_id)
-
-##############
-#
-# USERS
-#
-##############
-
-
-def _users_for_tenant(users, tenant):
- u_for_t = []
- for user in users:
- for n in user:
- if user[n]['tenant'] == tenant:
- u_for_t.append(user[n])
- return u_for_t
-
-
-def _tenants_from_users(users):
- tenants = set()
- for user in users:
- for n in user:
- tenants.add(user[n]['tenant'])
- return tenants
-
-
-def _assign_swift_role(user, swift_role):
- admin = keystone_admin()
- roles = admin.roles.list_roles()
- role = next(r for r in roles if r['name'] == swift_role)
- LOG.debug(USERS[user])
- try:
- admin.roles.create_user_role_on_project(
- USERS[user]['tenant_id'],
- USERS[user]['id'],
- role['id'])
- except lib_exc.Conflict:
- # don't care if it's already assigned
- pass
-
-
-def create_users(users):
- """Create tenants from resource definition.
-
- Don't create the tenants if they already exist.
- """
- global USERS
- LOG.info("Creating users")
- admin = keystone_admin()
- for u in users:
- try:
- tenant = identity.get_tenant_by_name(admin.tenants, u['tenant'])
- except lib_exc.NotFound:
- LOG.error("Tenant: %s - not found" % u['tenant'])
- continue
- try:
- identity.get_user_by_username(admin.tenants,
- tenant['id'], u['name'])
- LOG.warning("User '%s' already exists in this environment"
- % u['name'])
- except lib_exc.NotFound:
- admin.users.create_user(
- name=u['name'], password=u['pass'],
- tenantId=tenant['id'],
- email="%s@%s" % (u['name'], tenant['id']),
- enabled=True)
-
-
-def destroy_users(users):
- admin = keystone_admin()
- for user in users:
- tenant_id = identity.get_tenant_by_name(admin.tenants,
- user['tenant'])['id']
- user_id = identity.get_user_by_username(admin.tenants,
- tenant_id, user['name'])['id']
- admin.users.delete_user(user_id)
-
-
-def collect_users(users):
- global USERS
- LOG.info("Collecting users")
- admin = keystone_admin()
- for u in users:
- tenant = identity.get_tenant_by_name(admin.tenants, u['tenant'])
- u['tenant_id'] = tenant['id']
- USERS[u['name']] = u
- body = identity.get_user_by_username(admin.tenants,
- tenant['id'], u['name'])
- USERS[u['name']]['id'] = body['id']
-
-
-class JavelinCheck(unittest.TestCase):
- def __init__(self, users, resources):
- super(JavelinCheck, self).__init__()
- self.users = users
- self.res = resources
-
- def runTest(self, *args):
- pass
-
- def _ping_ip(self, ip_addr, count, namespace=None):
- if namespace is None:
- ping_cmd = "ping -c1 " + ip_addr
- else:
- ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
- ip_addr)
- for current in range(count):
- return_code = os.system(ping_cmd)
- if return_code is 0:
- break
- self.assertNotEqual(current, count - 1,
- "Server is not pingable at %s" % ip_addr)
-
- def check(self):
- self.check_users()
- self.check_objects()
- self.check_servers()
- self.check_volumes()
- self.check_secgroups()
-
- # validate neutron is enabled and ironic disabled:
- # Tenant network isolation is not supported when using ironic.
- # "admin" has set up a neutron flat network environment within a shared
- # fixed network for all tenants to use.
- # In this case, network/subnet/router creation can be skipped and the
- # server booted the same as nova network.
- if (CONF.service_available.neutron and
- not CONF.baremetal.driver_enabled):
- self.check_networking()
-
- def check_users(self):
- """Check that the users we expect to exist, do.
-
- We don't use the resource list for this because we need to validate
- that things like tenantId didn't drift across versions.
- """
- LOG.info("checking users")
- for name, user in six.iteritems(self.users):
- client = keystone_admin()
- found = client.users.show_user(user['id'])['user']
- self.assertEqual(found['name'], user['name'])
- self.assertEqual(found['tenantId'], user['tenant_id'])
-
- # also ensure we can auth with that user, and do something
- # on the cloud. We don't care about the results except that it
- # remains authorized.
- client = client_for_user(user['name'])
- client.servers.list_servers()
-
- def check_objects(self):
- """Check that the objects created are still there."""
- if not self.res.get('objects'):
- return
- LOG.info("checking objects")
- for obj in self.res['objects']:
- client = client_for_user(obj['owner'])
- r, contents = client.objects.get_object(
- obj['container'], obj['name'])
- source = _file_contents(obj['file'])
- self.assertEqual(contents, source)
-
- def check_servers(self):
- """Check that the servers are still up and running."""
- if not self.res.get('servers'):
- return
- LOG.info("checking servers")
- for server in self.res['servers']:
- client = client_for_user(server['owner'])
- found = _get_server_by_name(client, server['name'])
- self.assertIsNotNone(
- found,
- "Couldn't find expected server %s" % server['name'])
-
- found = client.servers.show_server(found['id'])['server']
- # validate neutron is enabled and ironic disabled:
- if (CONF.service_available.neutron and
- not CONF.baremetal.driver_enabled):
- _floating_is_alive = False
- for network_name, body in found['addresses'].items():
- for addr in body:
- ip = addr['addr']
- # Use floating IP, fixed IP or other type to
- # reach the server.
- # This is useful in multi-node environment.
- if CONF.validation.connect_method == 'floating':
- if addr.get('OS-EXT-IPS:type',
- 'floating') == 'floating':
- self._ping_ip(ip, 60)
- _floating_is_alive = True
- elif CONF.validation.connect_method == 'fixed':
- if addr.get('OS-EXT-IPS:type',
- 'fixed') == 'fixed':
- namespace = _get_router_namespace(client,
- network_name)
- self._ping_ip(ip, 60, namespace)
- else:
- self._ping_ip(ip, 60)
- # If CONF.validation.connect_method is floating, validate
- # that the floating IP is attached to the server and the
- # the server is pingable.
- if CONF.validation.connect_method == 'floating':
- self.assertTrue(_floating_is_alive,
- "Server %s has no floating IP." %
- server['name'])
- else:
- addr = found['addresses']['private'][0]['addr']
- self._ping_ip(addr, 60)
-
- def check_secgroups(self):
- """Check that the security groups still exist."""
- LOG.info("Checking security groups")
- for secgroup in self.res['secgroups']:
- client = client_for_user(secgroup['owner'])
- found = _get_resource_by_name(client.secgroups, 'security_groups',
- secgroup['name'])
- self.assertIsNotNone(
- found,
- "Couldn't find expected secgroup %s" % secgroup['name'])
-
- def check_volumes(self):
- """Check that the volumes are still there and attached."""
- if not self.res.get('volumes'):
- return
- LOG.info("checking volumes")
- for volume in self.res['volumes']:
- client = client_for_user(volume['owner'])
- vol_body = _get_volume_by_name(client, volume['name'])
- self.assertIsNotNone(
- vol_body,
- "Couldn't find expected volume %s" % volume['name'])
-
- # Verify that a volume's attachment retrieved
- server_id = _get_server_by_name(client, volume['server'])['id']
- attachment = client.volumes.get_attachment_from_volume(vol_body)
- self.assertEqual(vol_body['id'], attachment['volume_id'])
- self.assertEqual(server_id, attachment['server_id'])
-
- def check_networking(self):
- """Check that the networks are still there."""
- for res_type in ('networks', 'subnets', 'routers'):
- for res in self.res[res_type]:
- client = client_for_user(res['owner'])
- found = _get_resource_by_name(client.networks, res_type,
- res['name'])
- self.assertIsNotNone(
- found,
- "Couldn't find expected resource %s" % res['name'])
-
-
-#######################
-#
-# OBJECTS
-#
-#######################
-
-
-def _file_contents(fname):
- with open(fname, 'r') as f:
- return f.read()
-
-
-def create_objects(objects):
- if not objects:
- return
- LOG.info("Creating objects")
- for obj in objects:
- LOG.debug("Object %s" % obj)
- swift_role = obj.get('swift_role', 'Member')
- _assign_swift_role(obj['owner'], swift_role)
- client = client_for_user(obj['owner'])
- client.containers.create_container(obj['container'])
- client.objects.create_object(
- obj['container'], obj['name'],
- _file_contents(obj['file']))
-
-
-def destroy_objects(objects):
- for obj in objects:
- client = client_for_user(obj['owner'])
- r, body = client.objects.delete_object(obj['container'], obj['name'])
- if not (200 <= int(r['status']) < 299):
- raise ValueError("unable to destroy object: [%s] %s" % (r, body))
-
-
-#######################
-#
-# IMAGES
-#
-#######################
-
-
-def _resolve_image(image, imgtype):
- name = image[imgtype]
- fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
- return name, fname
-
-
-def _get_image_by_name(client, name):
- body = client.images.list_images()
- for image in body:
- if name == image['name']:
- return image
- return None
-
-
-def create_images(images):
- if not images:
- return
- LOG.info("Creating images")
- for image in images:
- client = client_for_user(image['owner'])
-
- # DEPRECATED: 'format' was used for ami images
- # Use 'disk_format' and 'container_format' instead
- if 'format' in image:
- LOG.warning("Deprecated: 'format' is deprecated for images "
- "description. Please use 'disk_format' and 'container_"
- "format' instead.")
- image['disk_format'] = image['format']
- image['container_format'] = image['format']
-
- # only upload a new image if the name isn't there
- if _get_image_by_name(client, image['name']):
- LOG.info("Image '%s' already exists" % image['name'])
- continue
-
- # special handling for 3 part image
- extras = {}
- if image['disk_format'] == 'ami':
- name, fname = _resolve_image(image, 'aki')
- aki = client.images.create_image(
- 'javelin_' + name, 'aki', 'aki')
- client.images.store_image_file(aki.get('id'), open(fname, 'r'))
- extras['kernel_id'] = aki.get('id')
-
- name, fname = _resolve_image(image, 'ari')
- ari = client.images.create_image(
- 'javelin_' + name, 'ari', 'ari')
- client.images.store_image_file(ari.get('id'), open(fname, 'r'))
- extras['ramdisk_id'] = ari.get('id')
-
- _, fname = _resolve_image(image, 'file')
- body = client.images.create_image(
- image['name'], image['container_format'],
- image['disk_format'], **extras)
- image_id = body.get('id')
- client.images.store_image_file(image_id, open(fname, 'r'))
-
-
-def destroy_images(images):
- if not images:
- return
- LOG.info("Destroying images")
- for image in images:
- client = client_for_user(image['owner'])
-
- response = _get_image_by_name(client, image['name'])
- if not response:
- LOG.info("Image '%s' does not exist" % image['name'])
- continue
- client.images.delete_image(response['id'])
-
-
-#######################
-#
-# NETWORKS
-#
-#######################
-
-def _get_router_namespace(client, network):
- network_id = _get_resource_by_name(client.networks,
- 'networks', network)['id']
- n_body = client.routers.list_routers()
- for router in n_body['routers']:
- router_id = router['id']
- r_body = client.ports.list_ports(device_id=router_id)
- for port in r_body['ports']:
- if port['network_id'] == network_id:
- return "qrouter-%s" % router_id
-
-
-def _get_resource_by_name(client, resource, name):
- get_resources = getattr(client, 'list_%s' % resource)
- if get_resources is None:
- raise AttributeError("client doesn't have method list_%s" % resource)
- # Until all tempest client methods are changed to return only one value,
- # we cannot assume they all have the same signature so we need to discard
- # the unused response first value it two values are being returned.
- body = get_resources()
- if isinstance(body, tuple):
- body = body[1]
- if isinstance(body, dict):
- body = body[resource]
- for res in body:
- if name == res['name']:
- return res
- raise ValueError('%s not found in %s resources' % (name, resource))
-
-
-def create_networks(networks):
- LOG.info("Creating networks")
- for network in networks:
- client = client_for_user(network['owner'])
-
- # only create a network if the name isn't here
- body = client.networks.list_networks()
- if any(item['name'] == network['name'] for item in body['networks']):
- LOG.warning("Duplicated network name: %s" % network['name'])
- continue
-
- client.networks.create_network(name=network['name'])
-
-
-def destroy_networks(networks):
- LOG.info("Destroying subnets")
- for network in networks:
- client = client_for_user(network['owner'])
- network_id = _get_resource_by_name(client.networks, 'networks',
- network['name'])['id']
- client.networks.delete_network(network_id)
-
-
-def create_subnets(subnets):
- LOG.info("Creating subnets")
- for subnet in subnets:
- client = client_for_user(subnet['owner'])
-
- network = _get_resource_by_name(client.networks, 'networks',
- subnet['network'])
- ip_version = netaddr.IPNetwork(subnet['range']).version
- # ensure we don't overlap with another subnet in the network
- try:
- client.networks.create_subnet(network_id=network['id'],
- cidr=subnet['range'],
- name=subnet['name'],
- ip_version=ip_version)
- except lib_exc.BadRequest as e:
- is_overlapping_cidr = 'overlaps with another subnet' in str(e)
- if not is_overlapping_cidr:
- raise
-
-
-def destroy_subnets(subnets):
- LOG.info("Destroying subnets")
- for subnet in subnets:
- client = client_for_user(subnet['owner'])
- subnet_id = _get_resource_by_name(client.subnets,
- 'subnets', subnet['name'])['id']
- client.subnets.delete_subnet(subnet_id)
-
-
-def create_routers(routers):
- LOG.info("Creating routers")
- for router in routers:
- client = client_for_user(router['owner'])
-
- # only create a router if the name isn't here
- body = client.routers.list_routers()
- if any(item['name'] == router['name'] for item in body['routers']):
- LOG.warning("Duplicated router name: %s" % router['name'])
- continue
-
- client.networks.create_router(name=router['name'])
-
-
-def destroy_routers(routers):
- LOG.info("Destroying routers")
- for router in routers:
- client = client_for_user(router['owner'])
- router_id = _get_resource_by_name(client.networks,
- 'routers', router['name'])['id']
- for subnet in router['subnet']:
- subnet_id = _get_resource_by_name(client.networks,
- 'subnets', subnet)['id']
- client.routers.remove_router_interface(router_id,
- subnet_id=subnet_id)
- client.routers.delete_router(router_id)
-
-
-def add_router_interface(routers):
- for router in routers:
- client = client_for_user(router['owner'])
- router_id = _get_resource_by_name(client.networks,
- 'routers', router['name'])['id']
-
- for subnet in router['subnet']:
- subnet_id = _get_resource_by_name(client.networks,
- 'subnets', subnet)['id']
- # connect routers to their subnets
- client.routers.add_router_interface(router_id,
- subnet_id=subnet_id)
- # connect routers to external network if set to "gateway"
- if router['gateway']:
- if CONF.network.public_network_id:
- ext_net = CONF.network.public_network_id
- client.routers.update_router(
- router_id, set_enable_snat=True,
- external_gateway_info={"network_id": ext_net})
- else:
- raise ValueError('public_network_id is not configured.')
-
-
-#######################
-#
-# SERVERS
-#
-#######################
-
-def _get_server_by_name(client, name):
- body = client.servers.list_servers()
- for server in body['servers']:
- if name == server['name']:
- return server
- return None
-
-
-def _get_flavor_by_name(client, name):
- body = client.flavors.list_flavors()['flavors']
- for flavor in body:
- if name == flavor['name']:
- return flavor
- return None
-
-
-def create_servers(servers):
- if not servers:
- return
- LOG.info("Creating servers")
- for server in servers:
- client = client_for_user(server['owner'])
-
- if _get_server_by_name(client, server['name']):
- LOG.info("Server '%s' already exists" % server['name'])
- continue
-
- image_id = _get_image_by_name(client, server['image'])['id']
- flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
- # validate neutron is enabled and ironic disabled
- kwargs = dict()
- if (CONF.service_available.neutron and
- not CONF.baremetal.driver_enabled and server.get('networks')):
- get_net_id = lambda x: (_get_resource_by_name(
- client.networks, 'networks', x)['id'])
- kwargs['networks'] = [{'uuid': get_net_id(network)}
- for network in server['networks']]
- body = client.servers.create_server(
- name=server['name'], imageRef=image_id, flavorRef=flavor_id,
- **kwargs)['server']
- server_id = body['id']
- client.servers.wait_for_server_status(server_id, 'ACTIVE')
- # create security group(s) after server spawning
- for secgroup in server['secgroups']:
- client.servers.add_security_group(server_id, name=secgroup)
- if CONF.validation.connect_method == 'floating':
- floating_ip_pool = server.get('floating_ip_pool')
- floating_ip = client.floating_ips.create_floating_ip(
- pool_name=floating_ip_pool)['floating_ip']
- client.floating_ips.associate_floating_ip_to_server(
- floating_ip['ip'], server_id)
-
-
-def destroy_servers(servers):
- if not servers:
- return
- LOG.info("Destroying servers")
- for server in servers:
- client = client_for_user(server['owner'])
-
- response = _get_server_by_name(client, server['name'])
- if not response:
- LOG.info("Server '%s' does not exist" % server['name'])
- continue
-
- # TODO(EmilienM): disassociate floating IP from server and release it.
- client.servers.delete_server(response['id'])
- waiters.wait_for_server_termination(client.servers, response['id'],
- ignore_error=True)
-
-
-def create_secgroups(secgroups):
- LOG.info("Creating security groups")
- for secgroup in secgroups:
- client = client_for_user(secgroup['owner'])
-
- # only create a security group if the name isn't here
- # i.e. a security group may be used by another server
- # only create a router if the name isn't here
- body = client.secgroups.list_security_groups()['security_groups']
- if any(item['name'] == secgroup['name'] for item in body):
- LOG.warning("Security group '%s' already exists" %
- secgroup['name'])
- continue
-
- body = client.secgroups.create_security_group(
- name=secgroup['name'],
- description=secgroup['description'])['security_group']
- secgroup_id = body['id']
- # for each security group, create the rules
- for rule in secgroup['rules']:
- ip_proto, from_port, to_port, cidr = rule.split()
- client.secrules.create_security_group_rule(
- parent_group_id=secgroup_id, ip_protocol=ip_proto,
- from_port=from_port, to_port=to_port, cidr=cidr)
-
-
-def destroy_secgroups(secgroups):
- LOG.info("Destroying security groups")
- for secgroup in secgroups:
- client = client_for_user(secgroup['owner'])
- sg_id = _get_resource_by_name(client.secgroups,
- 'security_groups',
- secgroup['name'])
- # sg rules are deleted automatically
- client.secgroups.delete_security_group(sg_id['id'])
-
-
-#######################
-#
-# VOLUMES
-#
-#######################
-
-def _get_volume_by_name(client, name):
- body = client.volumes.list_volumes()['volumes']
- for volume in body:
- if name == volume['display_name']:
- return volume
- return None
-
-
-def create_volumes(volumes):
- if not volumes:
- return
- LOG.info("Creating volumes")
- for volume in volumes:
- client = client_for_user(volume['owner'])
-
- # only create a volume if the name isn't here
- if _get_volume_by_name(client, volume['name']):
- LOG.info("volume '%s' already exists" % volume['name'])
- continue
-
- size = volume['gb']
- v_name = volume['name']
- body = client.volumes.create_volume(size=size,
- display_name=v_name)['volume']
- waiters.wait_for_volume_status(client.volumes, body['id'], 'available')
-
-
-def destroy_volumes(volumes):
- for volume in volumes:
- client = client_for_user(volume['owner'])
- volume_id = _get_volume_by_name(client, volume['name'])['id']
- client.volumes.detach_volume(volume_id)
- client.volumes.delete_volume(volume_id)
-
-
-def attach_volumes(volumes):
- for volume in volumes:
- client = client_for_user(volume['owner'])
- server_id = _get_server_by_name(client, volume['server'])['id']
- volume_id = _get_volume_by_name(client, volume['name'])['id']
- device = volume['device']
- client.volumes.attach_volume(volume_id,
- instance_uuid=server_id,
- mountpoint=device)
-
-
-#######################
-#
-# MAIN LOGIC
-#
-#######################
-
-def create_resources():
- LOG.info("Creating Resources")
- # first create keystone level resources, and we need to be admin
- # for this.
- create_tenants(RES['tenants'])
- create_users(RES['users'])
- collect_users(RES['users'])
-
- # next create resources in a well known order
- create_objects(RES['objects'])
- create_images(RES['images'])
-
- # validate neutron is enabled and ironic is disabled
- if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
- create_networks(RES['networks'])
- create_subnets(RES['subnets'])
- create_routers(RES['routers'])
- add_router_interface(RES['routers'])
-
- create_secgroups(RES['secgroups'])
- create_volumes(RES['volumes'])
-
- # Only attempt attaching the volumes if servers are defined in the
- # resource file
- if 'servers' in RES:
- create_servers(RES['servers'])
- attach_volumes(RES['volumes'])
-
-
-def destroy_resources():
- LOG.info("Destroying Resources")
- # Destroy in inverse order of create
- destroy_servers(RES['servers'])
- destroy_images(RES['images'])
- destroy_objects(RES['objects'])
- destroy_volumes(RES['volumes'])
- if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
- destroy_routers(RES['routers'])
- destroy_subnets(RES['subnets'])
- destroy_networks(RES['networks'])
- destroy_secgroups(RES['secgroups'])
- destroy_users(RES['users'])
- destroy_tenants(RES['tenants'])
- LOG.warning("Destroy mode incomplete")
-
-
-def get_options():
- global OPTS
- parser = argparse.ArgumentParser(
- description='Create and validate a fixed set of OpenStack resources')
- parser.add_argument('-m', '--mode',
- metavar='<create|check|destroy>',
- required=True,
- help=('One of (create, check, destroy)'))
- parser.add_argument('-r', '--resources',
- required=True,
- metavar='resourcefile.yaml',
- help='Resources definition yaml file')
-
- parser.add_argument(
- '-d', '--devstack-base',
- required=True,
- metavar='/opt/stack/old',
- help='Devstack base directory for retrieving artifacts')
- parser.add_argument(
- '-c', '--config-file',
- metavar='/etc/tempest.conf',
- help='path to javelin2(tempest) config file')
-
- # auth bits, letting us also just source the devstack openrc
- parser.add_argument('--os-username',
- metavar='<auth-user-name>',
- default=os.environ.get('OS_USERNAME'),
- help=('Defaults to env[OS_USERNAME].'))
- parser.add_argument('--os-password',
- metavar='<auth-password>',
- default=os.environ.get('OS_PASSWORD'),
- help=('Defaults to env[OS_PASSWORD].'))
- parser.add_argument('--os-tenant-name',
- metavar='<auth-tenant-name>',
- default=os.environ.get('OS_TENANT_NAME'),
- help=('Defaults to env[OS_TENANT_NAME].'))
-
- OPTS = parser.parse_args()
- if OPTS.mode not in ('create', 'check', 'destroy'):
- print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
- parser.print_help()
- sys.exit(1)
- if OPTS.config_file:
- config.CONF.set_config_path(OPTS.config_file)
-
-
-def setup_logging():
- global LOG
- logging.setup(CONF, __name__)
- LOG = logging.getLogger(__name__)
-
-
-def main():
- print("Javelin is deprecated and will be removed from Tempest in the "
- "future.")
- global RES
- get_options()
- setup_logging()
- RES.update(load_resources(OPTS.resources))
-
- if OPTS.mode == 'create':
- create_resources()
- # Make sure the resources we just created actually work
- checker = JavelinCheck(USERS, RES)
- checker.check()
- elif OPTS.mode == 'check':
- collect_users(RES['users'])
- checker = JavelinCheck(USERS, RES)
- checker.check()
- elif OPTS.mode == 'destroy':
- collect_users(RES['users'])
- destroy_resources()
- else:
- LOG.error('Unknown mode %s' % OPTS.mode)
- return 1
- LOG.info('javelin2 successfully finished')
- return 0
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
index 5f6b3e6..86732da 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -19,12 +19,9 @@
"""
from cliff import command
-from oslo_log import log as logging
import prettytable
-from tempest.test_discover.plugins import TempestTestPluginManager
-
-LOG = logging.getLogger(__name__)
+from tempest.test_discover import plugins as plg
class TempestListPlugins(command.Command):
@@ -35,7 +32,7 @@
return 'List all tempest plugins'
def _list_plugins(self):
- plugins = TempestTestPluginManager()
+ plugins = plg.TempestTestPluginManager()
output = prettytable.PrettyTable(["Name", "EntryPoint"])
for plugin in plugins.ext_plugins.extensions:
diff --git a/tempest/cmd/main.py b/tempest/cmd/main.py
index acd97a8..641d11c 100644
--- a/tempest/cmd/main.py
+++ b/tempest/cmd/main.py
@@ -26,7 +26,7 @@
def __init__(self):
super(Main, self).__init__(
description='Tempest cli application',
- version=version.VersionInfo('tempest').version_string(),
+ version=version.VersionInfo('tempest').version_string_with_vcs(),
command_manager=commandmanager.CommandManager('tempest.cm'),
deferred_help=True,
)
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
deleted file mode 100644
index 5c62ee3..0000000
--- a/tempest/cmd/resources.yaml
+++ /dev/null
@@ -1,95 +0,0 @@
-# This is a yaml description for the most basic definitions
-# of what should exist across the resource boundary. Perhaps
-# one day this will grow into a Heat resource template, but as
-# Heat isn't a known working element in the upgrades, we do
-# this much simpler thing for now.
-
-tenants:
- - javelin
- - discuss
-
-users:
- - name: javelin
- pass: gungnir
- tenant: javelin
- - name: javelin2
- pass: gungnir2
- tenant: discuss
-
-secgroups:
- - name: angon
- owner: javelin
- description: angon
- rules:
- - 'icmp -1 -1 0.0.0.0/0'
- - 'tcp 22 22 0.0.0.0/0'
- - name: baobab
- owner: javelin
- description: baobab
- rules:
- - 'tcp 80 80 0.0.0.0/0'
-
-# resources that we want to create
-images:
- - name: javelin_cirros
- owner: javelin
- imgdir: files/images/cirros-0.3.2-x86_64-uec
- file: cirros-0.3.2-x86_64-blank.img
- format: ami
- aki: cirros-0.3.2-x86_64-vmlinuz
- ari: cirros-0.3.2-x86_64-initrd
-volumes:
- - name: assegai
- server: peltast
- owner: javelin
- gb: 1
- device: /dev/vdb
- - name: pifpouf
- server: hoplite
- owner: javelin
- gb: 2
- device: /dev/vdb
-networks:
- - name: world1
- owner: javelin
- - name: world2
- owner: javelin
-subnets:
- - name: subnet1
- range: 10.1.0.0/24
- network: world1
- owner: javelin
- - name: subnet2
- range: 192.168.1.0/24
- network: world2
- owner: javelin
-routers:
- - name: connector
- owner: javelin
- gateway: true
- subnet:
- - subnet1
- - subnet2
-servers:
- - name: peltast
- owner: javelin
- flavor: m1.small
- image: javelin_cirros
- networks:
- - world1
- secgroups:
- - angon
- - baobab
- - name: hoplite
- owner: javelin
- flavor: m1.medium
- image: javelin_cirros
- networks:
- - world2
- secgroups:
- - angon
-objects:
- - container: jc1
- name: javelin1
- owner: javelin
- file: /etc/hosts
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 2eb122e..fef836c 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -23,9 +23,9 @@
any tests that match on re.match() with the regex
* **--smoke**: Run all the tests tagged as smoke
-There are also the **--blacklist_file** and **--whitelist_file** options that
+There are also the **--blacklist-file** and **--whitelist-file** options that
let you pass a filepath to tempest run with the file format being a line
-seperated regex, with '#' used to signify the start of a comment on a line.
+separated regex, with '#' used to signify the start of a comment on a line.
For example::
# Regex file
@@ -88,7 +88,6 @@
from cliff import command
from os_testr import regex_builder
from os_testr import subunit_trace
-from oslo_log import log as logging
from testrepository.commands import run_argv
from tempest.cmd import init
@@ -96,7 +95,6 @@
from tempest import config
-LOG = logging.getLogger(__name__)
CONF = config.CONF
@@ -193,11 +191,11 @@
help='A normal testr selection regex used to '
'specify a subset of tests to run')
list_selector = parser.add_mutually_exclusive_group()
- list_selector.add_argument('--whitelist_file',
+ list_selector.add_argument('--whitelist-file', '--whitelist_file',
help="Path to a whitelist file, this file "
- "contains a seperate regex on each "
+ "contains a separate regex on each "
"newline.")
- list_selector.add_argument('--blacklist_file',
+ list_selector.add_argument('--blacklist-file', '--blacklist_file',
help='Path to a blacklist file, this file '
'contains a separate regex exclude on '
'each newline')
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index da7f426..0f868a9 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -21,13 +21,14 @@
Runtime Arguments
-----------------
-**--subunit, -s**: (Required) The path to the subunit file being parsed
+**--subunit, -s**: (Optional) The path to the subunit file being parsed,
+defaults to stdin
**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
stored in
-**--output-file, -o**: (Required) The path where the JSON output will be
-written to
+**--output-file, -o**: (Optional) The path where the JSON output will be
+written to. This contains more information than is present in stdout.
**--ports, -p**: (Optional) The path to a JSON file describing the ports being
used by different services
@@ -35,13 +36,14 @@
Usage
-----
-subunit-describe-calls will take in a file path via the --subunit parameter
-which contains either a subunit v1 or v2 stream. This is then parsed checking
-for details contained in the file_bytes of the --non-subunit-name parameter
-(the default is pythonlogging which is what Tempest uses to store logs). By
-default the OpenStack Kilo release port defaults (http://bit.ly/22jpF5P)
-are used unless a file is provided via the --ports option. The resulting output
-is dumped in JSON output to the path provided in the --output-file option.
+subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a
+file path which contains either a subunit v1 or v2 stream passed via the
+--subunit parameter. This is then parsed checking for details contained in the
+file_bytes of the --non-subunit-name parameter (the default is pythonlogging
+which is what Tempest uses to store logs). By default the OpenStack Kilo
+release port defaults (http://bit.ly/22jpF5P) are used unless a file is
+provided via the --ports option. The resulting output is dumped in JSON output
+to the path provided in the --output-file option.
Ports file JSON structure
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -64,7 +66,11 @@
"verb": "HTTP Verb",
"service": "Name of the service",
"url": "A shortened version of the URL called",
- "status_code": "The status code of the response"
+ "status_code": "The status code of the response",
+ "request_headers": "The headers of the request",
+ "request_body": "The body of the request",
+ "response_headers": "The headers of the response",
+ "response_body": "The body of the response"
}
]
}
@@ -75,6 +81,7 @@
import json
import os
import re
+import sys
import subunit
import testtools
@@ -91,6 +98,9 @@
'(?P<verb>\w*) (?P<url>.*) .*')
port_re = re.compile(r'.*:(?P<port>\d+).*')
path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
+ request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
+ response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
+ body_re = re.compile(r'.*Body: (?P<body>.*)')
# Based on mitaka defaults:
# http://docs.openstack.org/mitaka/config-reference/
@@ -151,15 +161,46 @@
calls = []
for _, detail in details.items():
+ in_request = False
+ in_response = False
+ current_call = {}
for line in detail.as_text().split("\n"):
- match = self.url_re.match(line)
- if match is not None:
- calls.append({
- "name": match.group("name"),
- "verb": match.group("verb"),
- "status_code": match.group("code"),
- "service": self.get_service(match.group("url")),
- "url": self.url_path(match.group("url"))})
+ url_match = self.url_re.match(line)
+ request_match = self.request_re.match(line)
+ response_match = self.response_re.match(line)
+ body_match = self.body_re.match(line)
+
+ if url_match is not None:
+ if current_call != {}:
+ calls.append(current_call.copy())
+ current_call = {}
+ in_request, in_response = False, False
+ current_call.update({
+ "name": url_match.group("name"),
+ "verb": url_match.group("verb"),
+ "status_code": url_match.group("code"),
+ "service": self.get_service(url_match.group("url")),
+ "url": self.url_path(url_match.group("url"))})
+ elif request_match is not None:
+ in_request, in_response = True, False
+ current_call.update(
+ {"request_headers": request_match.group("headers")})
+ elif in_request and body_match is not None:
+ in_request = False
+ current_call.update(
+ {"request_body": body_match.group(
+ "body")})
+ elif response_match is not None:
+ in_request, in_response = False, True
+ current_call.update(
+ {"response_headers": response_match.group(
+ "headers")})
+ elif in_response and body_match is not None:
+ in_response = False
+ current_call.update(
+ {"response_body": body_match.group("body")})
+ if current_call != {}:
+ calls.append(current_call.copy())
return calls
@@ -203,11 +244,12 @@
desc = "Outputs all HTTP calls a given test made that were logged."
super(ArgumentParser, self).__init__(description=desc)
- self.prog = "Argument Parser"
+ self.prog = "subunit-describe-calls"
self.add_argument(
- "-s", "--subunit", metavar="<subunit file>", required=True,
- default=None, help="The path to the subunit output file.")
+ "-s", "--subunit", metavar="<subunit file>",
+ nargs="?", type=argparse.FileType('rb'), default=sys.stdin,
+ help="The path to the subunit output file.")
self.add_argument(
"-n", "--non-subunit-name", metavar="<non subunit name>",
@@ -216,19 +258,18 @@
self.add_argument(
"-o", "--output-file", metavar="<output file>", default=None,
- help="The output file name for the json.", required=True)
+ help="The output file name for the json.")
self.add_argument(
"-p", "--ports", metavar="<ports file>", default=None,
help="A JSON file describing the ports for each service.")
-def parse(subunit_file, non_subunit_name, ports):
+def parse(stream, non_subunit_name, ports):
if ports is not None and os.path.exists(ports):
ports = json.loads(open(ports).read())
url_parser = UrlParser(ports)
- stream = open(subunit_file, 'rb')
suite = subunit.ByteStreamToStreamResult(
stream, non_subunit_name=non_subunit_name)
result = testtools.StreamToExtendedDecorator(url_parser)
@@ -248,8 +289,21 @@
def output(url_parser, output_file):
- with open(output_file, "w") as outfile:
- outfile.write(json.dumps(url_parser.test_logs))
+ if output_file is not None:
+ with open(output_file, "w") as outfile:
+ outfile.write(json.dumps(url_parser.test_logs))
+ return
+
+ for test_name, items in url_parser.test_logs.iteritems():
+ sys.stdout.write('{0}\n'.format(test_name))
+ if not items:
+ sys.stdout.write('\n')
+ continue
+ for item in items:
+ sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
+ item.get('status_code'), item.get('verb'),
+ item.get('service'), item.get('url')))
+ sys.stdout.write('\n')
def entry_point():
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 77b88f9..c36c9be 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -46,7 +46,7 @@
conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir)
conf_file = os.environ.get('TEMPEST_CONFIG', default_config_file)
path = os.path.join(conf_dir, conf_file)
- fd = open(path, 'rw')
+ fd = open(path, 'r+')
return fd
@@ -147,6 +147,10 @@
contains_version('v2.', versions)):
print_and_or_update('api_v2', 'volume-feature-enabled',
not CONF.volume_feature_enabled.api_v2, update)
+ if (CONF.volume_feature_enabled.api_v3 !=
+ contains_version('v3.', versions)):
+ print_and_or_update('api_v3', 'volume-feature-enabled',
+ not CONF.volume_feature_enabled.api_v3, update)
def verify_api_versions(os, service, update):
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index cc82284..b36cf4e 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -53,13 +53,11 @@
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
@@ -185,25 +183,35 @@
subparsers = parser.add_subparsers()
- list_parser = subparsers.add_parser('list')
+ list_parser = subparsers.add_parser(
+ 'list', help='Outputs the name and path of all known tempest '
+ 'workspaces')
list_parser.set_defaults(list=True)
- register_parser = subparsers.add_parser('register')
+ register_parser = subparsers.add_parser(
+ 'register', help='Registers a new tempest workspace via a given '
+ '--name and --path')
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 = subparsers.add_parser(
+ 'rename', help='Renames a tempest workspace from --old-name to '
+ '--new-name')
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 = subparsers.add_parser(
+ 'move', help='Changes the path of a given tempest workspace '
+ '--name to --path')
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 = subparsers.add_parser(
+ 'remove', help='Deletes the entry for a given tempest workspace '
+ '--name')
remove_parser.add_argument('--name', required=True)
remove_parser.set_defaults(remove=True)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index c290b57..8e9f0b0 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -40,11 +40,20 @@
:param clients: Client manager which provides OpenStack Tempest clients.
:param validatable: Whether the server will be pingable or sshable.
:param validation_resources: Resources created for the connection to the
- server. Include a keypair, a security group and an IP.
+ server. Include a keypair, a security group and an IP.
:param tenant_network: Tenant network to be used for creating a server.
:param wait_until: Server status to wait for the server to reach after
- its creation.
+ its creation.
:param volume_backed: Whether the instance is volume backed or not.
+ :param name: Name of the server to be provisioned. If not defined a random
+ string ending with '-instance' will be generated.
+ :param flavor: Flavor of the server to be provisioned. If not defined,
+ CONF.compute.flavor_ref will be used instead.
+ :param image_id: ID of the image to be used to provision the server. If not
+ defined, CONF.compute.image_ref will be used instead.
+ :param delete_vol_on_termination: Controls whether the backing volume
+ should be deleted when the server is deleted. Only applies to volume
+ backed servers.
:returns: a tuple
"""
@@ -92,13 +101,14 @@
wait_until = 'ACTIVE'
if volume_backed:
- volume_name = data_utils.rand_name('volume')
+ volume_name = data_utils.rand_name(__name__ + '-volume')
volumes_client = clients.volumes_v2_client
if CONF.volume_feature_enabled.api_v1:
volumes_client = clients.volumes_client
volume = volumes_client.create_volume(
display_name=volume_name,
- imageRef=image_id)
+ imageRef=image_id,
+ size=CONF.volume.volume_size)
waiters.wait_for_volume_status(volumes_client,
volume['volume']['id'], 'available')
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 2ca9f40..ad968f1 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -17,7 +17,7 @@
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
-from tempest.services.identity.v2.json import identity_client as v2_identity
+from tempest.lib.services.identity.v2 import identity_client as v2_identity
LOG = logging.getLogger(__name__)
@@ -40,8 +40,10 @@
self.roles_client = roles_client
def create_user(self, username, password, project, email):
- params = self._create_user_params(username, password,
- project['id'], email)
+ params = {'name': username,
+ 'password': password,
+ self.project_id_param: project['id'],
+ 'email': email}
user = self.users_client.create_user(**params)
if 'user' in user:
user = user['user']
@@ -72,7 +74,9 @@
msg = 'No "%s" role found' % role_name
raise lib_exc.NotFound(msg)
try:
- self._assign_user_role(project, user, role)
+ self.roles_client.create_user_role_on_project(project['id'],
+ user['id'],
+ role['id'])
except lib_exc.Conflict:
LOG.debug("Role %s already assigned on project %s for user %s" % (
role['id'], project['id'], user['id']))
@@ -94,6 +98,7 @@
class V2CredsClient(CredsClient):
+ project_id_param = 'tenantId'
def __init__(self, identity_client, projects_client, users_client,
roles_client):
@@ -102,13 +107,6 @@
users_client,
roles_client)
- def _create_user_params(self, username, password, project_id, email):
- params = {'name': username,
- 'password': password,
- 'tenantId': project_id,
- 'email': email}
- return params
-
def create_project(self, name, description):
tenant = self.projects_client.create_tenant(
name=name, description=description)['tenant']
@@ -128,13 +126,9 @@
tenant_name=project['name'], tenant_id=project['id'],
password=password)
- def _assign_user_role(self, project, user, role):
- self.roles_client.create_user_role_on_project(project['id'],
- user['id'],
- role['id'])
-
class V3CredsClient(CredsClient):
+ project_id_param = 'project_id'
def __init__(self, identity_client, projects_client, users_client,
roles_client, domains_client, domain_name):
@@ -152,13 +146,6 @@
msg = "Requested domain %s could not be found" % domain_name
raise lib_exc.InvalidCredentials(msg)
- def _create_user_params(self, username, password, project_id, email):
- params = {'user_name': username,
- 'password': password,
- 'project_id': project_id,
- 'email': email}
- return params
-
def create_project(self, name, description):
project = self.projects_client.create_project(
name=name, description=description,
@@ -186,11 +173,6 @@
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
@@ -208,7 +190,7 @@
msg = 'No "%s" role found' % role_name
raise lib_exc.NotFound(msg)
try:
- self.roles_client.assign_user_role_on_domain(
+ self.roles_client.create_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",
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index bf6c537..1b450ab 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -94,7 +94,7 @@
self.router)
def set_resources(self, **kwargs):
- for key in kwargs.keys():
+ for key in kwargs:
if hasattr(self, key):
setattr(self, key, kwargs[key])
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index 8ba33ed..b6ff241 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -28,7 +28,7 @@
checked in each test code.
"""
- def __init__(self, target, method):
+ def __init__(self, target, method, policies=None):
"""Initialization of ExistsAllResponseHeaders
param: target Account/Container/Object
@@ -36,14 +36,34 @@
"""
self.target = target
self.method = method
+ self.policies = policies or []
+
+ def _content_length_required(self, resp):
+ # Verify whether given HTTP response must contain content-length.
+ # Take into account the exceptions defined in RFC 7230.
+ if resp.status in range(100, 200) or resp.status == 204:
+ return False
+
+ return True
def match(self, actual):
"""Check headers
- param: actual HTTP response headers
+ param: actual HTTP response object containing headers and status
"""
- # Check common headers for all HTTP methods
- if 'content-length' not in actual:
+ # Check common headers for all HTTP methods.
+ #
+ # Please note that for 1xx and 204 responses Content-Length presence
+ # is not checked intensionally. According to RFC 7230 a server MUST
+ # NOT send the header in such responses. Thus, clients should not
+ # depend on this header. However, the standard does not require them
+ # to validate the server's behavior. We leverage that to not refuse
+ # any implementation violating it like Swift [1] or some versions of
+ # Ceph RadosGW [2].
+ # [1] https://bugs.launchpad.net/swift/+bug/1537811
+ # [2] http://tracker.ceph.com/issues/13582
+ if ('content-length' not in actual and
+ self._content_length_required(actual)):
return NonExistentHeader('content-length')
if 'content-type' not in actual:
return NonExistentHeader('content-type')
@@ -65,11 +85,63 @@
return NonExistentHeader('x-account-container-count')
if 'x-account-object-count' not in actual:
return NonExistentHeader('x-account-object-count')
+ if actual['x-account-container-count'] > 0:
+ acct_header = "x-account-storage-policy-"
+ matched_policy_count = 0
+
+ # Loop through the policies and look for account
+ # usage data. There should be at least 1 set
+ for policy in self.policies:
+ front_header = acct_header + policy['name'].lower()
+
+ usage_policies = [
+ front_header + '-bytes-used',
+ front_header + '-object-count',
+ front_header + '-container-count'
+ ]
+
+ # There should be 3 usage values for a give storage
+ # policy in an account bytes, object count, and
+ # container count
+ policy_hdrs = sum(1 for use_hdr in usage_policies
+ if use_hdr in actual)
+
+ # If there are less than 3 headers here then 1 is
+ # missing, let's figure out which one and report
+ if policy_hdrs == 3:
+ matched_policy_count = matched_policy_count + 1
+ else:
+ if policy_hdrs > 0 and policy_hdrs < 3:
+ for use_hdr in usage_policies:
+ if use_hdr not in actual:
+ return NonExistentHeader(use_hdr)
+
+ # Only flag an error if actual policies have been read and
+ # no usage has been found
+ if self.policies and matched_policy_count == 0:
+ return GenericError("No storage policy usage headers")
+
elif self.target == 'Container':
if 'x-container-bytes-used' not in actual:
return NonExistentHeader('x-container-bytes-used')
if 'x-container-object-count' not in actual:
return NonExistentHeader('x-container-object-count')
+ if 'x-storage-policy' not in actual:
+ return NonExistentHeader('x-storage-policy')
+ else:
+ policy_name = actual['x-storage-policy']
+
+ # loop through the policies and ensure that
+ # the value in the container header matches
+ # one of the storage policies
+ for policy in self.policies:
+ if policy['name'] == policy_name:
+ break
+ else:
+ # Ensure that there are actual policies stored
+ if self.policies:
+ return InvalidHeaderValue('x-storage-policy',
+ policy_name)
elif self.target == 'Object':
if 'etag' not in actual:
return NonExistentHeader('etag')
@@ -95,6 +167,19 @@
return None
+class GenericError(object):
+ """Informs an error message of a generic error during header evaluation"""
+
+ def __init__(self, body):
+ self.body = body
+
+ def describe(self):
+ return "%s" % self.body
+
+ def get_details(self):
+ return {}
+
+
class NonExistentHeader(object):
"""Informs an error message in the case of missing a certain header"""
@@ -108,6 +193,20 @@
return {}
+class InvalidHeaderValue(object):
+ """Informs an error message when a header contains a bad value"""
+
+ def __init__(self, header, value):
+ self.header = header
+ self.value = value
+
+ def describe(self):
+ return "InvalidValue (%s, %s)" % (self.header, self.value)
+
+ def get_details(self):
+ return {}
+
+
class AreAllWellFormatted(object):
"""Specific matcher to check the correctness of formats of values
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index c9b9db1..04c9645 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -401,9 +401,18 @@
except lib_exc.NotFound:
LOG.warning("user with name: %s not found for delete" %
creds.username)
+ # NOTE(zhufl): Only when neutron's security_group ext is
+ # enabled, _cleanup_default_secgroup will not raise error. But
+ # here cannot use test.is_extension_enabled for it will cause
+ # "circular dependency". So here just use try...except to
+ # ensure tenant deletion without big changes.
try:
if CONF.service_available.neutron:
self._cleanup_default_secgroup(creds.tenant_id)
+ except lib_exc.NotFound:
+ LOG.warning("failed to cleanup tenant %s's secgroup" %
+ creds.tenant_name)
+ try:
self.creds_client.delete_project(creds.tenant_id)
except lib_exc.NotFound:
LOG.warning("tenant with name: %s not found for delete" %
diff --git a/tempest/common/utils/net_utils.py b/tempest/common/utils/net_utils.py
index d98fb32..f0d3da3 100644
--- a/tempest/common/utils/net_utils.py
+++ b/tempest/common/utils/net_utils.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import itertools
import netaddr
from tempest.lib import exceptions as lib_exc
@@ -38,11 +37,17 @@
for fixed_ip in port.get('fixed_ips'):
alloc_set.add(fixed_ip['ip_address'])
+ # exclude gateway_ip of subnet
+ gateway_ip = subnet['subnet']['gateway_ip']
+ if gateway_ip:
+ alloc_set.add(gateway_ip)
+
av_set = subnet_set - alloc_set
- ip_list = [str(ip) for ip in itertools.islice(av_set, count)]
-
- if len(ip_list) != count:
- msg = "Insufficient IP addresses available"
- raise lib_exc.BadRequest(message=msg)
-
- return ip_list
+ addrs = []
+ for cidr in reversed(av_set.iter_cidrs()):
+ for ip in reversed(cidr):
+ addrs.append(str(ip))
+ if len(addrs) == count:
+ return addrs
+ msg = "Insufficient IP addresses available"
+ raise lib_exc.BadRequest(message=msg)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index df08e30..9d307ee 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -53,9 +53,7 @@
return
# NOTE(afazekas): The instance is in "ready for action state"
# when no task in progress
- # NOTE(afazekas): Converted to string because of the XML
- # responses
- if str(task_state) == "None":
+ if task_state is None:
# without state api extension 3 sec usually enough
time.sleep(CONF.compute.ready_wait)
return
@@ -141,7 +139,7 @@
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.
+ # which is not the case with Glance image client.
if 'image' in image:
image = image['image']
diff --git a/tempest/config.py b/tempest/config.py
index edd04eb..f12e34e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -26,6 +26,7 @@
import testtools
from tempest.lib import exceptions
+from tempest.lib.services import clients
from tempest.test_discover import plugins
@@ -173,6 +174,16 @@
"a domain scoped token to use admin APIs")
]
+service_clients_group = cfg.OptGroup(name='service-clients',
+ title="Service Clients Options")
+
+ServiceClientsGroup = [
+ cfg.IntOpt('http_timeout',
+ default=60,
+ help='Timeout in seconds to wait for the http request to '
+ 'return'),
+]
+
identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',
title='Enabled Identity Features')
@@ -365,7 +376,8 @@
default=True,
help='Enables returning of the instance password by the '
'relevant server API calls such as create, rebuild '
- 'or rescue.'),
+ 'or rescue. This configuration value should be same as '
+ 'nova.conf: DEFAULT.enable_instance_password'),
cfg.BoolOpt('interface_attach',
default=True,
help='Does the test environment support dynamic network '
@@ -375,8 +387,9 @@
help='Does the test environment support creating snapshot '
'images of running instances?'),
cfg.BoolOpt('nova_cert',
- default=True,
- help='Does the test environment have the nova cert running?'),
+ default=False,
+ help='Does the test environment have the nova cert running?',
+ deprecated_for_removal=True),
cfg.BoolOpt('personality',
default=False,
help='Does the test environment support server personality'),
@@ -497,7 +510,7 @@
default=False,
help="Whether project networks can be reached directly from "
"the test client. This must be set to True when the "
- "'fixed' ssh_connect_method is selected."),
+ "'fixed' connect_method is selected."),
cfg.StrOpt('public_network_id',
default="",
help="Id of the public network that provides external "
@@ -648,7 +661,7 @@
cfg.StrOpt('network_for_ssh',
default='public',
help="Network used for SSH connections. Ignored if "
- "connect_method=floating or run_validation=false.",
+ "connect_method=floating.",
deprecated_opts=[cfg.DeprecatedOpt('network_for_ssh',
group='compute')]),
]
@@ -1125,6 +1138,7 @@
(compute_group, ComputeGroup),
(compute_features_group, ComputeFeaturesGroup),
(identity_group, IdentityGroup),
+ (service_clients_group, ServiceClientsGroup),
(identity_feature_group, IdentityFeatureGroup),
(image_group, ImageGroup),
(image_feature_group, ImageFeaturesGroup),
@@ -1190,6 +1204,7 @@
self.compute = _CONF.compute
self.compute_feature_enabled = _CONF['compute-feature-enabled']
self.identity = _CONF.identity
+ self.service_clients = _CONF['service-clients']
self.identity_feature_enabled = _CONF['identity-feature-enabled']
self.image = _CONF.image
self.image_feature_enabled = _CONF['image-feature-enabled']
@@ -1281,6 +1296,15 @@
lockutils.set_defaults(lock_dir)
self._config = TempestConfigPrivate(config_path=self._path)
+ # Pushing tempest internal service client configuration to the
+ # service clients register. Doing this in the config module ensures
+ # that the configuration is available by the time we register the
+ # service clients.
+ # NOTE(andreaf) This has to be done at the time the first
+ # attribute is accessed, to ensure all plugins have been already
+ # loaded, options registered, and _config is set.
+ _register_tempest_service_clients()
+
return getattr(self._config, attr)
def set_config_path(self, path):
@@ -1378,6 +1402,7 @@
* `disable_ssl_certificate_validation`
* `ca_certs`
* `trace_requests`
+ * `http_timeout`
The dict returned by this does not fit a few service clients:
@@ -1399,7 +1424,8 @@
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests
+ 'trace_requests': CONF.debug.trace_requests,
+ 'http_timeout': CONF.service_clients.http_timeout
}
if service_client_name is None:
@@ -1438,3 +1464,29 @@
# Set service
_parameters['service'] = getattr(options, 'catalog_type')
return _parameters
+
+
+def _register_tempest_service_clients():
+ # Register tempest own service clients using the same mechanism used
+ # for external plugins.
+ # The configuration data is pushed to the registry so that automatic
+ # configuration of tempest own service clients is possible both for
+ # tempest as well as for the plugins.
+ service_clients = clients.tempest_modules()
+ registry = clients.ClientsRegistry()
+ all_clients = []
+ for service_client in service_clients:
+ module = service_clients[service_client]
+ configs = service_client.split('.')[0]
+ service_client_data = dict(
+ name=service_client.replace('.', '_'),
+ service_version=service_client,
+ module_path=module.__name__,
+ client_names=module.__all__,
+ **service_client_config(configs)
+ )
+ all_clients.append(service_client_data)
+ # NOTE(andreaf) Internal service clients do not actually belong
+ # to a plugin, so using '__tempest__' to indicate a virtual plugin
+ # which holds internal service clients.
+ registry.register_service_client('__tempest__', all_clients)
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index f534f30..272f6e3 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -63,14 +63,15 @@
class ServerUnreachable(exceptions.TempestException):
- message = "The server is not reachable via the configured network"
+ message = ("Server %(server_id)s is not reachable via "
+ "the configured network")
# NOTE(andreaf) This exception is added here to facilitate the migration
# of get_network_from_name and preprov_creds to tempest.lib, and it should
# be migrated along with them
class InvalidTestResource(exceptions.TempestException):
- message = "%(name) is not a valid %(type), or the name is ambiguous"
+ message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
class RFCViolation(exceptions.RestClientException):
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/api_schema/response/compute/v2_16/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_16/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
new file mode 100644
index 0000000..6868110
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -0,0 +1,160 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_9 import servers
+
+# Compute microversion 2.16:
+# 1. New attributes in 'server' dict.
+# 'host_status'
+
+server_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6,
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'config_drive': {'type': 'string'},
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'delete_on_termination': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ },
+ },
+ 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']},
+ 'locked': {'type': 'boolean'},
+ # NOTE(gmann): new attributes in version 2.16
+ 'host_status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index 883839e..05cc32c 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -15,15 +15,18 @@
import copy
from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21
-from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
+from tempest.lib.api_schema.response.compute.v2_16 import servers \
+ as serversv216
-get_server = copy.deepcopy(serversv29.get_server)
+list_servers = copy.deepcopy(serversv216.list_servers)
+
+get_server = copy.deepcopy(serversv216.get_server)
get_server['response_body']['properties']['server'][
'properties'].update({'description': {'type': ['string', 'null']}})
get_server['response_body']['properties']['server'][
'required'].append('description')
-list_servers_detail = copy.deepcopy(serversv29.list_servers_detail)
+list_servers_detail = copy.deepcopy(serversv216.list_servers_detail)
list_servers_detail['response_body']['properties']['servers']['items'][
'properties'].update({'description': {'type': ['string', 'null']}})
list_servers_detail['response_body']['properties']['servers']['items'][
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/api_schema/response/compute/v2_23/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_23/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_23/migrations.py b/tempest/lib/api_schema/response/compute/v2_23/migrations.py
new file mode 100644
index 0000000..3cd0f6e
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_23/migrations.py
@@ -0,0 +1,62 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+# Compute microversion 2.23:
+# New attributes in 'migrations' list.
+# 'migration_type'
+# 'links'
+
+list_migrations = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'migrations': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'integer'},
+ 'status': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'source_node': {'type': ['string', 'null']},
+ 'source_compute': {'type': ['string', 'null']},
+ 'dest_node': {'type': ['string', 'null']},
+ 'dest_compute': {'type': ['string', 'null']},
+ 'dest_host': {'type': ['string', 'null']},
+ 'old_instance_type_id': {'type': ['integer', 'null']},
+ 'new_instance_type_id': {'type': ['integer', 'null']},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ # New attributes in version 2.23
+ 'migration_type': {'type': ['string', 'null']},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'id', 'status', 'instance_uuid', 'source_node',
+ 'source_compute', 'dest_node', 'dest_compute',
+ 'dest_host', 'old_instance_type_id',
+ 'new_instance_type_id', 'created_at', 'updated_at',
+ 'migration_type'
+ ]
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['migrations']
+ }
+}
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/api_schema/response/compute/v2_26/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_26/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
new file mode 100644
index 0000000..bc5d18e
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -0,0 +1,47 @@
+# Copyright 2016 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as servers21
+from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219
+
+# The 2.26 microversion changes the server GET and (detailed) LIST responses to
+# include the server 'tags' which is just a list of strings.
+
+tag_items = {
+ 'type': 'array',
+ 'maxItems': 50,
+ 'items': {
+ 'type': 'string',
+ 'pattern': '^[^,/]*$',
+ 'maxLength': 60
+ }
+}
+
+get_server = copy.deepcopy(servers219.get_server)
+get_server['response_body']['properties']['server'][
+ 'properties'].update({'tags': tag_items})
+get_server['response_body']['properties']['server'][
+ 'required'].append('tags')
+
+list_servers_detail = copy.deepcopy(servers219.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({'tags': tag_items})
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'required'].append('tags')
+
+# list response schema wasn't changed for v2.26 so use v2.1
+
+list_servers = copy.deepcopy(servers21.list_servers)
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/api_schema/response/compute/v2_3/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_3/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
new file mode 100644
index 0000000..ee16333
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -0,0 +1,166 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_1 import servers
+
+# Compute microversion 2.3:
+# 1. New attributes in 'os-extended-volumes:volumes_attached' dict.
+# 'delete_on_termination'
+# 2. New attributes in 'server' dict.
+# 'OS-EXT-SRV-ATTR:reservation_id'
+# 'OS-EXT-SRV-ATTR:launch_index'
+# 'OS-EXT-SRV-ATTR:kernel_id'
+# 'OS-EXT-SRV-ATTR:ramdisk_id'
+# 'OS-EXT-SRV-ATTR:hostname'
+# 'OS-EXT-SRV-ATTR:root_device_name'
+# 'OS-EXT-SRV-ATTR:user_data'
+
+server_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6,
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'config_drive': {'type': 'string'},
+ # NOTE(gmann): new attributes in version 2.3
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'delete_on_termination': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ },
+ },
+ 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index e9b7249..470190c 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,7 +14,9 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers
+from tempest.lib.api_schema.response.compute.v2_3 import servers
+
+list_servers = copy.deepcopy(servers.list_servers)
get_server = copy.deepcopy(servers.get_server)
get_server['response_body']['properties']['server'][
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 425758f..83aa405 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -260,11 +260,13 @@
def __init__(self, credentials, auth_url,
disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None, scope='project'):
+ ca_certs=None, trace_requests=None, scope='project',
+ http_timeout=None):
super(KeystoneAuthProvider, self).__init__(credentials, scope)
self.dscv = disable_ssl_certificate_validation
self.ca_certs = ca_certs
self.trace_requests = trace_requests
+ self.http_timeout = http_timeout
self.auth_url = auth_url
self.auth_client = self._auth_client(auth_url)
@@ -342,7 +344,8 @@
def _auth_client(self, auth_url):
return json_v2id.TokenClient(
auth_url, disable_ssl_certificate_validation=self.dscv,
- ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests,
+ http_timeout=self.http_timeout)
def _auth_params(self):
"""Auth parameters to be passed to the token request
@@ -429,7 +432,8 @@
def _auth_client(self, auth_url):
return json_v3id.V3TokenClient(
auth_url, disable_ssl_certificate_validation=self.dscv,
- ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests,
+ http_timeout=self.http_timeout)
def _auth_params(self):
"""Auth parameters to be passed to the token request
@@ -595,7 +599,7 @@
def get_credentials(auth_url, fill_in=True, identity_version='v2',
disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None, **kwargs):
+ trace_requests=None, http_timeout=None, **kwargs):
"""Builds a credentials object based on the configured auth_version
:param auth_url (string): Full URI of the OpenStack Identity API(Keystone)
@@ -611,6 +615,8 @@
:param ca_certs: CA certificate bundle for validation of certificates
in SSL API requests to the auth system
:param trace_requests: trace in log API requests to the auth system
+ :param http_timeout: timeout in seconds to wait for the http request to
+ return
:param kwargs (dict): Dict of credential key/value pairs
Examples:
@@ -634,7 +640,8 @@
dscv = disable_ssl_certificate_validation
auth_provider = auth_provider_class(
creds, auth_url, disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs, trace_requests=trace_requests)
+ ca_certs=ca_certs, trace_requests=trace_requests,
+ http_timeout=http_timeout)
creds = auth_provider.fill_credentials()
return creds
@@ -665,7 +672,7 @@
msg = ('Cannot have conflicting values for %s and %s' %
(key1, key2))
raise exceptions.InvalidCredentials(msg)
- for key in attr.keys():
+ for key in attr:
if key in self.ATTRIBUTES:
setattr(self, key, attr[key])
else:
@@ -682,6 +689,10 @@
"""Credentials are equal if attributes in self.ATTRIBUTES are equal"""
return str(self) == str(other)
+ def __ne__(self, other):
+ """Contrary to the __eq__"""
+ return not self.__eq__(other)
+
def __getattr__(self, key):
# If an attribute is set, __getattr__ is not invoked
# If an attribute is not set, and it is a known one, return None
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index 227ac37..33a32ee 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -13,14 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import os
import fixtures
import testtools
-LOG = logging.getLogger(__name__)
-
class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase):
setUpClassCalled = False
@@ -45,7 +42,7 @@
def setUp(self):
super(BaseTestCase, self).setUp()
if not self.setUpClassCalled:
- raise RuntimeError("setUpClass does not calls the super's"
+ raise RuntimeError("setUpClass does not calls the super's "
"setUpClass in the "
+ self.__class__.__name__)
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
index f35b14c..d95aa46 100755
--- a/tempest/lib/cmd/skip_tracker.py
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -103,7 +103,7 @@
def get_results(result_dict):
results = []
- for bug_no in result_dict.keys():
+ for bug_no in result_dict:
for method in result_dict[bug_no]:
results.append((method, bug_no))
return results
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index dffc5f9..86ea26e 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -18,7 +18,7 @@
class ClosingHttp(urllib3.poolmanager.PoolManager):
def __init__(self, disable_ssl_certificate_validation=False,
- ca_certs=None):
+ ca_certs=None, timeout=None):
kwargs = {}
if disable_ssl_certificate_validation:
@@ -29,6 +29,9 @@
kwargs['cert_reqs'] = 'CERT_REQUIRED'
kwargs['ca_certs'] = ca_certs
+ if timeout:
+ kwargs['timeout'] = timeout
+
super(ClosingHttp, self).__init__(**kwargs)
def request(self, url, method, *args, **kwargs):
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 1b0f53a..8507f8a 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -64,8 +64,10 @@
certificate validation
:param str ca_certs: File containing the CA Bundle to use in verifying a
TLS server cert
- :param str trace_request: Regex to use for specifying logging the entirety
+ :param str trace_requests: Regex to use for specifying logging the entirety
of the request and response payload
+ :param str http_timeout: Timeout in seconds to wait for the http request to
+ return
"""
TYPE = "json"
@@ -78,7 +80,7 @@
endpoint_type='publicURL',
build_interval=1, build_timeout=60,
disable_ssl_certificate_validation=False, ca_certs=None,
- trace_requests='', name=None):
+ trace_requests='', name=None, http_timeout=None):
self.auth_provider = auth_provider
self.service = service
self.region = region
@@ -99,9 +101,13 @@
'vary', 'www-authenticate'))
dscv = disable_ssl_certificate_validation
self.http_obj = http.ClosingHttp(
- disable_ssl_certificate_validation=dscv, ca_certs=ca_certs)
+ disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
+ timeout=http_timeout)
def _get_type(self):
+ if self.TYPE != "json":
+ self.LOG.warning("Tempest has dropped XML support and the TYPE "
+ "became meaningless")
return self.TYPE
def get_headers(self, accept_type=None, send_type=None):
@@ -229,8 +235,8 @@
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."
+ assert_msg = ("This function only allowed to use for HTTP status "
+ "codes which explicitly defined in the RFC 7231 & 4918. "
"{0} is not a defined Success Code!"
).format(expected_code)
if isinstance(expected_code, list):
@@ -397,19 +403,14 @@
else:
return text
- def _log_request_start(self, method, req_url, req_headers=None,
- req_body=None):
- if req_headers is None:
- req_headers = {}
+ def _log_request_start(self, method, req_url):
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))
- def _log_request_full(self, method, req_url, resp,
- secs="", req_headers=None,
- req_body=None, resp_body=None,
- caller_name=None, extra=None):
+ def _log_request_full(self, resp, req_headers=None, req_body=None,
+ resp_body=None, extra=None):
if 'X-Auth-Token' in req_headers:
req_headers['X-Auth-Token'] = '<omitted>'
# A shallow copy is sufficient
@@ -455,8 +456,8 @@
# Also look everything at DEBUG if you want to filter this
# out, don't run at debug.
if self.LOG.isEnabledFor(real_logging.DEBUG):
- self._log_request_full(method, req_url, resp, secs, req_headers,
- req_body, resp_body, caller_name, extra)
+ self._log_request_full(resp, req_headers, req_body,
+ resp_body, extra)
def _parse_resp(self, body):
try:
@@ -892,11 +893,11 @@
cls=JSONSCHEMA_VALIDATOR,
format_checker=FORMAT_CHECKER)
except jsonschema.ValidationError as ex:
- msg = ("HTTP response body is invalid (%s)") % ex
+ msg = ("HTTP response body is invalid (%s)" % ex)
raise exceptions.InvalidHTTPResponseBody(msg)
else:
if body:
- msg = ("HTTP response body should not exist (%s)") % body
+ msg = ("HTTP response body should not exist (%s)" % body)
raise exceptions.InvalidHTTPResponseBody(msg)
# Check the header of a response
@@ -907,7 +908,7 @@
cls=JSONSCHEMA_VALIDATOR,
format_checker=FORMAT_CHECKER)
except jsonschema.ValidationError as ex:
- msg = ("HTTP response header is invalid (%s)") % ex
+ msg = ("HTTP response header is invalid (%s)" % ex)
raise exceptions.InvalidHTTPResponseHeader(msg)
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index a831dbd..c13f41a 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -77,7 +77,7 @@
self.username, self.host)
return ssh
except (EOFError,
- socket.error,
+ socket.error, socket.timeout,
paramiko.SSHException) as e:
if self._is_timed_out(_start_time):
LOG.exception("Failed to establish authenticated ssh"
@@ -121,7 +121,6 @@
channel.fileno() # Register event pipe
channel.exec_command(cmd)
channel.shutdown_write()
- exit_status = channel.recv_exit_status()
# If the executing host is linux-based, poll the channel
if self._can_system_poll():
@@ -162,6 +161,8 @@
out_data = out_data.decode(encoding)
err_data = err_data.decode(encoding)
+ exit_status = channel.recv_exit_status()
+
if 0 != exit_status:
raise exceptions.SSHExecCommandFailed(
command=cmd, exit_status=exit_status,
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 7fcec8c..70be40c 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -47,8 +47,8 @@
:param str name: The name that you want to include
:param str prefix: The prefix that you want to include
:return: a random name. The format is
- '<prefix>-<random number>-<name>-<random number>'.
- (e.g. 'prefixfoo-1308607012-namebar-154876201')
+ '<prefix>-<name>-<random number>'.
+ (e.g. 'prefixfoo-namebar-154876201')
:rtype: string
"""
randbits = str(random.randint(1, 0x7fffffff))
@@ -75,7 +75,7 @@
ascii_char = string.ascii_letters
digits = string.digits
digit = random.choice(string.digits)
- puncs = '~!@#$%^&*_=+'
+ puncs = '~!@#%^&*_=+'
punc = random.choice(puncs)
seed = ascii_char + digits + puncs
pre = upper + digit + punc
@@ -171,7 +171,7 @@
:return: size randomly bytes
:rtype: string
"""
- return ''.join([chr(random.randint(0, 255))
+ return b''.join([six.int2byte(random.randint(0, 255))
for i in range(size)])
@@ -204,5 +204,5 @@
# 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):
+ for i in range(0, len(sequence), chunksize):
yield sequence[i:i + chunksize]
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index 50a1a7d..3b28701 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -14,6 +14,7 @@
# under the License.
import inspect
import re
+import time
from oslo_log import log as logging
@@ -83,3 +84,24 @@
return func(*args, **kwargs)
except exceptions.NotFound:
pass
+
+
+def call_until_true(func, duration, sleep_for):
+ """Call the given function until it returns True (and return True)
+
+ or until the specified duration (in seconds) elapses (and return False).
+
+ :param func: A zero argument callable that returns True on success.
+ :param duration: The number of seconds for which to attempt a
+ successful call of the function.
+ :param sleep_for: The number of seconds to sleep after an unsuccessful
+ invocation of the function.
+ """
+ now = time.time()
+ timeout = now + duration
+ while now < timeout:
+ if func():
+ return True
+ time.sleep(sleep_for)
+ now = time.time()
+ return False
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 5ca78f9..e3f25e6 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -100,7 +100,7 @@
class OverLimit(ClientRestClientException):
- message = "Quota exceeded"
+ message = "Request entity is too large"
class ServerFault(ServerRestClientException):
@@ -229,3 +229,13 @@
class UnknownServiceClient(TempestException):
message = "Service clients named %(services)s are not known"
+
+
+class ServiceClientRegistrationException(TempestException):
+ message = ("Error registering module %(name)s in path %(module_path)s, "
+ "with service %(service_version)s and clients "
+ "%(client_names)s: %(detailed_error)s")
+
+
+class PluginRegistrationException(TempestException):
+ message = "Error registering plugin %(name)s: %(detailed_error)s"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
new file mode 100644
index 0000000..adf666b
--- /dev/null
+++ b/tempest/lib/services/clients.py
@@ -0,0 +1,447 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import importlib
+import inspect
+import logging
+
+from tempest.lib import auth
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions
+from tempest.lib.services import compute
+from tempest.lib.services import image
+from tempest.lib.services import network
+
+
+LOG = logging.getLogger(__name__)
+
+
+def tempest_modules():
+ """Dict of service client modules available in Tempest.
+
+ Provides a dict of stable service modules available in Tempest, with
+ ``service_version`` as key, and the module object as value.
+ """
+ return {
+ 'compute': compute,
+ 'image.v1': image.v1,
+ 'image.v2': image.v2,
+ 'network': network
+ }
+
+
+def _tempest_internal_modules():
+ # Set of unstable service clients available in Tempest
+ # NOTE(andreaf) This list will exists only as long the remain clients
+ # are migrated to tempest.lib, and it will then be deleted without
+ # deprecation or advance notice
+ return set(['identity.v2', 'identity.v3', 'object-storage', 'volume.v1',
+ 'volume.v2', 'volume.v3'])
+
+
+def available_modules():
+ """Set of service client modules available in Tempest and plugins
+
+ Set of stable service clients from Tempest and service clients exposed
+ by plugins. This set of available modules can be used for automatic
+ configuration.
+
+ :raise PluginRegistrationException: if a plugin exposes a service_version
+ already defined by Tempest or another plugin.
+
+ Examples:
+
+ >>> from tempest import config
+ >>> params = {}
+ >>> for service_version in available_modules():
+ >>> service = service_version.split('.')[0]
+ >>> params[service] = config.service_client_config(service)
+ >>> service_clients = ServiceClients(creds, identity_uri,
+ >>> client_parameters=params)
+ """
+ extra_service_versions = set([])
+ _tempest_modules = set(tempest_modules())
+ plugin_services = ClientsRegistry().get_service_clients()
+ for plugin_name in plugin_services:
+ plug_service_versions = set([x['service_version'] for x in
+ plugin_services[plugin_name]])
+ # If a plugin exposes a duplicate service_version raise an exception
+ if plug_service_versions:
+ if not plug_service_versions.isdisjoint(extra_service_versions):
+ detailed_error = (
+ 'Plugin %s is trying to register a service %s already '
+ 'claimed by another one' % (plugin_name,
+ extra_service_versions &
+ plug_service_versions))
+ raise exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error)
+ # NOTE(andreaf) Once all tempest clients are stable, the following
+ # if will have to be removed.
+ if not plug_service_versions.isdisjoint(
+ _tempest_internal_modules()):
+ detailed_error = (
+ 'Plugin %s is trying to register a service %s already '
+ 'claimed by a Tempest one' % (plugin_name,
+ _tempest_internal_modules() &
+ plug_service_versions))
+ raise exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error)
+ extra_service_versions |= plug_service_versions
+ return _tempest_modules | extra_service_versions
+
+
+@misc.singleton
+class ClientsRegistry(object):
+ """Registry of all service clients available from plugins"""
+
+ def __init__(self):
+ self._service_clients = {}
+
+ def register_service_client(self, plugin_name, service_client_data):
+ if plugin_name in self._service_clients:
+ detailed_error = 'Clients for plugin %s already registered'
+ raise exceptions.PluginRegistrationException(
+ name=plugin_name,
+ detailed_error=detailed_error % plugin_name)
+ self._service_clients[plugin_name] = service_client_data
+
+ def get_service_clients(self):
+ return self._service_clients
+
+
+class ClientsFactory(object):
+ """Builds service clients for a service client module
+
+ This class implements the logic of feeding service client parameters
+ to service clients from a specific module. It allows setting the
+ parameters once and obtaining new instances of the clients without the
+ need of passing any parameter.
+
+ ClientsFactory can be used directly, or consumed via the `ServiceClients`
+ class, which manages the authorization part.
+ """
+
+ def __init__(self, module_path, client_names, auth_provider, **kwargs):
+ """Initialises the client factory
+
+ :param module_path: Path to module that includes all service clients.
+ All service client classes must be exposed by a single module.
+ If they are separated in different modules, defining __all__
+ in the root module can help, similar to what is done by service
+ clients in tempest.
+ :param client_names: List or set of names of the service client
+ classes.
+ :param auth_provider: The auth provider used to initialise client.
+ :param kwargs: Parameters to be passed to all clients. Parameters
+ values can be overwritten when clients are initialised, but
+ parameters cannot be deleted.
+ :raise ImportError if the specified module_path cannot be imported
+
+ Example:
+
+ >>> # Get credentials and an auth_provider
+ >>> clients = ClientsFactory(
+ >>> module_path='my_service.my_service_clients',
+ >>> client_names=['ServiceClient1', 'ServiceClient2'],
+ >>> auth_provider=auth_provider,
+ >>> service='my_service',
+ >>> region='region1')
+ >>> my_api_client = clients.MyApiClient()
+ >>> my_api_client_region2 = clients.MyApiClient(region='region2')
+
+ """
+ # Import the module. If it's not importable, the raised exception
+ # provides good enough information about what happened
+ _module = importlib.import_module(module_path)
+ # If any of the classes is not in the module we fail
+ for class_name in client_names:
+ # TODO(andreaf) This always passes all parameters to all clients.
+ # In future to allow clients to specify the list of parameters
+ # that they accept based out of a list of standard ones.
+
+ # Obtain the class
+ klass = self._get_class(_module, class_name)
+ final_kwargs = copy.copy(kwargs)
+
+ # Set the function as an attribute of the factory
+ setattr(self, class_name, self._get_partial_class(
+ klass, auth_provider, final_kwargs))
+
+ def _get_partial_class(self, klass, auth_provider, kwargs):
+
+ # Define a function that returns a new class instance by
+ # combining default kwargs with extra ones
+ def partial_class(alias=None, **later_kwargs):
+ """Returns a callable the initialises a service client
+
+ Builds a callable that accepts kwargs, which are passed through
+ to the __init__ of the service client, along with a set of defaults
+ set in factory at factory __init__ time.
+ Original args in the service client can only be passed as kwargs.
+
+ It accepts one extra parameter 'alias' compared to the original
+ service client. When alias is provided, the returned callable will
+ also set an attribute called with a name defined in 'alias', which
+ contains the instance of the service client.
+
+ :param alias: str Name of the attribute set on the factory once
+ the callable is invoked which contains the initialised
+ service client. If None, no attribute is set.
+ :param later_kwargs: kwargs passed through to the service client
+ __init__ on top of defaults set at factory level.
+ """
+ kwargs.update(later_kwargs)
+ _client = klass(auth_provider=auth_provider, **kwargs)
+ if alias:
+ setattr(self, alias, _client)
+ return _client
+
+ return partial_class
+
+ @classmethod
+ def _get_class(cls, module, class_name):
+ klass = getattr(module, class_name, None)
+ if not klass:
+ msg = 'Invalid class name, %s is not found in %s'
+ raise AttributeError(msg % (class_name, module))
+ if not inspect.isclass(klass):
+ msg = 'Expected a class, got %s of type %s instead'
+ raise TypeError(msg % (klass, type(klass)))
+ return klass
+
+
+class ServiceClients(object):
+ """Service client provider class
+
+ The ServiceClients object provides a useful means for tests to access
+ service clients configured for a specified set of credentials.
+ It hides some of the complexity from the authorization and configuration
+ layers.
+
+ Examples:
+
+ >>> from tempest.lib.services import clients
+ >>> johndoe = cred_provider.get_creds_by_role(['johndoe'])
+ >>> johndoe_clients = clients.ServiceClients(johndoe,
+ >>> identity_uri)
+ >>> johndoe_servers = johndoe_clients.servers_client.list_servers()
+
+ """
+ # NOTE(andreaf) This class does not depend on tempest configuration
+ # and its meant for direct consumption by external clients such as tempest
+ # plugins. Tempest provides a wrapper class, `clients.Manager`, that
+ # initialises this class using values from tempest CONF object. The wrapper
+ # class should only be used by tests hosted in Tempest.
+
+ def __init__(self, credentials, identity_uri, region=None, scope='project',
+ disable_ssl_certificate_validation=True, ca_certs=None,
+ trace_requests='', client_parameters=None):
+ """Service Clients provider
+
+ Instantiate a `ServiceClients` object, from a set of credentials and an
+ identity URI. The identity version is inferred from the credentials
+ object. Optionally auth scope can be provided.
+
+ A few parameters can be given a value which is applied as default
+ for all service clients: region, dscv, ca_certs, trace_requests.
+
+ Parameters dscv, ca_certs and trace_requests all apply to the auth
+ provider as well as any service clients provided by this manager.
+
+ Any other client parameter must be set via client_parameters.
+ The list of available parameters is defined in the service clients
+ interfaces. For reference, most clients will accept 'region',
+ 'service', 'endpoint_type', 'build_timeout' and 'build_interval', which
+ are all inherited from RestClient.
+
+ The `config` module in Tempest exposes an helper function
+ `service_client_config` that can be used to extract from configuration
+ a dictionary ready to be injected in kwargs.
+
+ Exceptions are:
+ - Token clients for 'identity' have a very different interface
+ - Volume client for 'volume' accepts 'default_volume_size'
+ - Servers client from 'compute' accepts 'enable_instance_password'
+
+ Examples:
+
+ >>> identity_params = config.service_client_config('identity')
+ >>> params = {
+ >>> 'identity': identity_params,
+ >>> 'compute': {'region': 'region2'}}
+ >>> manager = lib_manager.Manager(
+ >>> my_creds, identity_uri, client_parameters=params)
+
+ :param credentials: An instance of `auth.Credentials`
+ :param identity_uri: URI of the identity API. This should be a
+ mandatory parameter, and it will so soon.
+ :param region: Default value of region for service clients.
+ :param scope: default scope for tokens produced by the auth provider
+ :param disable_ssl_certificate_validation: Applies to auth and to all
+ service clients.
+ :param ca_certs: Applies to auth and to all service clients.
+ :param trace_requests: Applies to auth and to all service clients.
+ :param client_parameters: Dictionary with parameters for service
+ clients. Keys of the dictionary are the service client service
+ name, as declared in `service_clients.available_modules()` except
+ for the version. Values are dictionaries of parameters that are
+ going to be passed to all clients in the service client module.
+
+ Examples:
+
+ >>> params_service_x = {'param_name': 'param_value'}
+ >>> client_parameters = { 'service_x': params_service_x }
+
+ >>> params_service_y = config.service_client_config('service_y')
+ >>> client_parameters['service_y'] = params_service_y
+
+ """
+ self._registered_services = set([])
+ self.credentials = credentials
+ self.identity_uri = identity_uri
+ if not identity_uri:
+ raise exceptions.InvalidCredentials(
+ 'ServiceClients requires a non-empty identity_uri.')
+ self.region = region
+ # Check if passed or default credentials are valid
+ if not self.credentials.is_valid():
+ raise exceptions.InvalidCredentials()
+ # Get the identity classes matching the provided credentials
+ # TODO(andreaf) Define a new interface in Credentials to get
+ # the API version from an instance
+ identity = [(k, auth.IDENTITY_VERSION[k][1]) for k in
+ auth.IDENTITY_VERSION.keys() if
+ isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
+ # Zero matches or more than one are both not valid.
+ if len(identity) != 1:
+ raise exceptions.InvalidCredentials()
+ self.auth_version, auth_provider_class = identity[0]
+ self.dscv = disable_ssl_certificate_validation
+ self.ca_certs = ca_certs
+ self.trace_requests = trace_requests
+ # Creates an auth provider for the credentials
+ self.auth_provider = auth_provider_class(
+ self.credentials, self.identity_uri, scope=scope,
+ disable_ssl_certificate_validation=self.dscv,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+ # Setup some defaults for client parameters of registered services
+ client_parameters = client_parameters or {}
+ self.parameters = {}
+ # Parameters are provided for unversioned services
+ all_modules = available_modules() | _tempest_internal_modules()
+ unversioned_services = set(
+ [x.split('.')[0] for x in all_modules])
+ for service in unversioned_services:
+ self.parameters[service] = self._setup_parameters(
+ client_parameters.pop(service, {}))
+ # Check that no client parameters was supplied for unregistered clients
+ if client_parameters:
+ raise exceptions.UnknownServiceClient(
+ services=list(client_parameters.keys()))
+
+ # Register service clients from the registry (__tempest__ and plugins)
+ clients_registry = ClientsRegistry()
+ plugin_service_clients = clients_registry.get_service_clients()
+ for plugin in plugin_service_clients:
+ service_clients = plugin_service_clients[plugin]
+ # Each plugin returns a list of service client parameters
+ for service_client in service_clients:
+ # NOTE(andreaf) If a plugin cannot register, stop the
+ # registration process, log some details to help
+ # troubleshooting, and re-raise
+ try:
+ self.register_service_client_module(**service_client)
+ except Exception:
+ LOG.exception(
+ 'Failed to register service client from plugin %s '
+ 'with parameters %s' % (plugin, service_client))
+ raise
+
+ def register_service_client_module(self, name, service_version,
+ module_path, client_names, **kwargs):
+ """Register a service client module
+
+ Initiates a client factory for the specified module, using this
+ class auth_provider, and accessible via a `name` attribute in the
+ service client.
+
+ :param name: Name used to access the client
+ :param service_version: Name of the service complete with version.
+ Used to track registered services. When a plugin implements it,
+ it can be used by other plugins to obtain their configuration.
+ :param module_path: Path to module that includes all service clients.
+ All service client classes must be exposed by a single module.
+ If they are separated in different modules, defining __all__
+ in the root module can help, similar to what is done by service
+ clients in tempest.
+ :param client_names: List or set of names of service client classes.
+ :param kwargs: Extra optional parameters to be passed to all clients.
+ ServiceClient provides defaults for region, dscv, ca_certs and
+ trace_requests.
+ :raise ServiceClientRegistrationException: if the provided name is
+ already in use or if service_version is already registered.
+ :raise ImportError: if module_path cannot be imported.
+ """
+ if hasattr(self, name):
+ using_name = getattr(self, name)
+ detailed_error = 'Module name already in use: %s' % using_name
+ raise exceptions.ServiceClientRegistrationException(
+ name=name, service_version=service_version,
+ module_path=module_path, client_names=client_names,
+ detailed_error=detailed_error)
+ if service_version in self.registered_services:
+ detailed_error = 'Service %s already registered.' % service_version
+ raise exceptions.ServiceClientRegistrationException(
+ name=name, service_version=service_version,
+ module_path=module_path, client_names=client_names,
+ detailed_error=detailed_error)
+ params = dict(region=self.region,
+ disable_ssl_certificate_validation=self.dscv,
+ ca_certs=self.ca_certs,
+ trace_requests=self.trace_requests)
+ params.update(kwargs)
+ # Instantiate the client factory
+ _factory = ClientsFactory(module_path=module_path,
+ client_names=client_names,
+ auth_provider=self.auth_provider,
+ **params)
+ # Adds the client factory to the service_client
+ setattr(self, name, _factory)
+ # Add the name of the new service in self.SERVICES for discovery
+ self._registered_services.add(service_version)
+
+ @property
+ def registered_services(self):
+ # NOTE(andreaf) Once all tempest modules are stable this needs to
+ # be updated to remove _tempest_internal_modules
+ return self._registered_services | _tempest_internal_modules()
+
+ def _setup_parameters(self, parameters):
+ """Setup default values for client parameters
+
+ Region by default is the region passed as an __init__ parameter.
+ Checks that no parameter for an unknown service is provided.
+ """
+ _parameters = {}
+ # Use region from __init__
+ if self.region:
+ _parameters['region'] = self.region
+ # Update defaults with specified parameters
+ _parameters.update(parameters)
+ # If any parameter is left, parameters for an unknown service were
+ # provided as input. Fail rather than ignore silently.
+ return _parameters
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
old mode 100644
new mode 100755
index 6d3a817..3f05d3b
--- a/tempest/lib/services/compute/agents_client.py
+++ b/tempest/lib/services/compute/agents_client.py
@@ -24,7 +24,11 @@
"""Tests Agents API"""
def list_agents(self, **params):
- """List all agent builds."""
+ """List all agent builds.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listbuilds
+ """
url = 'os-agents'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -46,7 +50,11 @@
return rest_client.ResponseBody(resp, body)
def delete_agent(self, agent_id):
- """Delete an existing agent build."""
+ """Delete an existing agent build.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteBuild
+ """
resp, body = self.delete("os-agents/%s" % agent_id)
self.validate_response(schema.delete_agent, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
old mode 100644
new mode 100755
index 5be8272..ae1700c
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -28,6 +28,11 @@
class FlavorsClient(base_compute_client.BaseComputeClient):
def list_flavors(self, detail=False, **params):
+ """Lists flavors.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listFlavors
+ """
url = 'flavors'
_schema = schema.list_flavors
@@ -43,6 +48,11 @@
return rest_client.ResponseBody(resp, body)
def show_flavor(self, flavor_id):
+ """Shows details for a flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showFlavor
+ """
resp, body = self.get("flavors/%s" % flavor_id)
body = json.loads(body)
self.validate_response(schema.create_get_flavor_details, resp, body)
@@ -67,7 +77,11 @@
return rest_client.ResponseBody(resp, body)
def delete_flavor(self, flavor_id):
- """Delete the given flavor."""
+ """Delete the given flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteFlavor
+ """
resp, body = self.delete("flavors/{0}".format(flavor_id))
self.validate_response(schema.delete_flavor, resp, body)
return rest_client.ResponseBody(resp, body)
@@ -102,7 +116,11 @@
return rest_client.ResponseBody(resp, body)
def list_flavor_extra_specs(self, flavor_id):
- """Get extra Specs details of the mentioned flavor."""
+ """Get extra Specs details of the mentioned flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listFlavorExtraSpecs
+ """
resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
body = json.loads(body)
self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
@@ -110,7 +128,11 @@
return rest_client.ResponseBody(resp, body)
def show_flavor_extra_spec(self, flavor_id, key):
- """Get extra Specs key-value of the mentioned flavor and key."""
+ """Get extra Specs key-value of the mentioned flavor and key.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showFlavorExtraSpec
+ """
resp, body = self.get('flavors/%s/os-extra_specs/%s' % (flavor_id,
key))
body = json.loads(body)
@@ -136,14 +158,22 @@
def unset_flavor_extra_spec(self, flavor_id, key): # noqa
# NOTE: This noqa is for passing T111 check and we cannot rename
# to keep backwards compatibility.
- """Unset extra Specs from the mentioned flavor."""
+ """Unset extra Specs from the mentioned flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteFlavorExtraSpec
+ """
resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
(flavor_id, key))
self.validate_response(schema.unset_flavor_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def list_flavor_access(self, flavor_id):
- """Get flavor access information given the flavor id."""
+ """Get flavor access information given the flavor id.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listFlavorAccess
+ """
resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id)
body = json.loads(body)
self.validate_response(schema_access.add_remove_list_flavor_access,
@@ -151,7 +181,11 @@
return rest_client.ResponseBody(resp, body)
def add_flavor_access(self, flavor_id, tenant_id):
- """Add flavor access for the specified tenant."""
+ """Add flavor access for the specified tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addFlavorAccess
+ """
post_body = {
'addTenantAccess': {
'tenant': tenant_id
@@ -165,7 +199,11 @@
return rest_client.ResponseBody(resp, body)
def remove_flavor_access(self, flavor_id, tenant_id):
- """Remove flavor access from the specified tenant."""
+ """Remove flavor access from the specified tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removeFlavorAccess
+ """
post_body = {
'removeTenantAccess': {
'tenant': tenant_id
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
old mode 100644
new mode 100755
index 03e4894..6922c48
--- a/tempest/lib/services/compute/floating_ips_client.py
+++ b/tempest/lib/services/compute/floating_ips_client.py
@@ -25,7 +25,11 @@
class FloatingIPsClient(base_compute_client.BaseComputeClient):
def list_floating_ips(self, **params):
- """Returns a list of all floating IPs filtered by any parameters."""
+ """Returns a list of all floating IPs filtered by any parameters.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listfloatingipsObject
+ """
url = 'os-floating-ips'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -36,7 +40,11 @@
return rest_client.ResponseBody(resp, body)
def show_floating_ip(self, floating_ip_id):
- """Get the details of a floating IP."""
+ """Get the details of a floating IP.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showFloatingIP
+ """
url = "os-floating-ips/%s" % floating_ip_id
resp, body = self.get(url)
body = json.loads(body)
@@ -57,7 +65,11 @@
return rest_client.ResponseBody(resp, body)
def delete_floating_ip(self, floating_ip_id):
- """Deletes the provided floating IP from the project."""
+ """Deletes the provided floating IP from the project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteFloatingIP
+ """
url = "os-floating-ips/%s" % floating_ip_id
resp, body = self.delete(url)
self.validate_response(schema.add_remove_floating_ip, resp, body)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index da8a61e..3dc3749 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -61,7 +61,6 @@
def show_image(self, image_id):
"""Return the details of a single image."""
resp, body = self.get("images/%s" % image_id)
- self.expected_success(200, resp.status)
body = json.loads(body)
self.validate_response(schema.get_image, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
old mode 100644
new mode 100755
index 7b8e6b2..2246739
--- a/tempest/lib/services/compute/keypairs_client.py
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -28,6 +28,11 @@
{'min': '2.2', 'max': None, 'schema': schemav22}]
def list_keypairs(self, **params):
+ """Lists keypairs that are associated with the account.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listKeypairs
+ """
url = 'os-keypairs'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -38,6 +43,11 @@
return rest_client.ResponseBody(resp, body)
def show_keypair(self, keypair_name, **params):
+ """Shows details for a keypair that is associated with the account.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showKeypair
+ """
url = "os-keypairs/%s" % keypair_name
if params:
url += '?%s' % urllib.urlencode(params)
@@ -61,6 +71,11 @@
return rest_client.ResponseBody(resp, body)
def delete_keypair(self, keypair_name, **params):
+ """Deletes a keypair.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteKeypair
+ """
url = "os-keypairs/%s" % keypair_name
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 62246d3..c3bdba7 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -16,11 +16,16 @@
from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import migrations as schema
+from tempest.lib.api_schema.response.compute.v2_23 import migrations \
+ as schemav223
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class MigrationsClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.22', 'schema': schema},
+ {'min': '2.23', 'max': None, 'schema': schemav223}]
def list_migrations(self, **params):
"""List all migrations.
@@ -35,5 +40,6 @@
resp, body = self.get(url)
body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_migrations, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
old mode 100644
new mode 100755
index 6b9c7e1..386c214
--- a/tempest/lib/services/compute/security_groups_client.py
+++ b/tempest/lib/services/compute/security_groups_client.py
@@ -26,7 +26,11 @@
class SecurityGroupsClient(base_compute_client.BaseComputeClient):
def list_security_groups(self, **params):
- """List all security groups for a user."""
+ """List all security groups for a user.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listSecGroups
+ """
url = 'os-security-groups'
if params:
@@ -38,7 +42,11 @@
return rest_client.ResponseBody(resp, body)
def show_security_group(self, security_group_id):
- """Get the details of a Security Group."""
+ """Get the details of a Security Group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showSecGroup
+ """
url = "os-security-groups/%s" % security_group_id
resp, body = self.get(url)
body = json.loads(body)
@@ -71,7 +79,11 @@
return rest_client.ResponseBody(resp, body)
def delete_security_group(self, security_group_id):
- """Delete the provided Security Group."""
+ """Delete the provided Security Group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteSecGroup
+ """
resp, body = self.delete(
'os-security-groups/%s' % security_group_id)
self.validate_response(schema.delete_security_group, resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
old mode 100644
new mode 100755
index 0d31ac7..8b22be0
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -20,7 +20,10 @@
from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.api_schema.response.compute.v2_16 import servers as schemav216
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
+from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
+from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -28,9 +31,12 @@
class ServersClient(base_compute_client.BaseComputeClient):
schema_versions_info = [
- {'min': None, 'max': '2.8', 'schema': schema},
- {'min': '2.9', 'max': '2.18', 'schema': schemav29},
- {'min': '2.19', 'max': None, 'schema': schemav219}]
+ {'min': None, 'max': '2.2', 'schema': schema},
+ {'min': '2.3', 'max': '2.8', 'schema': schemav23},
+ {'min': '2.9', 'max': '2.15', 'schema': schemav29},
+ {'min': '2.16', 'max': '2.18', 'schema': schemav216},
+ {'min': '2.19', 'max': '2.25', 'schema': schemav219},
+ {'min': '2.26', 'max': None, 'schema': schemav226}]
def __init__(self, auth_provider, service, region,
enable_instance_password=True, **kwargs):
@@ -99,7 +105,11 @@
return rest_client.ResponseBody(resp, body)
def show_server(self, server_id):
- """Get server details."""
+ """Get server details.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showServer
+ """
resp, body = self.get("servers/%s" % server_id)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
@@ -107,7 +117,11 @@
return rest_client.ResponseBody(resp, body)
def delete_server(self, server_id):
- """Delete server."""
+ """Delete server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteServer
+ """
resp, body = self.delete("servers/%s" % server_id)
self.validate_response(schema.delete_server, resp, body)
return rest_client.ResponseBody(resp, body)
@@ -137,7 +151,11 @@
return rest_client.ResponseBody(resp, body)
def list_addresses(self, server_id):
- """Lists all addresses for a server."""
+ """Lists all addresses for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#list-ips
+ """
resp, body = self.get("servers/%s/ips" % server_id)
body = json.loads(body)
self.validate_response(schema.list_addresses, resp, body)
@@ -260,12 +278,22 @@
return self.action(server_id, 'revertResize', **kwargs)
def list_server_metadata(self, server_id):
+ """Lists all metadata for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listServerMetadata
+ """
resp, body = self.get("servers/%s/metadata" % server_id)
body = json.loads(body)
self.validate_response(schema.list_server_metadata, resp, body)
return rest_client.ResponseBody(resp, body)
def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ """Sets one or more metadata items for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createServerMetadata
+ """
if no_metadata_field:
post_body = ""
else:
@@ -277,6 +305,11 @@
return rest_client.ResponseBody(resp, body)
def update_server_metadata(self, server_id, meta):
+ """Updates one or more metadata items for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateServerMetadata
+ """
post_body = json.dumps({'metadata': meta})
resp, body = self.post('servers/%s/metadata' % server_id,
post_body)
@@ -286,6 +319,11 @@
return rest_client.ResponseBody(resp, body)
def show_server_metadata_item(self, server_id, key):
+ """Shows details for a metadata item, by key, for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showServerMetadataItem
+ """
resp, body = self.get("servers/%s/metadata/%s" % (server_id, key))
body = json.loads(body)
self.validate_response(schema.set_show_server_metadata_item,
@@ -293,6 +331,11 @@
return rest_client.ResponseBody(resp, body)
def set_server_metadata_item(self, server_id, key, meta):
+ """Sets a metadata item, by key, for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#setServerMetadataItem
+ """
post_body = json.dumps({'meta': meta})
resp, body = self.put('servers/%s/metadata/%s' % (server_id, key),
post_body)
@@ -302,6 +345,11 @@
return rest_client.ResponseBody(resp, body)
def delete_server_metadata_item(self, server_id, key):
+ """Deletes a metadata item, by key, from a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteServerMetadataItem
+ """
resp, body = self.delete("servers/%s/metadata/%s" %
(server_id, key))
self.validate_response(schema.delete_server_metadata_item,
@@ -309,9 +357,19 @@
return rest_client.ResponseBody(resp, body)
def stop_server(self, server_id, **kwargs):
+ """Stops a running server and changes its status to SHUTOFF.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#stop
+ """
return self.action(server_id, 'os-stop', **kwargs)
def start_server(self, server_id, **kwargs):
+ """Starts a stopped server and changes its status to ACTIVE.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#start
+ """
return self.action(server_id, 'os-start', **kwargs)
def attach_volume(self, server_id, **kwargs):
@@ -337,14 +395,23 @@
return rest_client.ResponseBody(resp, body)
def detach_volume(self, server_id, volume_id): # noqa
- """Detaches a volume from a server instance."""
+ """Detaches a volume from a server instance.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteVolumeAttachment
+ """
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
(server_id, volume_id))
self.validate_response(schema.detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_attachment(self, server_id, volume_id):
- """Return details about the given volume attachment."""
+ """Return details about the given volume attachment.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#
+ getVolumeAttachmentDetails
+ """
resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
server_id, volume_id))
body = json.loads(body)
@@ -352,7 +419,11 @@
return rest_client.ResponseBody(resp, body)
def list_volume_attachments(self, server_id):
- """Returns the list of volume attachments for a given instance."""
+ """Returns the list of volume attachments for a given instance.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listVolumeAttachments
+ """
resp, body = self.get('servers/%s/os-volume_attachments' % (
server_id))
body = json.loads(body)
@@ -362,7 +433,8 @@
def add_security_group(self, server_id, **kwargs):
"""Add a security group to the server.
- Available params: TODO
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addSecurityGroup
"""
# TODO(oomichi): The api-site doesn't contain this API description.
# So the above should be changed to the api-site link after
@@ -373,7 +445,8 @@
def remove_security_group(self, server_id, **kwargs):
"""Remove a security group from the server.
- Available params: TODO
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removeSecurityGroup
"""
# TODO(oomichi): The api-site doesn't contain this API description.
# So the above should be changed to the api-site link after
@@ -503,7 +576,11 @@
return self.action(server_id, 'rescue', schema.rescue_server, **kwargs)
def unrescue_server(self, server_id):
- """Unrescue the provided server."""
+ """Unrescue the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unrescue
+ """
return self.action(server_id, 'unrescue')
def show_server_diagnostics(self, server_id):
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
old mode 100644
new mode 100755
index a190e5f..b6dbe28
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -25,6 +25,11 @@
class ServicesClient(base_compute_client.BaseComputeClient):
def list_services(self, **params):
+ """Lists all running Compute services for a tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listServices
+ """
url = 'os-services'
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
old mode 100644
new mode 100755
index 41d9af2..2787779
--- a/tempest/lib/services/compute/volumes_client.py
+++ b/tempest/lib/services/compute/volumes_client.py
@@ -25,7 +25,11 @@
class VolumesClient(base_compute_client.BaseComputeClient):
def list_volumes(self, detail=False, **params):
- """List all the volumes created."""
+ """List all the volumes created.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listVolumes
+ """
url = 'os-volumes'
if detail:
@@ -39,7 +43,11 @@
return rest_client.ResponseBody(resp, body)
def show_volume(self, volume_id):
- """Return the details of a single volume."""
+ """Return the details of a single volume.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#showVolume
+ """
url = "os-volumes/%s" % volume_id
resp, body = self.get(url)
body = json.loads(body)
@@ -59,7 +67,11 @@
return rest_client.ResponseBody(resp, body)
def delete_volume(self, volume_id):
- """Delete the Specified Volume."""
+ """Delete the Specified Volume.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#deleteVolume
+ """
resp, body = self.delete("os-volumes/%s" % volume_id)
self.validate_response(schema.delete_volume, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v2/__init__.py b/tempest/lib/services/identity/v2/__init__.py
index e69de29..b7d3c74 100644
--- a/tempest/lib/services/identity/v2/__init__.py
+++ b/tempest/lib/services/identity/v2/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
+from tempest.lib.services.identity.v2.identity_client import IdentityClient
+from tempest.lib.services.identity.v2.roles_client import RolesClient
+from tempest.lib.services.identity.v2.services_client import ServicesClient
+from tempest.lib.services.identity.v2.tenants_client import TenantsClient
+from tempest.lib.services.identity.v2.token_client import TokenClient
+from tempest.lib.services.identity.v2.users_client import UsersClient
+
+__all__ = ['EndpointsClient', 'IdentityClient', 'RolesClient',
+ 'ServicesClient', 'TenantsClient', 'TokenClient', 'UsersClient']
diff --git a/tempest/services/identity/v2/json/identity_client.py b/tempest/lib/services/identity/v2/identity_client.py
similarity index 100%
rename from tempest/services/identity/v2/json/identity_client.py
rename to tempest/lib/services/identity/v2/identity_client.py
diff --git a/tempest/lib/services/identity/v2/roles_client.py b/tempest/lib/services/identity/v2/roles_client.py
index 15c8834..aaa75f1 100644
--- a/tempest/lib/services/identity/v2/roles_client.py
+++ b/tempest/lib/services/identity/v2/roles_client.py
@@ -66,7 +66,7 @@
Available params: see http://developer.openstack.org/
api-ref-identity-v2-ext.html#deleteRole
"""
- resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
+ resp, body = self.delete('OS-KSADM/roles/%s' % role_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v2/services_client.py b/tempest/lib/services/identity/v2/services_client.py
old mode 100644
new mode 100755
index 4a63d56..c26d419
--- a/tempest/lib/services/identity/v2/services_client.py
+++ b/tempest/lib/services/identity/v2/services_client.py
@@ -22,7 +22,11 @@
api_version = "v2.0"
def create_service(self, **kwargs):
- """Create a service."""
+ """Create a service.
+
+ Available params: see http://developer.openstack.org/api-ref/identity/
+ v2-ext/?expanded=#create-service-admin-extension
+ """
post_body = json.dumps({'OS-KSADM:service': kwargs})
resp, body = self.post('/OS-KSADM/services', post_body)
self.expected_success(200, resp.status)
@@ -38,7 +42,11 @@
return rest_client.ResponseBody(resp, body)
def list_services(self, **params):
- """List Service - Returns Services."""
+ """List Service - Returns Services.
+
+ Available params: see http://developer.openstack.org/api-ref/identity/
+ v2-ext/?expanded=#list-services-admin-extension
+ """
url = '/OS-KSADM/services'
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/identity/v2/tenants_client.py b/tempest/lib/services/identity/v2/tenants_client.py
index 77ddaa5..f92c703 100644
--- a/tempest/lib/services/identity/v2/tenants_client.py
+++ b/tempest/lib/services/identity/v2/tenants_client.py
@@ -25,7 +25,8 @@
"""Create a tenant
Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#createTenant
+ api-ref/identity/v2-admin/index.html#
+ create-tenant
"""
post_body = json.dumps({'tenant': kwargs})
resp, body = self.post('tenants', post_body)
@@ -59,7 +60,8 @@
"""Returns tenants.
Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#admin-listTenants
+ api-ref/identity/v2-admin/index.html#
+ list-tenants-admin-endpoint
"""
url = 'tenants'
if params:
@@ -73,7 +75,8 @@
"""Updates a tenant.
Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#updateTenant
+ api-ref/identity/v2-admin/index.html#
+ update-tenant
"""
if 'id' not in kwargs:
kwargs['id'] = tenant_id
@@ -87,7 +90,8 @@
"""List users for a Tenant.
Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#listUsersForTenant
+ api-ref/identity/v2-admin/index.html#
+ list-users-on-a-tenant
"""
url = '/tenants/%s/users' % tenant_id
if params:
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
index 5716027..a5d7c86 100644
--- a/tempest/lib/services/identity/v2/token_client.py
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -22,11 +22,11 @@
class TokenClient(rest_client.RestClient):
def __init__(self, auth_url, disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ ca_certs=None, trace_requests=None, **kwargs):
dscv = disable_ssl_certificate_validation
super(TokenClient, self).__init__(
None, None, None, disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs, trace_requests=trace_requests)
+ ca_certs=ca_certs, trace_requests=trace_requests, **kwargs)
if auth_url is None:
raise exceptions.IdentityError("Couldn't determine auth_url")
diff --git a/tempest/lib/services/identity/v2/users_client.py b/tempest/lib/services/identity/v2/users_client.py
index 4ea17f9..2a266d9 100644
--- a/tempest/lib/services/identity/v2/users_client.py
+++ b/tempest/lib/services/identity/v2/users_client.py
@@ -23,7 +23,8 @@
"""Create a user.
Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-createUser
+ api-ref/identity/v2-admin/index.html#
+ create-user-admin-endpoint
"""
post_body = json.dumps({'user': kwargs})
resp, body = self.post('users', post_body)
@@ -35,7 +36,8 @@
"""Updates a user.
Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-updateUser
+ api-ref/identity/v2-admin/index.html#
+ update-user-admin-endpoint
"""
put_body = json.dumps({'user': kwargs})
resp, body = self.put('users/%s' % user_id, put_body)
@@ -68,7 +70,8 @@
"""Get the list of users.
Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-listUsers
+ api-ref/identity/v2-admin/index.html#
+ list-users-admin-endpoint
"""
url = "users"
if params:
diff --git a/tempest/services/identity/v3/json/credentials_client.py b/tempest/lib/services/identity/v3/credentials_client.py
similarity index 69%
rename from tempest/services/identity/v3/json/credentials_client.py
rename to tempest/lib/services/identity/v3/credentials_client.py
index 6ab94d0..c063cae 100644
--- a/tempest/services/identity/v3/json/credentials_client.py
+++ b/tempest/lib/services/identity/v3/credentials_client.py
@@ -14,10 +14,11 @@
# under the License.
"""
-http://developer.openstack.org/api-ref-identity-v3.html#credentials-v3
+http://developer.openstack.org/api-ref/identity/v3/index.html#credentials
"""
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -29,7 +30,7 @@
"""Creates a credential.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createCredential
+ api-ref/identity/v3/index.html#create-credential
"""
post_body = json.dumps({'credential': kwargs})
resp, body = self.post('credentials', post_body)
@@ -42,7 +43,7 @@
"""Updates a credential.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateCredential
+ api-ref/identity/v3/index.html#update-credential
"""
post_body = json.dumps({'credential': kwargs})
resp, body = self.patch('credentials/%s' % credential_id, post_body)
@@ -52,22 +53,38 @@
return rest_client.ResponseBody(resp, body)
def show_credential(self, credential_id):
- """To GET Details of a credential."""
+ """To GET Details of a credential.
+
+ For API details, see http://developer.openstack.org/
+ api-ref/identity/v3/index.html#
+ show-credential-details
+ """
resp, body = self.get('credentials/%s' % credential_id)
self.expected_success(200, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
return rest_client.ResponseBody(resp, body)
- def list_credentials(self):
- """Lists out all the available credentials."""
- resp, body = self.get('credentials')
+ def list_credentials(self, **params):
+ """Lists out all the available credentials.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-credentials
+ """
+ url = 'credentials'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def delete_credential(self, credential_id):
- """Deletes a credential."""
+ """Deletes a credential.
+
+ For API details, see http://developer.openstack.org/
+ api-ref/identity/v3/#delete-credential
+ """
resp, body = self.delete('credentials/%s' % credential_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
similarity index 93%
rename from tempest/services/identity/v3/json/endpoints_client.py
rename to tempest/lib/services/identity/v3/endpoints_client.py
index db30508..3bb3deb 100644
--- a/tempest/services/identity/v3/json/endpoints_client.py
+++ b/tempest/lib/services/identity/v3/endpoints_client.py
@@ -36,7 +36,7 @@
"""Create endpoint.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createEndpoint
+ api-ref/identity/v3/index.html#create-endpoint
"""
post_body = json.dumps({'endpoint': kwargs})
resp, body = self.post('endpoints', post_body)
@@ -48,7 +48,7 @@
"""Updates an endpoint with given parameters.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateEndpoint
+ api-ref/identity/v3/index.html#update-endpoint
"""
post_body = json.dumps({'endpoint': kwargs})
resp, body = self.patch('endpoints/%s' % endpoint_id, post_body)
diff --git a/tempest/services/identity/v3/json/groups_client.py b/tempest/lib/services/identity/v3/groups_client.py
similarity index 76%
rename from tempest/services/identity/v3/json/groups_client.py
rename to tempest/lib/services/identity/v3/groups_client.py
index 1a495f8..200cb43 100644
--- a/tempest/services/identity/v3/json/groups_client.py
+++ b/tempest/lib/services/identity/v3/groups_client.py
@@ -18,6 +18,7 @@
"""
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -29,7 +30,7 @@
"""Creates a group.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createGroup
+ api-ref/identity/v3/index.html#create-group
"""
post_body = json.dumps({'group': kwargs})
resp, body = self.post('groups', post_body)
@@ -44,9 +45,16 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
- def list_groups(self):
- """Lists the groups."""
- resp, body = self.get('groups')
+ def list_groups(self, **params):
+ """Lists the groups.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-groups
+ """
+ url = 'groups'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
@@ -55,7 +63,7 @@
"""Updates a group.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateGroup
+ api-ref/identity/v3/index.html#update-group
"""
post_body = json.dumps({'group': kwargs})
resp, body = self.patch('groups/%s' % group_id, post_body)
@@ -65,7 +73,7 @@
def delete_group(self, group_id):
"""Delete a group."""
- resp, body = self.delete('groups/%s' % str(group_id))
+ resp, body = self.delete('groups/%s' % group_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -76,9 +84,16 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_group_users(self, group_id):
- """List users in group."""
- resp, body = self.get('groups/%s/users' % group_id)
+ def list_group_users(self, group_id, **params):
+ """List users in group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-users-in-group
+ """
+ url = 'groups/%s/users' % group_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/lib/services/identity/v3/identity_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/identity_client.py
rename to tempest/lib/services/identity/v3/identity_client.py
diff --git a/tempest/lib/services/identity/v3/inherited_roles_client.py b/tempest/lib/services/identity/v3/inherited_roles_client.py
new file mode 100644
index 0000000..691c7fd
--- /dev/null
+++ b/tempest/lib/services/identity/v3/inherited_roles_client.py
@@ -0,0 +1,151 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class InheritedRolesClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_inherited_role_on_domains_user(
+ self, domain_id, user_id, role_id):
+ """Assigns a role to a user on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_inherited_role_from_user_on_domain(
+ self, domain_id, user_id, role_id):
+ """Revokes an inherited project role from a user on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_user_on_domain(
+ self, domain_id, user_id):
+ """Lists the inherited project roles on a domain for a user."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/users/%s/roles/inherited_to_projects"
+ % (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_inherited_project_role_on_domain(
+ self, domain_id, user_id, role_id):
+ """Checks whether a user has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def create_inherited_role_on_domains_group(
+ self, domain_id, group_id, role_id):
+ """Assigns a role to a group on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_inherited_role_from_group_on_domain(
+ self, domain_id, group_id, role_id):
+ """Revokes an inherited project role from a group on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_group_on_domain(
+ self, domain_id, group_id):
+ """Lists the inherited project roles on a domain for a group."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/groups/%s/roles/inherited_to_projects"
+ % (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_inherited_project_role_on_domain(
+ self, domain_id, group_id, role_id):
+ """Checks whether a group has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def create_inherited_role_on_projects_user(
+ self, project_id, user_id, role_id):
+ """Assigns a role to a user on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_inherited_role_from_user_on_project(
+ self, project_id, user_id, role_id):
+ """Revokes an inherited role from a user on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_has_flag_on_inherited_to_project(
+ self, project_id, user_id, role_id):
+ """Checks whether a user has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def create_inherited_role_on_projects_group(
+ self, project_id, group_id, role_id):
+ """Assigns a role to a group on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_inherited_role_from_group_on_project(
+ self, project_id, group_id, role_id):
+ """Revokes an inherited role from a group on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_has_flag_on_inherited_to_project(
+ self, project_id, group_id, role_id):
+ """Checks whether a group has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
similarity index 94%
rename from tempest/services/identity/v3/json/policies_client.py
rename to tempest/lib/services/identity/v3/policies_client.py
index f28db9a..4c1b4ae 100644
--- a/tempest/services/identity/v3/json/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -29,7 +29,7 @@
"""Creates a Policy.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createPolicy
+ api-ref/identity/v3/index.html#create-policy
"""
post_body = json.dumps({'policy': kwargs})
resp, body = self.post('policies', post_body)
@@ -56,7 +56,7 @@
"""Updates a policy.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updatePolicy
+ api-ref/identity/v3/index.html#update-policy
"""
post_body = json.dumps({'policy': kwargs})
url = 'policies/%s' % policy_id
diff --git a/tempest/services/identity/v3/json/projects_client.py b/tempest/lib/services/identity/v3/projects_client.py
similarity index 91%
rename from tempest/services/identity/v3/json/projects_client.py
rename to tempest/lib/services/identity/v3/projects_client.py
index 97e43df..ff06a19 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/lib/services/identity/v3/projects_client.py
@@ -26,7 +26,7 @@
"""Create a Project.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createProject
+ api-ref/identity/v3/index.html#create-project
"""
# Include the project name to the kwargs parameters
@@ -50,7 +50,7 @@
"""Update a Project.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateProject
+ api-ref/identity/v3/index.html#update-project
"""
post_body = json.dumps({'project': kwargs})
@@ -68,6 +68,6 @@
def delete_project(self, project_id):
"""Delete a project."""
- resp, body = self.delete('projects/%s' % str(project_id))
+ resp, body = self.delete('projects/%s' % project_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/regions_client.py b/tempest/lib/services/identity/v3/regions_client.py
similarity index 90%
rename from tempest/services/identity/v3/json/regions_client.py
rename to tempest/lib/services/identity/v3/regions_client.py
index 90dd9d7..bddfc7b 100644
--- a/tempest/services/identity/v3/json/regions_client.py
+++ b/tempest/lib/services/identity/v3/regions_client.py
@@ -30,10 +30,7 @@
"""Create region.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createRegion
-
- see http://developer.openstack.org/
- api-ref-identity-v3.html#createRegionWithID
+ api-ref/identity/v3/index.html#create-region
"""
if region_id is not None:
method = self.put
@@ -51,7 +48,7 @@
"""Updates a region.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateRegion
+ api-ref/identity/v3/index.html#update-region
"""
post_body = json.dumps({'region': kwargs})
resp, body = self.patch('regions/%s' % region_id, post_body)
diff --git a/tempest/lib/services/identity/v3/roles_client.py b/tempest/lib/services/identity/v3/roles_client.py
new file mode 100644
index 0000000..2a97caf
--- /dev/null
+++ b/tempest/lib/services/identity/v3/roles_client.py
@@ -0,0 +1,190 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class RolesClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_role(self, **kwargs):
+ """Create a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/index.html#create-role
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.post('roles', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % role_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_roles(self, **params):
+ """Get the list of Roles."""
+
+ url = 'roles'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_role(self, role_id, **kwargs):
+ """Update a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/index.html#update-role
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.patch('roles/%s' % role_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % role_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_user_role_on_project(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_user_role_on_domain(self, domain_id, user_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_project(self, project_id, user_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/users/%s/roles' %
+ (project_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_domain(self, domain_id, user_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/users/%s/roles' %
+ (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_project(self, project_id, user_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_role_existence_on_project(self, project_id,
+ user_id, role_id):
+ """Check role of a user on a project."""
+ resp, body = self.head('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_user_role_existence_on_domain(self, domain_id,
+ user_id, role_id):
+ """Check role of a user on a domain."""
+ resp, body = self.head('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def create_group_role_on_project(self, project_id, group_id, role_id):
+ """Add roles to a group on a project."""
+ resp, body = self.put('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_group_role_on_domain(self, domain_id, group_id, role_id):
+ """Add roles to a group on a domain."""
+ resp, body = self.put('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_project(self, project_id, group_id):
+ """list roles of a group on a project."""
+ resp, body = self.get('projects/%s/groups/%s/roles' %
+ (project_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_domain(self, domain_id, group_id):
+ """list roles of a group on a domain."""
+ resp, body = self.get('domains/%s/groups/%s/roles' %
+ (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_project(self, project_id, group_id, role_id):
+ """Delete role of a group on a project."""
+ resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
+ """Delete role of a group on a domain."""
+ resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_role_from_group_on_project_existence(self, project_id,
+ group_id, role_id):
+ """Check role of a group on a project."""
+ resp, body = self.head('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_role_from_group_on_domain_existence(self, domain_id,
+ group_id, role_id):
+ """Check role of a group on a domain."""
+ resp, body = self.head('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/services_client.py b/tempest/lib/services/identity/v3/services_client.py
similarity index 79%
rename from tempest/services/identity/v3/json/services_client.py
rename to tempest/lib/services/identity/v3/services_client.py
index e863016..efa0d89 100644
--- a/tempest/services/identity/v3/json/services_client.py
+++ b/tempest/lib/services/identity/v3/services_client.py
@@ -18,6 +18,7 @@
"""
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -29,7 +30,7 @@
"""Updates a service.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateService
+ api-ref/identity/v3/index.html#update-service
"""
patch_body = json.dumps({'service': kwargs})
resp, body = self.patch('services/%s' % service_id, patch_body)
@@ -49,7 +50,7 @@
"""Creates a service.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createService
+ api-ref/identity/v3/index.html#create-service
"""
body = json.dumps({'service': kwargs})
resp, body = self.post("services", body)
@@ -57,13 +58,21 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
- def delete_service(self, serv_id):
- url = "services/" + serv_id
+ def delete_service(self, service_id):
+ url = "services/" + service_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_services(self):
+ def list_services(self, **params):
+ """List services.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-services
+ """
+ url = 'services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
resp, body = self.get('services')
self.expected_success(200, resp.status)
body = json.loads(body)
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index 964d43f..c1f7e7b 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -22,11 +22,11 @@
class V3TokenClient(rest_client.RestClient):
def __init__(self, auth_url, disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ ca_certs=None, trace_requests=None, **kwargs):
dscv = disable_ssl_certificate_validation
super(V3TokenClient, self).__init__(
None, None, None, disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs, trace_requests=trace_requests)
+ ca_certs=ca_certs, trace_requests=trace_requests, **kwargs)
if auth_url is None:
raise exceptions.IdentityError("Couldn't determine auth_url")
diff --git a/tempest/services/identity/v3/json/trusts_client.py b/tempest/lib/services/identity/v3/trusts_client.py
similarity index 83%
rename from tempest/services/identity/v3/json/trusts_client.py
rename to tempest/lib/services/identity/v3/trusts_client.py
index dedee05..e3d5a41 100644
--- a/tempest/services/identity/v3/json/trusts_client.py
+++ b/tempest/lib/services/identity/v3/trusts_client.py
@@ -13,6 +13,7 @@
# limitations under the License.
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -24,7 +25,7 @@
"""Creates a trust.
Available params: see http://developer.openstack.org/
- api-ref-identity-v3-ext.html#createTrust
+ api-ref/identity/v3-ext/index.html#create-trust
"""
post_body = json.dumps({'trust': kwargs})
resp, body = self.post('OS-TRUST/trusts', post_body)
@@ -38,16 +39,16 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_trusts(self, trustor_user_id=None, trustee_user_id=None):
- """GET trusts."""
- if trustor_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
- % trustor_user_id)
- elif trustee_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
- % trustee_user_id)
- else:
- resp, body = self.get("OS-TRUST/trusts")
+ def list_trusts(self, **params):
+ """Returns trusts
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3-ext/index.html#list-trusts
+ """
+ url = "OS-TRUST/trusts/"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/users_client.py b/tempest/lib/services/identity/v3/users_client.py
new file mode 100644
index 0000000..5398621
--- /dev/null
+++ b/tempest/lib/services/identity/v3/users_client.py
@@ -0,0 +1,115 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class UsersClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_user(self, **kwargs):
+ """Creates a user.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#create-user
+ """
+ post_body = json.dumps({'user': kwargs})
+ resp, body = self.post('users', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user(self, user_id, **kwargs):
+ """Updates a user.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#update-user
+ """
+ if 'id' not in kwargs:
+ kwargs['id'] = user_id
+ post_body = json.dumps({'user': kwargs})
+ resp, body = self.patch('users/%s' % user_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user_password(self, user_id, **kwargs):
+ """Update a user password
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/index.html#
+ change-password-for-user
+ """
+ update_user = json.dumps({'user': kwargs})
+ resp, _ = self.post('users/%s/password' % user_id, update_user)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def list_user_projects(self, user_id, **params):
+ """Lists the projects on which a user has roles assigned.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-projects-for-user
+ """
+ url = 'users/%s/projects' % user_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_users(self, **params):
+ """Get the list of users.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-users
+ """
+ url = 'users'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_groups(self, user_id, **params):
+ """Lists groups which a user belongs to.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/identity/v3/#list-groups-to-which-a-user-belongs
+ """
+ url = 'users/%s/groups' % user_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/image/v1/image_members_client.py b/tempest/lib/services/image/v1/image_members_client.py
index e7fa0c9..2318087 100644
--- a/tempest/lib/services/image/v1/image_members_client.py
+++ b/tempest/lib/services/image/v1/image_members_client.py
@@ -29,8 +29,9 @@
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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v1/#list-shared-images
"""
url = 'shared-images/%s' % tenant_id
@@ -42,8 +43,9 @@
def create_image_member(self, image_id, member_id, **kwargs):
"""Add a member to an image.
- Available params: see http://developer.openstack.org/
- api-ref-image-v1.html#addMember-v1
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v1/#add-member-to-image
"""
url = 'images/%s/members/%s' % (image_id, member_id)
body = json.dumps({'member': kwargs})
@@ -54,8 +56,9 @@
def delete_image_member(self, image_id, member_id):
"""Removes a membership from the image.
- Available params: see http://developer.openstack.org/
- api-ref-image-v1.html#removeMember-v1
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v1/#remove-member
"""
url = 'images/%s/members/%s' % (image_id, member_id)
resp, __ = self.delete(url)
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
index 0db98f8..9737be3 100644
--- a/tempest/lib/services/image/v1/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -61,8 +61,9 @@
def create_image(self, data=None, headers=None):
"""Create an image.
- Available params: http://developer.openstack.org/
- api-ref-image-v1.html#createImage-v1
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref-image-v1.html#createImage-v1
"""
if headers is None:
headers = {}
@@ -78,8 +79,9 @@
def update_image(self, image_id, data=None, headers=None):
"""Update an image.
- Available params: http://developer.openstack.org/
- api-ref-image-v1.html#updateImage-v1
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref-image-v1.html#updateImage-v1
"""
if headers is None:
headers = {}
@@ -102,8 +104,9 @@
def list_images(self, detail=False, **kwargs):
"""Return a list of all images filtered by input parameters.
- Available params: see http://developer.openstack.org/
- api-ref-image-v1.html#listImage-v1
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v1/#list-images
Most parameters except the following are passed to the API without
any changes.
diff --git a/tempest/lib/services/image/v2/image_members_client.py b/tempest/lib/services/image/v2/image_members_client.py
index d0ab165..941e2e3 100644
--- a/tempest/lib/services/image/v2/image_members_client.py
+++ b/tempest/lib/services/image/v2/image_members_client.py
@@ -22,7 +22,7 @@
"""List image members.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#listImageMembers-v2
+ api-ref/image/v2/#list-image-members
"""
url = 'images/%s/members' % image_id
resp, body = self.get(url)
@@ -34,7 +34,7 @@
"""Create an image member.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createImageMember-v2
+ api-ref/image/v2/#create-image-member
"""
url = 'images/%s/members' % image_id
data = json.dumps(kwargs)
@@ -47,7 +47,7 @@
"""Update an image member.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateImageMember-v2
+ api-ref/image/v2/#update-image-member
"""
url = 'images/%s/members/%s' % (image_id, member_id)
data = json.dumps(kwargs)
@@ -60,7 +60,7 @@
"""Show an image member.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#showImageMember-v2
+ api-ref/image/v2/#show-image-member-details
"""
url = 'images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
@@ -71,7 +71,7 @@
"""Delete an image member.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#deleteImageMember-v2
+ api-ref/image/v2/#delete-image-member
"""
url = 'images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 4276847..a6016a4 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -31,7 +31,7 @@
"""Update an image.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateImage-v2
+ api-ref/image/v2/index.html#update-an-image
"""
data = json.dumps(patch)
headers = {"Content-Type": "application/openstack-images-v2.0"
@@ -45,7 +45,7 @@
"""Create an image.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createImage-v2
+ api-ref/image/v2/index.html#create-an-image
"""
data = json.dumps(kwargs)
resp, body = self.post('images', data)
@@ -54,24 +54,44 @@
return rest_client.ResponseBody(resp, body)
def deactivate_image(self, image_id):
+ """Deactivate image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/#deactivate-image
+ """
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):
+ """Reactivate image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/#reactivate-image
+ """
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):
+ """Delete image.
+
+ Available params: see http://developer.openstack.org/
+ /api-ref/image/v2/#delete-an-image
+ """
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):
+ """List images.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/#show-images
+ """
url = 'images'
if params:
@@ -83,6 +103,11 @@
return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
+ """Show image details.
+
+ Available params: http://developer.openstack.org/
+ api-ref/image/v2/#show-image-details
+ """
url = 'images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
@@ -102,6 +127,11 @@
return 'image'
def store_image_file(self, image_id, data):
+ """Upload binary image data.
+
+ Available params: http://developer.openstack.org/
+ api-ref/image/v2/#upload-binary-image-data
+ """
url = 'images/%s/file' % image_id
# We are going to do chunked transfert, so split the input data
@@ -115,10 +145,10 @@
return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
- """Show an image file.
+ """Download binary image data.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#showImageFile-v2
+ api-ref/image/v2/#download-binary-image-data
"""
url = 'images/%s/file' % image_id
resp, body = self.get(url)
@@ -129,7 +159,7 @@
"""Add an image tag.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#addImageTag-v2
+ api-ref/image/v2/#add-image-tag
"""
url = 'images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
@@ -140,7 +170,7 @@
"""Delete an image tag.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#deleteImageTag-v2
+ api-ref/image/v2/#delete-image-tag
"""
url = 'images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
diff --git a/tempest/lib/services/image/v2/namespaces_client.py b/tempest/lib/services/image/v2/namespaces_client.py
index 5bd096d..aafa936 100644
--- a/tempest/lib/services/image/v2/namespaces_client.py
+++ b/tempest/lib/services/image/v2/namespaces_client.py
@@ -25,7 +25,7 @@
"""Create a namespace.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createNamespace-v2
+ api-ref/image/v2/metadefs-index.html#create-namespace
"""
data = json.dumps(kwargs)
resp, body = self.post('metadefs/namespaces', data)
@@ -34,6 +34,11 @@
return rest_client.ResponseBody(resp, body)
def show_namespace(self, namespace):
+ """Show namespace details.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/metadefs-index.html#get-namespace-details
+ """
url = 'metadefs/namespaces/%s' % namespace
resp, body = self.get(url)
self.expected_success(200, resp.status)
@@ -44,7 +49,7 @@
"""Update a namespace.
Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateNamespace-v2
+ api-ref/image/v2/metadefs-index.html#update-namespace
"""
# NOTE: On Glance API, we need to pass namespace on both URI
# and a request body.
@@ -61,7 +66,7 @@
"""Delete a namespace.
Available params: http://developer.openstack.org/
- api-ref-image-v2.html#deleteNamespace-v2
+ api-ref/image/v2/metadefs-index.html#delete-namespace
"""
url = 'metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
diff --git a/tempest/lib/services/image/v2/resource_types_client.py b/tempest/lib/services/image/v2/resource_types_client.py
index 1349c63..8f2a977 100644
--- a/tempest/lib/services/image/v2/resource_types_client.py
+++ b/tempest/lib/services/image/v2/resource_types_client.py
@@ -22,8 +22,54 @@
api_version = "v2"
def list_resource_types(self):
+ """Lists all resource types.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/metadefs-index.html?expanded=#
+ list-resource-types
+ """
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)
+
+ def create_resource_type_association(self, namespace_id, **kwargs):
+ """Creates a resource type association in given namespace.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/metadefs-index.html?expanded=#
+ create-resource-type-association
+ """
+ url = 'metadefs/namespaces/%s/resource_types' % namespace_id
+ data = json.dumps(kwargs)
+ resp, body = self.post(url, data)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_resource_type_association(self, namespace_id):
+ """Lists resource type associations in given namespace.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/metadefs-index.html?expanded=#
+ list-resource-type-associations
+ """
+ url = 'metadefs/namespaces/%s/resource_types' % namespace_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_resource_type_association(self, namespace_id, resource_name):
+ """Removes resource type association in given namespace.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/image/v2/metadefs-index.html?expanded=#
+ remove-resource-type-association
+ """
+ url = 'metadefs/namespaces/%s/resource_types/%s' % (namespace_id,
+ resource_name)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index c5d4c66..9bdf090 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -44,7 +44,7 @@
# link to api-site.
# LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526670
uri = '/agents/%s/l3-routers' % agent_id
- return self.create_resource(uri, kwargs)
+ return self.create_resource(uri, kwargs, expect_empty_body=True)
def delete_router_from_l3_agent(self, agent_id, router_id):
uri = '/agents/%s/l3-routers/%s' % (agent_id, router_id)
@@ -65,4 +65,4 @@
# link to api-site.
# LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
uri = '/agents/%s/dhcp-networks' % agent_id
- return self.create_resource(uri, kwargs)
+ return self.create_resource(uri, kwargs, expect_empty_body=True)
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index a6ada04..b6f9c91 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -54,18 +54,30 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def create_resource(self, uri, post_data):
+ def create_resource(self, uri, post_data, expect_empty_body=False):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.post(req_uri, req_post_data)
- body = json.loads(body)
+ # NOTE: RFC allows both a valid non-empty body and an empty body for
+ # response of POST API. If a body is expected not empty, we decode the
+ # body. Otherwise we returns the body as it is.
+ if not expect_empty_body:
+ body = json.loads(body)
+ else:
+ body = None
self.expected_success(201, resp.status)
return rest_client.ResponseBody(resp, body)
- def update_resource(self, uri, post_data):
+ def update_resource(self, uri, post_data, expect_empty_body=False):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.put(req_uri, req_post_data)
- body = json.loads(body)
+ # NOTE: RFC allows both a valid non-empty body and an empty body for
+ # response of PUT API. If a body is expected not empty, we decode the
+ # body. Otherwise we returns the body as it is.
+ if not expect_empty_body:
+ body = json.loads(body)
+ else:
+ body = None
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/floating_ips_client.py b/tempest/lib/services/network/floating_ips_client.py
old mode 100644
new mode 100755
index 1968e05..24e0e7d
--- a/tempest/lib/services/network/floating_ips_client.py
+++ b/tempest/lib/services/network/floating_ips_client.py
@@ -16,16 +16,37 @@
class FloatingIPsClient(base.BaseNetworkClient):
def create_floatingip(self, **kwargs):
+ """Creates a floating IP.
+
+ If you specify port information, associates the floating IP with an
+ internal port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ create-floating-ip
+ """
uri = '/floatingips'
post_data = {'floatingip': kwargs}
return self.create_resource(uri, post_data)
def update_floatingip(self, floatingip_id, **kwargs):
+ """Updates a floating IP and its association with an internal port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ update-floating-ip
+ """
uri = '/floatingips/%s' % floatingip_id
post_data = {'floatingip': kwargs}
return self.update_resource(uri, post_data)
def show_floatingip(self, floatingip_id, **fields):
+ """Shows details for a floating IP.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ show-floating-ip-details
+ """
uri = '/floatingips/%s' % floatingip_id
return self.show_resource(uri, **fields)
@@ -34,5 +55,11 @@
return self.delete_resource(uri)
def list_floatingips(self, **filters):
+ """Lists floating IPs.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ list-floating-ips
+ """
uri = '/floatingips'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/metering_labels_client.py b/tempest/lib/services/network/metering_labels_client.py
old mode 100644
new mode 100755
index 2350ecd..6932a62
--- a/tempest/lib/services/network/metering_labels_client.py
+++ b/tempest/lib/services/network/metering_labels_client.py
@@ -16,18 +16,42 @@
class MeteringLabelsClient(base.BaseNetworkClient):
def create_metering_label(self, **kwargs):
+ """Creates an L3 metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ create-metering-label
+ """
uri = '/metering/metering-labels'
post_data = {'metering_label': kwargs}
return self.create_resource(uri, post_data)
def show_metering_label(self, metering_label_id, **fields):
+ """Shows details for a metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ show-metering-label-details
+ """
uri = '/metering/metering-labels/%s' % metering_label_id
return self.show_resource(uri, **fields)
def delete_metering_label(self, metering_label_id):
+ """Deletes an L3 metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ delete-metering-label
+ """
uri = '/metering/metering-labels/%s' % metering_label_id
return self.delete_resource(uri)
def list_metering_labels(self, **filters):
+ """Lists all L3 metering labels that belong to the tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ list-metering-labels
+ """
uri = '/metering/metering-labels'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
index 19fa1db..6b601ee 100755
--- a/tempest/lib/services/network/networks_client.py
+++ b/tempest/lib/services/network/networks_client.py
@@ -19,7 +19,7 @@
"""Creates a network.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#createNetwork
+ api-ref/networking/v2/index.html#create-network
"""
uri = '/networks'
post_data = {'network': kwargs}
@@ -29,13 +29,18 @@
"""Updates a network.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#updateNetwork
+ api-ref/networking/v2/index.html#update-network
"""
uri = '/networks/%s' % network_id
post_data = {'network': kwargs}
return self.update_resource(uri, post_data)
def show_network(self, network_id, **fields):
+ """Shows details for a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#show-network-details
+ """
uri = '/networks/%s' % network_id
return self.show_resource(uri, **fields)
@@ -44,6 +49,11 @@
return self.delete_resource(uri)
def list_networks(self, **filters):
+ """Lists networks to which the tenant has access.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#list-networks
+ """
uri = '/networks'
return self.list_resources(uri, **filters)
@@ -51,7 +61,7 @@
"""Create multiple networks in a single request.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateNetwork
+ api-ref/networking/v2/index.html#bulk-create-networks
"""
uri = '/networks'
return self.create_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/ports_client.py b/tempest/lib/services/network/ports_client.py
old mode 100644
new mode 100755
index eba11d3..85f5a1d
--- a/tempest/lib/services/network/ports_client.py
+++ b/tempest/lib/services/network/ports_client.py
@@ -17,24 +17,49 @@
class PortsClient(base.BaseNetworkClient):
def create_port(self, **kwargs):
+ """Creates a port on a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#create-port
+ """
uri = '/ports'
post_data = {'port': kwargs}
return self.create_resource(uri, post_data)
def update_port(self, port_id, **kwargs):
+ """Updates a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#update-port
+ """
uri = '/ports/%s' % port_id
post_data = {'port': kwargs}
return self.update_resource(uri, post_data)
def show_port(self, port_id, **fields):
+ """Shows details for a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#show-port-details
+ """
uri = '/ports/%s' % port_id
return self.show_resource(uri, **fields)
def delete_port(self, port_id):
+ """Deletes a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#delete-port
+ """
uri = '/ports/%s' % port_id
return self.delete_resource(uri)
def list_ports(self, **filters):
+ """Lists ports to which the tenant has access.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#list-ports
+ """
uri = '/ports'
return self.list_resources(uri, **filters)
@@ -42,7 +67,7 @@
"""Create multiple ports in a single request.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreatePorts
+ api-ref/networking/v2/index.html?expanded=#bulk-create-ports
"""
uri = '/ports'
return self.create_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/routers_client.py b/tempest/lib/services/network/routers_client.py
old mode 100644
new mode 100755
index 2ba1938..5a72b5e
--- a/tempest/lib/services/network/routers_client.py
+++ b/tempest/lib/services/network/routers_client.py
@@ -19,18 +19,29 @@
"""Create a router.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#createRouter
+ api-ref/networking/v2/index.html#create-router
"""
post_body = {'router': kwargs}
uri = '/routers'
return self.create_resource(uri, post_body)
def update_router(self, router_id, **kwargs):
+ """Updates a logical router.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#update-router
+ """
uri = '/routers/%s' % router_id
update_body = {'router': kwargs}
return self.update_resource(uri, update_body)
def show_router(self, router_id, **fields):
+ """Shows details for a router.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ show-router-details
+ """
uri = '/routers/%s' % router_id
return self.show_resource(uri, **fields)
@@ -39,6 +50,11 @@
return self.delete_resource(uri)
def list_routers(self, **filters):
+ """Lists logical routers.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#list-routers
+ """
uri = '/routers'
return self.list_resources(uri, **filters)
@@ -46,7 +62,8 @@
"""Add router interface.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#addRouterInterface
+ api-ref/networking/v2/index.html#
+ add-interface-to-router
"""
uri = '/routers/%s/add_router_interface' % router_id
return self.update_resource(uri, kwargs)
@@ -55,7 +72,8 @@
"""Remove router interface.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#deleteRouterInterface
+ api-ref/networking/v2/index.html#
+ remove-interface-from-router
"""
uri = '/routers/%s/remove_router_interface' % router_id
return self.update_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/security_group_rules_client.py b/tempest/lib/services/network/security_group_rules_client.py
old mode 100644
new mode 100755
index 944eba6..e6f1bb8
--- a/tempest/lib/services/network/security_group_rules_client.py
+++ b/tempest/lib/services/network/security_group_rules_client.py
@@ -16,11 +16,23 @@
class SecurityGroupRulesClient(base.BaseNetworkClient):
def create_security_group_rule(self, **kwargs):
+ """Creates an OpenStack Networking security group rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ create-security-group-rule
+ """
uri = '/security-group-rules'
post_data = {'security_group_rule': kwargs}
return self.create_resource(uri, post_data)
def show_security_group_rule(self, security_group_rule_id, **fields):
+ """Shows detailed information for a security group rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ show-security-group-rule
+ """
uri = '/security-group-rules/%s' % security_group_rule_id
return self.show_resource(uri, **fields)
@@ -29,5 +41,11 @@
return self.delete_resource(uri)
def list_security_group_rules(self, **filters):
+ """Lists a summary of all OpenStack Networking security group rules.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ list-security-group-rules
+ """
uri = '/security-group-rules'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
old mode 100644
new mode 100755
index 0e25339..d5ac61c
--- a/tempest/lib/services/network/security_groups_client.py
+++ b/tempest/lib/services/network/security_groups_client.py
@@ -16,23 +16,53 @@
class SecurityGroupsClient(base.BaseNetworkClient):
def create_security_group(self, **kwargs):
+ """Creates an OpenStack Networking security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ create-security-group
+ """
uri = '/security-groups'
post_data = {'security_group': kwargs}
return self.create_resource(uri, post_data)
def update_security_group(self, security_group_id, **kwargs):
+ """Updates a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ update-security-group
+ """
uri = '/security-groups/%s' % security_group_id
post_data = {'security_group': kwargs}
return self.update_resource(uri, post_data)
def show_security_group(self, security_group_id, **fields):
+ """Shows details for a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ show-security-group
+ """
uri = '/security-groups/%s' % security_group_id
return self.show_resource(uri, **fields)
def delete_security_group(self, security_group_id):
+ """Deletes an OpenStack Networking security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ delete-security-group
+ """
uri = '/security-groups/%s' % security_group_id
return self.delete_resource(uri)
def list_security_groups(self, **filters):
+ """Lists OpenStack Networking security groups.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ list-security-groups
+ """
uri = '/security-groups'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
old mode 100644
new mode 100755
index 12349b1..ec110b9
--- a/tempest/lib/services/network/subnetpools_client.py
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -18,19 +18,42 @@
class SubnetpoolsClient(base.BaseNetworkClient):
def list_subnetpools(self, **filters):
+ """Lists subnet pools to which the tenant has access.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ list-subnet-pools
+ """
uri = '/subnetpools'
return self.list_resources(uri, **filters)
def create_subnetpool(self, **kwargs):
+ """Creates a subnet pool.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ create-subnet-pool
+ """
uri = '/subnetpools'
post_data = {'subnetpool': kwargs}
return self.create_resource(uri, post_data)
def show_subnetpool(self, subnetpool_id, **fields):
+ """Shows information for a subnet pool.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#show-subnet-pool
+ """
uri = '/subnetpools/%s' % subnetpool_id
return self.show_resource(uri, **fields)
def update_subnetpool(self, subnetpool_id, **kwargs):
+ """Updates a subnet pool.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#
+ update-subnet-pool
+ """
uri = '/subnetpools/%s' % subnetpool_id
post_data = {'subnetpool': kwargs}
return self.update_resource(uri, post_data)
diff --git a/tempest/lib/services/network/subnets_client.py b/tempest/lib/services/network/subnets_client.py
index 9de4a33..203b360 100755
--- a/tempest/lib/services/network/subnets_client.py
+++ b/tempest/lib/services/network/subnets_client.py
@@ -19,7 +19,7 @@
"""Creates a subnet on a network.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#createSubnet
+ api-ref/networking/v2/index.html#create-subnet
"""
uri = '/subnets'
post_data = {'subnet': kwargs}
@@ -29,13 +29,18 @@
"""Updates a subnet.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#updateSubnet
+ api-ref/networking/v2/index.html#update-subnet
"""
uri = '/subnets/%s' % subnet_id
post_data = {'subnet': kwargs}
return self.update_resource(uri, post_data)
def show_subnet(self, subnet_id, **fields):
+ """Shows details for a subnet.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#show-subnet-details
+ """
uri = '/subnets/%s' % subnet_id
return self.show_resource(uri, **fields)
@@ -44,6 +49,11 @@
return self.delete_resource(uri)
def list_subnets(self, **filters):
+ """Lists subnets to which the tenant has access.
+
+ Available params: see http://developer.openstack.org/
+ api-ref/networking/v2/index.html#list-subnets
+ """
uri = '/subnets'
return self.list_resources(uri, **filters)
@@ -51,7 +61,7 @@
"""Create multiple subnets in a single request.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateSubnet
+ api-ref/networking/v2/index.html#bulk-create-subnet
"""
uri = '/subnets'
return self.create_resource(uri, kwargs)
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/services/volume/__init__.py
similarity index 100%
rename from tempest/services/identity/v2/json/__init__.py
rename to tempest/lib/services/volume/__init__.py
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/services/volume/v1/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/services/volume/v1/__init__.py
diff --git a/tempest/services/volume/base/base_availability_zone_client.py b/tempest/lib/services/volume/v1/availability_zone_client.py
similarity index 90%
rename from tempest/services/volume/base/base_availability_zone_client.py
rename to tempest/lib/services/volume/v1/availability_zone_client.py
index 1c2deba..be4f539 100644
--- a/tempest/services/volume/base/base_availability_zone_client.py
+++ b/tempest/lib/services/volume/v1/availability_zone_client.py
@@ -18,7 +18,8 @@
from tempest.lib.common import rest_client
-class BaseAvailabilityZoneClient(rest_client.RestClient):
+class AvailabilityZoneClient(rest_client.RestClient):
+ """Volume V1 availability zone client."""
def list_availability_zones(self):
resp, body = self.get('os-availability-zone')
diff --git a/tempest/services/volume/base/base_extensions_client.py b/tempest/lib/services/volume/v1/extensions_client.py
similarity index 91%
rename from tempest/services/volume/base/base_extensions_client.py
rename to tempest/lib/services/volume/v1/extensions_client.py
index b90fe94..7b849a8 100644
--- a/tempest/services/volume/base/base_extensions_client.py
+++ b/tempest/lib/services/volume/v1/extensions_client.py
@@ -18,7 +18,8 @@
from tempest.lib.common import rest_client
-class BaseExtensionsClient(rest_client.RestClient):
+class ExtensionsClient(rest_client.RestClient):
+ """Volume V1 extensions client."""
def list_extensions(self):
url = 'extensions'
diff --git a/tempest/services/volume/base/admin/base_hosts_client.py b/tempest/lib/services/volume/v1/hosts_client.py
similarity index 90%
rename from tempest/services/volume/base/admin/base_hosts_client.py
rename to tempest/lib/services/volume/v1/hosts_client.py
index 382e9a8..56ba12c 100644
--- a/tempest/services/volume/base/admin/base_hosts_client.py
+++ b/tempest/lib/services/volume/v1/hosts_client.py
@@ -19,8 +19,8 @@
from tempest.lib.common import rest_client
-class BaseHostsClient(rest_client.RestClient):
- """Client class to send CRUD Volume Hosts API requests"""
+class HostsClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Host API V1 requests"""
def list_hosts(self, **params):
"""Lists all hosts."""
diff --git a/tempest/services/volume/base/admin/base_quotas_client.py b/tempest/lib/services/volume/v1/quotas_client.py
similarity index 84%
copy from tempest/services/volume/base/admin/base_quotas_client.py
copy to tempest/lib/services/volume/v1/quotas_client.py
index 83816f2..8924b42 100644
--- a/tempest/services/volume/base/admin/base_quotas_client.py
+++ b/tempest/lib/services/volume/v1/quotas_client.py
@@ -18,10 +18,8 @@
from tempest.lib.common import rest_client
-class BaseQuotasClient(rest_client.RestClient):
- """Client class to send CRUD Volume Quotas API requests"""
-
- TYPE = "json"
+class QuotasClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Quotas API V1 requests"""
def show_default_quota_set(self, tenant_id):
"""List the default volume quota set for a tenant."""
@@ -44,17 +42,11 @@
body = jsonutils.loads(body)
return rest_client.ResponseBody(resp, body)
- def show_quota_usage(self, tenant_id):
- """List the quota set for a tenant."""
-
- body = self.show_quota_set(tenant_id, params={'usage': True})
- return body
-
def update_quota_set(self, tenant_id, **kwargs):
"""Updates quota set
Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateQuotas-v2
+ api-ref-blockstorage-v1.html#updateQuota
"""
put_body = jsonutils.dumps({'quota_set': kwargs})
resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
diff --git a/tempest/services/volume/base/admin/base_services_client.py b/tempest/lib/services/volume/v1/services_client.py
similarity index 91%
rename from tempest/services/volume/base/admin/base_services_client.py
rename to tempest/lib/services/volume/v1/services_client.py
index 861eb92..d438a34 100644
--- a/tempest/services/volume/base/admin/base_services_client.py
+++ b/tempest/lib/services/volume/v1/services_client.py
@@ -19,7 +19,8 @@
from tempest.lib.common import rest_client
-class BaseServicesClient(rest_client.RestClient):
+class ServicesClient(rest_client.RestClient):
+ """Volume V1 volume services client"""
def list_services(self, **params):
url = 'os-services'
diff --git a/tempest/services/identity/v2/json/__init__.py b/tempest/lib/services/volume/v2/__init__.py
similarity index 100%
copy from tempest/services/identity/v2/json/__init__.py
copy to tempest/lib/services/volume/v2/__init__.py
diff --git a/tempest/services/volume/base/base_availability_zone_client.py b/tempest/lib/services/volume/v2/availability_zone_client.py
similarity index 89%
copy from tempest/services/volume/base/base_availability_zone_client.py
copy to tempest/lib/services/volume/v2/availability_zone_client.py
index 1c2deba..bb4a357 100644
--- a/tempest/services/volume/base/base_availability_zone_client.py
+++ b/tempest/lib/services/volume/v2/availability_zone_client.py
@@ -1,4 +1,4 @@
-# Copyright 2014 NEC Corporation.
+# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,7 +18,8 @@
from tempest.lib.common import rest_client
-class BaseAvailabilityZoneClient(rest_client.RestClient):
+class AvailabilityZoneClient(rest_client.RestClient):
+ api_version = "v2"
def list_availability_zones(self):
resp, body = self.get('os-availability-zone')
diff --git a/tempest/services/volume/base/base_extensions_client.py b/tempest/lib/services/volume/v2/extensions_client.py
similarity index 86%
copy from tempest/services/volume/base/base_extensions_client.py
copy to tempest/lib/services/volume/v2/extensions_client.py
index b90fe94..09279d5 100644
--- a/tempest/services/volume/base/base_extensions_client.py
+++ b/tempest/lib/services/volume/v2/extensions_client.py
@@ -1,4 +1,4 @@
-# Copyright 2012 OpenStack Foundation
+# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,7 +18,9 @@
from tempest.lib.common import rest_client
-class BaseExtensionsClient(rest_client.RestClient):
+class ExtensionsClient(rest_client.RestClient):
+ """Volume V2 extensions client."""
+ api_version = "v2"
def list_extensions(self):
url = 'extensions'
diff --git a/tempest/services/volume/base/admin/base_hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
similarity index 86%
copy from tempest/services/volume/base/admin/base_hosts_client.py
copy to tempest/lib/services/volume/v2/hosts_client.py
index 382e9a8..dd7c482 100644
--- a/tempest/services/volume/base/admin/base_hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack Foundation
+# Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,8 +19,9 @@
from tempest.lib.common import rest_client
-class BaseHostsClient(rest_client.RestClient):
- """Client class to send CRUD Volume Hosts API requests"""
+class HostsClient(rest_client.RestClient):
+ """Client class to send CRUD Volume V2 API requests"""
+ api_version = "v2"
def list_hosts(self, **params):
"""Lists all hosts."""
diff --git a/tempest/services/volume/base/admin/base_quotas_client.py b/tempest/lib/services/volume/v2/quotas_client.py
similarity index 85%
rename from tempest/services/volume/base/admin/base_quotas_client.py
rename to tempest/lib/services/volume/v2/quotas_client.py
index 83816f2..a302045 100644
--- a/tempest/services/volume/base/admin/base_quotas_client.py
+++ b/tempest/lib/services/volume/v2/quotas_client.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -18,10 +19,9 @@
from tempest.lib.common import rest_client
-class BaseQuotasClient(rest_client.RestClient):
- """Client class to send CRUD Volume Quotas API requests"""
-
- TYPE = "json"
+class QuotasClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Quotas API V2 requests"""
+ api_version = "v2"
def show_default_quota_set(self, tenant_id):
"""List the default volume quota set for a tenant."""
@@ -44,12 +44,6 @@
body = jsonutils.loads(body)
return rest_client.ResponseBody(resp, body)
- def show_quota_usage(self, tenant_id):
- """List the quota set for a tenant."""
-
- body = self.show_quota_set(tenant_id, params={'usage': True})
- return body
-
def update_quota_set(self, tenant_id, **kwargs):
"""Updates quota set
diff --git a/tempest/services/volume/base/admin/base_services_client.py b/tempest/lib/services/volume/v2/services_client.py
similarity index 85%
copy from tempest/services/volume/base/admin/base_services_client.py
copy to tempest/lib/services/volume/v2/services_client.py
index 861eb92..bc55469 100644
--- a/tempest/services/volume/base/admin/base_services_client.py
+++ b/tempest/lib/services/volume/v2/services_client.py
@@ -1,4 +1,4 @@
-# Copyright 2014 NEC Corporation
+# Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,7 +19,9 @@
from tempest.lib.common import rest_client
-class BaseServicesClient(rest_client.RestClient):
+class ServicesClient(rest_client.RestClient):
+ """Client class to send CRUD Volume V2 API requests"""
+ api_version = "v2"
def list_services(self, **params):
url = 'os-services'
diff --git a/tempest/manager.py b/tempest/manager.py
index 3d495b6..e3174d4 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -15,15 +15,15 @@
from oslo_log import log as logging
-from tempest import clients
+from tempest import clients as tempest_clients
from tempest import config
-from tempest import service_clients
+from tempest.lib.services import clients
CONF = config.CONF
LOG = logging.getLogger(__name__)
-class Manager(service_clients.ServiceClients):
+class Manager(clients.ServiceClients):
"""Service client manager class for backward compatibility
The former manager.Manager is not a stable interface in Tempest,
@@ -37,7 +37,7 @@
"soon as the client manager becomes available in tempest.lib.")
LOG.warning(msg)
dscv = CONF.identity.disable_ssl_certificate_validation
- _, uri = clients.get_auth_provider_class(credentials)
+ _, uri = tempest_clients.get_auth_provider_class(credentials)
super(Manager, self).__init__(
credentials=credentials, scope=scope,
identity_uri=uri,
@@ -58,5 +58,5 @@
"as such it should not imported directly. It will be removed as "
"the client manager becomes available in tempest.lib.")
LOG.warning(msg)
- return clients.get_auth_provider(credentials=credentials,
- pre_auth=pre_auth, scope=scope)
+ return tempest_clients.get_auth_provider(credentials=credentials,
+ pre_auth=pre_auth, scope=scope)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 9bd437e..2f31ea3 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -87,63 +87,26 @@
cls.volumes_client = cls.manager.volumes_v2_client
cls.snapshots_client = cls.manager.snapshots_v2_client
- # ## Methods to handle sync and async deletes
-
- def setUp(self):
- super(ScenarioTest, self).setUp()
- self.cleanup_waits = []
- # NOTE(mtreinish) This is safe to do in setUp instead of setUp class
- # because scenario tests in the same test class should not share
- # resources. If resources were shared between test cases then it
- # should be a single scenario test instead of multiples.
-
- # NOTE(yfried): this list is cleaned at the end of test_methods and
- # not at the end of the class
- self.addCleanup(self._wait_for_cleanups)
-
- def addCleanup_with_wait(self, waiter_callable, thing_id, thing_id_param,
- cleanup_callable, cleanup_args=None,
- cleanup_kwargs=None, waiter_client=None):
- """Adds wait for async resource deletion at the end of cleanups
-
- @param waiter_callable: callable to wait for the resource to delete
- with the following waiter_client if specified.
- @param thing_id: the id of the resource to be cleaned-up
- @param thing_id_param: the name of the id param in the waiter
- @param cleanup_callable: method to load pass to self.addCleanup with
- the following *cleanup_args, **cleanup_kwargs.
- usually a delete method.
- """
- if cleanup_args is None:
- cleanup_args = []
- if cleanup_kwargs is None:
- cleanup_kwargs = {}
- self.addCleanup(cleanup_callable, *cleanup_args, **cleanup_kwargs)
- wait_dict = {
- 'waiter_callable': waiter_callable,
- thing_id_param: thing_id
- }
- if waiter_client:
- wait_dict['client'] = waiter_client
- self.cleanup_waits.append(wait_dict)
-
- def _wait_for_cleanups(self):
- # To handle async delete actions, a list of waits is added
- # which will be iterated over as the last step of clearing the
- # cleanup queue. That way all the delete calls are made up front
- # and the tests won't succeed unless the deletes are eventually
- # successful. This is the same basic approach used in the api tests to
- # limit cleanup execution time except here it is multi-resource,
- # because of the nature of the scenario tests.
- for wait in self.cleanup_waits:
- waiter_callable = wait.pop('waiter_callable')
- waiter_callable(**wait)
-
# ## Test functions library
#
# The create_[resource] functions only return body and discard the
# resp part which is not used in scenario tests
+ def _create_port(self, network_id, client=None, namestart='port-quotatest',
+ **kwargs):
+ if not client:
+ client = self.ports_client
+ name = data_utils.rand_name(namestart)
+ result = client.create_port(
+ name=name,
+ network_id=network_id,
+ **kwargs)
+ self.assertIsNotNone(result, 'Unable to allocate port')
+ port = result['port']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ client.delete_port, port['id'])
+ return port
+
def create_keypair(self, client=None):
if not client:
client = self.keypairs_client
@@ -155,7 +118,7 @@
def create_server(self, name=None, image_id=None, flavor=None,
validatable=False, wait_until=None,
- wait_on_delete=True, clients=None, **kwargs):
+ clients=None, **kwargs):
"""Wrapper utility that returns a test server.
This wrapper utility calls the common create test server and
@@ -183,7 +146,7 @@
# every network
if vnic_type:
ports = []
- networks = []
+
create_port_body = {'binding:vnic_type': vnic_type,
'namestart': 'port-smoke'}
if kwargs:
@@ -204,25 +167,30 @@
if security_groups_ids:
create_port_body[
'security_groups'] = security_groups_ids
- networks = kwargs.pop('networks')
+ networks = kwargs.pop('networks', [])
+ else:
+ networks = []
# If there are no networks passed to us we look up
- # for the project's private networks and create a port
- # if there is only one private network. The same behaviour
- # as we would expect when passing the call to the clients
- # with no networks
+ # for the project's private networks and create a port.
+ # The same behaviour as we would expect when passing
+ # the call to the clients with no networks
if not networks:
networks = clients.networks_client.list_networks(
- filters={'router:external': False})
- self.assertEqual(1, len(networks),
- "There is more than one"
- " network for the tenant")
+ **{'router:external': False, 'fields': 'id'})['networks']
+
+ # It's net['uuid'] if networks come from kwargs
+ # and net['id'] if they come from
+ # clients.networks_client.list_networks
for net in networks:
- net_id = net['uuid']
- port = self._create_port(network_id=net_id,
- client=clients.ports_client,
- **create_port_body)
- ports.append({'port': port['id']})
+ net_id = net.get('uuid', net.get('id'))
+ if 'port' not in net:
+ port = self._create_port(network_id=net_id,
+ client=clients.ports_client,
+ **create_port_body)
+ ports.append({'port': port['id']})
+ else:
+ ports.append({'port': net['port']})
if ports:
kwargs['networks'] = ports
self.ports = ports
@@ -236,31 +204,24 @@
name=name, flavor=flavor,
image_id=image_id, **kwargs)
- # TODO(jlanoux) Move wait_on_delete in compute.py
- if wait_on_delete:
- self.addCleanup(waiters.wait_for_server_termination,
- clients.servers_client,
- body['id'])
-
- self.addCleanup_with_wait(
- waiter_callable=waiters.wait_for_server_termination,
- thing_id=body['id'], thing_id_param='server_id',
- cleanup_callable=test_utils.call_and_ignore_notfound_exc,
- cleanup_args=[clients.servers_client.delete_server, body['id']],
- waiter_client=clients.servers_client)
+ self.addCleanup(waiters.wait_for_server_termination,
+ clients.servers_client, body['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ clients.servers_client.delete_server, body['id'])
server = clients.servers_client.show_server(body['id'])['server']
return server
def create_volume(self, size=None, name=None, snapshot_id=None,
imageRef=None, volume_type=None):
+ if size is None:
+ size = CONF.volume.volume_size
if name is None:
name = data_utils.rand_name(self.__class__.__name__)
kwargs = {'display_name': name,
'snapshot_id': snapshot_id,
'imageRef': imageRef,
- 'volume_type': volume_type}
- if size is not None:
- kwargs.update({'size': size})
+ 'volume_type': volume_type,
+ 'size': size}
volume = self.volumes_client.create_volume(**kwargs)['volume']
self.addCleanup(self.volumes_client.wait_for_resource_deletion,
@@ -461,11 +422,12 @@
image = _images_client.create_image(server['id'], name=name)
image_id = image.response['location'].split('images/')[1]
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=test_utils.call_and_ignore_notfound_exc,
- cleanup_args=[_image_client.delete_image, image_id])
+
+ self.addCleanup(_image_client.wait_for_resource_deletion,
+ image_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ _image_client.delete_image, image_id)
+
if CONF.image_feature_enabled.api_v1:
# In glance v1 the additional properties are stored in the headers.
resp = _image_client.check_image(image_id)
@@ -552,7 +514,7 @@
'should_succeed':
'reachable' if should_succeed else 'unreachable'
})
- result = tempest.test.call_until_true(ping, timeout, 1)
+ result = test_utils.call_until_true(ping, timeout, 1)
LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
'ping result is %(result)s' % {
'caller': caller, 'ip': ip_address, 'timeout': timeout,
@@ -664,7 +626,7 @@
for address in addresses:
if address['version'] == CONF.validation.ip_version_for_ssh:
return address['addr']
- raise exceptions.ServerUnreachable()
+ raise exceptions.ServerUnreachable(server_id=server['id'])
else:
raise exceptions.InvalidConfiguration()
@@ -811,21 +773,6 @@
return subnet
- def _create_port(self, network_id, client=None, namestart='port-quotatest',
- **kwargs):
- if not client:
- client = self.ports_client
- name = data_utils.rand_name(namestart)
- result = client.create_port(
- name=name,
- network_id=network_id,
- **kwargs)
- self.assertIsNotNone(result, 'Unable to allocate port')
- port = result['port']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- client.delete_port, port['id'])
- return port
-
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
ports = self._list_ports(device_id=server['id'], fixed_ip=ip_addr)
# A port can have more then one IP address in some cases.
@@ -835,6 +782,7 @@
# 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.
+ # TODO(vsaienko) remove once bug: #1599836 is resolved.
if CONF.service_available.ironic:
p_status.append('DOWN')
port_map = [(p["id"], fxip["ip_address"])
@@ -913,9 +861,9 @@
show_floatingip(floatingip_id)['floatingip'])
return status == result['status']
- tempest.test.call_until_true(refresh,
- CONF.network.build_timeout,
- CONF.network.build_interval)
+ test_utils.call_until_true(refresh,
+ CONF.network.build_timeout,
+ CONF.network.build_interval)
floating_ip = self.floating_ips_client.show_floatingip(
floatingip_id)['floatingip']
self.assertEqual(status, floating_ip['status'],
@@ -970,9 +918,9 @@
return not should_succeed
return should_succeed
- return tempest.test.call_until_true(ping_remote,
- CONF.validation.ping_timeout,
- 1)
+ return test_utils.call_until_true(ping_remote,
+ CONF.validation.ping_timeout,
+ 1)
def _create_security_group(self, security_group_rules_client=None,
tenant_id=None,
@@ -1032,7 +980,7 @@
def _default_security_group(self, client=None, tenant_id=None):
"""Get default secgroup for given tenant_id.
- :returns: DeletableSecurityGroup -- default secgroup for given tenant
+ :returns: default secgroup for given tenant
"""
if client is None:
client = self.security_groups_client
@@ -1307,7 +1255,7 @@
return True
return False
- if not tempest.test.call_until_true(
+ if not test_utils.call_until_true(
check_state, timeout, interval):
msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
(node_id, state_attr, target_states))
@@ -1331,7 +1279,7 @@
self.get_node, instance_id=instance_id)
return node is not None
- if not tempest.test.call_until_true(
+ if not test_utils.call_until_true(
_get_node, CONF.baremetal.association_timeout, 1):
msg = ('Timed out waiting to get Ironic node by instance id %s'
% instance_id)
@@ -1403,8 +1351,12 @@
super(EncryptionScenarioTest, cls).setup_clients()
if CONF.volume_feature_enabled.api_v1:
cls.admin_volume_types_client = cls.os_adm.volume_types_client
+ cls.admin_encryption_types_client =\
+ cls.os_adm.encryption_types_client
else:
cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
+ cls.admin_encryption_types_client =\
+ cls.os_adm.encryption_types_v2_client
def create_volume_type(self, client=None, name=None):
if not client:
@@ -1423,7 +1375,7 @@
key_size=None, cipher=None,
control_location=None):
if not client:
- client = self.admin_volume_types_client
+ client = self.admin_encryption_types_client
if not type_id:
volume_type = self.create_volume_type()
type_id = volume_type['id']
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index cace90b..086b82d 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -74,7 +74,7 @@
self.assertEqual(aggregate_name, aggregate['name'])
self.assertEqual(azone, aggregate['availability_zone'])
self.assertEqual(hosts, aggregate['hosts'])
- for meta_key in metadata.keys():
+ for meta_key in metadata:
self.assertIn(meta_key, aggregate['metadata'])
self.assertEqual(metadata[meta_key],
aggregate['metadata'][meta_key])
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 655d19d..45c38f6 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -15,17 +15,14 @@
from oslo_log import log as logging
-from tempest import config
from tempest.scenario import manager
from tempest import test
-CONF = config.CONF
-
LOG = logging.getLogger(__name__)
class BaremetalBasicOps(manager.BaremetalScenarioTest):
- """This smoke test tests the pxe_ssh Ironic driver.
+ """This test tests the pxe_ssh Ironic driver.
It follows this basic set of operations:
* Creates a keypair
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index dcd77ad..1659ebe 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -53,7 +53,7 @@
volume_type = self.create_volume_type(name=volume_type)
self.create_encryption_type(type_id=volume_type['id'],
provider=encryption_provider,
- key_size=512,
+ key_size=256,
cipher='aes-xts-plain64',
control_location='front-end')
return self.create_volume(volume_type=volume_type['name'])
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index f7c7434..dba1c92 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -17,6 +17,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 import test
@@ -88,9 +89,9 @@
['server'])
return {'name': secgroup['name']} in body['security_groups']
- if not test.call_until_true(wait_for_secgroup_add,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if not test_utils.call_until_true(wait_for_secgroup_add,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
msg = ('Timed out waiting for adding security group %s to server '
'%s' % (secgroup['id'], server['id']))
raise exceptions.TimeoutException(msg)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e4b699e..8f27a5d 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -58,7 +58,7 @@
security_groups = [{'name': security_group['name']}]
network, subnet, router = self.create_networks()
public_network_id = CONF.network.public_network_id
- server_name = data_utils.rand_name('server-smoke')
+ server_name = data_utils.rand_name(self.__class__.__name__ + '-server')
server = self.create_server(
name=server_name,
networks=[{'uuid': network['id']}],
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 402a70c..519dbec 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -24,6 +24,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
from tempest.scenario import manager
from tempest import test
@@ -262,8 +263,9 @@
if port['id'] != old_port['id']]
return len(self.new_port_list) == 1
- if not test.call_until_true(check_ports, CONF.network.build_timeout,
- CONF.network.build_interval):
+ if not test_utils.call_until_true(
+ check_ports, CONF.network.build_timeout,
+ CONF.network.build_interval):
raise exceptions.TimeoutException(
"No new port attached to the server in time (%s sec)! "
"Old port: %s. Number of new ports: %d" % (
@@ -276,8 +278,9 @@
self.diff_list = [n for n in new_nic_list if n not in old_nic_list]
return len(self.diff_list) == 1
- if not test.call_until_true(check_new_nic, CONF.network.build_timeout,
- CONF.network.build_interval):
+ if not test_utils.call_until_true(
+ check_new_nic, CONF.network.build_timeout,
+ CONF.network.build_interval):
raise exceptions.TimeoutException("Interface not visible on the "
"guest after %s sec"
% CONF.network.build_timeout)
@@ -410,6 +413,7 @@
@test.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
@testtools.skipIf(CONF.baremetal.driver_enabled,
'Baremetal relies on a shared physical network.')
+ @decorators.skip_because(bug="1610994")
@test.services('compute', 'network')
def test_connectivity_between_vms_on_different_networks(self):
"""Test connectivity between VMs on different networks
@@ -591,9 +595,9 @@
return False
return True
- self.assertTrue(test.call_until_true(check_new_dns_server,
- renew_timeout,
- renew_delay),
+ self.assertTrue(test_utils.call_until_true(check_new_dns_server,
+ renew_timeout,
+ renew_delay),
msg="DHCP renewal failed to fetch "
"new DNS nameservers")
@@ -642,6 +646,8 @@
Nova should unbind the port from the instance on delete if the port was
not created by Nova as part of the boot request.
+
+ We should also be able to boot another server with the same port.
"""
# Setup the network, create a port and boot the server from that port.
self._setup_network_and_servers(boot_with_port=True)
@@ -670,6 +676,17 @@
self.assertEqual('', port['device_id'])
self.assertEqual('', port['device_owner'])
+ # Boot another server with the same port to make sure nothing was
+ # left around that could cause issues.
+ name = data_utils.rand_name('reuse-port')
+ server = self._create_server(name, self.network, port['id'])
+ port_list = self._list_ports(device_id=server['id'],
+ network_id=self.network['id'])
+ self.assertEqual(1, len(port_list),
+ 'There should only be one port created for '
+ 'server %s.' % server['id'])
+ self.assertEqual(port['id'], port_list[0]['id'])
+
@test.requires_ext(service='network', extension='l3_agent_scheduler')
@test.idempotent_id('2e788c46-fb3f-4ac9-8f82-0561555bea73')
@test.services('compute', 'network')
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 59ebb7a..dd86d90 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -18,6 +18,7 @@
from tempest import config
from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
from tempest.scenario import manager
from tempest import test
@@ -186,10 +187,10 @@
srv2_v6_addr_assigned = functools.partial(
guest_has_address, sshv4_2, ips_from_api_2['6'][i])
- self.assertTrue(test.call_until_true(srv1_v6_addr_assigned,
+ self.assertTrue(test_utils.call_until_true(srv1_v6_addr_assigned,
CONF.validation.ping_timeout, 1))
- self.assertTrue(test.call_until_true(srv2_v6_addr_assigned,
+ self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
CONF.validation.ping_timeout, 1))
self._check_connectivity(sshv4_1, ips_from_api_2['4'])
@@ -254,6 +255,7 @@
self._prepare_and_test(address6_mode='dhcpv6-stateless', n_subnets6=2,
dualnet=True)
+ @decorators.skip_because(bug="1540983")
@test.idempotent_id('9178ad42-10e4-47e9-8987-e02b170cc5cd')
@test.services('compute', 'network')
def test_dualnet_multi_prefix_slaac(self):
diff --git a/tempest/scenario/test_object_storage_basic_ops.py b/tempest/scenario/test_object_storage_basic_ops.py
index 63ffa0b..9ac1e30 100644
--- a/tempest/scenario/test_object_storage_basic_ops.py
+++ b/tempest/scenario/test_object_storage_basic_ops.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest import config
from tempest.scenario import manager
from tempest import test
-CONF = config.CONF
-
class TestObjectStorageBasicOps(manager.ObjectStorageScenarioTest):
"""Test swift basic ops.
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 446c87a..e031ff7 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -16,17 +16,14 @@
import json
import re
-from oslo_log import log as logging
-
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import test_utils
from tempest.scenario import manager
from tempest import test
CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
class TestServerBasicOps(manager.ScenarioTest):
@@ -74,29 +71,49 @@
self.assertEqual(self.fip, result, msg)
return 'Verification is successful!'
- if not test.call_until_true(exec_cmd_and_verify_output,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if not test_utils.call_until_true(exec_cmd_and_verify_output,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
raise exceptions.TimeoutException('Timed out while waiting to '
'verify metadata on server. '
'%s is empty.' % md_url)
+ def _mount_config_drive(self):
+ cmd_blkid = 'blkid | grep -i config-2'
+ result = self.ssh_client.exec_command(cmd_blkid)
+ dev_name = re.match('([^:]+)', result).group()
+ self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+
+ def _unmount_config_drive(self):
+ self.ssh_client.exec_command('sudo umount /mnt')
+
def verify_metadata_on_config_drive(self):
if self.run_ssh and CONF.compute_feature_enabled.config_drive:
# Verify metadata on config_drive
- cmd_blkid = 'blkid | grep -i config-2'
- result = self.ssh_client.exec_command(cmd_blkid)
- dev_name = re.match('([^:]+)', result).group()
- self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+ self._mount_config_drive()
cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
result = self.ssh_client.exec_command(cmd_md)
- self.ssh_client.exec_command('sudo umount /mnt')
+ self._unmount_config_drive()
result = json.loads(result)
self.assertIn('meta', result)
msg = ('Failed while verifying metadata on config_drive on server.'
' Result of command "%s" is NOT "%s".' % (cmd_md, self.md))
self.assertEqual(self.md, result['meta'], msg)
+ def verify_networkdata_on_config_drive(self):
+ if self.run_ssh and CONF.compute_feature_enabled.config_drive:
+ # Verify network data on config_drive
+ self._mount_config_drive()
+ cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json'
+ result = self.ssh_client.exec_command(cmd_md)
+ self._unmount_config_drive()
+ result = json.loads(result)
+ self.assertIn('services', result)
+ self.assertIn('links', result)
+ self.assertIn('networks', result)
+ # TODO(clarkb) construct network_data from known network
+ # instance info and do direct comparison.
+
@test.idempotent_id('7fff3fb3-91d8-4fd0-bd7d-0204f1f180ba')
@test.attr(type='smoke')
@test.services('compute', 'network')
@@ -116,4 +133,5 @@
self.verify_ssh(keypair)
self.verify_metadata()
self.verify_metadata_on_config_drive()
+ self.verify_networkdata_on_config_drive()
self.servers_client.delete_server(self.instance['id'])
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 6d3ecd4..7f04b0d 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common import compute
from tempest.common import waiters
from tempest import config
@@ -35,6 +33,12 @@
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestShelveInstance, cls).skip_checks()
+ if not CONF.compute_feature_enabled.shelve:
+ raise cls.skipException("Shelve is not available.")
+
def _shelve_then_unshelve_server(self, server):
compute.shelve_server(self.servers_client, server['id'],
force_shelve_offload=True)
@@ -49,25 +53,12 @@
security_group = self._create_security_group()
security_groups = [{'name': security_group['name']}]
- if boot_from_volume:
- volume = self.create_volume(size=CONF.volume.volume_size,
- imageRef=CONF.compute.image_ref)
- bd_map = [{
- 'device_name': 'vda',
- 'volume_id': volume['id'],
- 'delete_on_termination': '0'}]
-
- server = self.create_server(
- key_name=keypair['name'],
- security_groups=security_groups,
- block_device_mapping=bd_map,
- wait_until='ACTIVE')
- else:
- server = self.create_server(
- image_id=CONF.compute.image_ref,
- key_name=keypair['name'],
- security_groups=security_groups,
- wait_until='ACTIVE')
+ server = self.create_server(
+ image_id=CONF.compute.image_ref,
+ key_name=keypair['name'],
+ security_groups=security_groups,
+ wait_until='ACTIVE',
+ volume_backed=boot_from_volume)
instance_ip = self.get_server_ip(server)
timestamp = self.create_timestamp(instance_ip,
@@ -83,15 +74,11 @@
self.assertEqual(timestamp, timestamp2)
@test.idempotent_id('1164e700-0af0-4a4c-8792-35909a88743c')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
@test.services('compute', 'network', 'image')
def test_shelve_instance(self):
self._create_server_then_shelve_and_unshelve()
@test.idempotent_id('c1b6318c-b9da-490b-9c67-9339b627271f')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
@test.services('compute', 'volume', 'network', 'image')
def test_shelve_volume_backed_instance(self):
self._create_server_then_shelve_and_unshelve(boot_from_volume=True)
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index d6528a3..47c6e8d 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest import config
from tempest.scenario import manager
from tempest import test
@@ -33,9 +31,13 @@
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestSnapshotPattern, cls).skip_checks()
+ if not CONF.compute_feature_enabled.snapshot:
+ raise cls.skipException("Snapshotting is not available.")
+
@test.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4')
- @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
- 'Snapshotting is not available.')
@test.services('compute', 'network', 'image')
def test_snapshot_pattern(self):
# prepare for booting an instance
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index e7223c7..5fd934c 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -22,6 +22,7 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
@@ -89,9 +90,9 @@
LOG.debug("Partitions:%s" % part)
return CONF.compute.volume_device_name in part
- if not test.call_until_true(_func,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if not test_utils.call_until_true(_func,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
raise exceptions.TimeoutException
@decorators.skip_because(bug="1205344")
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
old mode 100644
new mode 100755
index 25d825a..3f6d9c4
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -48,7 +48,8 @@
def _create_volume_from_image(self):
img_uuid = CONF.compute.image_ref
- vol_name = data_utils.rand_name('volume-origin')
+ vol_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume-origin')
return self.create_volume(name=vol_name, imageRef=img_uuid)
def _get_bdm(self, vol_id, delete_on_termination=False):
@@ -79,7 +80,8 @@
**create_kwargs)
def _create_snapshot_from_volume(self, vol_id):
- snap_name = data_utils.rand_name('snapshot')
+ snap_name = data_utils.rand_name(
+ self.__class__.__name__ + '-snapshot')
snap = self.snapshots_client.create_snapshot(
volume_id=vol_id,
force=True,
@@ -99,7 +101,8 @@
return snap
def _create_volume_from_snapshot(self, snap_id):
- vol_name = data_utils.rand_name('volume')
+ vol_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume')
return self.create_volume(name=vol_name, snapshot_id=snap_id)
def _delete_server(self, server):
@@ -170,7 +173,7 @@
instance = self._boot_instance_from_volume(volume_origin['id'],
delete_on_termination=True)
# create EBS image
- name = data_utils.rand_name('image')
+ name = data_utils.rand_name(self.__class__.__name__ + '-image')
image = self.create_server_snapshot(instance, name=name)
# delete instance
diff --git a/tempest/service_clients.py b/tempest/service_clients.py
deleted file mode 100644
index 386e621..0000000
--- a/tempest/service_clients.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.lib import auth
-from tempest.lib import exceptions
-
-
-def tempest_modules():
- """List of service client modules available in Tempest.
-
- Provides a list of service modules available Tempest.
- """
- return set(['compute', 'identity.v2', 'identity.v3', 'image.v1',
- 'image.v2', 'network', 'object-storage', 'volume.v1',
- 'volume.v2', 'volume.v3'])
-
-
-def available_modules():
- """List of service client modules available in Tempest and plugins"""
- # TODO(andreaf) For now this returns only tempest_modules
- return tempest_modules()
-
-
-class ServiceClients(object):
- """Service client provider class
-
- The ServiceClients object provides a useful means for tests to access
- service clients configured for a specified set of credentials.
- It hides some of the complexity from the authorization and configuration
- layers.
-
- Examples:
-
- >>> from tempest import service_clients
- >>> johndoe = cred_provider.get_creds_by_role(['johndoe'])
- >>> johndoe_clients = service_clients.ServiceClients(johndoe,
- >>> identity_uri)
- >>> johndoe_servers = johndoe_clients.servers_client.list_servers()
-
- """
- # NOTE(andreaf) This class does not depend on tempest configuration
- # and its meant for direct consumption by external clients such as tempest
- # plugins. Tempest provides a wrapper class, `clients.Manager`, that
- # initialises this class using values from tempest CONF object. The wrapper
- # class should only be used by tests hosted in Tempest.
-
- def __init__(self, credentials, identity_uri, region=None, scope='project',
- disable_ssl_certificate_validation=True, ca_certs=None,
- trace_requests='', client_parameters=None):
- """Service Clients provider
-
- Instantiate a `ServiceClients` object, from a set of credentials and an
- identity URI. The identity version is inferred from the credentials
- object. Optionally auth scope can be provided.
-
- A few parameters can be given a value which is applied as default
- for all service clients: region, dscv, ca_certs, trace_requests.
-
- Parameters dscv, ca_certs and trace_requests all apply to the auth
- provider as well as any service clients provided by this manager.
-
- Any other client parameter must be set via client_parameters.
- The list of available parameters is defined in the service clients
- interfaces. For reference, most clients will accept 'region',
- 'service', 'endpoint_type', 'build_timeout' and 'build_interval', which
- are all inherited from RestClient.
-
- The `config` module in Tempest exposes an helper function
- `service_client_config` that can be used to extract from configuration
- a dictionary ready to be injected in kwargs.
-
- Exceptions are:
- - Token clients for 'identity' have a very different interface
- - Volume client for 'volume' accepts 'default_volume_size'
- - Servers client from 'compute' accepts 'enable_instance_password'
-
- Examples:
-
- >>> identity_params = config.service_client_config('identity')
- >>> params = {
- >>> 'identity': identity_params,
- >>> 'compute': {'region': 'region2'}}
- >>> manager = lib_manager.Manager(
- >>> my_creds, identity_uri, client_parameters=params)
-
- :param credentials: An instance of `auth.Credentials`
- :param identity_uri: URI of the identity API. This should be a
- mandatory parameter, and it will so soon.
- :param region: Default value of region for service clients.
- :param scope: default scope for tokens produced by the auth provider
- :param disable_ssl_certificate_validation Applies to auth and to all
- service clients.
- :param ca_certs Applies to auth and to all service clients.
- :param trace_requests Applies to auth and to all service clients.
- :param client_parameters Dictionary with parameters for service
- clients. Keys of the dictionary are the service client service
- name, as declared in `service_clients.available_modules()` except
- for the version. Values are dictionaries of parameters that are
- going to be passed to all clients in the service client module.
-
- Examples:
-
- >>> params_service_x = {'param_name': 'param_value'}
- >>> client_parameters = { 'service_x': params_service_x }
-
- >>> params_service_y = config.service_client_config('service_y')
- >>> client_parameters['service_y'] = params_service_y
-
- """
- self.credentials = credentials
- self.identity_uri = identity_uri
- if not identity_uri:
- raise exceptions.InvalidCredentials(
- 'ServiceClients requires a non-empty identity_uri.')
- self.region = region
- # Check if passed or default credentials are valid
- if not self.credentials.is_valid():
- raise exceptions.InvalidCredentials()
- # Get the identity classes matching the provided credentials
- # TODO(andreaf) Define a new interface in Credentials to get
- # the API version from an instance
- identity = [(k, auth.IDENTITY_VERSION[k][1]) for k in
- auth.IDENTITY_VERSION.keys() if
- isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
- # Zero matches or more than one are both not valid.
- if len(identity) != 1:
- raise exceptions.InvalidCredentials()
- self.auth_version, auth_provider_class = identity[0]
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
- self.trace_requests = trace_requests
- # Creates an auth provider for the credentials
- self.auth_provider = auth_provider_class(
- self.credentials, self.identity_uri, scope=scope,
- disable_ssl_certificate_validation=self.dscv,
- ca_certs=self.ca_certs, trace_requests=self.trace_requests)
- # Setup some defaults for client parameters of registered services
- client_parameters = client_parameters or {}
- self.parameters = {}
- # Parameters are provided for unversioned services
- unversioned_services = set(
- [x.split('.')[0] for x in available_modules()])
- for service in unversioned_services:
- self.parameters[service] = self._setup_parameters(
- client_parameters.pop(service, {}))
- # Check that no client parameters was supplied for unregistered clients
- if client_parameters:
- raise exceptions.UnknownServiceClient(
- services=list(client_parameters.keys()))
-
- def _setup_parameters(self, parameters):
- """Setup default values for client parameters
-
- Region by default is the region passed as an __init__ parameter.
- Checks that no parameter for an unknown service is provided.
- """
- _parameters = {}
- # Use region from __init__
- if self.region:
- _parameters['region'] = self.region
- # Update defaults with specified parameters
- _parameters.update(parameters)
- # If any parameter is left, parameters for an unknown service were
- # provided as input. Fail rather than ignore silently.
- return _parameters
diff --git a/tempest/services/baremetal/v1/json/baremetal_client.py b/tempest/services/baremetal/v1/json/baremetal_client.py
old mode 100644
new mode 100755
index f24ef68..ede0d90
--- a/tempest/services/baremetal/v1/json/baremetal_client.py
+++ b/tempest/services/baremetal/v1/json/baremetal_client.py
@@ -20,7 +20,11 @@
@base.handle_errors
def list_nodes(self, **kwargs):
- """List all existing nodes."""
+ """List all existing nodes.
+
+ Available params: see http://developer.openstack.org/api-ref/
+ baremetal/index.html#list-nodes
+ """
return self._list_request('nodes', **kwargs)
@base.handle_errors
@@ -35,7 +39,11 @@
@base.handle_errors
def list_ports(self, **kwargs):
- """List all existing ports."""
+ """List all existing ports.
+
+ Available params: see http://developer.openstack.org/api-ref/
+ baremetal/index.html?expanded=#list-ports
+ """
return self._list_request('ports', **kwargs)
@base.handle_errors
@@ -50,7 +58,11 @@
@base.handle_errors
def list_ports_detail(self, **kwargs):
- """Details list all existing ports."""
+ """Details list all existing ports.
+
+ Available params: see http://developer.openstack.org/api-ref/baremetal/
+ index.html?expanded=#list-detailed-ports
+ """
return self._list_request('/ports/detail', **kwargs)
@base.handle_errors
diff --git a/tempest/services/identity/__init__.py b/tempest/services/identity/__init__.py
index 0e24926..53c223f 100644
--- a/tempest/services/identity/__init__.py
+++ b/tempest/services/identity/__init__.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.services.identity import v2
+from tempest.lib.services.identity import v2
from tempest.services.identity import v3
__all__ = ['v2', 'v3']
diff --git a/tempest/services/identity/v2/__init__.py b/tempest/services/identity/v2/__init__.py
deleted file mode 100644
index ac2a874..0000000
--- a/tempest/services/identity/v2/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
-from tempest.lib.services.identity.v2.roles_client import RolesClient
-from tempest.lib.services.identity.v2.services_client import ServicesClient
-from tempest.lib.services.identity.v2.tenants_client import TenantsClient
-from tempest.lib.services.identity.v2.token_client import TokenClient
-from tempest.lib.services.identity.v2.users_client import UsersClient
-from tempest.services.identity.v2.json.identity_client import IdentityClient
-
-__all__ = ['EndpointsClient', 'TokenClient', 'IdentityClient', 'RolesClient',
- 'ServicesClient', 'TenantsClient', 'UsersClient']
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
index 144c5a9..3f5c3d5 100644
--- a/tempest/services/identity/v3/__init__.py
+++ b/tempest/services/identity/v3/__init__.py
@@ -12,22 +12,24 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.lib.services.identity.v3.token_client import V3TokenClient
-from tempest.services.identity.v3.json.credentials_client import \
+from tempest.lib.services.identity.v3.credentials_client import \
CredentialsClient
+from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
+from tempest.lib.services.identity.v3.groups_client import GroupsClient
+from tempest.lib.services.identity.v3.identity_client import IdentityClient
+from tempest.lib.services.identity.v3.inherited_roles_client import \
+ InheritedRolesClient
+from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.projects_client import ProjectsClient
+from tempest.lib.services.identity.v3.regions_client import RegionsClient
+from tempest.lib.services.identity.v3.roles_client import RolesClient
+from tempest.lib.services.identity.v3.services_client import ServicesClient
+from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.identity.v3.trusts_client import TrustsClient
+from tempest.lib.services.identity.v3.users_client import UsersClient
from tempest.services.identity.v3.json.domains_client import DomainsClient
-from tempest.services.identity.v3.json.endpoints_client import EndPointsClient
-from tempest.services.identity.v3.json.groups_client import GroupsClient
-from tempest.services.identity.v3.json.identity_client import IdentityClient
-from tempest.services.identity.v3.json.policies_client import PoliciesClient
-from tempest.services.identity.v3.json.projects_client import ProjectsClient
-from tempest.services.identity.v3.json.regions_client import RegionsClient
-from tempest.services.identity.v3.json.roles_client import RolesClient
-from tempest.services.identity.v3.json.services_client import ServicesClient
-from tempest.services.identity.v3.json.trusts_client import TrustsClient
-from tempest.services.identity.v3.json.users_clients import UsersClient
-__all__ = ['V3TokenClient', 'CredentialsClient', 'DomainsClient',
- 'EndPointsClient', 'GroupsClient', 'IdentityClient',
- 'PoliciesClient', 'ProjectsClient', 'RegionsClient', 'RolesClient',
- 'ServicesClient', 'TrustsClient', 'UsersClient', ]
+__all__ = ['CredentialsClient', 'EndPointsClient', 'GroupsClient',
+ 'IdentityClient', 'InheritedRolesClient', 'PoliciesClient',
+ 'ProjectsClient', 'RegionsClient', 'RolesClient', 'ServicesClient',
+ 'V3TokenClient', 'TrustsClient', 'UsersClient', 'DomainsClient', ]
diff --git a/tempest/services/identity/v3/json/domains_client.py b/tempest/services/identity/v3/json/domains_client.py
index d129a0a..fe929a5 100644
--- a/tempest/services/identity/v3/json/domains_client.py
+++ b/tempest/services/identity/v3/json/domains_client.py
@@ -38,7 +38,7 @@
def delete_domain(self, domain_id):
"""Deletes a domain."""
- resp, body = self.delete('domains/%s' % str(domain_id))
+ resp, body = self.delete('domains/%s' % domain_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/roles_client.py b/tempest/services/identity/v3/json/roles_client.py
deleted file mode 100644
index bdb0490..0000000
--- a/tempest/services/identity/v3/json/roles_client.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# Copyright 2016 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-
-
-class RolesClient(rest_client.RestClient):
- api_version = "v3"
-
- def create_role(self, **kwargs):
- """Create a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.post('roles', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_role(self, role_id):
- """GET a Role."""
- resp, body = self.get('roles/%s' % str(role_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_roles(self):
- """Get the list of Roles."""
- resp, body = self.get("roles")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_role(self, role_id, **kwargs):
- """Update a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.patch('roles/%s' % str(role_id), post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_role(self, role_id):
- """Delete a role."""
- resp, body = self.delete('roles/%s' % str(role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def assign_user_role_on_project(self, project_id, user_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def assign_user_role_on_domain(self, domain_id, user_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_user_roles_on_project(self, project_id, user_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/users/%s/roles' %
- (project_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_user_roles_on_domain(self, domain_id, user_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/users/%s/roles' %
- (domain_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_project(self, project_id, user_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def check_user_role_existence_on_project(self, project_id,
- user_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def check_user_role_existence_on_domain(self, domain_id,
- user_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def assign_group_role_on_project(self, project_id, group_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def assign_group_role_on_domain(self, domain_id, group_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_group_roles_on_project(self, project_id, group_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/groups/%s/roles' %
- (project_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_group_roles_on_domain(self, domain_id, group_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/groups/%s/roles' %
- (domain_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_project(self, project_id, group_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def check_role_from_group_on_project_existence(self, project_id,
- group_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def check_role_from_group_on_domain_existence(self, domain_id,
- group_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def assign_inherited_role_on_domains_user(
- self, domain_id, user_id, role_id):
- """Assigns a role to a user on projects owned by a domain."""
- resp, body = self.put(
- "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
- % (domain_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def revoke_inherited_role_from_user_on_domain(
- self, domain_id, user_id, role_id):
- """Revokes an inherited project role from a user on a domain."""
- resp, body = self.delete(
- "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
- % (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_inherited_project_role_for_user_on_domain(
- self, domain_id, user_id):
- """Lists the inherited project roles on a domain for a user."""
- resp, body = self.get(
- "OS-INHERIT/domains/%s/users/%s/roles/inherited_to_projects"
- % (domain_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def check_user_inherited_project_role_on_domain(
- self, domain_id, user_id, role_id):
- """Checks whether a user has an inherited project role on a domain."""
- resp, body = self.head(
- "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
- % (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def assign_inherited_role_on_domains_group(
- self, domain_id, group_id, role_id):
- """Assigns a role to a group on projects owned by a domain."""
- resp, body = self.put(
- "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
- % (domain_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def revoke_inherited_role_from_group_on_domain(
- self, domain_id, group_id, role_id):
- """Revokes an inherited project role from a group on a domain."""
- resp, body = self.delete(
- "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
- % (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_inherited_project_role_for_group_on_domain(
- self, domain_id, group_id):
- """Lists the inherited project roles on a domain for a group."""
- resp, body = self.get(
- "OS-INHERIT/domains/%s/groups/%s/roles/inherited_to_projects"
- % (domain_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def check_group_inherited_project_role_on_domain(
- self, domain_id, group_id, role_id):
- """Checks whether a group has an inherited project role on a domain."""
- resp, body = self.head(
- "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
- % (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def assign_inherited_role_on_projects_user(
- self, project_id, user_id, role_id):
- """Assigns a role to a user on projects in a subtree."""
- resp, body = self.put(
- "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
- % (project_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def revoke_inherited_role_from_user_on_project(
- self, project_id, user_id, role_id):
- """Revokes an inherited role from a user on a project."""
- resp, body = self.delete(
- "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
- % (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def check_user_has_flag_on_inherited_to_project(
- self, project_id, user_id, role_id):
- """Checks whether a user has a role assignment"""
- """with the inherited_to_projects flag on a project."""
- resp, body = self.head(
- "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
- % (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def assign_inherited_role_on_projects_group(
- self, project_id, group_id, role_id):
- """Assigns a role to a group on projects in a subtree."""
- resp, body = self.put(
- "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
- % (project_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def revoke_inherited_role_from_group_on_project(
- self, project_id, group_id, role_id):
- """Revokes an inherited role from a group on a project."""
- resp, body = self.delete(
- "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
- % (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def check_group_has_flag_on_inherited_to_project(
- self, project_id, group_id, role_id):
- """Checks whether a group has a role assignment"""
- """with the inherited_to_projects flag on a project."""
- resp, body = self.head(
- "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
- % (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/services/identity/v3/json/users_clients.py
deleted file mode 100644
index 73bd343..0000000
--- a/tempest/services/identity/v3/json/users_clients.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright 2016 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class UsersClient(rest_client.RestClient):
- api_version = "v3"
-
- def create_user(self, user_name, password=None, project_id=None,
- email=None, domain_id='default', **kwargs):
- """Creates a user."""
- en = kwargs.get('enabled', True)
- description = kwargs.get('description', None)
- default_project_id = kwargs.get('default_project_id')
- post_body = {
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'description': description,
- 'domain_id': domain_id,
- 'email': email,
- 'enabled': en,
- 'name': user_name,
- 'password': password
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.post('users', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user(self, user_id, name, **kwargs):
- """Updates a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateUser
- """
- body = self.show_user(user_id)['user']
- email = kwargs.get('email', body['email'])
- en = kwargs.get('enabled', body['enabled'])
- project_id = kwargs.get('project_id', body['project_id'])
- if 'default_project_id' in body.keys():
- default_project_id = kwargs.get('default_project_id',
- body['default_project_id'])
- else:
- default_project_id = kwargs.get('default_project_id')
- description = kwargs.get('description', body['description'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'name': name,
- 'email': email,
- 'enabled': en,
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'id': user_id,
- 'domain_id': domain_id,
- 'description': description
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.patch('users/%s' % user_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user_password(self, user_id, **kwargs):
- """Update a user password
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#changeUserPassword
- """
- update_user = json.dumps({'user': kwargs})
- resp, _ = self.post('users/%s/password' % user_id, update_user)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def list_user_projects(self, user_id):
- """Lists the projects on which a user has roles assigned."""
- resp, body = self.get('users/%s/projects' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_users(self, params=None):
- """Get the list of users."""
- url = 'users'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_user(self, user_id):
- """GET a user."""
- resp, body = self.get("users/%s" % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_user(self, user_id):
- """Deletes a User."""
- resp, body = self.delete("users/%s" % user_id)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_user_groups(self, user_id):
- """Lists groups which a user belongs to."""
- resp, body = self.get('users/%s/groups' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 33dba6e..ec36fb7 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -42,12 +42,6 @@
self.expected_success(201, resp.status)
return resp, body
- def update_object(self, container, object_name, data):
- """Upload data to replace current storage object."""
- resp, body = self.create_object(container, object_name, data)
- self.expected_success(201, resp.status)
- return resp, body
-
def delete_object(self, container, object_name, params=None):
"""Delete storage object."""
url = "%s/%s" % (str(container), str(object_name))
@@ -201,7 +195,6 @@
# Read the 100 status prior to sending the data
response = conn.response_class(conn.sock,
- strict=conn.strict,
method=conn._method)
_, status, _ = response._read_status()
@@ -237,37 +230,3 @@
conn = httplib.HTTPConnection(parsed_url.netloc)
return conn
-
-
-def put_object_connection(base_url, container, name, contents=None,
- chunk_size=65536, headers=None, query_string=None):
- """Helper function to make connection to put object with httplib
-
- :param base_url: base_url of an object client
- :param container: container name that the object is in
- :param name: object name to put
- :param contents: a string or a file like object to read object data
- from; if None, a zero-byte put will be done
- :param chunk_size: chunk size of data to write; it defaults to 65536;
- used only if the contents object has a 'read'
- method, eg. file-like objects, ignored otherwise
- :param headers: additional headers to include in the request, if any
- :param query_string: if set will be appended with '?' to generated path
- """
- parsed = urlparse.urlparse(base_url)
-
- path = str(parsed.path) + "/"
- path += "%s/%s" % (str(container), str(name))
-
- conn = create_connection(parsed)
-
- if query_string:
- path += '?' + query_string
- if headers:
- headers = dict(headers)
- else:
- headers = {}
-
- conn.request('PUT', path, contents, headers)
-
- return conn
diff --git a/tempest/services/volume/base/admin/__init__.py b/tempest/services/volume/base/admin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/volume/base/admin/__init__.py
+++ /dev/null
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
deleted file mode 100644
index e4d9014..0000000
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ /dev/null
@@ -1,215 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-
-class BaseTypesClient(rest_client.RestClient):
- """Client class to send CRUD Volume Types API requests"""
-
- def is_resource_deleted(self, resource):
- # to use this method self.resource must be defined to respective value
- # Resource is a dictionary containing resource id and type
- # Resource : {"id" : resource_id
- # "type": resource_type}
- try:
- if resource['type'] == "volume-type":
- self.show_volume_type(resource['id'])
- elif resource['type'] == "encryption-type":
- body = self.show_encryption_type(resource['id'])
- if not body:
- return True
- else:
- msg = (" resource value is either not defined or incorrect.")
- raise lib_exc.UnprocessableEntity(msg)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'volume-type/encryption-type'
-
- def list_volume_types(self, **params):
- """List all the volume_types created."""
- url = 'types'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_type(self, volume_id):
- """Returns the details of a single volume_type."""
- url = "types/%s" % str(volume_id)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume_type(self, **kwargs):
- """Create volume type.
-
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolumeType
- """
- post_body = json.dumps({'volume_type': kwargs})
- resp, body = self.post('types', post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_type(self, volume_id):
- """Deletes the Specified Volume_type."""
- resp, body = self.delete("types/%s" % str(volume_id))
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_volume_types_extra_specs(self, vol_type_id, **params):
- """List all the volume_types extra specs created.
-
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
-
-
- """
- url = 'types/%s/extra_specs' % str(vol_type_id)
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_type_extra_specs(self, vol_type_id, extra_specs_name):
- """Returns the details of a single volume_type extra spec."""
- url = "types/%s/extra_specs/%s" % (str(vol_type_id),
- str(extra_specs_name))
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume_type_extra_specs(self, vol_type_id, extra_specs):
- """Creates a new Volume_type extra spec.
-
- vol_type_id: Id of volume_type.
- extra_specs: A dictionary of values to be used as extra_specs.
- """
- url = "types/%s/extra_specs" % str(vol_type_id)
- post_body = json.dumps({'extra_specs': extra_specs})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
- """Deletes the Specified Volume_type extra spec."""
- resp, body = self.delete("types/%s/extra_specs/%s" % (
- (str(vol_id)), str(extra_spec_name)))
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
- extra_specs):
- """Update a volume_type extra spec.
-
- vol_type_id: Id of volume_type.
- extra_spec_name: Name of the extra spec to be updated.
- extra_spec: A dictionary of with key as extra_spec_name and the
- updated value.
- """
- url = "types/%s/extra_specs/%s" % (str(vol_type_id),
- str(extra_spec_name))
- put_body = json.dumps(extra_specs)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_encryption_type(self, vol_type_id):
- """Get the volume encryption type for the specified volume type.
-
- vol_type_id: Id of volume_type.
- """
- url = "/types/%s/encryption" % str(vol_type_id)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_encryption_type(self, vol_type_id, **kwargs):
- """Create encryption type.
-
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
- """
- url = "/types/%s/encryption" % str(vol_type_id)
- post_body = json.dumps({'encryption': kwargs})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_encryption_type(self, vol_type_id):
- """Delete the encryption type for the specified volume-type."""
- resp, body = self.delete(
- "/types/%s/encryption/provider" % str(vol_type_id))
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def add_type_access(self, volume_type_id, **kwargs):
- """Adds volume type access for the given project.
-
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html
- #createVolumeTypeAccessExt
- """
- post_body = json.dumps({'addProjectAccess': kwargs})
- url = 'types/%s/action' % (volume_type_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def remove_type_access(self, volume_type_id, **kwargs):
- """Removes volume type access for the given project.
-
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html
- #removeVolumeTypeAccessExt
- """
- post_body = json.dumps({'removeProjectAccess': kwargs})
- url = 'types/%s/action' % (volume_type_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_type_access(self, volume_type_id):
- """Print access information about the given volume type."""
- url = 'types/%s/os-volume-type-access' % (volume_type_id)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index 3842d66..a57e628 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -51,13 +51,13 @@
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
- resp, body = self.delete('backups/%s' % (str(backup_id)))
+ resp, body = self.delete('backups/%s' % backup_id)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
"""Returns the details of a single backup."""
- url = "backups/%s" % str(backup_id)
+ url = "backups/%s" % backup_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -89,6 +89,13 @@
self.expected_success(201, resp.status)
return rest_client.ResponseBody(resp, body)
+ def reset_backup_status(self, backup_id, status):
+ """Reset the specified backup's status."""
+ post_body = json.dumps({'os-reset_status': {"status": status}})
+ resp, body = self.post('backups/%s/action' % backup_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def wait_for_backup_status(self, backup_id, status):
"""Waits for a Backup to reach a given status."""
body = self.show_backup(backup_id)['backup']
@@ -99,7 +106,7 @@
time.sleep(self.build_interval)
body = self.show_backup(backup_id)['backup']
backup_status = body['status']
- if backup_status == 'error':
+ if backup_status == 'error' and backup_status != status:
raise exceptions.VolumeBackupException(backup_id=backup_id)
if int(time.time()) - start >= self.build_timeout:
@@ -109,14 +116,9 @@
self.build_timeout))
raise exceptions.TimeoutException(message)
- def wait_for_backup_deletion(self, backup_id):
- """Waits for backup deletion"""
- start_time = int(time.time())
- while True:
- try:
- self.show_backup(backup_id)
- except lib_exc.NotFound:
- return
- if int(time.time()) - start_time >= self.build_timeout:
- raise exceptions.TimeoutException
- time.sleep(self.build_interval)
+ def is_resource_deleted(self, id):
+ try:
+ self.show_backup(id)
+ except lib_exc.NotFound:
+ return True
+ return False
diff --git a/tempest/services/volume/base/base_qos_client.py b/tempest/services/volume/base/base_qos_client.py
index 2d9f02a..0ce76a7 100644
--- a/tempest/services/volume/base/base_qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -12,11 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
from oslo_serialization import jsonutils as json
-from tempest import exceptions
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
@@ -36,37 +33,6 @@
"""Returns the primary type of resource this client works with."""
return 'qos'
- def wait_for_qos_operations(self, qos_id, operation, args=None):
- """Waits for a qos operations to be completed.
-
- NOTE : operation value is required for wait_for_qos_operations()
- operation = 'qos-key' / 'disassociate' / 'disassociate-all'
- args = keys[] when operation = 'qos-key'
- args = volume-type-id disassociated when operation = 'disassociate'
- args = None when operation = 'disassociate-all'
- """
- start_time = int(time.time())
- while True:
- if operation == 'qos-key-unset':
- body = self.show_qos(qos_id)['qos_specs']
- if not any(key in body['specs'] for key in args):
- return
- elif operation == 'disassociate':
- body = self.show_association_qos(qos_id)['qos_associations']
- if not any(args in body[i]['id'] for i in range(0, len(body))):
- return
- elif operation == 'disassociate-all':
- body = self.show_association_qos(qos_id)['qos_associations']
- if not body:
- return
- else:
- msg = (" operation value is either not defined or incorrect.")
- raise lib_exc.UnprocessableEntity(msg)
-
- if int(time.time()) - start_time >= self.build_timeout:
- raise exceptions.TimeoutException
- time.sleep(self.build_interval)
-
def create_qos(self, **kwargs):
"""Create a QoS Specification.
@@ -82,7 +48,7 @@
def delete_qos(self, qos_id, force=False):
"""Delete the specified QoS specification."""
resp, body = self.delete(
- "qos-specs/%s?force=%s" % (str(qos_id), force))
+ "qos-specs/%s?force=%s" % (qos_id, force))
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -96,7 +62,7 @@
def show_qos(self, qos_id):
"""Get the specified QoS specification."""
- url = "qos-specs/%s" % str(qos_id)
+ url = "qos-specs/%s" % qos_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -128,7 +94,7 @@
def associate_qos(self, qos_id, vol_type_id):
"""Associate the specified QoS with specified volume-type."""
- url = "qos-specs/%s/associate" % str(qos_id)
+ url = "qos-specs/%s/associate" % qos_id
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
@@ -136,7 +102,7 @@
def show_association_qos(self, qos_id):
"""Get the association of the specified QoS specification."""
- url = "qos-specs/%s/associations" % str(qos_id)
+ url = "qos-specs/%s/associations" % qos_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -144,7 +110,7 @@
def disassociate_qos(self, qos_id, vol_type_id):
"""Disassociate the specified QoS with specified volume-type."""
- url = "qos-specs/%s/disassociate" % str(qos_id)
+ url = "qos-specs/%s/disassociate" % qos_id
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
@@ -152,7 +118,7 @@
def disassociate_all_qos(self, qos_id):
"""Disassociate the specified QoS with all associations."""
- url = "qos-specs/%s/disassociate_all" % str(qos_id)
+ url = "qos-specs/%s/disassociate_all" % qos_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
old mode 100644
new mode 100755
index da7bb01..38a6dc7
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
@@ -18,16 +17,17 @@
from tempest.lib import exceptions as lib_exc
-LOG = logging.getLogger(__name__)
-
-
class BaseSnapshotsClient(rest_client.RestClient):
"""Base Client class to send CRUD Volume API requests."""
create_resp = 200
def list_snapshots(self, detail=False, **params):
- """List all the snapshot."""
+ """List all the snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#listSnapshots
+ """
url = 'snapshots'
if detail:
url += '/detail'
@@ -40,8 +40,12 @@
return rest_client.ResponseBody(resp, body)
def show_snapshot(self, snapshot_id):
- """Returns the details of a single snapshot."""
- url = "snapshots/%s" % str(snapshot_id)
+ """Returns the details of a single snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#showSnapshot
+ """
+ url = "snapshots/%s" % snapshot_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -72,8 +76,12 @@
return rest_client.ResponseBody(resp, body)
def delete_snapshot(self, snapshot_id):
- """Delete Snapshot."""
- resp, body = self.delete("snapshots/%s" % str(snapshot_id))
+ """Delete Snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#deleteSnapshot
+ """
+ resp, body = self.delete("snapshots/%s" % snapshot_id)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -104,7 +112,7 @@
# Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
post_body = json.dumps({'os-update_snapshot_status': kwargs})
- url = 'snapshots/%s/action' % str(snapshot_id)
+ url = 'snapshots/%s/action' % snapshot_id
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -112,15 +120,20 @@
def create_snapshot_metadata(self, snapshot_id, metadata):
"""Create metadata for the snapshot."""
put_body = json.dumps({'metadata': metadata})
- url = "snapshots/%s/metadata" % str(snapshot_id)
+ url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata(self, snapshot_id):
- """Get metadata of the snapshot."""
- url = "snapshots/%s/metadata" % str(snapshot_id)
+ """Get metadata of the snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#
+ showSnapshotMetadata
+ """
+ url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -130,10 +143,11 @@
"""Update metadata for the snapshot.
Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateSnapshotMetadata
+ api-ref-blockstorage-v2.html#
+ updateSnapshotMetadata
"""
put_body = json.dumps(kwargs)
- url = "snapshots/%s/metadata" % str(snapshot_id)
+ url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -146,7 +160,7 @@
# link to api-site.
# LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
put_body = json.dumps(kwargs)
- url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -154,7 +168,7 @@
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
- url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
resp, body = self.delete(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
index d694c53..1cb1ef5 100755
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -26,16 +26,6 @@
create_resp = 200
- def __init__(self, auth_provider, service, region,
- default_volume_size=1, **kwargs):
- super(BaseVolumesClient, self).__init__(
- auth_provider, service, region, **kwargs)
- self.default_volume_size = default_volume_size
-
- def get_attachment_from_volume(self, volume):
- """Return the element 'attachment' from input volumes."""
- return volume['attachments'][0]
-
def _prepare_params(self, params):
"""Prepares params for use in get or _ext_get methods.
@@ -62,20 +52,9 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def show_pools(self, detail=False):
- # List all the volumes pools (hosts)
- url = 'scheduler-stats/get_pools'
- if detail:
- url += '?detail=True'
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
def show_volume(self, volume_id):
"""Returns the details of a single volume."""
- url = "volumes/%s" % str(volume_id)
+ url = "volumes/%s" % volume_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -87,8 +66,6 @@
Available params: see http://developer.openstack.org/
api-ref-blockstorage-v2.html#createVolume
"""
- if 'size' not in kwargs:
- kwargs['size'] = self.default_volume_size
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('volumes', post_body)
body = json.loads(body)
@@ -109,7 +86,7 @@
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
- resp, body = self.delete("volumes/%s" % str(volume_id))
+ resp, body = self.delete("volumes/%s" % volume_id)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -201,22 +178,6 @@
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
- def volume_begin_detaching(self, volume_id):
- """Volume Begin Detaching."""
- # ref cinder/api/contrib/volume_actions.py#L158
- post_body = json.dumps({'os-begin_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def volume_roll_detaching(self, volume_id):
- """Volume Roll Detaching."""
- # cinder/api/contrib/volume_actions.py#L170
- post_body = json.dumps({'os-roll_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer.
@@ -231,7 +192,7 @@
def show_volume_transfer(self, transfer_id):
"""Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % str(transfer_id)
+ url = "os-volume-transfer/%s" % transfer_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -253,7 +214,7 @@
def delete_volume_transfer(self, transfer_id):
"""Delete a volume transfer."""
- resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
+ resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -288,7 +249,7 @@
def create_volume_metadata(self, volume_id, metadata):
"""Create metadata for the volume."""
put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
+ url = "volumes/%s/metadata" % volume_id
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -296,7 +257,7 @@
def show_volume_metadata(self, volume_id):
"""Get metadata of the volume."""
- url = "volumes/%s/metadata" % str(volume_id)
+ url = "volumes/%s/metadata" % volume_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -305,7 +266,7 @@
def update_volume_metadata(self, volume_id, metadata):
"""Update metadata for the volume."""
put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
+ url = "volumes/%s/metadata" % volume_id
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -314,7 +275,7 @@
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
put_body = json.dumps({'meta': meta_item})
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
@@ -322,33 +283,11 @@
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
resp, body = self.delete(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def update_volume_image_metadata(self, volume_id, **kwargs):
- """Update image metadata for the volume.
-
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html
- #setVolumeimagemetadata
- """
- post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
- url = "volumes/%s/action" % (volume_id)
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_image_metadata(self, volume_id, key_name):
- """Delete image metadata item for the volume."""
- post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
- url = "volumes/%s/action" % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
def retype_volume(self, volume_id, **kwargs):
"""Updates volume with new volume type."""
post_body = json.dumps({'os-retype': kwargs})
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index 6bdb8c4..72868bc 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -12,19 +12,21 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.services.volume.v1.json.admin.hosts_client import HostsClient
-from tempest.services.volume.v1.json.admin.quotas_client import QuotasClient
-from tempest.services.volume.v1.json.admin.services_client import \
- ServicesClient
-from tempest.services.volume.v1.json.admin.types_client import TypesClient
-from tempest.services.volume.v1.json.availability_zone_client import \
+from tempest.lib.services.volume.v1.availability_zone_client import \
AvailabilityZoneClient
+from tempest.lib.services.volume.v1.extensions_client import ExtensionsClient
+from tempest.lib.services.volume.v1.hosts_client import HostsClient
+from tempest.lib.services.volume.v1.quotas_client import QuotasClient
+from tempest.lib.services.volume.v1.services_client import ServicesClient
+from tempest.services.volume.v1.json.admin.types_client import TypesClient
from tempest.services.volume.v1.json.backups_client import BackupsClient
-from tempest.services.volume.v1.json.extensions_client import ExtensionsClient
+from tempest.services.volume.v1.json.encryption_types_client import \
+ EncryptionTypesClient
from tempest.services.volume.v1.json.qos_client import QosSpecsClient
from tempest.services.volume.v1.json.snapshots_client import SnapshotsClient
from tempest.services.volume.v1.json.volumes_client import VolumesClient
__all__ = ['HostsClient', 'QuotasClient', 'ServicesClient', 'TypesClient',
'AvailabilityZoneClient', 'BackupsClient', 'ExtensionsClient',
- 'QosSpecsClient', 'SnapshotsClient', 'VolumesClient']
+ 'QosSpecsClient', 'SnapshotsClient', 'VolumesClient',
+ 'EncryptionTypesClient']
diff --git a/tempest/services/volume/v1/json/admin/hosts_client.py b/tempest/services/volume/v1/json/admin/hosts_client.py
deleted file mode 100644
index 3b52968..0000000
--- a/tempest/services/volume/v1/json/admin/hosts_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_hosts_client
-
-
-class HostsClient(base_hosts_client.BaseHostsClient):
- """Client class to send CRUD Volume Host API V1 requests"""
diff --git a/tempest/services/volume/v1/json/admin/quotas_client.py b/tempest/services/volume/v1/json/admin/quotas_client.py
deleted file mode 100644
index 27fc301..0000000
--- a/tempest/services/volume/v1/json/admin/quotas_client.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_quotas_client
-
-
-class QuotasClient(base_quotas_client.BaseQuotasClient):
- """Client class to send CRUD Volume Type API V1 requests"""
diff --git a/tempest/services/volume/v1/json/admin/services_client.py b/tempest/services/volume/v1/json/admin/services_client.py
deleted file mode 100644
index 2bffd55..0000000
--- a/tempest/services/volume/v1/json/admin/services_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2014 NEC Corporation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_services_client
-
-
-class ServicesClient(base_services_client.BaseServicesClient):
- """Volume V1 volume services client"""
diff --git a/tempest/services/volume/v1/json/admin/types_client.py b/tempest/services/volume/v1/json/admin/types_client.py
index 0e84296..dce728d 100644
--- a/tempest/services/volume/v1/json/admin/types_client.py
+++ b/tempest/services/volume/v1/json/admin/types_client.py
@@ -13,8 +13,148 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.base.admin import base_types_client
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class TypesClient(base_types_client.BaseTypesClient):
- """Volume V1 Volume Types client"""
+class TypesClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Types API requests"""
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_volume_type(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'volume-type'
+
+ def list_volume_types(self, **params):
+ """List all the volume_types created.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v1.html#listVolumeTypes
+ """
+ url = 'types'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type(self, volume_type_id):
+ """Returns the details of a single volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v1.html#showVolumeType
+ """
+ url = "types/%s" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type(self, **kwargs):
+ """Create volume type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v1.html#createVolumeType
+ """
+ post_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.post('types', post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type(self, volume_type_id):
+ """Deletes the Specified Volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v1.html#deleteVolumeType
+ """
+ resp, body = self.delete("types/%s" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_types_extra_specs(self, volume_type_id, **params):
+ """List all the volume_types extra specs created.
+
+ TODO: Current api-site doesn't contain this API description.
+ After fixing the api-site, we need to fix here also for putting
+ the link to api-site.
+ """
+ url = 'types/%s/extra_specs' % volume_type_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
+ """Returns the details of a single volume_type extra spec."""
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
+ """Creates a new Volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_specs: A dictionary of values to be used as extra_specs.
+ """
+ url = "types/%s/extra_specs" % volume_type_id
+ post_body = json.dumps({'extra_specs': extra_specs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
+ """Deletes the Specified Volume_type extra spec."""
+ resp, body = self.delete("types/%s/extra_specs/%s" % (
+ volume_type_id, extra_spec_name))
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type(self, volume_type_id, **kwargs):
+ """Updates volume type name, description, and/or is_public.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#updateVolumeType
+ """
+ put_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.put('types/%s' % volume_type_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
+ extra_specs):
+ """Update a volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_spec_name: Name of the extra spec to be updated.
+ extra_spec: A dictionary of with key as extra_spec_name and the
+ updated value.
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#
+ updateVolumeTypeExtraSpecs
+ """
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
+ put_body = json.dumps(extra_specs)
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/json/availability_zone_client.py b/tempest/services/volume/v1/json/availability_zone_client.py
deleted file mode 100644
index 3a27027..0000000
--- a/tempest/services/volume/v1/json/availability_zone_client.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2014 NEC Corporation.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base import base_availability_zone_client
-
-
-class AvailabilityZoneClient(
- base_availability_zone_client.BaseAvailabilityZoneClient):
- """Volume V1 availability zone client."""
diff --git a/tempest/services/volume/v1/json/encryption_types_client.py b/tempest/services/volume/v1/json/encryption_types_client.py
new file mode 100755
index 0000000..067b4e8
--- /dev/null
+++ b/tempest/services/volume/v1/json/encryption_types_client.py
@@ -0,0 +1,68 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+
+ def is_resource_deleted(self, id):
+ try:
+ body = self.show_encryption_type(id)
+ if not body:
+ return True
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'encryption-type'
+
+ def show_encryption_type(self, volume_type_id):
+ """Get the volume encryption type for the specified volume type.
+
+ volume_type_id: Id of volume_type.
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_encryption_type(self, volume_type_id, **kwargs):
+ """Create encryption type.
+
+ TODO: Current api-site doesn't contain this API description.
+ After fixing the api-site, we need to fix here also for putting
+ the link to api-site.
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ post_body = json.dumps({'encryption': kwargs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_encryption_type(self, volume_type_id):
+ """Delete the encryption type for the specified volume-type."""
+ resp, body = self.delete(
+ "/types/%s/encryption/provider" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/json/extensions_client.py b/tempest/services/volume/v1/json/extensions_client.py
deleted file mode 100644
index f99d0f5..0000000
--- a/tempest/services/volume/v1/json/extensions_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base import base_extensions_client
-
-
-class ExtensionsClient(base_extensions_client.BaseExtensionsClient):
- """Volume V1 extensions client."""
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index c75b0e5..4afcc29 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -12,19 +12,21 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.services.volume.v2.json.admin.hosts_client import HostsClient
-from tempest.services.volume.v2.json.admin.quotas_client import QuotasClient
-from tempest.services.volume.v2.json.admin.services_client import \
- ServicesClient
-from tempest.services.volume.v2.json.admin.types_client import TypesClient
-from tempest.services.volume.v2.json.availability_zone_client import \
+from tempest.lib.services.volume.v2.availability_zone_client import \
AvailabilityZoneClient
+from tempest.lib.services.volume.v2.extensions_client import ExtensionsClient
+from tempest.lib.services.volume.v2.hosts_client import HostsClient
+from tempest.lib.services.volume.v2.quotas_client import QuotasClient
+from tempest.lib.services.volume.v2.services_client import ServicesClient
+from tempest.services.volume.v2.json.admin.types_client import TypesClient
from tempest.services.volume.v2.json.backups_client import BackupsClient
-from tempest.services.volume.v2.json.extensions_client import ExtensionsClient
+from tempest.services.volume.v2.json.encryption_types_client import \
+ EncryptionTypesClient
from tempest.services.volume.v2.json.qos_client import QosSpecsClient
from tempest.services.volume.v2.json.snapshots_client import SnapshotsClient
from tempest.services.volume.v2.json.volumes_client import VolumesClient
__all__ = ['HostsClient', 'QuotasClient', 'ServicesClient', 'TypesClient',
'AvailabilityZoneClient', 'BackupsClient', 'ExtensionsClient',
- 'QosSpecsClient', 'SnapshotsClient', 'VolumesClient']
+ 'QosSpecsClient', 'SnapshotsClient', 'VolumesClient',
+ 'EncryptionTypesClient']
diff --git a/tempest/services/volume/v2/json/admin/hosts_client.py b/tempest/services/volume/v2/json/admin/hosts_client.py
deleted file mode 100644
index e092c6a..0000000
--- a/tempest/services/volume/v2/json/admin/hosts_client.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_hosts_client
-
-
-class HostsClient(base_hosts_client.BaseHostsClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/quotas_client.py b/tempest/services/volume/v2/json/admin/quotas_client.py
deleted file mode 100644
index 11e0e22..0000000
--- a/tempest/services/volume/v2/json/admin/quotas_client.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_quotas_client
-
-
-class QuotasClient(base_quotas_client.BaseQuotasClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/services_client.py b/tempest/services/volume/v2/json/admin/services_client.py
deleted file mode 100644
index db19ba9..0000000
--- a/tempest/services/volume/v2/json/admin/services_client.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base.admin import base_services_client
-
-
-class ServicesClient(base_services_client.BaseServicesClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/types_client.py b/tempest/services/volume/v2/json/admin/types_client.py
index ecf5131..d399e99 100644
--- a/tempest/services/volume/v2/json/admin/types_client.py
+++ b/tempest/services/volume/v2/json/admin/types_client.py
@@ -13,9 +13,188 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.base.admin import base_types_client
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class TypesClient(base_types_client.BaseTypesClient):
+class TypesClient(rest_client.RestClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_volume_type(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'volume-type'
+
+ def list_volume_types(self, **params):
+ """List all the volume_types created.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#showVolumeTypes
+ """
+ url = 'types'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type(self, volume_type_id):
+ """Returns the details of a single volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#showVolumeType
+ """
+ url = "types/%s" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type(self, **kwargs):
+ """Create volume type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#createVolumeType
+ """
+ post_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.post('types', post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type(self, volume_type_id):
+ """Deletes the Specified Volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#deleteVolumeType
+ """
+ resp, body = self.delete("types/%s" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_types_extra_specs(self, volume_type_id, **params):
+ """List all the volume_types extra specs created.
+
+ TODO: Current api-site doesn't contain this API description.
+ After fixing the api-site, we need to fix here also for putting
+ the link to api-site.
+ """
+ url = 'types/%s/extra_specs' % volume_type_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
+ """Returns the details of a single volume_type extra spec."""
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
+ """Creates a new Volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_specs: A dictionary of values to be used as extra_specs.
+ """
+ url = "types/%s/extra_specs" % volume_type_id
+ post_body = json.dumps({'extra_specs': extra_specs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
+ """Deletes the Specified Volume_type extra spec."""
+ resp, body = self.delete("types/%s/extra_specs/%s" % (
+ volume_type_id, extra_spec_name))
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type(self, volume_type_id, **kwargs):
+ """Updates volume type name, description, and/or is_public.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#updateVolumeType
+ """
+ put_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.put('types/%s' % volume_type_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
+ extra_specs):
+ """Update a volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_spec_name: Name of the extra spec to be updated.
+ extra_spec: A dictionary of with key as extra_spec_name and the
+ updated value.
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#
+ updateVolumeTypeExtraSpecs
+ """
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
+ put_body = json.dumps(extra_specs)
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_type_access(self, volume_type_id, **kwargs):
+ """Adds volume type access for the given project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #createVolumeTypeAccessExt
+ """
+ post_body = json.dumps({'addProjectAccess': kwargs})
+ url = 'types/%s/action' % volume_type_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_type_access(self, volume_type_id, **kwargs):
+ """Removes volume type access for the given project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #removeVolumeTypeAccessExt
+ """
+ post_body = json.dumps({'removeProjectAccess': kwargs})
+ url = 'types/%s/action' % volume_type_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_type_access(self, volume_type_id):
+ """Print access information about the given volume type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#
+ listVolumeTypeAccessExt
+ """
+ url = 'types/%s/os-volume-type-access' % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v2/json/availability_zone_client.py b/tempest/services/volume/v2/json/availability_zone_client.py
deleted file mode 100644
index 905ebdc..0000000
--- a/tempest/services/volume/v2/json/availability_zone_client.py
+++ /dev/null
@@ -1,21 +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.
-
-from tempest.services.volume.base import base_availability_zone_client
-
-
-class AvailabilityZoneClient(
- base_availability_zone_client.BaseAvailabilityZoneClient):
- api_version = "v2"
diff --git a/tempest/services/volume/v2/json/encryption_types_client.py b/tempest/services/volume/v2/json/encryption_types_client.py
new file mode 100755
index 0000000..8b01f11
--- /dev/null
+++ b/tempest/services/volume/v2/json/encryption_types_client.py
@@ -0,0 +1,69 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def is_resource_deleted(self, id):
+ try:
+ body = self.show_encryption_type(id)
+ if not body:
+ return True
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'encryption-type'
+
+ def show_encryption_type(self, volume_type_id):
+ """Get the volume encryption type for the specified volume type.
+
+ volume_type_id: Id of volume_type.
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_encryption_type(self, volume_type_id, **kwargs):
+ """Create encryption type.
+
+ TODO: Current api-site doesn't contain this API description.
+ After fixing the api-site, we need to fix here also for putting
+ the link to api-site.
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ post_body = json.dumps({'encryption': kwargs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_encryption_type(self, volume_type_id):
+ """Delete the encryption type for the specified volume-type."""
+ resp, body = self.delete(
+ "/types/%s/encryption/provider" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v2/json/extensions_client.py b/tempest/services/volume/v2/json/extensions_client.py
deleted file mode 100644
index 245906f..0000000
--- a/tempest/services/volume/v2/json/extensions_client.py
+++ /dev/null
@@ -1,20 +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.
-
-from tempest.services.volume.base import base_extensions_client
-
-
-class ExtensionsClient(base_extensions_client.BaseExtensionsClient):
- api_version = "v2"
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index b7d9dfb..f21a1a3 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -13,6 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
from tempest.services.volume.base import base_volumes_client
@@ -20,3 +23,49 @@
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
create_resp = 202
+
+ def update_volume_image_metadata(self, volume_id, **kwargs):
+ """Update image metadata for the volume.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #setVolumeimagemetadata
+ """
+ post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
+ url = "volumes/%s/action" % (volume_id)
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_image_metadata(self, volume_id, key_name):
+ """Delete image metadata item for the volume."""
+ post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
+ url = "volumes/%s/action" % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_pools(self, detail=False):
+ # List all the volumes pools (hosts)
+ url = 'scheduler-stats/get_pools'
+ if detail:
+ url += '?detail=True'
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_backend_capabilities(self, host):
+ """Shows capabilities for a storage back end.
+
+ Output params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #showBackendCapabilities
+ """
+ url = 'capabilities/%s' % host
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/stress/actions/server_create_destroy.py b/tempest/stress/actions/server_create_destroy.py
index 44b6f62..183bc6c 100644
--- a/tempest/stress/actions/server_create_destroy.py
+++ b/tempest/stress/actions/server_create_destroy.py
@@ -27,7 +27,7 @@
self.flavor = CONF.compute.flavor_ref
def run(self):
- name = data_utils.rand_name("instance")
+ name = data_utils.rand_name(self.__class__.__name__ + "-instance")
self.logger.info("creating %s" % name)
server = self.manager.servers_client.create_server(
name=name, imageRef=self.image, flavorRef=self.flavor)['server']
diff --git a/tempest/stress/actions/ssh_floating.py b/tempest/stress/actions/ssh_floating.py
index 4f8c6bd..845b4a7 100644
--- a/tempest/stress/actions/ssh_floating.py
+++ b/tempest/stress/actions/ssh_floating.py
@@ -16,8 +16,8 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import test_utils
import tempest.stress.stressaction as stressaction
-import tempest.test
CONF = config.CONF
@@ -52,8 +52,8 @@
def check_port_ssh(self):
def func():
return self.tcp_connect_scan(self.floating['ip'], 22)
- if not tempest.test.call_until_true(func, self.check_timeout,
- self.check_interval):
+ if not test_utils.call_until_true(func, self.check_timeout,
+ self.check_interval):
raise RuntimeError("Cannot connect to the ssh port.")
def check_icmp_echo(self):
@@ -62,15 +62,16 @@
def func():
return self.ping_ip_address(self.floating['ip'])
- if not tempest.test.call_until_true(func, self.check_timeout,
- self.check_interval):
+ if not test_utils.call_until_true(func, self.check_timeout,
+ self.check_interval):
raise RuntimeError("%s(%s): Cannot ping the machine.",
self.server_id, self.floating['ip'])
self.logger.info("%s(%s): pong :)",
self.server_id, self.floating['ip'])
def _create_vm(self):
- self.name = name = data_utils.rand_name("instance")
+ self.name = name = data_utils.rand_name(
+ self.__class__.__name__ + "-instance")
servers_client = self.manager.servers_client
self.logger.info("creating %s" % name)
vm_args = self.vm_extra_args.copy()
@@ -92,7 +93,7 @@
def _create_sec_group(self):
sec_grp_cli = self.manager.compute_security_groups_client
- s_name = data_utils.rand_name('sec_grp')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-sec_grp')
s_description = data_utils.rand_name('desc')
self.sec_grp = sec_grp_cli.create_security_group(
name=s_name, description=s_description)['security_group']
@@ -153,8 +154,8 @@
['floating_ip'])
return floating['instance_id'] is None
- if not tempest.test.call_until_true(func, self.check_timeout,
- self.check_interval):
+ if not test_utils.call_until_true(func, self.check_timeout,
+ self.check_interval):
raise RuntimeError("IP disassociate timeout!")
def run_core(self):
diff --git a/tempest/stress/actions/unit_test.py b/tempest/stress/actions/unit_test.py
index 3b27885..e016c61 100644
--- a/tempest/stress/actions/unit_test.py
+++ b/tempest/stress/actions/unit_test.py
@@ -80,8 +80,6 @@
try:
self.run_core()
- except Exception as e:
- raise e
finally:
if (CONF.stress.leave_dirty_stack is False
and self.class_setup_per == SetUpClassRunTime.action):
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index 847f342..5fc006e 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -27,16 +27,16 @@
def run(self):
# Step 1: create volume
- name = data_utils.rand_name("volume")
+ name = data_utils.rand_name(self.__class__.__name__ + "-volume")
self.logger.info("creating volume: %s" % name)
volume = self.manager.volumes_client.create_volume(
- display_name=name)['volume']
+ display_name=name, size=CONF.volume.volume_size)['volume']
self.manager.volumes_client.wait_for_volume_status(volume['id'],
'available')
self.logger.info("created volume: %s" % volume['id'])
# Step 2: create vm instance
- vm_name = data_utils.rand_name("instance")
+ vm_name = data_utils.rand_name(self.__class__.__name__ + "-instance")
self.logger.info("creating vm: %s" % vm_name)
server = self.manager.servers_client.create_server(
name=vm_name, imageRef=self.image, flavorRef=self.flavor)['server']
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
index 8bbbfc4..4fbb851 100644
--- a/tempest/stress/actions/volume_attach_verify.py
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -16,8 +16,8 @@
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import test_utils
import tempest.stress.stressaction as stressaction
-import tempest.test
CONF = config.CONF
@@ -33,7 +33,8 @@
self.manager.keypairs_client.delete_keypair(self.key['name'])
def _create_vm(self):
- self.name = name = data_utils.rand_name("instance")
+ self.name = name = data_utils.rand_name(
+ self.__class__.__name__ + "-instance")
servers_client = self.manager.servers_client
self.logger.info("creating %s" % name)
vm_args = self.vm_extra_args.copy()
@@ -55,7 +56,7 @@
def _create_sec_group(self):
sec_grp_cli = self.manager.compute_security_groups_client
- s_name = data_utils.rand_name('sec_grp')
+ s_name = data_utils.rand_name(self.__class__.__name__ + '-sec_grp')
s_description = data_utils.rand_name('desc')
self.sec_grp = sec_grp_cli.create_security_group(
name=s_name, description=s_description)['security_group']
@@ -81,11 +82,11 @@
self.logger.info("Deleted Floating IP %s", str(self.floating['ip']))
def _create_volume(self):
- name = data_utils.rand_name("volume")
+ name = data_utils.rand_name(self.__class__.__name__ + "-volume")
self.logger.info("creating volume: %s" % name)
volumes_client = self.manager.volumes_client
self.volume = volumes_client.create_volume(
- display_name=name)['volume']
+ display_name=name, size=CONF.volume.volume_size)['volume']
volumes_client.wait_for_volume_status(self.volume['id'],
'available')
self.logger.info("created volume: %s" % self.volume['id'])
@@ -105,8 +106,8 @@
['floating_ip'])
return floating['instance_id'] is None
- if not tempest.test.call_until_true(func, CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if not test_utils.call_until_true(func, CONF.compute.build_timeout,
+ CONF.compute.build_interval):
raise RuntimeError("IP disassociate timeout!")
def new_server_ops(self):
@@ -179,9 +180,9 @@
if self.part_line_re.match(part_line):
matching += 1
return matching == num_match
- if tempest.test.call_until_true(_part_state,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
+ if test_utils.call_until_true(_part_state,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
return
else:
raise RuntimeError("Unexpected partitions: %s",
diff --git a/tempest/stress/actions/volume_create_delete.py b/tempest/stress/actions/volume_create_delete.py
index 3986748..66971ea 100644
--- a/tempest/stress/actions/volume_create_delete.py
+++ b/tempest/stress/actions/volume_create_delete.py
@@ -11,8 +11,11 @@
# limitations under the License.
from tempest.common.utils import data_utils
+from tempest import config
import tempest.stress.stressaction as stressaction
+CONF = config.CONF
+
class VolumeCreateDeleteTest(stressaction.StressAction):
@@ -20,7 +23,8 @@
name = data_utils.rand_name("volume")
self.logger.info("creating %s" % name)
volumes_client = self.manager.volumes_client
- volume = volumes_client.create_volume(display_name=name)['volume']
+ volume = volumes_client.create_volume(
+ display_name=name, size=CONF.volume.volume_size)['volume']
vol_id = volume['id']
volumes_client.wait_for_volume_status(vol_id, 'available')
self.logger.info("created %s" % volume['id'])
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 2beaaa9..1e33e88 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -20,8 +20,6 @@
from oslo_log import log as logging
from oslo_utils import importutils
import six
-from six import moves
-
from tempest import clients
from tempest.common import cred_client
@@ -142,7 +140,7 @@
manager = admin_manager
else:
raise NotImplemented('Non admin tests are not supported')
- for p_number in moves.xrange(test.get('threads', default_thread_num)):
+ for p_number in range(test.get('threads', default_thread_num)):
if test.get('use_isolated_tenants', False):
username = data_utils.rand_name("stress_user")
tenant_name = data_utils.rand_name("stress_tenant")
@@ -249,13 +247,13 @@
had_errors = True
sum_runs += process['statistic']['runs']
sum_fails += process['statistic']['fails']
- print ("Process %d (%s): Run %d actions (%d failed)" % (
- process['p_number'],
- process['action'],
- process['statistic']['runs'],
- process['statistic']['fails']))
- print ("Summary:")
- print ("Run %d actions (%d failed)" % (sum_runs, sum_fails))
+ print("Process %d (%s): Run %d actions (%d failed)" % (
+ process['p_number'],
+ process['action'],
+ process['statistic']['runs'],
+ process['statistic']['fails']))
+ print("Summary:")
+ print("Run %d actions (%d failed)" % (sum_runs, sum_fails))
if not had_errors and CONF.stress.full_clean_stack:
LOG.info("cleaning up")
diff --git a/tempest/test.py b/tempest/test.py
index 97ab25c..609f1f6 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -18,8 +18,8 @@
import os
import re
import sys
-import time
+import debtcollector.moves
import fixtures
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
@@ -38,6 +38,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -866,22 +867,6 @@
return klass
-def call_until_true(func, duration, sleep_for):
- """Call the given function until it returns True (and return True)
-
- or until the specified duration (in seconds) elapses (and return False).
-
- :param func: A zero argument callable that returns True on success.
- :param duration: The number of seconds for which to attempt a
- successful call of the function.
- :param sleep_for: The number of seconds to sleep after an unsuccessful
- invocation of the function.
- """
- now = time.time()
- timeout = now + duration
- while now < timeout:
- if func():
- return True
- time.sleep(sleep_for)
- now = time.time()
- return False
+call_until_true = debtcollector.moves.moved_function(
+ test_utils.call_until_true, 'call_until_true', __name__,
+ version='Newton', removal_version='Ocata')
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index d604b28..eb50126 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -19,6 +19,7 @@
import stevedore
from tempest.lib.common.utils import misc
+from tempest.lib.services import clients
LOG = logging.getLogger(__name__)
@@ -62,6 +63,54 @@
"""
return []
+ def get_service_clients(self):
+ """Get a list of the service clients for registration
+
+ If the plugin implements service clients for one or more APIs, it
+ may return their details by this method for automatic registration
+ in any ServiceClients object instantiated by tests.
+ The default implementation returns an empty list.
+
+ :return list of dictionaries. Each element of the list represents
+ the service client for an API. Each dict must define all
+ parameters required for the invocation of
+ `service_clients.ServiceClients.register_service_client_module`.
+ :rtype: list
+
+ Example:
+
+ >>> # Example implementation with one service client
+ >>> myservice_config = config.service_client_config('myservice')
+ >>> params = {
+ >>> 'name': 'myservice',
+ >>> 'service_version': 'myservice',
+ >>> 'module_path': 'myservice_tempest_tests.services',
+ >>> 'client_names': ['API1Client', 'API2Client'],
+ >>> }
+ >>> params.update(myservice_config)
+ >>> return [params]
+
+ >>> # Example implementation with two service clients
+ >>> foo1_config = config.service_client_config('foo')
+ >>> params_foo1 = {
+ >>> 'name': 'foo_v1',
+ >>> 'service_version': 'foo.v1',
+ >>> 'module_path': 'bar_tempest_tests.services.foo.v1',
+ >>> 'client_names': ['API1Client', 'API2Client'],
+ >>> }
+ >>> params_foo1.update(foo_config)
+ >>> foo2_config = config.service_client_config('foo')
+ >>> params_foo2 = {
+ >>> 'name': 'foo_v2',
+ >>> 'service_version': 'foo.v2',
+ >>> 'module_path': 'bar_tempest_tests.services.foo.v2',
+ >>> 'client_names': ['API1Client', 'API2Client'],
+ >>> }
+ >>> params_foo2.update(foo2_config)
+ >>> return [params_foo1, params_foo2]
+ """
+ return []
+
@misc.singleton
class TempestTestPluginManager(object):
@@ -75,6 +124,7 @@
'tempest.test_plugins', invoke_on_load=True,
propagate_map_exceptions=True,
on_load_failure_callback=self.failure_hook)
+ self._register_service_clients()
@staticmethod
def failure_hook(_, ep, err):
@@ -102,3 +152,13 @@
if opt_list:
plugin_options.extend(opt_list)
return plugin_options
+
+ def _register_service_clients(self):
+ registry = clients.ClientsRegistry()
+ for plug in self.ext_plugins:
+ try:
+ registry.register_service_client(
+ plug.name, plug.obj.get_service_clients())
+ except Exception:
+ LOG.exception('Plugin %s raised an exception trying to run '
+ 'get_service_clients' % plug.name)
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
deleted file mode 100644
index 5ec9720..0000000
--- a/tempest/tests/cmd/test_javelin.py
+++ /dev/null
@@ -1,422 +0,0 @@
-#!/usr/bin/env python
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# 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 oslotest import mockpatch
-
-from tempest.cmd import javelin
-from tempest.lib import exceptions as lib_exc
-from tempest.tests import base
-
-
-class JavelinUnitTest(base.TestCase):
-
- def setUp(self):
- super(JavelinUnitTest, self).setUp()
- javelin.LOG = mock.MagicMock()
- self.fake_client = mock.MagicMock()
- self.fake_object = mock.MagicMock()
-
- def test_load_resources(self):
- with mock.patch('six.moves.builtins.open', mock.mock_open(),
- create=True) as open_mock:
- with mock.patch('yaml.load', mock.MagicMock(),
- create=True) as load_mock:
- javelin.load_resources(self.fake_object)
- load_mock.assert_called_once_with(open_mock(self.fake_object))
-
- def test_keystone_admin(self):
- self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
- javelin.OPTS = self.fake_object
- javelin.keystone_admin()
- javelin.OSClient.assert_called_once_with(
- self.fake_object.os_username,
- self.fake_object.os_password,
- self.fake_object.os_tenant_name)
-
- def test_client_for_user(self):
- fake_user = mock.MagicMock()
- javelin.USERS = {fake_user['name']: fake_user}
- self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
- javelin.client_for_user(fake_user['name'])
- javelin.OSClient.assert_called_once_with(
- fake_user['name'], fake_user['pass'], fake_user['tenant'])
-
- def test_client_for_non_existing_user(self):
- fake_non_existing_user = self.fake_object
- fake_user = mock.MagicMock()
- javelin.USERS = {fake_user['name']: fake_user}
- self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
- javelin.client_for_user(fake_non_existing_user['name'])
- self.assertFalse(javelin.OSClient.called)
-
- def test_attach_volumes(self):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- self.useFixture(mockpatch.PatchObject(
- javelin, "_get_volume_by_name",
- return_value=self.fake_object.volume))
-
- self.useFixture(mockpatch.PatchObject(
- javelin, "_get_server_by_name",
- return_value=self.fake_object.server))
-
- javelin.attach_volumes([self.fake_object])
-
- mocked_function = self.fake_client.volumes.attach_volume
- mocked_function.assert_called_once_with(
- self.fake_object.volume['id'],
- instance_uuid=self.fake_object.server['id'],
- mountpoint=self.fake_object['device'])
-
-
-class TestCreateResources(JavelinUnitTest):
- def test_create_tenants(self):
-
- self.fake_client.tenants.list_tenants.return_value = {'tenants': []}
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=self.fake_client))
-
- javelin.create_tenants([self.fake_object['name']])
-
- mocked_function = self.fake_client.tenants.create_tenant
- mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
- def test_create_duplicate_tenant(self):
- self.fake_client.tenants.list_tenants.return_value = {'tenants': [
- {'name': self.fake_object['name']}]}
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=self.fake_client))
-
- javelin.create_tenants([self.fake_object['name']])
-
- mocked_function = self.fake_client.tenants.create_tenant
- self.assertFalse(mocked_function.called)
-
- def test_create_users(self):
- self.useFixture(mockpatch.Patch(
- 'tempest.common.identity.get_tenant_by_name',
- return_value=self.fake_object['tenant']))
- self.useFixture(mockpatch.Patch(
- 'tempest.common.identity.get_user_by_username',
- side_effect=lib_exc.NotFound("user is not found")))
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=self.fake_client))
-
- javelin.create_users([self.fake_object])
-
- fake_tenant_id = self.fake_object['tenant']['id']
- fake_email = "%s@%s" % (self.fake_object['user'], fake_tenant_id)
- mocked_function = self.fake_client.users.create_user
- mocked_function.assert_called_once_with(
- name=self.fake_object['name'],
- password=self.fake_object['password'],
- tenantId=fake_tenant_id,
- email=fake_email,
- enabled=True)
-
- def test_create_user_missing_tenant(self):
- self.useFixture(mockpatch.Patch(
- 'tempest.common.identity.get_tenant_by_name',
- side_effect=lib_exc.NotFound("tenant is not found")))
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=self.fake_client))
-
- javelin.create_users([self.fake_object])
-
- mocked_function = self.fake_client.users.create_user
- self.assertFalse(mocked_function.called)
-
- def test_create_objects(self):
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_assign_swift_role"))
- self.useFixture(mockpatch.PatchObject(javelin, "_file_contents",
- return_value=self.fake_object.content))
-
- javelin.create_objects([self.fake_object])
-
- mocked_function = self.fake_client.containers.create_container
- mocked_function.assert_called_once_with(self.fake_object['container'])
- mocked_function = self.fake_client.objects.create_object
- mocked_function.assert_called_once_with(self.fake_object['container'],
- self.fake_object['name'],
- self.fake_object.content)
-
- def test_create_images(self):
- self.fake_client.images.create_image.return_value = \
- self.fake_object['body']
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
- return_value=[]))
- self.useFixture(mockpatch.PatchObject(javelin, "_resolve_image",
- return_value=(None, None)))
-
- with mock.patch('six.moves.builtins.open', mock.mock_open(),
- create=True) as open_mock:
- javelin.create_images([self.fake_object])
-
- mocked_function = self.fake_client.images.create_image
- mocked_function.assert_called_once_with(self.fake_object['name'],
- self.fake_object['format'],
- self.fake_object['format'])
-
- mocked_function = self.fake_client.images.store_image_file
- fake_image_id = self.fake_object['body'].get('id')
- mocked_function.assert_called_once_with(fake_image_id, open_mock())
-
- def test_create_networks(self):
- self.fake_client.networks.list_networks.return_value = {
- 'networks': []}
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- javelin.create_networks([self.fake_object])
-
- mocked_function = self.fake_client.networks.create_network
- mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
- def test_create_subnet(self):
-
- fake_network = self.fake_object['network']
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
- return_value=fake_network))
-
- fake_netaddr = mock.MagicMock()
- self.useFixture(mockpatch.PatchObject(javelin, "netaddr",
- return_value=fake_netaddr))
- fake_version = javelin.netaddr.IPNetwork().version
-
- javelin.create_subnets([self.fake_object])
-
- mocked_function = self.fake_client.networks.create_subnet
- mocked_function.assert_called_once_with(network_id=fake_network['id'],
- cidr=self.fake_object['range'],
- name=self.fake_object['name'],
- ip_version=fake_version)
-
- @mock.patch("tempest.common.waiters.wait_for_volume_status")
- def test_create_volumes(self, mock_wait_for_volume_status):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
- return_value=None))
- self.fake_client.volumes.create_volume.return_value = \
- self.fake_object.body
-
- javelin.create_volumes([self.fake_object])
-
- mocked_function = self.fake_client.volumes.create_volume
- mocked_function.assert_called_once_with(
- size=self.fake_object['gb'],
- display_name=self.fake_object['name'])
- mock_wait_for_volume_status.assert_called_once_with(
- self.fake_client.volumes, self.fake_object.body['volume']['id'],
- 'available')
-
- @mock.patch("tempest.common.waiters.wait_for_volume_status")
- def test_create_volume_existing(self, mock_wait_for_volume_status):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
- return_value=self.fake_object))
- self.fake_client.volumes.create_volume.return_value = \
- self.fake_object.body
-
- javelin.create_volumes([self.fake_object])
-
- mocked_function = self.fake_client.volumes.create_volume
- self.assertFalse(mocked_function.called)
- self.assertFalse(mock_wait_for_volume_status.called)
-
- def test_create_router(self):
-
- self.fake_client.routers.list_routers.return_value = {'routers': []}
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- javelin.create_routers([self.fake_object])
-
- mocked_function = self.fake_client.networks.create_router
- mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
- def test_create_router_existing(self):
- self.fake_client.routers.list_routers.return_value = {
- 'routers': [self.fake_object]}
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- javelin.create_routers([self.fake_object])
-
- mocked_function = self.fake_client.networks.create_router
- self.assertFalse(mocked_function.called)
-
- def test_create_secgroup(self):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.fake_client.secgroups.list_security_groups.return_value = (
- {'security_groups': []})
- self.fake_client.secgroups.create_security_group.return_value = \
- {'security_group': {'id': self.fake_object['secgroup_id']}}
-
- javelin.create_secgroups([self.fake_object])
-
- mocked_function = self.fake_client.secgroups.create_security_group
- mocked_function.assert_called_once_with(
- name=self.fake_object['name'],
- description=self.fake_object['description'])
-
-
-class TestDestroyResources(JavelinUnitTest):
-
- def test_destroy_tenants(self):
-
- fake_tenant = self.fake_object['tenant']
- fake_auth = self.fake_client
- self.useFixture(mockpatch.Patch(
- 'tempest.common.identity.get_tenant_by_name',
- return_value=fake_tenant))
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=fake_auth))
- javelin.destroy_tenants([fake_tenant])
-
- mocked_function = fake_auth.tenants.delete_tenant
- mocked_function.assert_called_once_with(fake_tenant['id'])
-
- def test_destroy_users(self):
-
- fake_user = self.fake_object['user']
- fake_tenant = self.fake_object['tenant']
-
- fake_auth = self.fake_client
- fake_auth.tenants.list_tenants.return_value = \
- {'tenants': [fake_tenant]}
- fake_auth.users.list_users.return_value = {'users': [fake_user]}
-
- self.useFixture(mockpatch.Patch(
- 'tempest.common.identity.get_user_by_username',
- return_value=fake_user))
- self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
- return_value=fake_auth))
-
- javelin.destroy_users([fake_user])
-
- mocked_function = fake_auth.users.delete_user
- mocked_function.assert_called_once_with(fake_user['id'])
-
- def test_destroy_objects(self):
-
- self.fake_client.objects.delete_object.return_value = \
- {'status': "200"}, ""
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- javelin.destroy_objects([self.fake_object])
-
- mocked_function = self.fake_client.objects.delete_object
- mocked_function.asswert_called_once(self.fake_object['container'],
- self.fake_object['name'])
-
- def test_destroy_images(self):
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
- return_value=self.fake_object['image']))
-
- javelin.destroy_images([self.fake_object])
-
- mocked_function = self.fake_client.images.delete_image
- mocked_function.assert_called_once_with(
- self.fake_object['image']['id'])
-
- def test_destroy_networks(self):
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- self.useFixture(mockpatch.PatchObject(
- javelin, "_get_resource_by_name",
- return_value=self.fake_object['resource']))
-
- javelin.destroy_networks([self.fake_object])
-
- mocked_function = self.fake_client.networks.delete_network
- mocked_function.assert_called_once_with(
- self.fake_object['resource']['id'])
-
- def test_destroy_volumes(self):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- self.useFixture(mockpatch.PatchObject(
- javelin, "_get_volume_by_name",
- return_value=self.fake_object.volume))
-
- javelin.destroy_volumes([self.fake_object])
-
- mocked_function = self.fake_client.volumes.detach_volume
- mocked_function.assert_called_once_with(self.fake_object.volume['id'])
- mocked_function = self.fake_client.volumes.delete_volume
- mocked_function.assert_called_once_with(self.fake_object.volume['id'])
-
- def test_destroy_subnets(self):
-
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- fake_subnet_id = self.fake_object['subnet_id']
- self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
- return_value={
- 'id': fake_subnet_id}))
-
- javelin.destroy_subnets([self.fake_object])
-
- mocked_function = self.fake_client.subnets.delete_subnet
- mocked_function.assert_called_once_with(fake_subnet_id)
-
- def test_destroy_routers(self):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
-
- # this function is used on 2 different occasions in the code
- def _fake_get_resource_by_name(*args):
- if args[1] == "routers":
- return {"id": self.fake_object['router_id']}
- elif args[1] == "subnets":
- return {"id": self.fake_object['subnet_id']}
- javelin._get_resource_by_name = _fake_get_resource_by_name
-
- javelin.destroy_routers([self.fake_object])
-
- mocked_function = self.fake_client.routers.delete_router
- mocked_function.assert_called_once_with(
- self.fake_object['router_id'])
-
- def test_destroy_secgroup(self):
- self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
- return_value=self.fake_client))
- fake_secgroup = {'id': self.fake_object['id']}
- self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
- return_value=fake_secgroup))
-
- javelin.destroy_secgroups([self.fake_object])
-
- mocked_function = self.fake_client.secgroups.delete_security_group
- mocked_function.assert_called_once_with(self.fake_object['id'])
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index 43b417a..1c24c37 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -38,46 +38,159 @@
os.path.dirname(os.path.abspath(__file__)),
'sample_streams/calls.subunit')
parser = subunit_describe_calls.parse(
- subunit_file, "pythonlogging", None)
+ open(subunit_file), "pythonlogging", None)
expected_result = {
- 'bar': [{'name': 'AgentsAdminTestJSON:setUp',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'},
- {'name': 'AgentsAdminTestJSON:test_create_agent',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'},
- {'name': 'AgentsAdminTestJSON:tearDown',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/1',
- 'verb': 'DELETE'},
- {'name': 'AgentsAdminTestJSON:_run_cleanups',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/2',
- 'verb': 'DELETE'}],
- 'foo': [{'name': 'AgentsAdminTestJSON:setUp',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'POST'},
- {'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'},
- {'name': 'AgentsAdminTestJSON:test_delete_agent',
- 'service': 'Nova',
- 'status_code': '200',
- 'url': 'v2.1/<id>/os-agents',
- 'verb': 'GET'},
- {'name': 'AgentsAdminTestJSON:tearDown',
- 'service': 'Nova',
- 'status_code': '404',
- 'url': 'v2.1/<id>/os-agents/3',
- 'verb': 'DELETE'}]}
+ 'bar': [{
+ 'name': 'AgentsAdminTestJSON:setUp',
+ 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "common", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86_64-424013832", "os": "linux"}}',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "common", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86_64-424013832", "os": "linux", '
+ '"agent_id": 1}}',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'203', 'x-compute-request-id': "
+ "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents',
+ 'verb': 'POST'}, {
+ 'name': 'AgentsAdminTestJSON:test_create_agent',
+ 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "kvm", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86-252246646", "os": "win"}}',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "kvm", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86-252246646", "os": "win", '
+ '"agent_id": 2}}',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'195', 'x-compute-request-id': "
+ "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents',
+ 'verb': 'POST'}, {
+ 'name': 'AgentsAdminTestJSON:tearDown',
+ 'request_body': 'None',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'0', 'x-compute-request-id': "
+ "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents/1',
+ 'verb': 'DELETE'}, {
+ 'name': 'AgentsAdminTestJSON:_run_cleanups',
+ 'request_body': 'None',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'0', 'x-compute-request-id': "
+ "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents/2',
+ 'verb': 'DELETE'}],
+ 'foo': [{
+ 'name': 'AgentsAdminTestJSON:setUp',
+ 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "common", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86_64-948635295", "os": "linux"}}',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
+ '"hypervisor": "common", "md5hash": '
+ '"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
+ '"architecture": "tempest-x86_64-948635295", "os": "linux", '
+ '"agent_id": 3}}',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'203', 'x-compute-request-id': "
+ "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents',
+ 'verb': 'POST'}, {
+ 'name': 'AgentsAdminTestJSON:test_delete_agent',
+ 'request_body': 'None',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'0', 'x-compute-request-id': "
+ "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents/3',
+ 'verb': 'DELETE'}, {
+ 'name': 'AgentsAdminTestJSON:test_delete_agent',
+ 'request_body': 'None',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_body': '{"agents": []}',
+ 'response_headers': "{'status': '200', 'content-length': "
+ "'14', 'content-location': "
+ "'http://23.253.76.97:8774/v2.1/"
+ "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', "
+ "'x-compute-request-id': "
+ "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
+ "'application/json'}",
+ 'service': 'Nova',
+ 'status_code': '200',
+ 'url': 'v2.1/<id>/os-agents',
+ 'verb': 'GET'}, {
+ 'name': 'AgentsAdminTestJSON:tearDown',
+ 'request_body': 'None',
+ 'request_headers': "{'Content-Type': 'application/json', "
+ "'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
+ 'response_headers': "{'status': '404', 'content-length': "
+ "'82', 'x-compute-request-id': "
+ "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': "
+ "'X-OpenStack-Nova-API-Version', 'connection': 'close', "
+ "'x-openstack-nova-api-version': '2.1', 'date': "
+ "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': "
+ "'application/json; charset=UTF-8'}",
+ 'service': 'Nova',
+ 'status_code': '404',
+ 'url': 'v2.1/<id>/os-agents/3',
+ 'verb': 'DELETE'}]}
+
self.assertEqual(expected_result, parser.test_logs)
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 031bf4d..2844371 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -45,6 +45,7 @@
init_cmd = init.TempestInit(None, None)
local_sample_conf_file = os.path.join(etc_dir_path,
'tempest.conf.sample')
+
# Verify no sample config file exist
self.assertFalse(os.path.isfile(local_sample_conf_file))
init_cmd.generate_sample_config(local_dir.path)
@@ -53,6 +54,52 @@
self.assertTrue(os.path.isfile(local_sample_conf_file))
self.assertGreater(os.path.getsize(local_sample_conf_file), 0)
+ def test_update_local_conf(self):
+ local_dir = self.useFixture(fixtures.TempDir())
+ etc_dir_path = os.path.join(local_dir.path, 'etc/')
+ os.mkdir(etc_dir_path)
+ lock_dir = os.path.join(local_dir.path, 'tempest_lock')
+ config_path = os.path.join(etc_dir_path, 'tempest.conf')
+ log_dir = os.path.join(local_dir.path, 'logs')
+
+ init_cmd = init.TempestInit(None, None)
+
+ # Generate the config file
+ init_cmd.generate_sample_config(local_dir.path)
+
+ # Create a conf file with populated values
+ config_parser_pre = init_cmd.get_configparser(config_path)
+ with open(config_path, 'w+') as conf_file:
+ # create the same section init will check for and add values to
+ config_parser_pre.add_section('oslo_concurrency')
+ config_parser_pre.set('oslo_concurrency', 'TEST', local_dir.path)
+ # create a new section
+ config_parser_pre.add_section('TEST')
+ config_parser_pre.set('TEST', 'foo', "bar")
+ config_parser_pre.write(conf_file)
+
+ # Update the config file the same way tempest init does
+ init_cmd.update_local_conf(config_path, lock_dir, log_dir)
+
+ # parse the new config file to verify it
+ config_parser_post = init_cmd.get_configparser(config_path)
+
+ # check that our value in oslo_concurrency wasn't overwritten
+ self.assertTrue(config_parser_post.has_section('oslo_concurrency'))
+ self.assertEqual(config_parser_post.get('oslo_concurrency', 'TEST'),
+ local_dir.path)
+ # check that the lock directory was set correctly
+ self.assertEqual(config_parser_post.get('oslo_concurrency',
+ 'lock_path'), lock_dir)
+
+ # check that our new section still exists and wasn't modified
+ self.assertTrue(config_parser_post.has_section('TEST'))
+ self.assertEqual(config_parser_post.get('TEST', 'foo'), 'bar')
+
+ # check that the DEFAULT values are correct
+ # NOTE(auggy): has_section ignores DEFAULT
+ self.assertEqual(config_parser_post.get('DEFAULT', 'log_dir'), log_dir)
+
def test_create_working_dir_with_existing_local_dir_non_empty(self):
fake_local_dir = self.useFixture(fixtures.TempDir())
fake_local_conf_dir = self.useFixture(fixtures.TempDir())
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 70cbf87..00b4542 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -188,34 +188,54 @@
False, True)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
- def test_verify_cinder_api_versions_no_v2(self, mock_request):
+ def test_verify_cinder_api_versions_no_v3(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v1.0'}]}
+ fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]}
fake_resp = json.dumps(fake_resp)
mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_called_once_with('api_v2', 'volume-feature-enabled',
- False, True)
+ print_mock.assert_not_called()
+
+ @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+ def test_verify_cinder_api_versions_no_v2(self, mock_request):
+ self.useFixture(mockpatch.PatchObject(
+ verify_tempest_config, '_get_unversioned_endpoint',
+ return_value='http://fake_endpoint:5000'))
+ fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v3.0'}]}
+ fake_resp = json.dumps(fake_resp)
+ mock_request.return_value = (None, fake_resp)
+ fake_os = mock.MagicMock()
+ with mock.patch.object(verify_tempest_config,
+ 'print_and_or_update') as print_mock:
+ verify_tempest_config.verify_cinder_api_versions(fake_os, True)
+ print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
+ False, True)
+ print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+ True, True)
+ self.assertEqual(2, print_mock.call_count)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v1(self, mock_request):
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v2.0'}]}
+ fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
fake_resp = json.dumps(fake_resp)
mock_request.return_value = (None, fake_resp)
fake_os = mock.MagicMock()
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_called_once_with('api_v1', 'volume-feature-enabled',
- False, True)
+ print_mock.assert_any_call('api_v1', 'volume-feature-enabled',
+ False, True)
+ print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+ True, True)
+ self.assertEqual(2, print_mock.call_count)
def test_verify_glance_version_no_v2_with_v1_1(self):
def fake_get_versions():
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index 2639d93..6ca4d42 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -17,7 +17,7 @@
import subprocess
import tempfile
-from tempest.cmd.workspace import WorkspaceManager
+from tempest.cmd import workspace
from tempest.lib.common.utils import data_utils
from tempest.tests import base
@@ -31,7 +31,8 @@
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 = workspace.WorkspaceManager(
+ path=self.store_file)
self.workspace_manager.register_new_workspace(self.name, self.path)
@@ -92,7 +93,8 @@
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 = workspace.WorkspaceManager(
+ path=self.store_file)
self.workspace_manager.register_new_workspace(self.name, self.path)
def test_workspace_manager_get(self):
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index b7cc05d..0033d4e 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -22,20 +22,21 @@
from tempest import exceptions
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import identity_client as v2_iden_client
from tempest.lib.services.identity.v2 import roles_client as v2_roles_client
from tempest.lib.services.identity.v2 import tenants_client as \
v2_tenants_client
from tempest.lib.services.identity.v2 import token_client as v2_token_client
from tempest.lib.services.identity.v2 import users_client as v2_users_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.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 \
+from tempest.lib.services.identity.v3 import identity_client as v3_iden_client
+from tempest.lib.services.identity.v3 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.lib.services.identity.v3 import roles_client as v3_roles_client
+from tempest.lib.services.identity.v3 import token_client as v3_token_client
+from tempest.lib.services.identity.v3 import users_client as \
+ v3_users_client
+from tempest.lib.services.network import routers_client
+from tempest.services.identity.v3.json import domains_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests.lib import fake_http
@@ -55,7 +56,6 @@
users_client = v2_users_client
token_client_class = token_client.TokenClient
fake_response = fake_identity._fake_v2_response
- assign_role_on_project = 'create_user_role_on_project'
tenants_client_class = tenants_client.TenantsClient
delete_tenant = 'delete_tenant'
@@ -125,7 +125,7 @@
def _mock_assign_user_role(self):
tenant_fix = self.useFixture(mockpatch.PatchObject(
self.roles_client.RolesClient,
- self.assign_role_on_project,
+ 'create_user_role_on_project',
return_value=(rest_client.ResponseBody
(200, {}))))
return tenant_fix
@@ -198,11 +198,11 @@
self._mock_tenant_create('1234', 'fake_admin_tenant')
user_mock = mock.patch.object(self.roles_client.RolesClient,
- self.assign_role_on_project)
+ 'create_user_role_on_project')
user_mock.start()
self.addCleanup(user_mock.stop)
with mock.patch.object(self.roles_client.RolesClient,
- self.assign_role_on_project) as user_mock:
+ 'create_user_role_on_project') as user_mock:
admin_creds = creds.get_admin_creds()
user_mock.assert_has_calls([
mock.call('1234', '1234', '1234')])
@@ -221,11 +221,11 @@
self._mock_tenant_create('1234', 'fake_role_tenant')
user_mock = mock.patch.object(self.roles_client.RolesClient,
- self.assign_role_on_project)
+ 'create_user_role_on_project')
user_mock.start()
self.addCleanup(user_mock.stop)
with mock.patch.object(self.roles_client.RolesClient,
- self.assign_role_on_project) as user_mock:
+ 'create_user_role_on_project') as user_mock:
role_creds = creds.get_creds_by_roles(
roles=['role1', 'role2'])
calls = user_mock.mock_calls
@@ -612,7 +612,6 @@
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'
@@ -624,7 +623,7 @@
return_value=dict(domains=[dict(id='default',
name='Default')])))
self.patchobject(self.roles_client.RolesClient,
- 'assign_user_role_on_domain')
+ 'create_user_role_on_domain')
def _mock_list_ec2_credentials(self, user_id, tenant_id):
pass
diff --git a/tempest/tests/fake_auth_provider.py b/tempest/tests/fake_auth_provider.py
deleted file mode 100644
index 769f6a6..0000000
--- a/tempest/tests/fake_auth_provider.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-class FakeAuthProvider(object):
-
- def auth_request(self, method, url, headers=None, body=None, filters=None):
- return url, headers, body
-
- def get_token(self):
- return "faketoken"
-
- def base_url(self, filters, auth_data=None):
- return "https://example.com"
diff --git a/tempest/tests/fake_tempest_plugin.py b/tempest/tests/fake_tempest_plugin.py
index f718d0b..56aae1e 100644
--- a/tempest/tests/fake_tempest_plugin.py
+++ b/tempest/tests/fake_tempest_plugin.py
@@ -18,6 +18,7 @@
class FakePlugin(plugins.TempestPlugin):
expected_load_test = ["my/test/path", "/home/dir"]
+ expected_service_clients = [{'foo': 'bar'}]
def load_tests(self):
return self.expected_load_test
@@ -28,6 +29,9 @@
def get_opt_lists(self):
return []
+ def get_service_clients(self):
+ return self.expected_service_clients
+
class FakeStevedoreObj(object):
obj = FakePlugin()
@@ -38,3 +42,26 @@
def __init__(self, name='Test1'):
self._name = name
+
+
+class FakePluginNoServiceClients(plugins.TempestPlugin):
+
+ def load_tests(self):
+ return []
+
+ def register_opts(self, conf):
+ return
+
+ def get_opt_lists(self):
+ return []
+
+
+class FakeStevedoreObjNoServiceClients(object):
+ obj = FakePluginNoServiceClients()
+
+ @property
+ def name(self):
+ return self._name
+
+ def __init__(self, name='Test2'):
+ self._name = name
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index 399c4af..4446e5c 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -59,7 +59,7 @@
def test_rand_password(self):
actual = data_utils.rand_password()
self.assertIsInstance(actual, str)
- self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{15,}")
+ self.assertRegex(actual, "[A-Za-z0-9~!@#%^&*_=+]{15,}")
actual2 = data_utils.rand_password()
self.assertNotEqual(actual, actual2)
@@ -67,7 +67,7 @@
actual = data_utils.rand_password(8)
self.assertIsInstance(actual, str)
self.assertEqual(len(actual), 8)
- self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{8}")
+ self.assertRegex(actual, "[A-Za-z0-9~!@#%^&*_=+]{8}")
actual2 = data_utils.rand_password(8)
self.assertNotEqual(actual, actual2)
@@ -75,7 +75,7 @@
actual = data_utils.rand_password(2)
self.assertIsInstance(actual, str)
self.assertEqual(len(actual), 3)
- self.assertRegex(actual, "[A-Za-z0-9~!@#$%^&*_=+]{3}")
+ self.assertRegex(actual, "[A-Za-z0-9~!@#%^&*_=+]{3}")
actual2 = data_utils.rand_password(2)
self.assertNotEqual(actual, actual2)
@@ -125,13 +125,13 @@
def test_random_bytes(self):
actual = data_utils.random_bytes() # default size=1024
- self.assertIsInstance(actual, str)
- self.assertRegex(actual, "^[\x00-\xFF]{1024}")
+ self.assertIsInstance(actual, bytes)
+ self.assertEqual(1024, len(actual))
actual2 = data_utils.random_bytes()
self.assertNotEqual(actual, actual2)
actual = data_utils.random_bytes(size=2048)
- self.assertRegex(actual, "^[\x00-\xFF]{2048}")
+ self.assertEqual(2048, len(actual))
def test_get_ipv6_addr_by_EUI64(self):
actual = data_utils.get_ipv6_addr_by_EUI64('2001:db8::',
diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
index 919e219..29c5684 100644
--- a/tempest/tests/lib/common/utils/test_test_utils.py
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -17,6 +17,7 @@
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions
from tempest.tests import base
+from tempest.tests import utils
class TestTestUtils(base.TestCase):
@@ -76,3 +77,27 @@
self.assertEqual(
42, test_utils.call_and_ignore_notfound_exc(m, *args, **kwargs))
m.assert_called_once_with(*args, **kwargs)
+
+ @mock.patch('time.sleep')
+ @mock.patch('time.time')
+ def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep):
+ timeout = 42 # The value doesn't matter as we mock time.time()
+ sleep = 60 # The value doesn't matter as we mock time.sleep()
+ m_time.side_effect = utils.generate_timeout_series(timeout)
+ self.assertEqual(
+ False, test_utils.call_until_true(lambda: False, timeout, sleep)
+ )
+ m_sleep.call_args_list = [mock.call(sleep)] * 2
+ m_time.call_args_list = [mock.call()] * 2
+
+ @mock.patch('time.sleep')
+ @mock.patch('time.time')
+ def test_call_until_true_when_f_returns_true(self, m_time, m_sleep):
+ timeout = 42 # The value doesn't matter as we mock time.time()
+ sleep = 60 # The value doesn't matter as we mock time.sleep()
+ m_time.return_value = 0
+ self.assertEqual(
+ True, test_utils.call_until_true(lambda: True, timeout, sleep)
+ )
+ self.assertEqual(0, m_sleep.call_count)
+ self.assertEqual(1, m_time.call_count)
diff --git a/tempest/tests/lib/fake_auth_provider.py b/tempest/tests/lib/fake_auth_provider.py
index 8095453..e4582f8 100644
--- a/tempest/tests/lib/fake_auth_provider.py
+++ b/tempest/tests/lib/fake_auth_provider.py
@@ -27,9 +27,12 @@
def base_url(self, filters, auth_data=None):
return self.fake_base_url or "https://example.com"
+ def get_token(self):
+ return "faketoken"
+
class FakeCredentials(object):
def __init__(self, creds_dict):
- for key in creds_dict.keys():
+ for key in creds_dict:
setattr(self, key, creds_dict[key])
diff --git a/tempest/tests/lib/services/base.py b/tempest/tests/lib/services/base.py
index a244aa2..3165689 100644
--- a/tempest/tests/lib/services/base.py
+++ b/tempest/tests/lib/services/base.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
from oslo_serialization import jsonutils as json
from oslotest import mockpatch
@@ -31,7 +32,7 @@
def check_service_client_function(self, function, function2mock,
body, to_utf=False, status=200,
- headers=None, **kwargs):
+ headers=None, cr_blob=False, **kwargs):
mocked_response = self.create_response(body, to_utf, status, headers)
self.useFixture(mockpatch.Patch(
function2mock, return_value=mocked_response))
@@ -39,4 +40,11 @@
resp = function(**kwargs)
else:
resp = function()
- self.assertEqual(body, resp)
+
+ if cr_blob:
+ evaluated_body = copy.deepcopy(body)
+ nested_json = json.loads(evaluated_body['credential']['blob'])
+ evaluated_body['credential']['blob'] = nested_json
+ self.assertEqual(evaluated_body, resp)
+ else:
+ self.assertEqual(body, resp)
diff --git a/tempest/tests/lib/services/identity/v2/test_identity_client.py b/tempest/tests/lib/services/identity/v2/test_identity_client.py
new file mode 100644
index 0000000..96d50d7
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_identity_client.py
@@ -0,0 +1,175 @@
+# 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.identity.v2 import identity_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestIdentityClient(base.BaseServiceTest):
+ FAKE_TOKEN = {
+ "tokens": {
+ "id": "cbc36478b0bd8e67e89",
+ "name": "FakeToken",
+ "type": "token",
+ }
+ }
+
+ FAKE_API_INFO = {
+ "name": "API_info",
+ "type": "API",
+ "description": "test_description"
+ }
+
+ FAKE_LIST_EXTENSIONS = {
+ "extensions": {
+ "values": [
+ {
+ "updated": "2013-07-07T12:00:0-00:00",
+ "name": "OpenStack S3 API",
+ "links": [
+ {
+ "href": "https://github.com/openstack/" +
+ "identity-api",
+ "type": "text/html",
+ "rel": "describedby"
+ }
+ ],
+ "namespace": "http://docs.openstack.org/identity/" +
+ "api/ext/s3tokens/v1.0",
+ "alias": "s3tokens",
+ "description": "OpenStack S3 API."
+ },
+ {
+ "updated": "2013-12-17T12:00:0-00:00",
+ "name": "OpenStack Federation APIs",
+ "links": [
+ {
+ "href": "https://github.com/openstack/" +
+ "identity-api",
+ "type": "text/html",
+ "rel": "describedby"
+ }
+ ],
+ "namespace": "http://docs.openstack.org/identity/" +
+ "api/ext/OS-FEDERATION/v1.0",
+ "alias": "OS-FEDERATION",
+ "description": "OpenStack Identity Providers Mechanism."
+ },
+ {
+ "updated": "2014-01-20T12:00:0-00:00",
+ "name": "OpenStack Simple Certificate API",
+ "links": [
+ {
+ "href": "https://github.com/openstack/" +
+ "identity-api",
+ "type": "text/html",
+ "rel": "describedby"
+ }
+ ],
+ "namespace": "http://docs.openstack.org/identity/api/" +
+ "ext/OS-SIMPLE-CERT/v1.0",
+ "alias": "OS-SIMPLE-CERT",
+ "description": "OpenStack simple certificate extension"
+ },
+ {
+ "updated": "2013-07-07T12:00:0-00:00",
+ "name": "OpenStack OAUTH1 API",
+ "links": [
+ {
+ "href": "https://github.com/openstack/" +
+ "identity-api",
+ "type": "text/html",
+ "rel": "describedby"
+ }
+ ],
+ "namespace": "http://docs.openstack.org/identity/" +
+ "api/ext/OS-OAUTH1/v1.0",
+ "alias": "OS-OAUTH1",
+ "description": "OpenStack OAuth Delegated Auth Mechanism."
+ },
+ {
+ "updated": "2013-07-07T12:00:0-00:00",
+ "name": "OpenStack EC2 API",
+ "links": [
+ {
+ "href": "https://github.com/openstack/" +
+ "identity-api",
+ "type": "text/html",
+ "rel": "describedby"
+ }
+ ],
+ "namespace": "http://docs.openstack.org/identity/api/" +
+ "ext/OS-EC2/v1.0",
+ "alias": "OS-EC2",
+ "description": "OpenStack EC2 Credentials backend."
+ }
+ ]
+ }
+ }
+
+ def setUp(self):
+ super(TestIdentityClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = identity_client.IdentityClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _test_show_api_description(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_api_description,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_API_INFO,
+ bytes_body)
+
+ def _test_list_extensions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_extensions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_EXTENSIONS,
+ bytes_body)
+
+ def _test_show_token(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_token,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TOKEN,
+ bytes_body,
+ token_id="cbc36478b0bd8e67e89")
+
+ def test_show_api_description_with_str_body(self):
+ self._test_show_api_description()
+
+ def test_show_api_description_with_bytes_body(self):
+ self._test_show_api_description(bytes_body=True)
+
+ def test_show_list_extensions_with_str_body(self):
+ self._test_list_extensions()
+
+ def test_show_list_extensions_with_bytes_body(self):
+ self._test_list_extensions(bytes_body=True)
+
+ def test_show_token_with_str_body(self):
+ self._test_show_token()
+
+ def test_show_token_with_bytes_body(self):
+ self._test_show_token(bytes_body=True)
+
+ def test_delete_token(self):
+ self.check_service_client_function(
+ self.client.delete_token,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ token_id="cbc36478b0bd8e67e89",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_credentials_client.py b/tempest/tests/lib/services/identity/v3/test_credentials_client.py
new file mode 100644
index 0000000..a2a22ff
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_credentials_client.py
@@ -0,0 +1,179 @@
+# 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.identity.v3 import credentials_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestCredentialsClient(base.BaseServiceTest):
+ FAKE_CREATE_CREDENTIAL = {
+ "credential": {
+ "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}",
+ "project_id": "731fc6f265cd486d900f16e84c5cb594",
+ "type": "ec2",
+ "user_id": "bb5476fd12884539b41d5a88f838d773"
+ }
+ }
+
+ FAKE_INFO_CREDENTIAL = {
+ "credential": {
+ "user_id": "bb5476fd12884539b41d5a88f838d773",
+ "links": {
+ "self": "http://example.com/identity/v3/credentials/" +
+ "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4" +
+ "ada4ff8b76165a07311d7"
+ },
+ "blob": "{\"access\": \"a42a27755ce6442596b049bd7dd8a563\"," +
+ " \"secret\": \"71faf1d40bb24c82b479b1c6fbbd9f0c\"}",
+ "project_id": "6e01855f345f4c59812999b5e459137d",
+ "type": "ec2",
+ "id": "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f"
+ }
+ }
+
+ FAKE_LIST_CREDENTIALS = {
+ "credentials": [
+ {
+ "user_id": "bb5476fd12884539b41d5a88f838d773",
+ "links": {
+ "self": "http://example.com/identity/v3/credentials/" +
+ "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4" +
+ "ada4ff8b76165a07311d7"
+ },
+ "blob": "{\"access\": \"a42a27755ce6442596b049bd7dd8a563\"," +
+ " \"secret\": \"71faf1d40bb24c82b479b1c6fbbd9f0c\"," +
+ " \"trust_id\": null}",
+ "project_id": "6e01855f345f4c59812999b5e459137d",
+ "type": "ec2",
+ "id": "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f"
+ },
+ {
+ "user_id": "6f556708d04b4ea6bc72d7df2296b71a",
+ "links": {
+ "self": "http://example.com/identity/v3/credentials/" +
+ "2441494e52ab6d594a34d74586075cb299489bdd1e9" +
+ "389e3ab06467a4f460609"
+ },
+ "blob": "{\"access\": \"7da79ff0aa364e1396f067e352b9b79a\"," +
+ " \"secret\": \"7a18d68ba8834b799d396f3ff6f1e98c\"," +
+ " \"trust_id\": null}",
+ "project_id": "1a1d14690f3c4ec5bf5f321c5fde3c16",
+ "type": "ec2",
+ "id": "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3"
+ },
+ {
+ "user_id": "c14107e65d5c4a7f8894fc4b3fc209ff",
+ "links": {
+ "self": "http://example.com/identity/v3/credentials/" +
+ "3397b204b5f04c495bcdc8f34c8a39996f280f91726" +
+ "58241873e15f070ec79d7"
+ },
+ "blob": "{\"access\": \"db9c58a558534a10a070110de4f9f20c\"," +
+ " \"secret\": \"973e790b88db447ba6f93bca02bc745b\"," +
+ " \"trust_id\": null}",
+ "project_id": "7396e43183db40dcbf40dd727637b548",
+ "type": "ec2",
+ "id": "3397b204b5f04c495bcdc8f34c8a39996f280f9172658241"
+ },
+ {
+ "user_id": "bb5476fd12884539b41d5a88f838d773",
+ "links": {
+ "self": "http://example.com/identity/v3/credentials/" +
+ "7ef4faa904ae7b8b4ddc7bad15b05ee359dad7d7a9b" +
+ "82861d4ad92fdbbb2eb4e"
+ },
+ "blob": "{\"access\": \"7d7559359b57419eb5f5f5dcd65ab57d\"," +
+ " \"secret\": \"570652bcf8c2483c86eb29e9734eed3c\"," +
+ " \"trust_id\": null}",
+ "project_id": "731fc6f265cd486d900f16e84c5cb594",
+ "type": "ec2",
+ "id": "7ef4faa904ae7b8b4ddc7bad15b05ee359dad7d7a9b82861"
+ },
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/credentials",
+ "previous": None,
+ "next": None
+ }
+ }
+
+ def setUp(self):
+ super(TestCredentialsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = credentials_client.CredentialsClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _test_create_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_credential,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_CREDENTIAL,
+ bytes_body, status=201, cr_blob=True)
+
+ def _test_show_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_credential,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_INFO_CREDENTIAL,
+ bytes_body, cr_blob=True,
+ credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f")
+
+ def _test_update_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_credential,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_INFO_CREDENTIAL,
+ bytes_body, cr_blob=True,
+ credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f")
+
+ def _test_list_credentials(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_credentials,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_CREDENTIALS,
+ bytes_body)
+
+ def test_create_credential_with_str_body(self):
+ self._test_create_credential()
+
+ def test_create_credential_with_bytes_body(self):
+ self._test_create_credential(bytes_body=True)
+
+ def test_show_credential_with_str_body(self):
+ self._test_show_credential()
+
+ def test_show_credential_with_bytes_body(self):
+ self._test_show_credential(bytes_body=True)
+
+ def test_update_credential_with_str_body(self):
+ self._test_update_credential()
+
+ def test_update_credential_with_bytes_body(self):
+ self._test_update_credential(bytes_body=True)
+
+ def test_list_credentials_with_str_body(self):
+ self._test_list_credentials()
+
+ def test_list_credentials_with_bytes_body(self):
+ self._test_list_credentials(bytes_body=True)
+
+ def test_delete_credential(self):
+ self.check_service_client_function(
+ self.client.delete_credential,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
new file mode 100644
index 0000000..f8c553f
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -0,0 +1,100 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import endpoints_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndpointsClient(base.BaseServiceTest):
+ FAKE_CREATE_ENDPOINT = {
+ "endpoint": {
+ "id": 1,
+ "tenantId": 1,
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ }
+
+ FAKE_LIST_ENDPOINTS = {
+ "endpoints": [
+ {
+ "id": 1,
+ "tenantId": "1",
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ },
+ {
+ "id": 2,
+ "tenantId": "1",
+ "region": "South",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestEndpointsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoints_client.EndPointsClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_ENDPOINT,
+ bytes_body,
+ status=201,
+ service_id="b344506af7644f6794d9cb316600b020",
+ region="region-demo",
+ publicurl="https://compute.north.public.com/v1",
+ adminurl="https://compute.north.internal.com/v1",
+ internalurl="https://compute.north.internal.com/v1")
+
+ def _test_list_endpoints(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS,
+ bytes_body)
+
+ def test_create_endpoint_with_str_body(self):
+ self._test_create_endpoint()
+
+ def test_create_endpoint_with_bytes_body(self):
+ self._test_create_endpoint(bytes_body=True)
+
+ def test_list_endpoints_with_str_body(self):
+ self._test_list_endpoints()
+
+ def test_list_endpoints_with_bytes_body(self):
+ self._test_list_endpoints(bytes_body=True)
+
+ def test_delete_endpoint(self):
+ self.check_service_client_function(
+ self.client.delete_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ endpoint_id="b344506af7644f6794d9cb316600b020",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_groups_client.py b/tempest/tests/lib/services/identity/v3/test_groups_client.py
new file mode 100644
index 0000000..38cf3ae
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_groups_client.py
@@ -0,0 +1,213 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestGroupsClient(base.BaseServiceTest):
+ FAKE_CREATE_GROUP = {
+ 'group': {
+ 'description': 'Tempest Group Description',
+ 'domain_id': 'TempestDomain',
+ 'name': 'Tempest Group',
+ }
+ }
+
+ FAKE_GROUP_INFO = {
+ 'group': {
+ 'description': 'Tempest Group Description',
+ 'domain_id': 'TempestDomain',
+ 'id': '6e13e2068cf9466e98950595baf6bb35',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ '6e13e2068cf9466e98950595baf6bb35'
+ },
+ 'name': 'Tempest Group',
+ }
+ }
+
+ FAKE_GROUP_LIST = {
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups',
+ 'previous': None,
+ 'next': None,
+ },
+ 'groups': [
+ {
+ 'description': 'Tempest Group One Description',
+ 'domain_id': 'TempestDomain',
+ 'id': '1c92f3453ed34291a074b87493455b8f',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ '1c92f3453ed34291a074b87493455b8f'
+ },
+ 'name': 'Tempest Group One',
+ },
+ {
+ 'description': 'Tempest Group Two Description',
+ 'domain_id': 'TempestDomain',
+ 'id': 'ce9e7dafed3b4877a7d4466ed730a9ee',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ 'ce9e7dafed3b4877a7d4466ed730a9ee'
+ },
+ 'name': 'Tempest Group Two',
+ },
+ ]
+ }
+
+ FAKE_USER_LIST = {
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ '6e13e2068cf9466e98950595baf6bb35/users',
+ 'previous': None,
+ 'next': None,
+ },
+ 'users': [
+ {
+ 'domain_id': 'TempestDomain',
+ 'description': 'Tempest Test User One Description',
+ 'enabled': True,
+ 'id': '642688fa65a84217b86cef3c063de2b9',
+ 'name': 'TempestUserOne',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/users/' +
+ '642688fa65a84217b86cef3c063de2b9'
+ }
+ },
+ {
+ 'domain_id': 'TempestDomain',
+ 'description': 'Tempest Test User Two Description',
+ 'enabled': True,
+ 'id': '1048ead6f8ef4a859b44ffbce3ac0b52',
+ 'name': 'TempestUserTwo',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/users/' +
+ '1048ead6f8ef4a859b44ffbce3ac0b52'
+ }
+ },
+ ]
+ }
+
+ def setUp(self):
+ super(TestGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = groups_client.GroupsClient(fake_auth, 'identity',
+ 'regionOne')
+
+ def _test_create_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_GROUP,
+ bytes_body,
+ status=201,
+ )
+
+ def _test_show_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_GROUP_INFO,
+ bytes_body,
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ )
+
+ def _test_list_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_GROUP_LIST,
+ bytes_body,
+ )
+
+ def _test_update_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_group,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_GROUP_INFO,
+ bytes_body,
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ name='NewName',
+ )
+
+ def _test_list_users_in_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_group_users,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_LIST,
+ bytes_body,
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ )
+
+ def test_create_group_with_string_body(self):
+ self._test_create_group()
+
+ def test_create_group_with_bytes_body(self):
+ self._test_create_group(bytes_body=True)
+
+ def test_show_group_with_string_body(self):
+ self._test_show_group()
+
+ def test_show_group_with_bytes_body(self):
+ self._test_show_group(bytes_body=True)
+
+ def test_list_groups_with_string_body(self):
+ self._test_list_groups()
+
+ def test_list_groups_with_bytes_body(self):
+ self._test_list_groups(bytes_body=True)
+
+ def test_update_group_with_string_body(self):
+ self._test_update_group()
+
+ def test_update_group_with_bytes_body(self):
+ self._test_update_group(bytes_body=True)
+
+ def test_list_users_in_group_with_string_body(self):
+ self._test_list_users_in_group()
+
+ def test_list_users_in_group_with_bytes_body(self):
+ self._test_list_users_in_group(bytes_body=True)
+
+ def test_delete_group(self):
+ self.check_service_client_function(
+ self.client.delete_group,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ status=204,
+ )
+
+ def test_add_user_to_group(self):
+ self.check_service_client_function(
+ self.client.add_group_user,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ status=204,
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ user_id='642688fa65a84217b86cef3c063de2b9',
+ )
+
+ def test_check_user_in_group(self):
+ self.check_service_client_function(
+ self.client.check_group_user_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ status=204,
+ group_id='6e13e2068cf9466e98950595baf6bb35',
+ user_id='642688fa65a84217b86cef3c063de2b9',
+ )
diff --git a/tempest/tests/lib/services/identity/v3/test_identity_client.py b/tempest/tests/lib/services/identity/v3/test_identity_client.py
new file mode 100644
index 0000000..9eaaaaf
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_identity_client.py
@@ -0,0 +1,75 @@
+# 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.identity.v3 import identity_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestIdentityClient(base.BaseServiceTest):
+ FAKE_TOKEN = {
+ "tokens": {
+ "id": "cbc36478b0bd8e67e89",
+ "name": "FakeToken",
+ "type": "token",
+ }
+ }
+
+ FAKE_API_INFO = {
+ "name": "API_info",
+ "type": "API",
+ "description": "test_description"
+ }
+
+ def setUp(self):
+ super(TestIdentityClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = identity_client.IdentityClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _test_show_api_description(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_api_description,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_API_INFO,
+ bytes_body)
+
+ def _test_show_token(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_token,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TOKEN,
+ bytes_body,
+ resp_token="cbc36478b0bd8e67e89")
+
+ def test_show_api_description_with_str_body(self):
+ self._test_show_api_description()
+
+ def test_show_api_description_with_bytes_body(self):
+ self._test_show_api_description(bytes_body=True)
+
+ def test_show_token_with_str_body(self):
+ self._test_show_token()
+
+ def test_show_token_with_bytes_body(self):
+ self._test_show_token(bytes_body=True)
+
+ def test_delete_token(self):
+ self.check_service_client_function(
+ self.client.delete_token,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ resp_token="cbc36478b0bd8e67e89",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py b/tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py
new file mode 100644
index 0000000..9da3cce
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py
@@ -0,0 +1,220 @@
+# 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.identity.v3 import inherited_roles_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestInheritedRolesClient(base.BaseServiceTest):
+ FAKE_LIST_INHERITED_ROLES = {
+ "roles": [
+ {
+ "id": "1",
+ "name": "test",
+ "links": "example.com"
+ },
+ {
+ "id": "2",
+ "name": "test2",
+ "links": "example.com"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestInheritedRolesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = inherited_roles_client.InheritedRolesClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_create_inherited_role_on_domains_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_inherited_role_on_domains_user,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_list_inherited_project_role_for_user_on_domain(
+ self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_inherited_project_role_for_user_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_INHERITED_ROLES,
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123")
+
+ def _test_create_inherited_role_on_domains_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_inherited_role_on_domains_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_list_inherited_project_role_for_group_on_domain(
+ self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_inherited_project_role_for_group_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_INHERITED_ROLES,
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123")
+
+ def _test_create_inherited_role_on_projects_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_inherited_role_on_projects_user,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_create_inherited_role_on_projects_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_inherited_role_on_projects_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_create_inherited_role_on_domains_user_with_str_body(self):
+ self._test_create_inherited_role_on_domains_user()
+
+ def test_create_inherited_role_on_domains_user_with_bytes_body(self):
+ self._test_create_inherited_role_on_domains_user(bytes_body=True)
+
+ def test_create_inherited_role_on_domains_group_with_str_body(self):
+ self._test_create_inherited_role_on_domains_group()
+
+ def test_create_inherited_role_on_domains_group_with_bytes_body(self):
+ self._test_create_inherited_role_on_domains_group(bytes_body=True)
+
+ def test_create_inherited_role_on_projects_user_with_str_body(self):
+ self._test_create_inherited_role_on_projects_user()
+
+ def test_create_inherited_role_on_projects_group_with_bytes_body(self):
+ self._test_create_inherited_role_on_projects_group(bytes_body=True)
+
+ def test_list_inherited_project_role_for_user_on_domain_with_str_body(
+ self):
+ self._test_list_inherited_project_role_for_user_on_domain()
+
+ def test_list_inherited_project_role_for_user_on_domain_with_bytes_body(
+ self):
+ self._test_list_inherited_project_role_for_user_on_domain(
+ bytes_body=True)
+
+ def test_list_inherited_project_role_for_group_on_domain_with_str_body(
+ self):
+ self._test_list_inherited_project_role_for_group_on_domain()
+
+ def test_list_inherited_project_role_for_group_on_domain_with_bytes_body(
+ self):
+ self._test_list_inherited_project_role_for_group_on_domain(
+ bytes_body=True)
+
+ def test_delete_inherited_role_from_user_on_domain(self):
+ self.check_service_client_function(
+ self.client.delete_inherited_role_from_user_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_user_inherited_project_role_on_domain(self):
+ self.check_service_client_function(
+ self.client.check_user_inherited_project_role_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_inherited_role_from_group_on_domain(self):
+ self.check_service_client_function(
+ self.client.delete_inherited_role_from_group_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_group_inherited_project_role_on_domain(self):
+ self.check_service_client_function(
+ self.client.check_group_inherited_project_role_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_inherited_role_from_user_on_project(self):
+ self.check_service_client_function(
+ self.client.delete_inherited_role_from_user_on_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_user_has_flag_on_inherited_to_project(self):
+ self.check_service_client_function(
+ self.client.check_user_has_flag_on_inherited_to_project,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_inherited_role_from_group_on_project(self):
+ self.check_service_client_function(
+ self.client.delete_inherited_role_from_group_on_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_group_has_flag_on_inherited_to_project(self):
+ self.check_service_client_function(
+ self.client.check_group_has_flag_on_inherited_to_project,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_policies_client.py b/tempest/tests/lib/services/identity/v3/test_policies_client.py
new file mode 100644
index 0000000..66c3d65
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -0,0 +1,152 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import policies_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestPoliciesClient(base.BaseServiceTest):
+ FAKE_CREATE_POLICY = {
+ "policy": {
+ "blob": "{'foobar_user': 'role:compute-user'}",
+ "project_id": "0426ac1e48f642ef9544c2251e07e261",
+ "type": "application/json",
+ "user_id": "0ffd248c55b443eaac5253b4e9cbf9b5"
+ }
+ }
+
+ FAKE_POLICY_INFO = {
+ "policy": {
+ "blob": {
+ "foobar_user": [
+ "role:compute-user"
+ ]
+ },
+ "id": "717273",
+ "links": {
+ "self": "http://example.com/identity/v3/policies/717273"
+ },
+ "project_id": "456789",
+ "type": "application/json",
+ "user_id": "616263"
+ }
+ }
+
+ FAKE_LIST_POLICIES = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/policies"
+ },
+ "policies": [
+ {
+ "blob": {
+ "foobar_user": [
+ "role:compute-user"
+ ]
+ },
+ "id": "717273",
+ "links": {
+ "self": "http://example.com/identity/v3/policies/717273"
+ },
+ "project_id": "456789",
+ "type": "application/json",
+ "user_id": "616263"
+ },
+ {
+ "blob": {
+ "foobar_user": [
+ "role:compute-user"
+ ]
+ },
+ "id": "717274",
+ "links": {
+ "self": "http://example.com/identity/v3/policies/717274"
+ },
+ "project_id": "456789",
+ "type": "application/json",
+ "user_id": "616263"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestPoliciesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = policies_client.PoliciesClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_policy,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_POLICY,
+ bytes_body,
+ status=201)
+
+ def _test_show_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_policy,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_POLICY_INFO,
+ bytes_body,
+ policy_id="717273")
+
+ def _test_list_policies(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_policies,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_POLICIES,
+ bytes_body)
+
+ def _test_update_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_policy,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_POLICY_INFO,
+ bytes_body,
+ policy_id="717273")
+
+ def test_create_policy_with_str_body(self):
+ self._test_create_policy()
+
+ def test_create_policy_with_bytes_body(self):
+ self._test_create_policy(bytes_body=True)
+
+ def test_show_policy_with_str_body(self):
+ self._test_show_policy()
+
+ def test_show_policy_with_bytes_body(self):
+ self._test_show_policy(bytes_body=True)
+
+ def test_list_policies_with_str_body(self):
+ self._test_list_policies()
+
+ def test_list_policies_with_bytes_body(self):
+ self._test_list_policies(bytes_body=True)
+
+ def test_update_policy_with_str_body(self):
+ self._test_update_policy()
+
+ def test_update_policy_with_bytes_body(self):
+ self._test_update_policy(bytes_body=True)
+
+ def test_delete_policy(self):
+ self.check_service_client_function(
+ self.client.delete_policy,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ policy_id="717273",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_projects_client.py b/tempest/tests/lib/services/identity/v3/test_projects_client.py
new file mode 100644
index 0000000..6ffbcde
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_projects_client.py
@@ -0,0 +1,178 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import projects_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestProjectsClient(base.BaseServiceTest):
+ FAKE_CREATE_PROJECT = {
+ "project": {
+ "description": "My new project",
+ "domain_id": "default",
+ "enabled": True,
+ "is_domain": False,
+ "name": "myNewProject"
+ }
+ }
+
+ FAKE_PROJECT_INFO = {
+ "project": {
+ "is_domain": False,
+ "description": None,
+ "domain_id": "default",
+ "enabled": True,
+ "id": "0c4e939acacf4376bdcd1129f1a054ad",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/0c4e" +
+ "939acacf4376bdcd1129f1a054ad"
+ },
+ "name": "admin",
+ "parent_id": "default"
+ }
+ }
+
+ FAKE_LIST_PROJECTS = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/projects"
+ },
+ "projects": [
+ {
+ "is_domain": False,
+ "description": None,
+ "domain_id": "default",
+ "enabled": True,
+ "id": "0c4e939acacf4376bdcd1129f1a054ad",
+ "links": {
+ "self": "http://example.com/identity/v3/projects" +
+ "/0c4e939acacf4376bdcd1129f1a054ad"
+ },
+ "name": "admin",
+ "parent_id": None
+ },
+ {
+ "is_domain": False,
+ "description": None,
+ "domain_id": "default",
+ "enabled": True,
+ "id": "0cbd49cbf76d405d9c86562e1d579bd3",
+ "links": {
+ "self": "http://example.com/identity/v3/projects" +
+ "/0cbd49cbf76d405d9c86562e1d579bd3"
+ },
+ "name": "demo",
+ "parent_id": None
+ },
+ {
+ "is_domain": False,
+ "description": None,
+ "domain_id": "default",
+ "enabled": True,
+ "id": "2db68fed84324f29bb73130c6c2094fb",
+ "links": {
+ "self": "http://example.com/identity/v3/projects" +
+ "/2db68fed84324f29bb73130c6c2094fb"
+ },
+ "name": "swifttenanttest2",
+ "parent_id": None
+ },
+ {
+ "is_domain": False,
+ "description": None,
+ "domain_id": "default",
+ "enabled": True,
+ "id": "3d594eb0f04741069dbbb521635b21c7",
+ "links": {
+ "self": "http://example.com/identity/v3/projects" +
+ "/3d594eb0f04741069dbbb521635b21c7"
+ },
+ "name": "service",
+ "parent_id": None
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestProjectsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = projects_client.ProjectsClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _test_create_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_project,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_PROJECT,
+ bytes_body,
+ name=self.FAKE_CREATE_PROJECT["project"]["name"],
+ status=201)
+
+ def _test_show_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_PROJECT_INFO,
+ bytes_body,
+ project_id="0c4e939acacf4376bdcd1129f1a054ad")
+
+ def _test_list_projects(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_projects,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_PROJECTS,
+ bytes_body)
+
+ def _test_update_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_project,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_PROJECT_INFO,
+ bytes_body,
+ project_id="0c4e939acacf4376bdcd1129f1a054ad")
+
+ def test_create_project_with_str_body(self):
+ self._test_create_project()
+
+ def test_create_project_with_bytes_body(self):
+ self._test_create_project(bytes_body=True)
+
+ def test_show_project_with_str_body(self):
+ self._test_show_project()
+
+ def test_show_project_with_bytes_body(self):
+ self._test_show_project(bytes_body=True)
+
+ def test_list_projects_with_str_body(self):
+ self._test_list_projects()
+
+ def test_list_projects_with_bytes_body(self):
+ self._test_list_projects(bytes_body=True)
+
+ def test_update_project_with_str_body(self):
+ self._test_update_project()
+
+ def test_update_project_with_bytes_body(self):
+ self._test_update_project(bytes_body=True)
+
+ def test_delete_project(self):
+ self.check_service_client_function(
+ self.client.delete_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ project_id="0c4e939acacf4376bdcd1129f1a054ad",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_regions_client.py b/tempest/tests/lib/services/identity/v3/test_regions_client.py
new file mode 100644
index 0000000..a2cb86f
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_regions_client.py
@@ -0,0 +1,125 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import regions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestRegionsClient(base.BaseServiceTest):
+ FAKE_CREATE_REGION = {
+ "region": {
+ "description": "My subregion",
+ "id": "RegionOneSubRegion",
+ "parent_region_id": "RegionOne"
+ }
+ }
+
+ FAKE_REGION_INFO = {
+ "region": {
+ "description": "My subregion 3",
+ "id": "RegionThree",
+ "links": {
+ "self": "http://example.com/identity/v3/regions/RegionThree"
+ },
+ "parent_region_id": "RegionOne"
+ }
+ }
+
+ FAKE_LIST_REGIONS = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/regions"
+ },
+ "regions": [
+ {
+ "description": "",
+ "id": "RegionOne",
+ "links": {
+ "self": "http://example.com/identity/v3/regions/RegionOne"
+ },
+ "parent_region_id": None
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestRegionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = regions_client.RegionsClient(fake_auth, 'identity',
+ 'regionOne')
+
+ def _test_create_region(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_region,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_REGION,
+ bytes_body,
+ status=201)
+
+ def _test_show_region(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_region,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_REGION_INFO,
+ bytes_body,
+ region_id="RegionThree")
+
+ def _test_list_regions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_regions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_REGIONS,
+ bytes_body)
+
+ def _test_update_region(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_region,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_REGION_INFO,
+ bytes_body,
+ region_id="RegionThree")
+
+ def test_create_region_with_str_body(self):
+ self._test_create_region()
+
+ def test_create_region_with_bytes_body(self):
+ self._test_create_region(bytes_body=True)
+
+ def test_show_region_with_str_body(self):
+ self._test_show_region()
+
+ def test_show_region_with_bytes_body(self):
+ self._test_show_region(bytes_body=True)
+
+ def test_list_regions_with_str_body(self):
+ self._test_list_regions()
+
+ def test_list_regions_with_bytes_body(self):
+ self._test_list_regions(bytes_body=True)
+
+ def test_update_region_with_str_body(self):
+ self._test_update_region()
+
+ def test_update_region_with_bytes_body(self):
+ self._test_update_region(bytes_body=True)
+
+ def test_delete_region(self):
+ self.check_service_client_function(
+ self.client.delete_region,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ region_id="RegionThree",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_roles_client.py b/tempest/tests/lib/services/identity/v3/test_roles_client.py
new file mode 100644
index 0000000..bad1ef9
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_roles_client.py
@@ -0,0 +1,313 @@
+# 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.identity.v3 import roles_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestRolesClient(base.BaseServiceTest):
+ FAKE_ROLE_INFO = {
+ "role": {
+ "domain_id": "1",
+ "id": "1",
+ "name": "test",
+ "links": "example.com"
+ }
+ }
+
+ FAKE_LIST_ROLES = {
+ "roles": [
+ {
+ "domain_id": "1",
+ "id": "1",
+ "name": "test",
+ "links": "example.com"
+ },
+ {
+ "domain_id": "2",
+ "id": "2",
+ "name": "test2",
+ "links": "example.com"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestRolesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = roles_client.RolesClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_role(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_role,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_ROLE_INFO,
+ bytes_body,
+ domain_id="1",
+ name="test",
+ status=201)
+
+ def _test_show_role(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_role,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ROLE_INFO,
+ bytes_body,
+ role_id="1")
+
+ def _test_list_roles(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_roles,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body)
+
+ def _test_update_role(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_role,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_ROLE_INFO,
+ bytes_body,
+ role_id="1",
+ name="test")
+
+ def _test_create_user_role_on_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user_role_on_project,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_create_user_role_on_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user_role_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_list_user_roles_on_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_roles_on_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123")
+
+ def _test_list_user_roles_on_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_roles_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123")
+
+ def _test_create_group_role_on_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group_role_on_project,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_create_group_role_on_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group_role_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def _test_list_group_roles_on_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_group_roles_on_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123")
+
+ def _test_list_group_roles_on_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_group_roles_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123")
+
+ def test_create_role_with_str_body(self):
+ self._test_create_role()
+
+ def test_create_role_with_bytes_body(self):
+ self._test_create_role(bytes_body=True)
+
+ def test_show_role_with_str_body(self):
+ self._test_show_role()
+
+ def test_show_role_with_bytes_body(self):
+ self._test_show_role(bytes_body=True)
+
+ def test_list_roles_with_str_body(self):
+ self._test_list_roles()
+
+ def test_list_roles_with_bytes_body(self):
+ self._test_list_roles(bytes_body=True)
+
+ def test_update_role_with_str_body(self):
+ self._test_update_role()
+
+ def test_update_role_with_bytes_body(self):
+ self._test_update_role(bytes_body=True)
+
+ def test_delete_role(self):
+ self.check_service_client_function(
+ self.client.delete_role,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ role_id="1",
+ status=204)
+
+ def test_create_user_role_on_project_with_str_body(self):
+ self._test_create_user_role_on_project()
+
+ def test_create_user_role_on_project_with_bytes_body(self):
+ self._test_create_user_role_on_project(bytes_body=True)
+
+ def test_create_user_role_on_domain_with_str_body(self):
+ self._test_create_user_role_on_domain()
+
+ def test_create_user_role_on_domain_with_bytes_body(self):
+ self._test_create_user_role_on_domain(bytes_body=True)
+
+ def test_create_group_role_on_domain_with_str_body(self):
+ self._test_create_group_role_on_domain()
+
+ def test_create_group_role_on_domain_with_bytes_body(self):
+ self._test_create_group_role_on_domain(bytes_body=True)
+
+ def test_list_user_roles_on_project_with_str_body(self):
+ self._test_list_user_roles_on_project()
+
+ def test_list_user_roles_on_project_with_bytes_body(self):
+ self._test_list_user_roles_on_project(bytes_body=True)
+
+ def test_list_user_roles_on_domain_with_str_body(self):
+ self._test_list_user_roles_on_domain()
+
+ def test_list_user_roles_on_domain_with_bytes_body(self):
+ self._test_list_user_roles_on_domain(bytes_body=True)
+
+ def test_list_group_roles_on_domain_with_str_body(self):
+ self._test_list_group_roles_on_domain()
+
+ def test_list_group_roles_on_domain_with_bytes_body(self):
+ self._test_list_group_roles_on_domain(bytes_body=True)
+
+ def test_delete_role_from_user_on_project(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_user_on_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_role_from_user_on_domain(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_user_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_role_from_group_on_project(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_group_on_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_delete_role_from_group_on_domain(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_group_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_user_role_existence_on_project(self):
+ self.check_service_client_function(
+ self.client.check_user_role_existence_on_project,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_user_role_existence_on_domain(self):
+ self.check_service_client_function(
+ self.client.check_user_role_existence_on_domain,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ user_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_role_from_group_on_project_existence(self):
+ self.check_service_client_function(
+ self.client.check_role_from_group_on_project_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ project_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
+
+ def test_check_role_from_group_on_domain_existence(self):
+ self.check_service_client_function(
+ self.client.check_role_from_group_on_domain_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123",
+ role_id="1234",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_services_client.py b/tempest/tests/lib/services/identity/v3/test_services_client.py
new file mode 100644
index 0000000..f87fcce
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_services_client.py
@@ -0,0 +1,149 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServicesClient(base.BaseServiceTest):
+ FAKE_CREATE_SERVICE = {
+ "service": {
+ "type": "compute",
+ "name": "compute2",
+ "description": "Compute service 2"
+ }
+ }
+
+ FAKE_SERVICE_INFO = {
+ "service": {
+ "description": "Keystone Identity Service",
+ "enabled": True,
+ "id": "686766",
+ "links": {
+ "self": "http://example.com/identity/v3/services/686766"
+ },
+ "name": "keystone",
+ "type": "identity"
+ }
+ }
+
+ FAKE_LIST_SERVICES = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/services"
+ },
+ "services": [
+ {
+ "description": "Nova Compute Service",
+ "enabled": True,
+ "id": "1999c3",
+ "links": {
+ "self": "http://example.com/identity/v3/services/1999c3"
+ },
+ "name": "nova",
+ "type": "compute"
+ },
+ {
+ "description": "Cinder Volume Service V2",
+ "enabled": True,
+ "id": "392166",
+ "links": {
+ "self": "http://example.com/identity/v3/services/392166"
+ },
+ "name": "cinderv2",
+ "type": "volumev2"
+ },
+ {
+ "description": "Neutron Service",
+ "enabled": True,
+ "id": "4fe41a",
+ "links": {
+ "self": "http://example.com/identity/v3/services/4fe41a"
+ },
+ "name": "neutron",
+ "type": "network"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestServicesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = services_client.ServicesClient(fake_auth, 'identity',
+ 'regionOne')
+
+ def _test_create_service(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_service,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_SERVICE,
+ bytes_body,
+ status=201)
+
+ def _test_show_service(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_service,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICE_INFO,
+ bytes_body,
+ service_id="686766")
+
+ def _test_list_services(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_services,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_SERVICES,
+ bytes_body)
+
+ def _test_update_service(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_service,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_SERVICE_INFO,
+ bytes_body,
+ service_id="686766")
+
+ def test_create_service_with_str_body(self):
+ self._test_create_service()
+
+ def test_create_service_with_bytes_body(self):
+ self._test_create_service(bytes_body=True)
+
+ def test_show_service_with_str_body(self):
+ self._test_show_service()
+
+ def test_show_service_with_bytes_body(self):
+ self._test_show_service(bytes_body=True)
+
+ def test_list_services_with_str_body(self):
+ self._test_list_services()
+
+ def test_list_services_with_bytes_body(self):
+ self._test_list_services(bytes_body=True)
+
+ def test_update_service_with_str_body(self):
+ self._test_update_service()
+
+ def test_update_service_with_bytes_body(self):
+ self._test_update_service(bytes_body=True)
+
+ def test_delete_service(self):
+ self.check_service_client_function(
+ self.client.delete_service,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ service_id="686766",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_trusts_client.py b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
new file mode 100644
index 0000000..a1ca020
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
@@ -0,0 +1,150 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import trusts_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTrustsClient(base.BaseServiceTest):
+ FAKE_CREATE_TRUST = {
+ "trust": {
+ "expires_at": "2013-02-27T18:30:59.999999Z",
+ "impersonation": True,
+ "allow_redelegation": True,
+ "project_id": "ddef321",
+ "roles": [
+ {
+ "name": "member"
+ }
+ ],
+ "trustee_user_id": "86c0d5",
+ "trustor_user_id": "a0fdfd"
+ }
+ }
+
+ FAKE_LIST_TRUSTS = {
+ "trusts": [
+ {
+ "id": "1ff900",
+ "expires_at":
+ "2013-02-27T18:30:59.999999Z",
+ "impersonation": True,
+ "links": {
+ "self":
+ "http://example.com/identity/v3/OS-TRUST/trusts/1ff900"
+ },
+ "project_id": "0f1233",
+ "trustee_user_id": "86c0d5",
+ "trustor_user_id": "a0fdfd"
+ },
+ {
+ "id": "f4513a",
+ "impersonation": False,
+ "links": {
+ "self":
+ "http://example.com/identity/v3/OS-TRUST/trusts/f45513a"
+ },
+ "project_id": "0f1233",
+ "trustee_user_id": "86c0d5",
+ "trustor_user_id": "3cd2ce"
+ }
+ ]
+ }
+
+ FAKE_TRUST_INFO = {
+ "trust": {
+ "id": "987fe8",
+ "expires_at": "2013-02-27T18:30:59.999999Z",
+ "impersonation": True,
+ "links": {
+ "self":
+ "http://example.com/identity/v3/OS-TRUST/trusts/987fe8"
+ },
+ "roles": [
+ {
+ "id": "ed7b78",
+ "links": {
+ "self":
+ "http://example.com/identity/v3/roles/ed7b78"
+ },
+ "name": "member"
+ }
+ ],
+ "roles_links": {
+ "next": None,
+ "previous": None,
+ "self":
+ "http://example.com/identity/v3/OS-TRUST/trusts/1ff900/roles"
+ },
+ "project_id": "0f1233",
+ "trustee_user_id": "be34d1",
+ "trustor_user_id": "56ae32"
+ }
+ }
+
+ def setUp(self):
+ super(TestTrustsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = trusts_client.TrustsClient(fake_auth, 'identity',
+ 'regionOne')
+
+ def _test_create_trust(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_trust,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_TRUST,
+ bytes_body,
+ status=201)
+
+ def _test_show_trust(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_trust,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TRUST_INFO,
+ bytes_body,
+ trust_id="1ff900")
+
+ def _test_list_trusts(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_trusts,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_TRUSTS,
+ bytes_body)
+
+ def test_create_trust_with_str_body(self):
+ self._test_create_trust()
+
+ def test_create_trust_with_bytes_body(self):
+ self._test_create_trust(bytes_body=True)
+
+ def test_show_trust_with_str_body(self):
+ self._test_show_trust()
+
+ def test_show_trust_with_bytes_body(self):
+ self._test_show_trust(bytes_body=True)
+
+ def test_list_trusts_with_str_body(self):
+ self._test_list_trusts()
+
+ def test_list_trusts_with_bytes_body(self):
+ self._test_list_trusts(bytes_body=True)
+
+ def test_delete_trust(self):
+ self.check_service_client_function(
+ self.client.delete_trust,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ trust_id="1ff900",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_users_client.py b/tempest/tests/lib/services/identity/v3/test_users_client.py
new file mode 100644
index 0000000..5b572f5
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_users_client.py
@@ -0,0 +1,205 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import users_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestUsersClient(base.BaseServiceTest):
+ FAKE_CREATE_USER = {
+ 'user': {
+ 'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0',
+ 'domain_id': '8347b31afc3545c4b311cb4cce788a08',
+ 'enabled': True,
+ 'name': 'Tempest User',
+ 'password': 'TempestPassword',
+ }
+ }
+
+ FAKE_USER_INFO = {
+ 'user': {
+ 'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0',
+ 'domain_id': '8347b31afc3545c4b311cb4cce788a08',
+ 'enabled': True,
+ 'id': '817fb3c23fd7465ba6d7fe1b1320121d',
+ 'links': {
+ 'self': 'http://example.com/identity',
+ },
+ 'name': 'Tempest User',
+ 'password_expires_at': '2016-11-06T15:32:17.000000',
+ }
+ }
+
+ FAKE_USER_LIST = {
+ 'links': {
+ 'next': None,
+ 'previous': None,
+ 'self': 'http://example.com/identity/v3/users',
+ },
+ 'users': [
+ {
+ 'domain_id': 'TempestDomain',
+ 'enabled': True,
+ 'id': '817fb3c23fd7465ba6d7fe1b1320121d',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/users/' +
+ '817fb3c23fd7465ba6d7fe1b1320121d',
+ },
+ 'name': 'Tempest User',
+ 'password_expires_at': '2016-11-06T15:32:17.000000',
+ },
+ {
+ 'domain_id': 'TempestDomain',
+ 'enabled': True,
+ 'id': 'bdbfb1e2f1344be197e90a778379cca1',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/users/' +
+ 'bdbfb1e2f1344be197e90a778379cca1',
+ },
+ 'name': 'Tempest User',
+ 'password_expires_at': None,
+ },
+ ]
+ }
+
+ FAKE_GROUP_LIST = {
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups',
+ 'previous': None,
+ 'next': None,
+ },
+ 'groups': [
+ {
+ 'description': 'Tempest Group One Description',
+ 'domain_id': 'TempestDomain',
+ 'id': '1c92f3453ed34291a074b87493455b8f',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ '1c92f3453ed34291a074b87493455b8f'
+ },
+ 'name': 'Tempest Group One',
+ },
+ {
+ 'description': 'Tempest Group Two Description',
+ 'domain_id': 'TempestDomain',
+ 'id': 'ce9e7dafed3b4877a7d4466ed730a9ee',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/groups/' +
+ 'ce9e7dafed3b4877a7d4466ed730a9ee'
+ },
+ 'name': 'Tempest Group Two',
+ },
+ ]
+ }
+
+ def setUp(self):
+ super(TestUsersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = users_client.UsersClient(fake_auth, 'identity',
+ 'regionOne')
+
+ def _test_create_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_USER,
+ bytes_body,
+ status=201,
+ )
+
+ def _test_show_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_user,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+ )
+
+ def _test_list_users(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_users,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_LIST,
+ bytes_body,
+ )
+
+ def _test_update_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_user,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+ name='NewName',
+ )
+
+ def _test_list_user_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_GROUP_LIST,
+ bytes_body,
+ user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+ )
+
+ def test_create_user_with_string_body(self):
+ self._test_create_user()
+
+ def test_create_user_with_bytes_body(self):
+ self._test_create_user(bytes_body=True)
+
+ def test_show_user_with_string_body(self):
+ self._test_show_user()
+
+ def test_show_user_with_bytes_body(self):
+ self._test_show_user(bytes_body=True)
+
+ def test_list_users_with_string_body(self):
+ self._test_list_users()
+
+ def test_list_users_with_bytes_body(self):
+ self._test_list_users(bytes_body=True)
+
+ def test_update_user_with_string_body(self):
+ self._test_update_user()
+
+ def test_update_user_with_bytes_body(self):
+ self._test_update_user(bytes_body=True)
+
+ def test_list_user_groups_with_string_body(self):
+ self._test_list_user_groups()
+
+ def test_list_user_groups_with_bytes_body(self):
+ self._test_list_user_groups(bytes_body=True)
+
+ def test_delete_user(self):
+ self.check_service_client_function(
+ self.client.delete_user,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+ status=204,
+ )
+
+ def test_change_user_password(self):
+ self.check_service_client_function(
+ self.client.update_user_password,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=204,
+ user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+ password='NewTempestPassword',
+ original_password='OldTempestPassword')
diff --git a/tempest/tests/lib/services/network/test_versions_client.py b/tempest/tests/lib/services/network/test_versions_client.py
index 715176b..ae52c8a 100644
--- a/tempest/tests/lib/services/network/test_versions_client.py
+++ b/tempest/tests/lib/services/network/test_versions_client.py
@@ -14,7 +14,7 @@
import copy
-from tempest.lib.services.network.versions_client import NetworkVersionsClient
+from tempest.lib.services.network import versions_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -59,7 +59,7 @@
super(TestNetworkVersionsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.versions_client = (
- NetworkVersionsClient
+ versions_client.NetworkVersionsClient
(fake_auth, 'compute', 'regionOne'))
def _test_versions_client(self, bytes_body=False):
diff --git a/tempest/tests/lib/services/test_clients.py b/tempest/tests/lib/services/test_clients.py
new file mode 100644
index 0000000..5db932c
--- /dev/null
+++ b/tempest/tests/lib/services/test_clients.py
@@ -0,0 +1,370 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import fixtures
+import mock
+import testtools
+import types
+
+from tempest.lib import auth
+from tempest.lib import exceptions
+from tempest.lib.services import clients
+from tempest.tests import base
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_credentials
+
+
+has_attribute = testtools.matchers.MatchesPredicateWithParams(
+ lambda x, y: hasattr(x, y), '{0} does not have an attribute {1}')
+
+
+class TestClientsFactory(base.TestCase):
+
+ def setUp(self):
+ super(TestClientsFactory, self).setUp()
+ self.classes = []
+
+ def _setup_fake_module(self, class_names=None, extra_dict=None):
+ class_names = class_names or []
+ fake_module = types.ModuleType('fake_service_client')
+ _dict = {}
+ # Add fake classes to the fake module
+ for name in class_names:
+ _dict[name] = type(name, (object,), {})
+ # Store it for assertions
+ self.classes.append(_dict[name])
+ if extra_dict:
+ _dict[extra_dict] = extra_dict
+ fake_module.__dict__.update(_dict)
+ fixture_importlib = self.useFixture(fixtures.MockPatch(
+ 'importlib.import_module', return_value=fake_module))
+ return fixture_importlib.mock
+
+ def test___init___one_class(self):
+ fake_partial = 'fake_partial'
+ partial_mock = self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory._get_partial_class',
+ return_value=fake_partial)).mock
+ class_names = ['FakeServiceClient1']
+ mock_importlib = self._setup_fake_module(class_names=class_names)
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ params = {'k1': 'v1', 'k2': 'v2'}
+ factory = clients.ClientsFactory('fake_path', class_names,
+ auth_provider, **params)
+ # Assert module has been imported
+ mock_importlib.assert_called_once_with('fake_path')
+ # All attributes have been created
+ for client in class_names:
+ self.assertThat(factory, has_attribute(client))
+ # Partial have been invoked correctly
+ partial_mock.assert_called_once_with(
+ self.classes[0], auth_provider, params)
+ # Get the clients
+ for name in class_names:
+ self.assertEqual(fake_partial, getattr(factory, name))
+
+ def test___init___two_classes(self):
+ fake_partial = 'fake_partial'
+ partial_mock = self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory._get_partial_class',
+ return_value=fake_partial)).mock
+ class_names = ['FakeServiceClient1', 'FakeServiceClient2']
+ mock_importlib = self._setup_fake_module(class_names=class_names)
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ params = {'k1': 'v1', 'k2': 'v2'}
+ factory = clients.ClientsFactory('fake_path', class_names,
+ auth_provider, **params)
+ # Assert module has been imported
+ mock_importlib.assert_called_once_with('fake_path')
+ # All attributes have been created
+ for client in class_names:
+ self.assertThat(factory, has_attribute(client))
+ # Partial have been invoked the right number of times
+ partial_mock.call_count = len(class_names)
+ # Get the clients
+ for name in class_names:
+ self.assertEqual(fake_partial, getattr(factory, name))
+
+ def test___init___no_module(self):
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ class_names = ['FakeServiceClient1', 'FakeServiceClient2']
+ with testtools.ExpectedException(ImportError, '.*fake_module.*'):
+ clients.ClientsFactory('fake_module', class_names,
+ auth_provider)
+
+ def test___init___not_a_class(self):
+ class_names = ['FakeServiceClient1', 'FakeServiceClient2']
+ extended_class_names = class_names + ['not_really_a_class']
+ self._setup_fake_module(
+ class_names=class_names, extra_dict='not_really_a_class')
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ expected_msg = '.*not_really_a_class.*str.*'
+ with testtools.ExpectedException(TypeError, expected_msg):
+ clients.ClientsFactory('fake_module', extended_class_names,
+ auth_provider)
+
+ def test___init___class_not_found(self):
+ class_names = ['FakeServiceClient1', 'FakeServiceClient2']
+ extended_class_names = class_names + ['not_really_a_class']
+ self._setup_fake_module(class_names=class_names)
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ expected_msg = '.*not_really_a_class.*fake_service_client.*'
+ with testtools.ExpectedException(AttributeError, expected_msg):
+ clients.ClientsFactory('fake_module', extended_class_names,
+ auth_provider)
+
+ def test__get_partial_class_no_later_kwargs(self):
+ expected_fake_client = 'not_really_a_client'
+ self._setup_fake_module(class_names=[])
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ params = {'k1': 'v1', 'k2': 'v2'}
+ factory = clients.ClientsFactory(
+ 'fake_path', [], auth_provider, **params)
+ klass_mock = mock.Mock(return_value=expected_fake_client)
+ partial = factory._get_partial_class(klass_mock, auth_provider, params)
+ # Class has not be initialised yet
+ klass_mock.assert_not_called()
+ # Use partial and assert on parameters
+ client = partial()
+ self.assertEqual(expected_fake_client, client)
+ klass_mock.assert_called_once_with(auth_provider=auth_provider,
+ **params)
+
+ def test__get_partial_class_later_kwargs(self):
+ expected_fake_client = 'not_really_a_client'
+ self._setup_fake_module(class_names=[])
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ params = {'k1': 'v1', 'k2': 'v2'}
+ later_params = {'k2': 'v4', 'k3': 'v3'}
+ factory = clients.ClientsFactory(
+ 'fake_path', [], auth_provider, **params)
+ klass_mock = mock.Mock(return_value=expected_fake_client)
+ partial = factory._get_partial_class(klass_mock, auth_provider, params)
+ # Class has not be initialised yet
+ klass_mock.assert_not_called()
+ # Use partial and assert on parameters
+ client = partial(**later_params)
+ params.update(later_params)
+ self.assertEqual(expected_fake_client, client)
+ klass_mock.assert_called_once_with(auth_provider=auth_provider,
+ **params)
+
+ def test__get_partial_class_with_alias(self):
+ expected_fake_client = 'not_really_a_client'
+ client_alias = 'fake_client'
+ self._setup_fake_module(class_names=[])
+ auth_provider = fake_auth_provider.FakeAuthProvider()
+ params = {'k1': 'v1', 'k2': 'v2'}
+ later_params = {'k2': 'v4', 'k3': 'v3'}
+ factory = clients.ClientsFactory(
+ 'fake_path', [], auth_provider, **params)
+ klass_mock = mock.Mock(return_value=expected_fake_client)
+ partial = factory._get_partial_class(klass_mock, auth_provider, params)
+ # Class has not be initialised yet
+ klass_mock.assert_not_called()
+ # Use partial and assert on parameters
+ client = partial(alias=client_alias, **later_params)
+ params.update(later_params)
+ self.assertEqual(expected_fake_client, client)
+ klass_mock.assert_called_once_with(auth_provider=auth_provider,
+ **params)
+ self.assertThat(factory, has_attribute(client_alias))
+ self.assertEqual(expected_fake_client, getattr(factory, client_alias))
+
+
+class TestServiceClients(base.TestCase):
+
+ def setUp(self):
+ super(TestServiceClients, self).setUp()
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.tempest_modules', return_value={}))
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients._tempest_internal_modules',
+ return_value=set(['fake_service1'])))
+
+ def test___init___creds_v2_uri(self):
+ # Verify that no API request is made, since no mock
+ # is required to run the test successfully
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ uri = 'fake_uri'
+ _manager = clients.ServiceClients(creds, identity_uri=uri)
+ self.assertIsInstance(_manager.auth_provider,
+ auth.KeystoneV2AuthProvider)
+
+ def test___init___creds_v3_uri(self):
+ # Verify that no API request is made, since no mock
+ # is required to run the test successfully
+ creds = fake_credentials.FakeKeystoneV3Credentials()
+ uri = 'fake_uri'
+ _manager = clients.ServiceClients(creds, identity_uri=uri)
+ self.assertIsInstance(_manager.auth_provider,
+ auth.KeystoneV3AuthProvider)
+
+ def test___init___base_creds_uri(self):
+ creds = fake_credentials.FakeCredentials()
+ uri = 'fake_uri'
+ with testtools.ExpectedException(exceptions.InvalidCredentials):
+ clients.ServiceClients(creds, identity_uri=uri)
+
+ def test___init___invalid_creds_uri(self):
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ delattr(creds, 'username')
+ uri = 'fake_uri'
+ with testtools.ExpectedException(exceptions.InvalidCredentials):
+ clients.ServiceClients(creds, identity_uri=uri)
+
+ def test___init___creds_uri_none(self):
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ msg = ("Invalid Credentials\nDetails: ServiceClients requires a "
+ "non-empty")
+ with testtools.ExpectedException(exceptions.InvalidCredentials,
+ value_re=msg):
+ clients.ServiceClients(creds, None)
+
+ def test___init___creds_uri_params(self):
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ expeted_params = {'fake_param1': 'fake_value1',
+ 'fake_param2': 'fake_value2'}
+ params = {'fake_service1': expeted_params}
+ uri = 'fake_uri'
+ _manager = clients.ServiceClients(creds, identity_uri=uri,
+ client_parameters=params)
+ self.assertIn('fake_service1', _manager.parameters)
+ for _key in expeted_params:
+ self.assertIn(_key, _manager.parameters['fake_service1'].keys())
+ self.assertEqual(expeted_params[_key],
+ _manager.parameters['fake_service1'].get(_key))
+
+ def test___init___creds_uri_params_unknown_services(self):
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ fake_params = {'fake_param1': 'fake_value1'}
+ params = {'unknown_service1': fake_params,
+ 'unknown_service2': fake_params}
+ uri = 'fake_uri'
+ msg = "(?=.*{0})(?=.*{1})".format(*list(params.keys()))
+ with testtools.ExpectedException(
+ exceptions.UnknownServiceClient, value_re=msg):
+ clients.ServiceClients(creds, identity_uri=uri,
+ client_parameters=params)
+
+ def _get_manager(self, init_region='fake_region'):
+ # Get a manager to invoke _setup_parameters on
+ creds = fake_credentials.FakeKeystoneV2Credentials()
+ return clients.ServiceClients(creds, identity_uri='fake_uri',
+ region=init_region)
+
+ def test__setup_parameters_none_no_region(self):
+ kwargs = {}
+ _manager = self._get_manager(init_region=None)
+ _params = _manager._setup_parameters(kwargs)
+ self.assertNotIn('region', _params)
+
+ def test__setup_parameters_none(self):
+ kwargs = {}
+ _manager = self._get_manager()
+ _params = _manager._setup_parameters(kwargs)
+ self.assertIn('region', _params)
+ self.assertEqual('fake_region', _params['region'])
+
+ def test__setup_parameters_all(self):
+ expected_params = {'region': 'fake_region1',
+ 'catalog_type': 'fake_service2_mod',
+ 'fake_param1': 'fake_value1',
+ 'fake_param2': 'fake_value2'}
+ _manager = self._get_manager()
+ _params = _manager._setup_parameters(expected_params)
+ for _key in _params.keys():
+ self.assertEqual(expected_params[_key],
+ _params[_key])
+
+ def test_register_service_client_module(self):
+ expected_params = {'fake_param1': 'fake_value1',
+ 'fake_param2': 'fake_value2'}
+ _manager = self._get_manager(init_region='fake_region_default')
+ # Mock after the _manager is setup to preserve the call count
+ factory_mock = self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory')).mock
+ _manager.register_service_client_module(
+ name='fake_module',
+ service_version='fake_service',
+ module_path='fake.path.to.module',
+ client_names=[],
+ **expected_params)
+ self.assertThat(_manager, has_attribute('fake_module'))
+ # Assert called once, without check for exact parameters
+ self.assertTrue(factory_mock.called)
+ self.assertEqual(1, factory_mock.call_count)
+ # Assert expected params are in with their values
+ actual_kwargs = factory_mock.call_args[1]
+ self.assertIn('region', actual_kwargs)
+ self.assertEqual('fake_region_default', actual_kwargs['region'])
+ for param in expected_params:
+ self.assertIn(param, actual_kwargs)
+ self.assertEqual(expected_params[param], actual_kwargs[param])
+ # Assert the new service is registered
+ self.assertIn('fake_service', _manager._registered_services)
+
+ def test_register_service_client_module_override_default(self):
+ new_region = 'new_region'
+ expected_params = {'fake_param1': 'fake_value1',
+ 'fake_param2': 'fake_value2',
+ 'region': new_region}
+ _manager = self._get_manager(init_region='fake_region_default')
+ # Mock after the _manager is setup to preserve the call count
+ factory_mock = self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory')).mock
+ _manager.register_service_client_module(
+ name='fake_module',
+ service_version='fake_service',
+ module_path='fake.path.to.module',
+ client_names=[],
+ **expected_params)
+ self.assertThat(_manager, has_attribute('fake_module'))
+ # Assert called once, without check for exact parameters
+ self.assertTrue(factory_mock.called)
+ self.assertEqual(1, factory_mock.call_count)
+ # Assert expected params are in with their values
+ actual_kwargs = factory_mock.call_args[1]
+ self.assertIn('region', actual_kwargs)
+ self.assertEqual(new_region, actual_kwargs['region'])
+ for param in expected_params:
+ self.assertIn(param, actual_kwargs)
+ self.assertEqual(expected_params[param], actual_kwargs[param])
+ # Assert the new service is registered
+ self.assertIn('fake_service', _manager._registered_services)
+
+ def test_register_service_client_module_duplicate_name(self):
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory')).mock
+ _manager = self._get_manager()
+ name_owner = 'this_is_a_string'
+ setattr(_manager, 'fake_module', name_owner)
+ expected_error = '.*' + name_owner
+ with testtools.ExpectedException(
+ exceptions.ServiceClientRegistrationException, expected_error):
+ _manager.register_service_client_module(
+ name='fake_module', module_path='fake.path.to.module',
+ service_version='fake_service', client_names=[])
+
+ def test_register_service_client_module_duplicate_service(self):
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients.ClientsFactory')).mock
+ _manager = self._get_manager()
+ duplicate_service = 'fake_service1'
+ expected_error = '.*' + duplicate_service
+ with testtools.ExpectedException(
+ exceptions.ServiceClientRegistrationException, expected_error):
+ _manager.register_service_client_module(
+ name='fake_module', module_path='fake.path.to.module',
+ service_version=duplicate_service, client_names=[])
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index 12590a3..6da7e41 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -244,7 +244,7 @@
# The original headers where empty
self.assertNotEqual(url, self.target_url)
self.assertIsNone(headers)
- self.assertEqual(body, None)
+ self.assertIsNone(body)
def _test_request_with_alt_part_without_alt_data_no_change(self, body):
"""Test empty alternate auth data with no effect
diff --git a/tempest/tests/lib/test_credentials.py b/tempest/tests/lib/test_credentials.py
index b6f2cf6..c910d6d 100644
--- a/tempest/tests/lib/test_credentials.py
+++ b/tempest/tests/lib/test_credentials.py
@@ -99,7 +99,7 @@
def _test_is_not_valid(self, ignore_key):
creds = self._get_credentials()
- for attr in self.attributes.keys():
+ for attr in self.attributes:
if attr == ignore_key:
continue
temp_attr = getattr(creds, attr)
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index 78fd80d..2e45ef7 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -146,5 +146,5 @@
schema_under_test = copy.copy(valid_schema)
expected_result = \
self.generator.generate_payload(test, schema_under_test)
- self.assertEqual(expected_result, None)
+ self.assertIsNone(expected_result)
self._validate_result(valid_schema, schema_under_test)
diff --git a/tempest/tests/services/object_storage/test_object_client.py b/tempest/tests/services/object_storage/test_object_client.py
index cd8c8f1..cc1dc1a 100644
--- a/tempest/tests/services/object_storage/test_object_client.py
+++ b/tempest/tests/services/object_storage/test_object_client.py
@@ -20,7 +20,7 @@
from tempest.lib import exceptions
from tempest.services.object_storage import object_client
from tempest.tests import base
-from tempest.tests import fake_auth_provider
+from tempest.tests.lib import fake_auth_provider
class TestObjectClient(base.TestCase):
diff --git a/tempest/tests/test_negative_rest_client.py b/tempest/tests/test_negative_rest_client.py
index 9d9c20f..05f9f3e 100644
--- a/tempest/tests/test_negative_rest_client.py
+++ b/tempest/tests/test_negative_rest_client.py
@@ -21,8 +21,8 @@
from tempest.common import negative_rest_client
from tempest import config
from tempest.tests import base
-from tempest.tests import fake_auth_provider
from tempest.tests import fake_config
+from tempest.tests.lib import fake_auth_provider
class TestNegativeRestClient(base.TestCase):
diff --git a/tempest/tests/test_service_clients.py b/tempest/tests/test_service_clients.py
deleted file mode 100644
index d0158c7..0000000
--- a/tempest/tests/test_service_clients.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-import fixtures
-import testtools
-
-from tempest.lib import auth
-from tempest.lib import exceptions
-from tempest import service_clients
-from tempest.tests import base
-from tempest.tests.lib import fake_credentials
-
-
-class TestServiceClients(base.TestCase):
-
- def setUp(self):
- super(TestServiceClients, self).setUp()
- self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.tempest_modules',
- return_value=set(['fake_service1', 'fake_service2'])))
-
- def test___init___creds_v2_uri(self):
- # Verify that no API request is made, since no mock
- # is required to run the test successfully
- creds = fake_credentials.FakeKeystoneV2Credentials()
- uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri)
- self.assertIsInstance(_manager.auth_provider,
- auth.KeystoneV2AuthProvider)
-
- def test___init___creds_v3_uri(self):
- # Verify that no API request is made, since no mock
- # is required to run the test successfully
- creds = fake_credentials.FakeKeystoneV3Credentials()
- uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri)
- self.assertIsInstance(_manager.auth_provider,
- auth.KeystoneV3AuthProvider)
-
- def test___init___base_creds_uri(self):
- creds = fake_credentials.FakeCredentials()
- uri = 'fake_uri'
- with testtools.ExpectedException(exceptions.InvalidCredentials):
- service_clients.ServiceClients(creds, identity_uri=uri)
-
- def test___init___invalid_creds_uri(self):
- creds = fake_credentials.FakeKeystoneV2Credentials()
- delattr(creds, 'username')
- uri = 'fake_uri'
- with testtools.ExpectedException(exceptions.InvalidCredentials):
- service_clients.ServiceClients(creds, identity_uri=uri)
-
- def test___init___creds_uri_none(self):
- creds = fake_credentials.FakeKeystoneV2Credentials()
- msg = ("Invalid Credentials\nDetails: ServiceClients requires a "
- "non-empty")
- with testtools.ExpectedException(exceptions.InvalidCredentials,
- value_re=msg):
- service_clients.ServiceClients(creds, None)
-
- def test___init___creds_uri_params(self):
- creds = fake_credentials.FakeKeystoneV2Credentials()
- expeted_params = {'fake_param1': 'fake_value1',
- 'fake_param2': 'fake_value2'}
- params = {'fake_service1': expeted_params}
- uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri,
- client_parameters=params)
- self.assertIn('fake_service1', _manager.parameters.keys())
- for _key in expeted_params.keys():
- self.assertIn(_key, _manager.parameters['fake_service1'].keys())
- self.assertEqual(expeted_params[_key],
- _manager.parameters['fake_service1'].get(_key))
-
- def test___init___creds_uri_params_unknown_services(self):
- creds = fake_credentials.FakeKeystoneV2Credentials()
- fake_params = {'fake_param1': 'fake_value1'}
- params = {'unknown_service1': fake_params,
- 'unknown_service2': fake_params}
- uri = 'fake_uri'
- msg = "(?=.*{0})(?=.*{1})".format(*list(params.keys()))
- with testtools.ExpectedException(
- exceptions.UnknownServiceClient, value_re=msg):
- service_clients.ServiceClients(creds, identity_uri=uri,
- client_parameters=params)
-
- def _get_manager(self, init_region='fake_region'):
- # Get a manager to invoke _setup_parameters on
- creds = fake_credentials.FakeKeystoneV2Credentials()
- return service_clients.ServiceClients(creds, identity_uri='fake_uri',
- region=init_region)
-
- def test__setup_parameters_none_no_region(self):
- kwargs = {}
- _manager = self._get_manager(init_region=None)
- _params = _manager._setup_parameters(kwargs)
- self.assertNotIn('region', _params)
-
- def test__setup_parameters_none(self):
- kwargs = {}
- _manager = self._get_manager()
- _params = _manager._setup_parameters(kwargs)
- self.assertIn('region', _params)
- self.assertEqual('fake_region', _params['region'])
-
- def test__setup_parameters_all(self):
- expected_params = {'region': 'fake_region1',
- 'catalog_type': 'fake_service2_mod',
- 'fake_param1': 'fake_value1',
- 'fake_param2': 'fake_value2'}
- _manager = self._get_manager()
- _params = _manager._setup_parameters(expected_params)
- for _key in _params.keys():
- self.assertEqual(expected_params[_key],
- _params[_key])
diff --git a/tempest/tests/test_tempest_plugin.py b/tempest/tests/test_tempest_plugin.py
index c07e98c..dd50125 100644
--- a/tempest/tests/test_tempest_plugin.py
+++ b/tempest/tests/test_tempest_plugin.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib.services import clients
from tempest.test_discover import plugins
from tempest.tests import base
from tempest.tests import fake_tempest_plugin as fake_plugin
@@ -42,3 +43,39 @@
result['fake01'])
self.assertEqual(fake_plugin.FakePlugin.expected_load_test,
result['fake02'])
+
+ def test__register_service_clients_with_one_plugin(self):
+ registry = clients.ClientsRegistry()
+ manager = plugins.TempestTestPluginManager()
+ fake_obj = fake_plugin.FakeStevedoreObj()
+ manager.ext_plugins = [fake_obj]
+ manager._register_service_clients()
+ expected_result = fake_plugin.FakePlugin.expected_service_clients
+ registered_clients = registry.get_service_clients()
+ self.assertIn(fake_obj.name, registered_clients)
+ self.assertEqual(expected_result, registered_clients[fake_obj.name])
+
+ def test__get_service_clients_with_two_plugins(self):
+ registry = clients.ClientsRegistry()
+ manager = plugins.TempestTestPluginManager()
+ obj1 = fake_plugin.FakeStevedoreObj('fake01')
+ obj2 = fake_plugin.FakeStevedoreObj('fake02')
+ manager.ext_plugins = [obj1, obj2]
+ manager._register_service_clients()
+ expected_result = fake_plugin.FakePlugin.expected_service_clients
+ registered_clients = registry.get_service_clients()
+ self.assertIn('fake01', registered_clients)
+ self.assertIn('fake02', registered_clients)
+ self.assertEqual(expected_result, registered_clients['fake01'])
+ self.assertEqual(expected_result, registered_clients['fake02'])
+
+ def test__register_service_clients_one_plugin_no_service_clients(self):
+ registry = clients.ClientsRegistry()
+ manager = plugins.TempestTestPluginManager()
+ fake_obj = fake_plugin.FakeStevedoreObjNoServiceClients()
+ manager.ext_plugins = [fake_obj]
+ manager._register_service_clients()
+ expected_result = []
+ registered_clients = registry.get_service_clients()
+ self.assertIn(fake_obj.name, registered_clients)
+ self.assertEqual(expected_result, registered_clients[fake_obj.name])
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 03dbd9b..03e838e 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -48,6 +48,8 @@
def has_tempest_plugin(proj):
+ if proj.startswith('openstack/deb-'):
+ return False
r = requests.get(
"https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
p = re.compile('^tempest\.test_plugins', re.M)
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index b554514..55f41a6 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -95,7 +95,7 @@
def get_results(result_dict):
results = []
- for bug_no in result_dict.keys():
+ for bug_no in result_dict:
for method in result_dict[bug_no]:
results.append((method, bug_no))
return results
diff --git a/tox.ini b/tox.ini
index cff222d..a621492 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = pep8,py34,py27
+envlist = pep8,py35,py34,py27
minversion = 2.3.1
skipsdist = True
@@ -77,7 +77,7 @@
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)) {posargs}'
[testenv:full-serial]
envdir = .tox/tempest
@@ -88,7 +88,7 @@
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
+ bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)) {posargs}'
[testenv:smoke]
envdir = .tox/tempest