Merge "Add python 3.5 classifier and venv"
diff --git a/README.rst b/README.rst
index 53c7de5..13f4f61 100644
--- a/README.rst
+++ b/README.rst
@@ -178,9 +178,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 +223,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/configuration.rst b/doc/source/configuration.rst
index e4b104f..6c55015 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -135,7 +135,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/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..b8dcfce
--- /dev/null
+++ b/releasenotes/notes/add-new-identity-clients-3c3afd674a395bde.yaml
@@ -0,0 +1,10 @@
+---
+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)
diff --git a/releasenotes/notes/add-new-identity-clients-as-library-3e6559d4bff3e776.yaml b/releasenotes/notes/add-new-identity-clients-as-library-3e6559d4bff3e776.yaml
new file mode 100644
index 0000000..9feb3c3
--- /dev/null
+++ b/releasenotes/notes/add-new-identity-clients-as-library-3e6559d4bff3e776.yaml
@@ -0,0 +1,9 @@
+---
+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.
+
+ * regions_client(v3)
+ * services_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/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 216dd50..d698cda 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,18 +8,17 @@
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.12.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.15.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
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 76bed2b..50bf891 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,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..ac1bfee 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -181,7 +181,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'])
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..052c17d
--- /dev/null
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -0,0 +1,205 @@
+# 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 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()
+ 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..dd7beaa 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -27,6 +27,8 @@
class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
_host_key = 'OS-EXT-SRV-ATTR:host'
+ max_microversion = '2.24'
+ block_migration = None
@classmethod
def skip_checks(cls):
@@ -64,12 +66,16 @@
return self._get_server_details(server_id)[self._host_key]
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):
@@ -167,3 +173,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_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..dd4a533
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -37,12 +37,12 @@
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']
@@ -70,6 +70,13 @@
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('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 +84,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 +110,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
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..5ff5d58
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -40,7 +40,7 @@
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']
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_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..c0caa52
--- 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'
}
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..e8df52d
--- 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(
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_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_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..2304cb7
--- 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)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 05c23ee..a5c303c 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -84,19 +84,21 @@
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
+ if CONF.validation.run_validation:
+ # 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, or it was skipped, shelve the server
compute.shelve_server(self.servers_client, self.server['id'])
# Attach the volume to the server
@@ -110,8 +112,6 @@
self.addCleanup(self._detach, self.server['id'], self.volume['id'])
@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.
@@ -125,16 +125,17 @@
waiters.wait_for_server_status(self.servers_client, self.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(self.server),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- self.assertIn(self.device, partitions)
+ partitions = linux_client.get_partitions()
+ self.assertIn(self.device, partitions)
self._detach(self.server['id'], self.volume['id'])
self.attachment = None
@@ -146,16 +147,17 @@
waiters.wait_for_server_status(self.servers_client, self.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(self.server),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- self.assertNotIn(self.device, partitions)
+ partitions = linux_client.get_partitions()
+ self.assertNotIn(self.device, partitions)
@test.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
def test_list_get_volume_attachments(self):
@@ -192,23 +194,22 @@
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)
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(self.server['id']),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
- command = 'grep [vs]d /proc/partitions | wc -l'
- nb_partitions = linux_client.exec_command(command).strip()
- self.assertEqual(number_of_partition, nb_partitions)
+ command = 'grep [vs]d /proc/partitions | wc -l'
+ nb_partitions = linux_client.exec_command(command).strip()
+ self.assertEqual(number_of_partition, nb_partitions)
@test.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
- @testtools.skipUnless(CONF.validation.run_validation,
- 'SSH required for this test')
def test_attach_volume_shelved_or_offload_server(self):
self._create_and_attach(shelve_server=True)
@@ -228,8 +229,6 @@
@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)
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 d5ba76c..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))
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 0924619..380920f 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -17,6 +17,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
@@ -25,17 +26,22 @@
@classmethod
def resource_setup(cls):
super(RolesTestJSON, cls).resource_setup()
+ cls.roles = list()
for _ in moves.xrange(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 +55,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 +81,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..94291f8 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -93,7 +93,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..4faf184 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -17,6 +17,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
@@ -30,16 +31,17 @@
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 +55,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 +67,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 +75,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 +85,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 +93,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 +105,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 +112,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 +132,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 +141,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 +162,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 +170,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 +191,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..3616e77 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']
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..48e5f02 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,14 +76,15 @@
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'])
@@ -96,14 +104,15 @@
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'])
@@ -121,7 +130,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..76771bb 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']
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..4ecf0a5 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):
@@ -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..fcf4772 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'])
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 9c8f1f6..58e2ab8 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,
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 371da9c..1bcec62 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'])
@@ -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..deb5413 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -13,15 +13,11 @@
# 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):
@@ -40,7 +36,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 +60,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,14 +117,30 @@
@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):
@@ -160,18 +189,23 @@
@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 +213,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/base.py b/tempest/api/image/base.py
old mode 100644
new mode 100755
index 6fd6ea6..e7a46b0
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -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..694408d 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -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)
@@ -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..42a4352 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -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/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..85026af 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,41 @@
cls.container_client.auth_provider.clear_auth()
cls.account_client.auth_provider.clear_auth()
+ 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 +102,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
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..723b870 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -50,7 +50,7 @@
@classmethod
def resource_cleanup(cls):
- cls.delete_containers(cls.containers)
+ cls.delete_containers()
super(AccountTest, cls).resource_cleanup()
@test.attr(type='smoke')
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..a707ebb 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -35,24 +35,13 @@
@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')
@@ -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'}
@@ -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 aa0b46a..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):
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..f665e69 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}
+ params = {cls.name_field: vol_name, 'volume_type': type_name}
- self.volume = self.admin_volume_client.create_volume(
+ 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..f1101e1
--- 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']:
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..89e4d18 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -14,12 +14,9 @@
# under the License.
from tempest.api.volume import base
-from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
-CONF = config.CONF
-
class BaseVolumeQuotasNegativeV2TestJSON(base.BaseVolumeAdminTest):
force_tenant_isolation = True
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..7b4379b
--- 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,7 +43,8 @@
"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)
@@ -87,19 +88,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,7 +123,7 @@
# 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 = \
@@ -158,6 +162,28 @@
encryption_type['volume_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..03927e6
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -59,7 +59,7 @@
def _create_temp_volume(self):
# Create a temp volume for force delete tests
- vol_name = utils.rand_name('Volume')
+ vol_name = utils.rand_name(self.__class__.__name__ + '-Volume')
params = {self.name_field: vol_name}
temp_volume = self.client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.client,
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..f7013d8
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -43,7 +43,7 @@
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))
@@ -59,44 +59,6 @@
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 +67,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 +128,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..70c16f3 100644
--- a/tempest/api/volume/admin/test_volumes_list.py
+++ b/tempest/api/volume/admin/test_volumes_list.py
@@ -53,7 +53,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..ef28add 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -110,7 +110,7 @@
@classmethod
def create_volume(cls, **kwargs):
"""Wrapper utility that returns a test volume."""
- name = data_utils.rand_name('Volume')
+ name = data_utils.rand_name(cls.__name__ + '-Volume')
name_field = cls.special_fields['name_field']
@@ -236,7 +236,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'])
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..d5e1e6b
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -129,7 +129,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..50a1360
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -34,7 +34,47 @@
def resource_setup(cls):
super(VolumesBackupsV2Test, cls).resource_setup()
- cls.volume = cls.create_volume()
+ @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')
+
+ # 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 +85,27 @@
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_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..7a1c0a1
--- 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,7 +103,6 @@
# 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']}
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..d2c05ae
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -56,7 +56,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 +66,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 +75,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 +84,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 +93,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 +103,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 +113,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 +122,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 +132,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 +141,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,7 +177,7 @@
@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_until='ACTIVE')
@@ -267,7 +267,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 +275,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']
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..20c647a
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -75,7 +75,8 @@
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')
@@ -98,7 +99,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 +117,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 +140,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 +162,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..517c8d7
--- a/tempest/api/volume/v3/admin/test_user_messages.py
+++ b/tempest/api/volume/v3/admin/test_user_messages.py
@@ -42,7 +42,8 @@
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']
diff --git a/tempest/clients.py b/tempest/clients.py
index fd010f2..e14f6f8 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']
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..eeca063 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__)
@@ -89,18 +89,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 +167,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 36e45a5..86732da 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -21,7 +21,7 @@
from cliff import command
import prettytable
-from tempest.test_discover.plugins import TempestTestPluginManager
+from tempest.test_discover import plugins as plg
class TempestListPlugins(command.Command):
@@ -32,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/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 b6b70d7..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
@@ -191,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..9947f2a 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -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/common/compute.py b/tempest/common/compute.py
index a2edcdc..194b9e9 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -101,7 +101,7 @@
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
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 2ca9f40..4e3c395 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -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']
@@ -94,6 +96,7 @@
class V2CredsClient(CredsClient):
+ project_id_param = 'tenantId'
def __init__(self, identity_client, projects_client, users_client,
roles_client):
@@ -102,13 +105,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']
@@ -135,6 +131,7 @@
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 +149,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,
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 0c2b913..3fd20ab 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')
@@ -369,8 +380,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'),
@@ -491,7 +503,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 "
@@ -642,7 +654,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')]),
]
@@ -1119,6 +1131,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),
@@ -1184,6 +1197,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']
@@ -1275,6 +1289,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):
@@ -1372,6 +1395,7 @@
* `disable_ssl_certificate_validation`
* `ca_certs`
* `trace_requests`
+ * `http_timeout`
The dict returned by this does not fit a few service clients:
@@ -1393,7 +1417,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:
@@ -1432,3 +1457,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/lib/api_schema/response/compute/v2_16/__init__.py b/tempest/lib/api_schema/response/compute/v2_16/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/lib/api_schema/response/compute/v2_23/__init__.py b/tempest/lib/api_schema/response/compute/v2_23/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/lib/api_schema/response/compute/v2_26/__init__.py b/tempest/lib/api_schema/response/compute/v2_26/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/lib/api_schema/response/compute/v2_3/__init__.py b/tempest/lib/api_schema/response/compute/v2_3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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 54a7002..1857a43 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
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index f687343..33a32ee 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -42,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/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..5c5d3c2 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):
@@ -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:
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 7fcec8c..6b6548e 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
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/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/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/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/services/identity/v3/json/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/endpoints_client.py
rename to tempest/lib/services/identity/v3/endpoints_client.py
diff --git a/tempest/services/identity/v3/json/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/policies_client.py
rename to tempest/lib/services/identity/v3/policies_client.py
diff --git a/tempest/services/identity/v3/json/regions_client.py b/tempest/lib/services/identity/v3/regions_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/regions_client.py
rename to tempest/lib/services/identity/v3/regions_client.py
diff --git a/tempest/services/identity/v3/json/services_client.py b/tempest/lib/services/identity/v3/services_client.py
similarity index 84%
rename from tempest/services/identity/v3/json/services_client.py
rename to tempest/lib/services/identity/v3/services_client.py
index e863016..95caf7d 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
@@ -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/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 4276847..996ce94 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -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.html#deactivateImage-v2
+ """
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.html#reactivateImage-v2
+ """
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.html#deleteImage-v2
+ """
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.html#listImages-v2
+ """
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.html#showImage-v2
+ """
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.html#storeImageFile-v2
+ """
url = 'images/%s/file' % image_id
# We are going to do chunked transfert, so split the input data
@@ -115,7 +145,7 @@
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
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..f6cc0ff
--- a/tempest/lib/services/network/floating_ips_client.py
+++ b/tempest/lib/services/network/floating_ips_client.py
@@ -16,16 +16,34 @@
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-ext.html#createFloatingIp
+ """
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-ext.html#updateFloatingIp
+ """
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-ext.html#showFloatingIp
+ """
uri = '/floatingips/%s' % floatingip_id
return self.show_resource(uri, **fields)
@@ -34,5 +52,10 @@
return self.delete_resource(uri)
def list_floatingips(self, **filters):
+ """Lists floating IPs.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#listFloatingIps
+ """
uri = '/floatingips'
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/routers_client.py b/tempest/lib/services/network/routers_client.py
old mode 100644
new mode 100755
index 2ba1938..23e9c4e
--- a/tempest/lib/services/network/routers_client.py
+++ b/tempest/lib/services/network/routers_client.py
@@ -26,11 +26,21 @@
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-ext.html#updateRouter
+ """
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-ext.html#showRouter
+ """
uri = '/routers/%s' % router_id
return self.show_resource(uri, **fields)
@@ -39,6 +49,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-ext.html#listRouters
+ """
uri = '/routers'
return self.list_resources(uri, **filters)
@@ -46,7 +61,8 @@
"""Add router interface.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#addRouterInterface
+ api-ref-networking-v2-ext.html#
+ addRouterInterface
"""
uri = '/routers/%s/add_router_interface' % router_id
return self.update_resource(uri, kwargs)
@@ -55,7 +71,8 @@
"""Remove router interface.
Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#deleteRouterInterface
+ api-ref-networking-v2-ext.html#
+ deleteRouterInterface
"""
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..6cd01e1
--- a/tempest/lib/services/network/security_group_rules_client.py
+++ b/tempest/lib/services/network/security_group_rules_client.py
@@ -16,11 +16,22 @@
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-ext.html#
+ createSecGroupRule
+ """
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-ext.html#showSecGroupRule
+ """
uri = '/security-group-rules/%s' % security_group_rule_id
return self.show_resource(uri, **fields)
@@ -29,5 +40,10 @@
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-ext.html#listSecGroupRules
+ """
uri = '/security-group-rules'
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..f0a66a0
--- a/tempest/lib/services/network/subnetpools_client.py
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -18,19 +18,39 @@
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-ext.html#listSubnetPools
+ """
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-ext.html#createSubnetPool
+ """
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-ext.html#showSubnetPool
+ """
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-ext.html#updateSubnetPool
+ """
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..0fde3ee 100755
--- a/tempest/lib/services/network/subnets_client.py
+++ b/tempest/lib/services/network/subnets_client.py
@@ -36,6 +36,11 @@
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.html#showSubnet
+ """
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.html#listSubnets
+ """
uri = '/subnets'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/volume/__init__.py b/tempest/lib/services/volume/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/volume/__init__.py
diff --git a/tempest/lib/services/volume/v1/__init__.py b/tempest/lib/services/volume/v1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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 f889c44..fdccfc3 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,18 +204,10 @@
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
@@ -461,11 +421,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)
@@ -664,7 +625,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()
@@ -808,21 +769,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.
@@ -832,6 +778,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"])
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 655d19d..c060c61 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -15,12 +15,9 @@
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__)
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_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 402a70c..e0e1204 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
@@ -410,6 +411,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
@@ -642,6 +644,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 +674,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..364b6f5 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
@@ -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 b58479d..60dca3d 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -16,8 +16,6 @@
import json
import re
-from oslo_log import log as logging
-
from tempest import config
from tempest import exceptions
from tempest.scenario import manager
@@ -25,8 +23,6 @@
CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
class TestServerBasicOps(manager.ScenarioTest):
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 4b9c61c..7f04b0d 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -53,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,
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_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/v3/__init__.py b/tempest/services/identity/v3/__init__.py
index 144c5a9..a4e55d4 100644
--- a/tempest/services/identity/v3/__init__.py
+++ b/tempest/services/identity/v3/__init__.py
@@ -12,22 +12,22 @@
# License for the specific language governing permissions and limitations under
# the License.
+from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
+from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.regions_client import RegionsClient
+from tempest.lib.services.identity.v3.services_client import ServicesClient
from tempest.lib.services.identity.v3.token_client import V3TokenClient
from tempest.services.identity.v3.json.credentials_client import \
CredentialsClient
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__ = ['EndPointsClient', 'PoliciesClient', 'RegionsClient',
+ 'ServicesClient', 'V3TokenClient', 'CredentialsClient',
+ 'DomainsClient', 'GroupsClient', 'IdentityClient', 'ProjectsClient',
+ 'RolesClient', 'TrustsClient', 'UsersClient', ]
diff --git a/tempest/services/identity/v3/json/credentials_client.py b/tempest/services/identity/v3/json/credentials_client.py
index 6ab94d0..55eeee4 100644
--- a/tempest/services/identity/v3/json/credentials_client.py
+++ b/tempest/services/identity/v3/json/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.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.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.html#update-credential
"""
post_body = json.dumps({'credential': kwargs})
resp, body = self.patch('credentials/%s' % credential_id, post_body)
@@ -52,22 +53,37 @@
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.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/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/groups_client.py b/tempest/services/identity/v3/json/groups_client.py
index 1a495f8..3674496 100644
--- a/tempest/services/identity/v3/json/groups_client.py
+++ b/tempest/services/identity/v3/json/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
@@ -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)
@@ -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/projects_client.py b/tempest/services/identity/v3/json/projects_client.py
index 97e43df..ce2f38d 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/services/identity/v3/json/projects_client.py
@@ -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/roles_client.py b/tempest/services/identity/v3/json/roles_client.py
index bdb0490..aab203f 100644
--- a/tempest/services/identity/v3/json/roles_client.py
+++ b/tempest/services/identity/v3/json/roles_client.py
@@ -34,7 +34,7 @@
def show_role(self, role_id):
"""GET a Role."""
- resp, body = self.get('roles/%s' % str(role_id))
+ resp, body = self.get('roles/%s' % role_id)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
@@ -53,14 +53,14 @@
api-ref-identity-v3.html#updateRole
"""
post_body = json.dumps({'role': kwargs})
- resp, body = self.patch('roles/%s' % str(role_id), post_body)
+ 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' % str(role_id))
+ resp, body = self.delete('roles/%s' % role_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/services/identity/v3/json/users_clients.py
index 73bd343..0dcdacd 100644
--- a/tempest/services/identity/v3/json/users_clients.py
+++ b/tempest/services/identity/v3/json/users_clients.py
@@ -21,56 +21,27 @@
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})
+ 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, name, **kwargs):
+ def update_user(self, user_id, **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})
+ 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)
@@ -87,15 +58,26 @@
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)
+ 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=None):
- """Get the list of users."""
+ 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)
@@ -117,9 +99,16 @@
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)
+ 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/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 33dba6e..a7db30e 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))
@@ -237,37 +231,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/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
old mode 100644
new mode 100755
index e4d9014..83870ae
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ b/tempest/services/volume/base/admin/base_types_client.py
@@ -48,7 +48,11 @@
return 'volume-type/encryption-type'
def list_volume_types(self, **params):
- """List all the volume_types created."""
+ """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)
@@ -58,9 +62,13 @@
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)
+ 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)
@@ -78,22 +86,24 @@
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))
+ 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, vol_type_id, **params):
+ 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' % str(vol_type_id)
+ url = 'types/%s/extra_specs' % volume_type_id
if params:
url += '?%s' % urllib.urlencode(params)
@@ -102,114 +112,93 @@
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):
+ 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" % (str(vol_type_id),
- str(extra_specs_name))
+ 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, vol_type_id, extra_specs):
+ def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
"""Creates a new Volume_type extra spec.
- vol_type_id: Id of volume_type.
+ volume_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)
+ 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, vol_id, extra_spec_name):
+ 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" % (
- (str(vol_id)), str(extra_spec_name)))
+ volume_type_id, 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,
+ 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.
- vol_type_id: Id of volume_type.
+ 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" % (str(vol_type_id),
- str(extra_spec_name))
+ 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 show_encryption_type(self, vol_type_id):
+ def show_encryption_type(self, volume_type_id):
"""Get the volume encryption type for the specified volume type.
- vol_type_id: Id of volume_type.
+ volume_type_id: Id of volume_type.
"""
- url = "/types/%s/encryption" % str(vol_type_id)
+ 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, vol_type_id, **kwargs):
+ 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" % str(vol_type_id)
+ 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, vol_type_id):
+ 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" % str(vol_type_id))
+ "/types/%s/encryption/provider" % volume_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..e00ac68 100644
--- a/tempest/services/volume/base/base_qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -82,7 +82,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 +96,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 +128,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 +136,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 +144,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 +152,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 6d3f03b..38a6dc7
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -23,7 +23,11 @@
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'
@@ -36,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)
@@ -68,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)
@@ -100,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)
@@ -108,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)
@@ -126,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)
@@ -142,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)
@@ -150,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..273dbbb 100755
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -63,7 +63,11 @@
return rest_client.ResponseBody(resp, body)
def show_pools(self, detail=False):
- # List all the volumes pools (hosts)
+ """List all the volumes pools (hosts).
+
+ Output params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#listPools
+ """
url = 'scheduler-stats/get_pools'
if detail:
url += '?detail=True'
@@ -73,9 +77,22 @@
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)
+
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)
@@ -109,7 +126,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)
@@ -231,7 +248,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 +270,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 +305,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 +313,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 +322,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 +331,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,7 +339,7 @@
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)
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index 6bdb8c4..945d2f2 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -12,15 +12,14 @@
# 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.qos_client import QosSpecsClient
from tempest.services.volume.v1.json.snapshots_client import SnapshotsClient
from tempest.services.volume.v1.json.volumes_client import VolumesClient
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/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/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..80f7a94 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -12,15 +12,14 @@
# 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.qos_client import QosSpecsClient
from tempest.services.volume.v2.json.snapshots_client import SnapshotsClient
from tempest.services.volume.v2.json.volumes_client import VolumesClient
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..f76e8dc 100644
--- a/tempest/services/volume/v2/json/admin/types_client.py
+++ b/tempest/services/volume/v2/json/admin/types_client.py
@@ -13,9 +13,51 @@
# 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.admin import base_types_client
class TypesClient(base_types_client.BaseTypesClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
+
+ 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/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/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/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/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..94a4847 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)
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_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_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_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/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/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/test_service_clients.py b/tempest/tests/test_service_clients.py
deleted file mode 100644
index a559086..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)
- 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):
- 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/tox.ini b/tox.ini
index a33101b..a621492 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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