Merge "Add request/response to subunit-describe-calls"
diff --git a/.gitignore b/.gitignore
index 5b87cec..9292dbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
AUTHORS
ChangeLog
*.pyc
+__pycache__/
etc/tempest.conf
etc/tempest.conf.sample
etc/logging.conf
@@ -23,6 +24,7 @@
!.coveragerc
cover/
doc/source/_static/tempest.conf.sample
+doc/source/plugin-registry.rst
# Files created by releasenotes build
releasenotes/build
diff --git a/README.rst b/README.rst
index 725a890..13f4f61 100644
--- a/README.rst
+++ b/README.rst
@@ -1,13 +1,8 @@
Tempest - The OpenStack Integration Test Suite
==============================================
-.. image:: https://img.shields.io/pypi/v/tempest.svg
- :target: https://pypi.python.org/pypi/tempest/
- :alt: Latest Version
-
-.. image:: https://img.shields.io/pypi/dm/tempest.svg
- :target: https://pypi.python.org/pypi/tempest/
- :alt: Downloads
+The documentation for Tempest is officially hosted at:
+http://docs.openstack.org/developer/tempest/
This is a set of integration tests to be run against a live OpenStack
cluster. Tempest has batteries of tests for OpenStack API validation,
@@ -63,19 +58,21 @@
This can be done within a venv, but the assumption for this guide is that
the Tempest cli entry point will be in your shell's PATH.
-#. Installing Tempest will create a /etc/tempest dir which will contain the
- sample config file packaged with Tempest. The contents of /etc/tempest will
- be copied to all local working dirs, so if there is any common configuration
- you'd like to be shared between anyone setting up local Tempest working dirs
- it's recommended that you copy or rename tempest.conf.sample to tempest.conf
- and make those changes to that file in /etc/tempest
+#. Installing Tempest may create a /etc/tempest dir, however if one isn't
+ created you can create one or use ~/.tempest/etc or ~/.config/tempest in
+ place of /etc/tempest. If none of these dirs are created tempest will create
+ ~/.tempest/etc when it's needed. The contents of this dir will always
+ automatically be copied to all etc/ dirs in local workspaces as an initial
+ setup step. So if there is any common configuration you'd like to be shared
+ between local Tempest workspaces it's recommended that you pre-populate it
+ before running ``tempest init``.
-#. Setup a local working Tempest dir. This is done by using the tempest init
+#. Setup a local Tempest workspace. This is done by using the tempest init
command::
$ tempest init cloud-01
- works the same as::
+ which also works the same as::
$ mkdir cloud-01 && cd cloud-01 && tempest init
@@ -88,11 +85,23 @@
config files located in the etc/ subdir created by the ``tempest init``
command. Tempest is expecting a tempest.conf file in etc/ so if only a
sample exists you must rename or copy it to tempest.conf before making
- any changes to it otherwise Tempest will not know how to load it.
+ any changes to it otherwise Tempest will not know how to load it. For
+ details on configuring tempest refer to the :ref:`tempest-configuration`.
#. Once the configuration is done you're now ready to run Tempest. This can
- be done with testr directly or any `testr`_ based test runner, like
- `ostestr`_. For example, from the working dir running::
+ be done using the :ref:`tempest_run` command. This can be done by either
+ running::
+
+ $ tempest run
+
+ from the Tempest workspace directory. Or you can use the ``--workspace``
+ argument to run in the workspace you created regarless of your current
+ working directory. For example::
+
+ $ tempest run --workspace cloud-01
+
+ There is also the option to use testr directly, or any `testr`_ based test
+ runner, like `ostestr`_. For example, from the workspace dir run::
$ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
@@ -169,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
----------
@@ -215,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/conf.py b/doc/source/conf.py
index eef3620..127613d 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -15,6 +15,17 @@
import os
import subprocess
+# Build the plugin registry
+def build_plugin_registry(app):
+ root_dir = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir)
+
+def setup(app):
+ app.connect('builder-inited', build_plugin_registry)
+
+
+
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 60ff46b..f1ede06 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -2,19 +2,14 @@
Tempest Testing Project
=======================
-Contents:
+--------
+Overview
+--------
.. toctree::
:maxdepth: 2
overview
- HACKING
- REVIEWING
- plugin
- plugin-registry
- library
- microversion_testing
- test-removal
------------
Field Guides
@@ -32,6 +27,10 @@
field_guide/stress
field_guide/unit_tests
+=========
+For users
+=========
+
---------------------------
Tempest Configuration Guide
---------------------------
@@ -56,6 +55,41 @@
workspace
run
+==============
+For developers
+==============
+
+-----------
+Development
+-----------
+
+.. toctree::
+ :maxdepth: 2
+
+ HACKING
+ REVIEWING
+ microversion_testing
+ test-removal
+
+-------
+Plugins
+-------
+
+.. toctree::
+ :maxdepth: 2
+
+ plugin
+ plugin-registry
+
+-------
+Library
+-------
+
+.. toctree::
+ :maxdepth: 2
+
+ library
+
==================
Indices and tables
==================
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 17059e4..5263fdd 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -217,3 +217,7 @@
* `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
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
deleted file mode 100644
index 517e5b8..0000000
--- a/doc/source/plugin-registry.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-..
- Note to patch submitters: this file is covered by a periodic proposal
- job. You should edit the files data/tempest-plugins-registry.footer
- data/tempest-plugins-registry.header instead of this one.
-
-==========================
- Tempest Plugin Registry
-==========================
-
-Since we've created the external plugin mechanism, it's gotten used by
-a lot of projects. The following is a list of plugins that currently
-exist.
-
-Detected Plugins
-================
-
-The following will list plugins that a script has found in the openstack/
-namespace, which includes but is not limited to official OpenStack
-projects.
-
-+----------------------------+-------------------------------------------------------------------------+
-|Plugin Name |URL |
-+----------------------------+-------------------------------------------------------------------------+
diff --git a/doc/source/run.rst b/doc/source/run.rst
index 07fa5f7..ce7f03e 100644
--- a/doc/source/run.rst
+++ b/doc/source/run.rst
@@ -1,3 +1,5 @@
+.. _tempest_run:
+
-----------
Tempest Run
-----------
diff --git a/doc/source/test-removal.rst b/doc/source/test-removal.rst
index 6570bb7..79a5846 100644
--- a/doc/source/test-removal.rst
+++ b/doc/source/test-removal.rst
@@ -31,7 +31,7 @@
#. The tests proposed for removal must have equiv. coverage in a different
project's test suite (whether this is another gating test project, or an in
- tree funcitonal test suite) For API tests preferably the other project will
+ tree functional test suite). For API tests preferably the other project will
have a similar source of friction in place to prevent breaking api changes
so that we don't regress and let breaking api changes slip through the
gate.
@@ -62,7 +62,7 @@
SELECT * from tests where test_id like "%test_id%";
(where $test_id is the full test_id, but truncated to the class because of
-setupclass or teardownclass failures)
+setupClass or tearDownClass failures)
You can access the infra mysql subunit2sql db w/ read-only permissions with:
@@ -113,7 +113,7 @@
well ahead of the scheduled meeting. Since the meeting time will be well known
ahead of time anyone who depends on the tests will have ample time beforehand
to outline any concerns on the before the meeting. To give ample time for
-people to respond to removal proposals please add things to the agend by the
+people to respond to removal proposals please add things to the agenda by the
Monday before the meeting.
The other option is to raise the removal on the openstack-dev mailing list.
@@ -163,6 +163,6 @@
anything that lives in tempest which doesn't test one of these projects can be
removed assuming there is equivalent testing elsewhere. Preferably using the
`tempest plugin mechanism`_
-to mantain continuity after migrating the tests out of tempest
+to maintain continuity after migrating the tests out of tempest.
.. _tempest plugin mechanism: http://docs.openstack.org/developer/tempest/plugin.html
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
deleted file mode 100644
index e69de29..0000000
--- a/releasenotes/notes/.placeholder
+++ /dev/null
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/identity-clients-as-library-e663c6132fcac6c2.yaml b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
index b7850d0..f9173a0 100644
--- a/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
+++ b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
@@ -7,3 +7,7 @@
any maintenance changes.
* endpoints_client(v2)
+ * roles_client(v2)
+ * services_client(v2)
+ * tenants_client(v2)
+ * users_client(v2)
diff --git a/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
index 90a5056..1fa4ddd 100644
--- a/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
+++ b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
@@ -7,6 +7,7 @@
without any maintenance changes.
* image_members_client(v1)
+ * images_client(v1)
* image_members_client(v2)
* images_client(v2)
* namespaces_client(v2)
diff --git a/releasenotes/notes/remove-trove-tests-666522e9113549f9.yaml b/releasenotes/notes/remove-trove-tests-666522e9113549f9.yaml
new file mode 100644
index 0000000..1157a4f
--- /dev/null
+++ b/releasenotes/notes/remove-trove-tests-666522e9113549f9.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - All tests for the Trove project have been removed from tempest. They now
+ live as a tempest plugin in the the trove project.
diff --git a/releasenotes/notes/service_client_config-8a1d7b4de769c633.yaml b/releasenotes/notes/service_client_config-8a1d7b4de769c633.yaml
new file mode 100644
index 0000000..3e43f9a
--- /dev/null
+++ b/releasenotes/notes/service_client_config-8a1d7b4de769c633.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - A new helper method `service_client_config` has been added
+ to the stable module config.py that returns extracts from
+ configuration into a dictionary the configuration settings
+ relevant for the initisialisation of a service client.
diff --git a/requirements.txt b/requirements.txt
index 45fe345..058ea00 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,21 +5,20 @@
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
testtools>=1.4.0 # MIT
-paramiko>=2.0 # LGPL
+paramiko>=2.0 # LGPLv2.1+
netaddr!=0.7.16,>=0.7.12 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
-pyOpenSSL>=0.14 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0
-oslo.config>=3.10.0 # Apache-2.0
+oslo.config>=3.12.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.11.0 # Apache-2.0
+oslo.utils>=3.15.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/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_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..58da8b9 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -65,7 +65,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
index 3eb6d94..09253b0 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -77,7 +77,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)
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 427a748..6f80730 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -16,6 +16,7 @@
import six
from tempest.api.compute import base
+from tempest.common import image as common_image
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
@@ -55,15 +56,18 @@
super(ImagesMetadataTestJSON, cls).resource_setup()
cls.image_id = None
- name = data_utils.rand_name('image')
+ params = {
+ 'name': data_utils.rand_name('image'),
+ 'container_format': 'bare',
+ 'disk_format': 'raw'
+ }
if CONF.image_feature_enabled.api_v1:
- kwargs = dict(is_public=False)
+ params.update({'is_public': False})
+ params = {'headers': common_image.image_meta_to_headers(**params)}
else:
- kwargs = dict(visibility='private')
- body = cls.glance_client.create_image(name=name,
- container_format='bare',
- disk_format='raw',
- **kwargs)
+ params.update({'visibility': 'private'})
+
+ body = cls.glance_client.create_image(**params)
body = body['image'] if 'image' in body else body
cls.image_id = body['id']
cls.images.append(cls.image_id)
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index e74ca67..9017461 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -19,6 +19,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import image as common_image
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
@@ -58,15 +59,19 @@
super(ListImageFiltersTestJSON, cls).resource_setup()
def _create_image():
- name = data_utils.rand_name('image')
+ params = {
+ 'name': data_utils.rand_name('image'),
+ 'container_format': 'bare',
+ 'disk_format': 'raw'
+ }
if CONF.image_feature_enabled.api_v1:
- kwargs = dict(is_public=False)
+ params.update({'is_public': False})
+ params = {'headers':
+ common_image.image_meta_to_headers(**params)}
else:
- kwargs = dict(visibility='private')
- body = cls.glance_client.create_image(name=name,
- container_format='bare',
- disk_format='raw',
- **kwargs)
+ params.update({'visibility': 'private'})
+
+ body = cls.glance_client.create_image(**params)
body = body['image'] if 'image' in body else body
image_id = body['id']
cls.images.append(image_id)
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index fdf55e5..d02f86f 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -16,7 +16,9 @@
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 exceptions as lib_exc
@@ -48,6 +50,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 +76,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 +271,40 @@
if fixed_ip is not None:
break
self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
+
+ @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_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 617cdd5..aba0240 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -37,17 +37,11 @@
super(ServerDiskConfigTestJSON, cls).setup_clients()
cls.client = cls.os.servers_client
- @classmethod
- def resource_setup(cls):
- super(ServerDiskConfigTestJSON, cls).resource_setup()
- server = cls.create_test_server(wait_until='ACTIVE')
- cls.server_id = server['id']
-
- def _update_server_with_disk_config(self, disk_config):
- server = self.client.show_server(self.server_id)['server']
+ def _update_server_with_disk_config(self, server_id, disk_config):
+ server = self.client.show_server(server_id)['server']
if disk_config != server['OS-DCF:diskConfig']:
server = self.client.update_server(
- self.server_id, disk_config=disk_config)['server']
+ server_id, disk_config=disk_config)['server']
waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
server = self.client.show_server(server['id'])['server']
self.assertEqual(disk_config, server['OS-DCF:diskConfig'])
@@ -55,9 +49,12 @@
@test.idempotent_id('bef56b09-2e8c-4883-a370-4950812f430e')
def test_rebuild_server_with_manual_disk_config(self):
# A server should be rebuilt using the manual disk config option
- self._update_server_with_disk_config(disk_config='AUTO')
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.client.delete_server, server['id'])
+ self._update_server_with_disk_config(server['id'],
+ disk_config='AUTO')
- server = self.client.rebuild_server(self.server_id,
+ server = self.client.rebuild_server(server['id'],
self.image_ref_alt,
disk_config='MANUAL')['server']
@@ -71,9 +68,12 @@
@test.idempotent_id('9c9fae77-4feb-402f-8450-bf1c8b609713')
def test_rebuild_server_with_auto_disk_config(self):
# A server should be rebuilt using the auto disk config option
- self._update_server_with_disk_config(disk_config='MANUAL')
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.client.delete_server, server['id'])
+ self._update_server_with_disk_config(server['id'],
+ disk_config='MANUAL')
- server = self.client.rebuild_server(self.server_id,
+ server = self.client.rebuild_server(server['id'],
self.image_ref_alt,
disk_config='AUTO')['server']
@@ -84,31 +84,24 @@
server = self.client.show_server(server['id'])['server']
self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
- def _get_alternative_flavor(self):
- server = self.client.show_server(self.server_id)['server']
-
- if server['flavor']['id'] == self.flavor_ref:
- return self.flavor_ref_alt
- else:
- return self.flavor_ref
-
@test.idempotent_id('414e7e93-45b5-44bc-8e03-55159c6bfc97')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
def test_resize_server_from_manual_to_auto(self):
# A server should be resized from manual to auto disk config
- self._update_server_with_disk_config(disk_config='MANUAL')
-
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.client.delete_server, server['id'])
+ self._update_server_with_disk_config(server['id'],
+ disk_config='MANUAL')
# Resize with auto option
- flavor_id = self._get_alternative_flavor()
- self.client.resize_server(self.server_id, flavor_id,
+ self.client.resize_server(server['id'], self.flavor_ref_alt,
disk_config='AUTO')
- waiters.wait_for_server_status(self.client, self.server_id,
+ waiters.wait_for_server_status(self.client, server['id'],
'VERIFY_RESIZE')
- self.client.confirm_resize_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ self.client.confirm_resize_server(server['id'])
+ waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
- server = self.client.show_server(self.server_id)['server']
+ server = self.client.show_server(server['id'])['server']
self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
@test.idempotent_id('693d16f3-556c-489a-8bac-3d0ca2490bad')
@@ -116,27 +109,31 @@
'Resize not available.')
def test_resize_server_from_auto_to_manual(self):
# A server should be resized from auto to manual disk config
- self._update_server_with_disk_config(disk_config='AUTO')
-
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.client.delete_server, server['id'])
+ self._update_server_with_disk_config(server['id'],
+ disk_config='AUTO')
# Resize with manual option
- flavor_id = self._get_alternative_flavor()
- self.client.resize_server(self.server_id, flavor_id,
+ self.client.resize_server(server['id'], self.flavor_ref_alt,
disk_config='MANUAL')
- waiters.wait_for_server_status(self.client, self.server_id,
+ waiters.wait_for_server_status(self.client, server['id'],
'VERIFY_RESIZE')
- self.client.confirm_resize_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ self.client.confirm_resize_server(server['id'])
+ waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
- server = self.client.show_server(self.server_id)['server']
+ server = self.client.show_server(server['id'])['server']
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
@test.idempotent_id('5ef18867-358d-4de9-b3c9-94d4ba35742f')
def test_update_server_from_auto_to_manual(self):
# A server should be updated from auto to manual disk config
- self._update_server_with_disk_config(disk_config='AUTO')
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.client.delete_server, server['id'])
+ self._update_server_with_disk_config(server['id'],
+ disk_config='AUTO')
# Update the disk_config attribute to manual
- server = self.client.update_server(self.server_id,
+ server = self.client.update_server(server['id'],
disk_config='MANUAL')['server']
waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 0d06119..21e7d10 100644
--- 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')
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index decccf3..d5ba76c 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -349,7 +349,7 @@
return None
for plugin in CONF.data_processing_feature_enabled.plugins:
- if plugin in DEFAULT_TEMPLATES.keys():
+ if plugin in DEFAULT_TEMPLATES:
break
else:
plugin = ''
diff --git a/tempest/api/database/base.py b/tempest/api/database/base.py
deleted file mode 100644
index 01e05db..0000000
--- a/tempest/api/database/base.py
+++ /dev/null
@@ -1,48 +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 import config
-import tempest.test
-
-CONF = config.CONF
-
-
-class BaseDatabaseTest(tempest.test.BaseTestCase):
- """Base test case class for all Database API tests."""
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseDatabaseTest, cls).skip_checks()
- if not CONF.service_available.trove:
- skip_msg = ("%s skipped as trove is not available" % cls.__name__)
- raise cls.skipException(skip_msg)
-
- @classmethod
- def setup_clients(cls):
- super(BaseDatabaseTest, cls).setup_clients()
- cls.database_flavors_client = cls.os.database_flavors_client
- cls.os_flavors_client = cls.os.flavors_client
- cls.database_limits_client = cls.os.database_limits_client
- cls.database_versions_client = cls.os.database_versions_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseDatabaseTest, cls).resource_setup()
-
- cls.catalog_type = CONF.database.catalog_type
- cls.db_flavor_ref = CONF.database.db_flavor_ref
- cls.db_current_version = CONF.database.db_current_version
diff --git a/tempest/api/database/flavors/__init__.py b/tempest/api/database/flavors/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/database/flavors/__init__.py
+++ /dev/null
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
deleted file mode 100644
index bb7a0a4..0000000
--- a/tempest/api/database/flavors/test_flavors.py
+++ /dev/null
@@ -1,77 +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.api.database import base
-from tempest.lib import decorators
-from tempest import test
-
-
-class DatabaseFlavorsTest(base.BaseDatabaseTest):
-
- @classmethod
- def setup_clients(cls):
- super(DatabaseFlavorsTest, cls).setup_clients()
- cls.client = cls.database_flavors_client
-
- @test.attr(type='smoke')
- @test.idempotent_id('c94b825e-0132-4686-8049-8a4a2bc09525')
- @decorators.skip_because(bug='1567134')
- def test_get_db_flavor(self):
- # The expected flavor details should be returned
- flavor = (self.client.show_db_flavor(self.db_flavor_ref)
- ['flavor'])
- self.assertEqual(self.db_flavor_ref, str(flavor['id']))
- self.assertIn('ram', flavor)
- self.assertIn('links', flavor)
- self.assertIn('name', flavor)
-
- @test.attr(type='smoke')
- @test.idempotent_id('685025d6-0cec-4673-8a8d-995cb8e0d3bb')
- @decorators.skip_because(bug='1567134')
- def test_list_db_flavors(self):
- flavor = (self.client.show_db_flavor(self.db_flavor_ref)
- ['flavor'])
- # List of all flavors should contain the expected flavor
- flavors = self.client.list_db_flavors()['flavors']
- self.assertIn(flavor, flavors)
-
- def _check_values(self, names, db_flavor, os_flavor, in_db=True):
- for name in names:
- self.assertIn(name, os_flavor)
- if in_db:
- self.assertIn(name, db_flavor)
- self.assertEqual(str(db_flavor[name]), str(os_flavor[name]),
- "DB flavor differs from OS on '%s' value"
- % name)
- else:
- self.assertNotIn(name, db_flavor)
-
- @test.attr(type='smoke')
- @test.idempotent_id('afb2667f-4ec2-4925-bcb7-313fdcffb80d')
- @test.services('compute')
- @decorators.skip_because(bug='1567134')
- def test_compare_db_flavors_with_os(self):
- db_flavors = self.client.list_db_flavors()['flavors']
- os_flavors = (self.os_flavors_client.list_flavors(detail=True)
- ['flavors'])
- self.assertEqual(len(os_flavors), len(db_flavors),
- "OS flavors %s do not match DB flavors %s" %
- (os_flavors, db_flavors))
- for os_flavor in os_flavors:
- db_flavor =\
- self.client.show_db_flavor(os_flavor['id'])['flavor']
- self._check_values(['id', 'name', 'ram'], db_flavor, os_flavor)
- self._check_values(['disk', 'vcpus', 'swap'], db_flavor, os_flavor,
- in_db=False)
diff --git a/tempest/api/database/flavors/test_flavors_negative.py b/tempest/api/database/flavors/test_flavors_negative.py
deleted file mode 100644
index cd2981b..0000000
--- a/tempest/api/database/flavors/test_flavors_negative.py
+++ /dev/null
@@ -1,33 +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.api.database import base
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-class DatabaseFlavorsNegativeTest(base.BaseDatabaseTest):
-
- @classmethod
- def setup_clients(cls):
- super(DatabaseFlavorsNegativeTest, cls).setup_clients()
- cls.client = cls.database_flavors_client
-
- @test.attr(type=['negative'])
- @test.idempotent_id('f8e7b721-373f-4a64-8e9c-5327e975af3e')
- def test_get_non_existent_db_flavor(self):
- # flavor details are not returned for non-existent flavors
- self.assertRaises(lib_exc.NotFound,
- self.client.show_db_flavor, -1)
diff --git a/tempest/api/database/limits/__init__.py b/tempest/api/database/limits/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/database/limits/__init__.py
+++ /dev/null
diff --git a/tempest/api/database/limits/test_limits.py b/tempest/api/database/limits/test_limits.py
deleted file mode 100644
index ee51b1d..0000000
--- a/tempest/api/database/limits/test_limits.py
+++ /dev/null
@@ -1,45 +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.api.database import base
-from tempest import test
-
-
-class DatabaseLimitsTest(base.BaseDatabaseTest):
-
- @classmethod
- def resource_setup(cls):
- super(DatabaseLimitsTest, cls).resource_setup()
- cls.client = cls.database_limits_client
-
- @test.attr(type='smoke')
- @test.idempotent_id('73024538-f316-4829-b3e9-b459290e137a')
- def test_absolute_limits(self):
- # Test to verify if all absolute limit parameters are
- # present when verb is ABSOLUTE
- limits = self.client.list_db_limits()['limits']
- expected_abs_limits = ['max_backups', 'max_volumes',
- 'max_instances', 'verb']
- absolute_limit = [l for l in limits
- if l['verb'] == 'ABSOLUTE']
- self.assertEqual(1, len(absolute_limit), "One ABSOLUTE limit "
- "verb is allowed. Fetched %s"
- % len(absolute_limit))
- actual_abs_limits = absolute_limit[0].keys()
- missing_abs_limit = set(expected_abs_limits) - set(actual_abs_limits)
- self.assertEmpty(missing_abs_limit,
- "Failed to find the following absolute limit(s)"
- " in a fetched list: %s" %
- ', '.join(str(a) for a in missing_abs_limit))
diff --git a/tempest/api/database/versions/__init__.py b/tempest/api/database/versions/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/database/versions/__init__.py
+++ /dev/null
diff --git a/tempest/api/database/versions/test_versions.py b/tempest/api/database/versions/test_versions.py
deleted file mode 100644
index ae568b1..0000000
--- a/tempest/api/database/versions/test_versions.py
+++ /dev/null
@@ -1,39 +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.api.database import base
-from tempest import test
-
-
-class DatabaseVersionsTest(base.BaseDatabaseTest):
-
- @classmethod
- def setup_clients(cls):
- super(DatabaseVersionsTest, cls).setup_clients()
- cls.client = cls.database_versions_client
-
- @test.attr(type='smoke')
- @test.idempotent_id('6952cd77-90cd-4dca-bb60-8e2c797940cf')
- def test_list_db_versions(self):
- versions = self.client.list_db_versions()['versions']
- self.assertTrue(len(versions) > 0, "No database versions found")
- # List of all versions should contain the current version, and there
- # should only be one 'current' version
- current_versions = list()
- for version in versions:
- if 'CURRENT' == version['status']:
- current_versions.append(version['id'])
- self.assertEqual(1, len(current_versions))
- self.assertIn(self.db_current_version, current_versions)
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_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 18a50d0..a540da7 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -9,13 +9,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
from tempest.api.identity import base
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
from tempest.lib import auth
-from tempest import manager
from tempest import test
CONF = config.CONF
@@ -78,7 +76,7 @@
creds = auth.KeystoneV3Credentials(username=user_name,
password=user_name,
user_domain_name=dom_name)
- auth_provider = manager.get_auth_provider(creds)
+ auth_provider = clients.get_auth_provider(creds)
creds = auth_provider.fill_credentials()
admin_client = clients.Manager(credentials=creds)
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index a3ada21..59fcec6 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,7 +76,7 @@
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 = []
@@ -103,7 +110,7 @@
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 +128,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_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..9691ee8 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -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)
+ 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,
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 607bebe..1879e46 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
@@ -13,10 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
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 ProjectsTestJSON(base.BaseIdentityV3AdminTest):
@@ -27,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 '
@@ -40,17 +45,48 @@
@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
+ 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(
+ root_project_name, domain_id=domain_id)['project']
+ self.addCleanup(
+ self.projects_client.delete_project, root_project['id'])
+
+ root_project_id = root_project['id']
+ parent_id = root_project['parent_id']
+ self.assertEqual(root_project_name, root_project['name'])
+ # If not provided, the parent_id must point to the top level
+ # project in the hierarchy, i.e. its domain
+ self.assertEqual(domain_id, parent_id)
+
+ # Create a project using root_project_id as parent_id
+ project_name = data_utils.rand_name('project')
+ project = self.projects_client.create_project(
+ project_name, domain_id=domain_id,
+ parent_id=root_project_id)['project']
+ self.addCleanup(self.projects_client.delete_project, project['id'])
+ parent_id = project['parent_id']
+ self.assertEqual(project_name, project['name'])
+ self.assertEqual(root_project_id, parent_id)
@test.idempotent_id('1f66dc76-50cc-4741-a200-af984509e480')
def test_project_create_enabled(self):
@@ -58,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')
@@ -72,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')
@@ -86,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']
@@ -110,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')
@@ -133,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']
@@ -156,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')
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..2b77023 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
@@ -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_users.py b/tempest/api/identity/admin/v3/test_users.py
index 371da9c..f200095 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -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..71e8bc5 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -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 7a1e3a5..ce052e6 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -17,7 +17,6 @@
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
@@ -64,6 +63,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 +120,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,12 +192,9 @@
@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
@@ -173,106 +202,42 @@
user = cls.get_user_by_name(user_name, domain_id)
cls.users_client.update_user(user['id'], 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
# otherwise it raises Forbidden exception
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)
- for tenant in self.tenants:
- test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_tenant, tenant)
- for project in reversed(self.projects):
- test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_project, project)
- for role in self.roles:
- test_utils.call_and_ignore_notfound_exc(
- self.roles_client.delete_role, role)
- for domain in self.domains:
- test_utils.call_and_ignore_notfound_exc(
- self.domains_client.update_domain, domain, enabled=False)
- test_utils.call_and_ignore_notfound_exc(
- self.domains_client.delete_domain, domain)
-
-
-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(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/image/base.py b/tempest/api/image/base.py
index 3fefc81..6fd6ea6 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -14,6 +14,7 @@
from six import moves
+from tempest.common import image as common_image
from tempest.common.utils import data_utils
from tempest import config
from tempest.lib.common.utils import test_utils
@@ -55,14 +56,20 @@
super(BaseImageTest, cls).resource_cleanup()
@classmethod
- def create_image(cls, **kwargs):
+ def create_image(cls, data=None, **kwargs):
"""Wrapper that returns a test image."""
if 'name' not in kwargs:
name = data_utils.rand_name(cls.__name__ + "-instance")
kwargs['name'] = name
- image = cls.client.create_image(**kwargs)
+ params = cls._get_create_params(**kwargs)
+ if data:
+ # NOTE: On glance v1 API, the data should be passed on
+ # a header. Then here handles the data separately.
+ params['data'] = data
+
+ image = cls.client.create_image(**params)
# Image objects returned by the v1 client have the image
# data inside a dict that is keyed against 'image'.
if 'image' in image:
@@ -70,6 +77,10 @@
cls.created_images.append(image['id'])
return image
+ @classmethod
+ def _get_create_params(cls, **kwargs):
+ return kwargs
+
class BaseV1ImageTest(BaseImageTest):
@@ -85,6 +96,10 @@
super(BaseV1ImageTest, cls).setup_clients()
cls.client = cls.os.image_client
+ @classmethod
+ def _get_create_params(cls, **kwargs):
+ return {'headers': common_image.image_meta_to_headers(**kwargs)}
+
class BaseV1ImageMembersTest(BaseV1ImageTest):
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 59ac646..e4fbbe3 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)
@@ -320,8 +320,10 @@
metadata = common_image.get_image_meta_from_headers(resp)
self.assertEqual(metadata['properties'], {'key1': 'value1'})
metadata['properties'].update(req_metadata)
- metadata = self.client.update_image(
- self.image_id, properties=metadata['properties'])['image']
+ headers = common_image.image_meta_to_headers(
+ properties=metadata['properties'])
+ metadata = self.client.update_image(self.image_id,
+ headers=headers)['image']
resp = self.client.check_image(self.image_id)
resp_metadata = common_image.get_image_meta_from_headers(resp)
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index babee74..9e67c25 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -27,17 +27,17 @@
def test_register_with_invalid_container_format(self):
# Negative tests for invalid data supplied to POST /images
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
- name='test',
- container_format='wrong',
- disk_format='vhd',)
+ headers={'x-image-meta-name': 'test',
+ 'x-image-meta-container_format': 'wrong',
+ 'x-image-meta-disk_format': 'vhd'})
@test.attr(type=['negative'])
@test.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67')
def test_register_with_invalid_disk_format(self):
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
- name='test',
- container_format='bare',
- disk_format='wrong',)
+ headers={'x-image-meta-name': 'test',
+ 'x-image-meta-container_format': 'bare',
+ 'x-image-meta-disk_format': 'wrong'})
@test.attr(type=['negative'])
@test.idempotent_id('bb016f15-0820-4f27-a92d-09b2f67d2488')
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/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index b2892e5..92dfc56 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -14,6 +14,7 @@
# under the License.
import netaddr
+import six
from tempest.api.network import base
from tempest import config
@@ -90,7 +91,8 @@
body = self.ports_client.update_port(
port_id, allowed_address_pairs=allowed_address_pairs)
allowed_address_pair = body['port']['allowed_address_pairs']
- self.assertEqual(allowed_address_pair, allowed_address_pairs)
+ six.assertCountEqual(self, allowed_address_pair,
+ allowed_address_pairs)
@test.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
def test_update_port_with_address_pair(self):
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/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 3be5bb6..4ead084 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -125,7 +125,7 @@
'resource_status_reason',
'resource_status', 'event_time')
- resource_statuses = map(lambda event: event['resource_status'], events)
+ resource_statuses = [event['resource_status'] for event in events]
self.assertIn('CREATE_IN_PROGRESS', resource_statuses)
self.assertIn('CREATE_COMPLETE', resource_statuses)
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
index 6a4e2b9..aa0b46a 100644
--- a/tempest/api/orchestration/stacks/test_soft_conf.py
+++ b/tempest/api/orchestration/stacks/test_soft_conf.py
@@ -45,7 +45,7 @@
def _validate_config(self, configuration, api_config):
# Assert all expected keys are present with matching data
- for k in configuration.keys():
+ for k in configuration:
self.assertEqual(configuration[k],
api_config['software_config'][k])
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 5e1c20b..5615cf3 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -63,9 +63,8 @@
extra_specs = {spec_key_with_prefix: backend_name_key}
else:
extra_specs = {spec_key_without_prefix: backend_name_key}
- self.type = self.admin_volume_types_client.create_volume_type(
- name=type_name, extra_specs=extra_specs)['volume_type']
- self.volume_type_id_list.append(self.type['id'])
+ self.type = self.create_volume_type(name=type_name,
+ extra_specs=extra_specs)
params = {self.name_field: vol_name, 'volume_type': type_name}
@@ -92,11 +91,6 @@
cls.admin_volume_client.delete_volume(volume_id)
cls.admin_volume_client.wait_for_resource_deletion(volume_id)
- # volume types deletion
- volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
- for volume_type_id in volume_type_id_list:
- cls.admin_volume_types_client.delete_volume_type(volume_type_id)
-
super(VolumeMultiBackendV2Test, cls).resource_cleanup()
@test.idempotent_id('c1a41f3f-9dad-493e-9f09-3ff197d477cc')
diff --git a/tempest/api/volume/admin/test_qos.py b/tempest/api/volume/admin/test_qos.py
index 68e57f5..9402668 100644
--- a/tempest/api/volume/admin/test_qos.py
+++ b/tempest/api/volume/admin/test_qos.py
@@ -50,14 +50,6 @@
list_qos = self.admin_volume_qos_client.list_qos()['qos_specs']
self.assertNotIn(body, list_qos)
- def _create_test_volume_type(self):
- vol_type_name = utils.rand_name("volume-type")
- vol_type = self.admin_volume_types_client.create_volume_type(
- name=vol_type_name)['volume_type']
- self.addCleanup(self.admin_volume_types_client.delete_volume_type,
- vol_type['id'])
- return vol_type
-
def _test_associate_qos(self, vol_type_id):
self.admin_volume_qos_client.associate_qos(
self.created_qos['id'], vol_type_id)
@@ -146,7 +138,7 @@
# create a test volume-type
vol_type = []
for _ in range(0, 3):
- vol_type.append(self._create_test_volume_type())
+ vol_type.append(self.create_volume_type())
# associate the qos-specs with volume-types
for i in range(0, 3):
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
new file mode 100644
index 0000000..fac71a8
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -0,0 +1,94 @@
+# 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.common import waiters
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+
+class VolumeTypesAccessV2Test(base.BaseVolumeAdminTest):
+
+ credentials = ['primary', 'alt', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(VolumeTypesAccessV2Test, cls).setup_clients()
+ cls.alt_client = cls.os_alt.volumes_client
+
+ @test.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
+ def test_volume_type_access_add(self):
+ # Creating a NON public volume type
+ params = {'os-volume-type-access:is_public': False}
+ volume_type = self.create_volume_type(**params)
+
+ # Try creating a volume from volume type in primary tenant
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
+ volume_type=volume_type['id'])
+
+ # Adding volume type access for primary tenant
+ self.admin_volume_types_client.add_type_access(
+ volume_type['id'], project=self.volumes_client.tenant_id)
+ self.addCleanup(self.admin_volume_types_client.remove_type_access,
+ volume_type['id'],
+ project=self.volumes_client.tenant_id)
+
+ # Creating a volume from primary tenant
+ volume = self.volumes_client.create_volume(
+ volume_type=volume_type['id'])['volume']
+ self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+
+ # Validating the created volume is based on the volume type
+ self.assertEqual(volume_type['name'], volume['volume_type'])
+
+ @test.idempotent_id('5220eb28-a435-43ce-baaf-ed46f0e95159')
+ def test_volume_type_access_list(self):
+ # Creating a NON public volume type
+ params = {'os-volume-type-access:is_public': False}
+ volume_type = self.create_volume_type(**params)
+
+ # Adding volume type access for primary tenant
+ self.admin_volume_types_client.add_type_access(
+ volume_type['id'], project=self.volumes_client.tenant_id)
+ self.addCleanup(self.admin_volume_types_client.remove_type_access,
+ volume_type['id'],
+ project=self.volumes_client.tenant_id)
+
+ # Adding volume type access for alt tenant
+ self.admin_volume_types_client.add_type_access(
+ volume_type['id'], project=self.alt_client.tenant_id)
+ self.addCleanup(self.admin_volume_types_client.remove_type_access,
+ volume_type['id'],
+ project=self.alt_client.tenant_id)
+
+ # List tenant access for the given volume type
+ type_access_list = self.admin_volume_types_client.list_type_access(
+ volume_type['id'])['volume_type_access']
+ volume_type_ids = [
+ vol_type['volume_type_id'] for vol_type in type_access_list
+ ]
+
+ # Validating volume type available for only two tenants
+ self.assertEqual(2, volume_type_ids.count(volume_type['id']))
+
+ # Validating the permitted tenants are the expected tenants
+ self.assertIn(self.volumes_client.tenant_id,
+ map(operator.itemgetter('project_id'), type_access_list))
+ self.assertIn(self.alt_client.tenant_id,
+ map(operator.itemgetter('project_id'), type_access_list))
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 9023037..27f6ccb 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -44,12 +44,10 @@
# Create two volume_types
for i in range(2):
vol_type_name = data_utils.rand_name("volume-type")
- vol_type = self.admin_volume_types_client.create_volume_type(
+ vol_type = self.create_volume_type(
name=vol_type_name,
- extra_specs=extra_specs)['volume_type']
+ extra_specs=extra_specs)
volume_types.append(vol_type)
- self.addCleanup(self.admin_volume_types_client.delete_volume_type,
- vol_type['id'])
params = {self.name_field: vol_name,
'volume_type': volume_types[0]['id']}
@@ -94,12 +92,10 @@
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
- body = self.admin_volume_types_client.create_volume_type(
+ body = self.create_volume_type(
name=name,
- extra_specs=extra_specs)['volume_type']
+ extra_specs=extra_specs)
self.assertIn('id', body)
- self.addCleanup(self.admin_volume_types_client.delete_volume_type,
- body['id'])
self.assertIn('name', body)
self.assertEqual(body['name'], name,
"The created volume_type name is not equal "
@@ -124,11 +120,7 @@
provider = "LuksEncryptor"
control_location = "front-end"
name = data_utils.rand_name("volume-type")
- body = self.admin_volume_types_client.create_volume_type(
- name=name)['volume_type']
- self.addCleanup(self.admin_volume_types_client.delete_volume_type,
- body['id'])
-
+ body = self.create_volume_type(name=name)
# Create encryption type
encryption_type = \
self.admin_volume_types_client.create_encryption_type(
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index b5c5d15..9e49b94 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -24,14 +24,7 @@
def resource_setup(cls):
super(VolumeTypesExtraSpecsV2Test, cls).resource_setup()
vol_type_name = data_utils.rand_name('Volume-type')
- cls.volume_type = \
- cls.admin_volume_types_client.create_volume_type(
- name=vol_type_name)['volume_type']
-
- @classmethod
- def resource_cleanup(cls):
- cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
- super(VolumeTypesExtraSpecsV2Test, cls).resource_cleanup()
+ cls.volume_type = cls.create_volume_type(name=vol_type_name)
@test.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
def test_volume_type_extra_specs_list(self):
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index c7c3e1a..2193aa6 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -26,14 +26,8 @@
super(ExtraSpecsNegativeV2Test, cls).resource_setup()
vol_type_name = data_utils.rand_name('Volume-type')
cls.extra_specs = {"spec1": "val1"}
- cls.volume_type = cls.admin_volume_types_client.create_volume_type(
- name=vol_type_name,
- extra_specs=cls.extra_specs)['volume_type']
-
- @classmethod
- def resource_cleanup(cls):
- cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
- super(ExtraSpecsNegativeV2Test, cls).resource_cleanup()
+ cls.volume_type = cls.create_volume_type(name=vol_type_name,
+ extra_specs=cls.extra_specs)
@test.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
def test_update_no_body(self):
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 66bab51..b144c7c 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -27,17 +27,17 @@
CONF = config.CONF
-class VolumesBackupsV2Test(base.BaseVolumeAdminTest):
+class VolumesBackupsAdminV2Test(base.BaseVolumeAdminTest):
@classmethod
def skip_checks(cls):
- super(VolumesBackupsV2Test, cls).skip_checks()
+ super(VolumesBackupsAdminV2Test, cls).skip_checks()
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
@classmethod
def resource_setup(cls):
- super(VolumesBackupsV2Test, cls).resource_setup()
+ super(VolumesBackupsAdminV2Test, cls).resource_setup()
cls.volume = cls.create_volume()
@@ -166,6 +166,23 @@
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('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 VolumesBackupsV1Test(VolumesBackupsV2Test):
+
+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 665036b..087b9a8 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -76,6 +76,7 @@
else:
cls.snapshots_client = cls.os.snapshots_v2_client
cls.volumes_client = cls.os.volumes_v2_client
+ cls.backups_client = cls.os.backups_v2_client
cls.volumes_extension_client = cls.os.volumes_v2_extension_client
cls.availability_zone_client = (
cls.os.volume_v2_availability_zone_client)
@@ -214,11 +215,13 @@
super(BaseVolumeAdminTest, cls).resource_setup()
cls.qos_specs = []
+ cls.volume_types = []
@classmethod
def resource_cleanup(cls):
cls.clear_qos_specs()
super(BaseVolumeAdminTest, cls).resource_cleanup()
+ cls.clear_volume_types()
@classmethod
def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
@@ -231,6 +234,15 @@
return qos_specs
@classmethod
+ def create_volume_type(cls, name=None, **kwargs):
+ """Create a test volume-type"""
+ name = name or data_utils.rand_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'])
+ return volume_type
+
+ @classmethod
def clear_qos_specs(cls):
for qos_id in cls.qos_specs:
test_utils.call_and_ignore_notfound_exc(
@@ -239,3 +251,17 @@
for qos_id in cls.qos_specs:
test_utils.call_and_ignore_notfound_exc(
cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
+
+ @classmethod
+ def clear_volume_types(cls):
+ for vol_type in cls.volume_types:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_types_client.delete_volume_type, vol_type)
+
+ for vol_type in cls.volume_types:
+ # Resource dictionary uses for is_resource_deleted method,
+ # to distinguish between volume-type to encryption-type.
+ resource = {'id': vol_type, 'type': 'volume-type'}
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_types_client.wait_for_resource_deletion,
+ resource)
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
new file mode 100644
index 0000000..87146db
--- /dev/null
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -0,0 +1,72 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+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
+
+CONF = config.CONF
+
+
+class VolumesBackupsV2Test(base.BaseVolumeTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesBackupsV2Test, cls).skip_checks()
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException("Cinder backup feature disabled")
+
+ @classmethod
+ def resource_setup(cls):
+ super(VolumesBackupsV2Test, cls).resource_setup()
+
+ cls.volume = cls.create_volume()
+
+ @test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
+ @test.services('compute')
+ def test_backup_create_attached_volume(self):
+ """Test backup create using force flag.
+
+ Cinder allows to create a volume backup, whether the volume status
+ is "available" or "in-use".
+ """
+ # Create a server
+ server_name = data_utils.rand_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'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume['id'], 'in-use')
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ self.volume['id'], 'available')
+ self.addCleanup(self.servers_client.detach_volume, server['id'],
+ self.volume['id'])
+ # Create backup using force flag
+ backup_name = data_utils.rand_name('Backup')
+ backup = self.backups_client.create_backup(
+ volume_id=self.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'],
+ 'available')
+ self.assertEqual(backup_name, backup['name'])
+
+
+class VolumesBackupsV1Test(VolumesBackupsV2Test):
+ _api_version = 1
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
new file mode 100644
index 0000000..f38a068
--- /dev/null
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -0,0 +1,44 @@
+# Copyright 2016 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest import config
+from tempest import test
+
+
+CONF = config.CONF
+
+
+class VolumesCloneTest(base.BaseVolumeTest):
+
+ @test.idempotent_id('9adae371-a257-43a5-9555-dc7c88e66e0e')
+ def test_create_from_volume(self):
+ # Creates a volume from another volume passing a size different from
+ # the source volume.
+ src_size = CONF.volume.volume_size
+
+ src_vol = self.create_volume(size=src_size)
+ # Destination volume bigger than source
+ dst_vol = self.create_volume(source_volid=src_vol['id'],
+ size=src_size + 1)
+
+ volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
+ # Should allow
+ self.assertEqual(volume['source_volid'], src_vol['id'])
+ self.assertEqual(int(volume['size']), src_size + 1)
+
+
+class VolumesV1CloneTest(VolumesCloneTest):
+ _api_version = 1
diff --git a/tempest/api/volume/test_volumes_clone_negative.py b/tempest/api/volume/test_volumes_clone_negative.py
new file mode 100644
index 0000000..ee51e00
--- /dev/null
+++ b/tempest/api/volume/test_volumes_clone_negative.py
@@ -0,0 +1,42 @@
+# Copyright 2016 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest import config
+from tempest.lib import exceptions
+from tempest import test
+
+
+CONF = config.CONF
+
+
+class VolumesCloneTest(base.BaseVolumeTest):
+
+ @test.idempotent_id('9adae371-a257-43a5-459a-dc7c88e66e0e')
+ def test_create_from_volume_decreasing_size(self):
+ # Creates a volume from another volume passing a size different from
+ # the source volume.
+ src_size = CONF.volume.volume_size + 1
+ src_vol = self.create_volume(size=src_size)
+
+ # Destination volume smaller than source
+ self.assertRaises(exceptions.BadRequest,
+ self.volumes_client.create_volume,
+ size=src_size - 1,
+ source_volid=src_vol['id'])
+
+
+class VolumesV1CloneTest(VolumesCloneTest):
+ _api_version = 1
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index a93025d..f7176f4 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:
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 0f7c4f6..c7f1e6e 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -178,16 +178,19 @@
@test.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
def test_volume_from_snapshot(self):
- # Create a temporary snap using wrapper method from base, then
- # create a snap based volume and deletes it
- snapshot = self.create_snapshot(self.volume_origin['id'])
- # NOTE(gfidente): size is required also when passing snapshot_id
- volume = self.volumes_client.create_volume(
- snapshot_id=snapshot['id'])['volume']
- waiters.wait_for_volume_status(self.volumes_client,
- volume['id'], 'available')
- self.delete_volume(self.volumes_client, volume['id'])
- self.cleanup_snapshot(snapshot)
+ # Creates a volume a snapshot passing a size different from the source
+ src_size = CONF.volume.volume_size
+
+ src_vol = self.create_volume(size=src_size)
+ src_snap = self.create_snapshot(src_vol['id'])
+ # Destination volume bigger than source snapshot
+ dst_vol = self.create_volume(snapshot_id=src_snap['id'],
+ size=src_size + 1)
+
+ volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
+ # Should allow
+ self.assertEqual(volume['snapshot_id'], src_snap['id'])
+ self.assertEqual(int(volume['size']), src_size + 1)
@test.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
def test_snapshot_list_param_limit(self):
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 374979c..2df9523 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -46,6 +46,20 @@
self.snapshots_client.create_snapshot,
volume_id=None, display_name=s_name)
+ @test.idempotent_id('677863d1-34f9-456d-b6ac-9924f667a7f4')
+ def test_volume_from_snapshot_decreasing_size(self):
+ # Creates a volume a snapshot passing a size different from the source
+ src_size = CONF.volume.volume_size + 1
+
+ src_vol = self.create_volume(size=src_size)
+ src_snap = self.create_snapshot(src_vol['id'])
+
+ # Destination volume smaller than source
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
+ size=src_size - 1,
+ snapshot_id=src_snap['id'])
+
class VolumesV1SnapshotNegativeTestJSON(VolumesV2SnapshotNegativeTestJSON):
_api_version = 1
diff --git a/tempest/clients.py b/tempest/clients.py
index b7bc4fa..fd010f2 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -20,14 +20,15 @@
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 manager
+from tempest import service_clients
from tempest.services import baremetal
from tempest.services import data_processing
-from tempest.services import database
from tempest.services import identity
-from tempest.services import image
from tempest.services import object_storage
from tempest.services import orchestration
from tempest.services import volume
@@ -36,18 +37,13 @@
LOG = logging.getLogger(__name__)
-class Manager(manager.Manager):
+class Manager(service_clients.ServiceClients):
"""Top level manager for OpenStack tempest clients"""
- 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 = config.service_client_config()
- # NOTE: Tempest uses timeout values of compute API if project specific
- # timeout values don't exist.
+ # TODO(andreaf) This is only used by data_processing and baremetal clients,
+ # and should be removed once they are out of Tempest
default_params_with_timeout_values = {
'build_interval': CONF.compute.build_interval,
'build_timeout': CONF.compute.build_timeout
@@ -62,9 +58,15 @@
:param service: Service name
:param scope: default scope for tokens produced by the auth provider
"""
- super(Manager, self).__init__(credentials=credentials, scope=scope)
+ _, identity_uri = get_auth_provider_class(credentials)
+ super(Manager, self).__init__(
+ credentials=credentials, identity_uri=identity_uri, scope=scope,
+ region=CONF.identity.region,
+ client_parameters=self._prepare_configuration())
+ # TODO(andreaf) When clients are initialised without the right
+ # parameters available, the calls below will trigger a KeyError.
+ # We should catch that and raise a better error.
self._set_compute_clients()
- self._set_database_clients()
self._set_identity_clients()
self._set_volume_clients()
self._set_object_storage_clients()
@@ -94,15 +96,38 @@
self.negative_client = negative_rest_client.NegativeRestClient(
self.auth_provider, service, **self.default_params)
+ def _prepare_configuration(self):
+ """Map values from CONF into Manager parameters
+
+ 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
+
+ configuration = {}
+
+ # Setup the parameters for all Tempest services.
+ # 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():
+ try:
+ # NOTE(andreaf) Use the unversioned service name to fetch
+ # the configuration since configuration is not versioned.
+ service_for_config = service.split('.')[0]
+ if service_for_config not in configuration:
+ configuration[service_for_config] = (
+ config.service_client_config(service_for_config))
+ except lib_exc.UnknownServiceClient:
+ LOG.warn(
+ 'Could not load configuration for service %s' % service)
+
+ return configuration
+
def _set_network_clients(self):
- params = {
- 'service': CONF.network.catalog_type,
- 'region': CONF.network.region or CONF.identity.region,
- 'endpoint_type': CONF.network.endpoint_type,
- 'build_interval': CONF.network.build_interval,
- 'build_timeout': CONF.network.build_timeout
- }
- params.update(self.default_params)
+ params = self.parameters['network']
self.network_agents_client = network.AgentsClient(
self.auth_provider, **params)
self.network_extensions_client = network.ExtensionsClient(
@@ -133,20 +158,13 @@
self.auth_provider, **params)
def _set_image_clients(self):
- params = {
- 'service': CONF.image.catalog_type,
- 'region': CONF.image.region or CONF.identity.region,
- 'endpoint_type': CONF.image.endpoint_type,
- 'build_interval': CONF.image.build_interval,
- 'build_timeout': CONF.image.build_timeout
- }
- params.update(self.default_params)
-
if CONF.service_available.glance:
+ 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(
@@ -159,14 +177,7 @@
self.auth_provider, **params)
def _set_compute_clients(self):
- 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
- }
- params.update(self.default_params)
+ params = self.parameters['compute']
self.agents_client = compute.AgentsClient(self.auth_provider, **params)
self.compute_networks_client = compute.NetworksClient(
@@ -232,10 +243,11 @@
# NOTE: The following client needs special timeout values because
# the API is a proxy for the other component.
params_volume = copy.deepcopy(params)
- params_volume.update({
- 'build_interval': CONF.volume.build_interval,
- 'build_timeout': CONF.volume.build_timeout
- })
+ # Optional parameters
+ 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(
@@ -243,32 +255,11 @@
self.snapshots_extensions_client = compute.SnapshotsClient(
self.auth_provider, **params_volume)
- def _set_database_clients(self):
- self.database_flavors_client = database.DatabaseFlavorsClient(
- self.auth_provider,
- CONF.database.catalog_type,
- CONF.identity.region,
- **self.default_params_with_timeout_values)
- self.database_limits_client = database.DatabaseLimitsClient(
- self.auth_provider,
- CONF.database.catalog_type,
- CONF.identity.region,
- **self.default_params_with_timeout_values)
- self.database_versions_client = database.DatabaseVersionsClient(
- self.auth_provider,
- CONF.database.catalog_type,
- CONF.identity.region,
- **self.default_params_with_timeout_values)
-
def _set_identity_clients(self):
- params = {
- 'service': CONF.identity.catalog_type,
- 'region': CONF.identity.region
- }
- params.update(self.default_params_with_timeout_values)
+ params = self.parameters['identity']
# Clients below use the admin endpoint type of Keystone API v2
- params_v2_admin = params.copy()
+ params_v2_admin = copy.copy(params)
params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
self.endpoints_client = identity.v2.EndpointsClient(self.auth_provider,
**params_v2_admin)
@@ -284,7 +275,7 @@
self.auth_provider, **params_v2_admin)
# Clients below use the public endpoint type of Keystone API v2
- params_v2_public = params.copy()
+ params_v2_public = copy.copy(params)
params_v2_public['endpoint_type'] = (
CONF.identity.v2_public_endpoint_type)
self.identity_public_client = identity.v2.IdentityClient(
@@ -294,8 +285,9 @@
self.users_public_client = identity.v2.UsersClient(
self.auth_provider, **params_v2_public)
- # Clients below use the endpoint type of Keystone API v3
- params_v3 = params.copy()
+ # Clients below use the endpoint type of Keystone API v3, which is set
+ # in endpoint_type
+ params_v3 = copy.copy(params)
params_v3['endpoint_type'] = CONF.identity.v3_endpoint_type
self.domains_client = identity.v3.DomainsClient(self.auth_provider,
**params_v3)
@@ -341,14 +333,8 @@
raise exceptions.InvalidConfiguration(msg)
def _set_volume_clients(self):
- params = {
- 'service': CONF.volume.catalog_type,
- 'region': 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
- }
- params.update(self.default_params)
+ # Mandatory parameters (always defined)
+ params = self.parameters['volume']
self.volume_qos_client = volume.v1.QosSpecsClient(self.auth_provider,
**params)
@@ -396,12 +382,8 @@
volume.v2.AvailabilityZoneClient(self.auth_provider, **params)
def _set_object_storage_clients(self):
- params = {
- 'service': CONF.object_storage.catalog_type,
- 'region': CONF.object_storage.region or CONF.identity.region,
- 'endpoint_type': CONF.object_storage.endpoint_type
- }
- params.update(self.default_params_with_timeout_values)
+ # Mandatory parameters (always defined)
+ params = self.parameters['object-storage']
self.account_client = object_storage.AccountClient(self.auth_provider,
**params)
@@ -409,3 +391,26 @@
self.auth_provider, **params)
self.object_client = object_storage.ObjectClient(self.auth_provider,
**params)
+
+
+def get_auth_provider_class(credentials):
+ if isinstance(credentials, auth.KeystoneV3Credentials):
+ return auth.KeystoneV3AuthProvider, CONF.identity.uri_v3
+ else:
+ return auth.KeystoneV2AuthProvider, CONF.identity.uri
+
+
+def get_auth_provider(credentials, pre_auth=False, scope='project'):
+ # kwargs for auth provider match the common ones used by service clients
+ default_params = config.service_client_config()
+ if credentials is None:
+ raise exceptions.InvalidCredentials(
+ 'Credentials must be specified')
+ auth_provider_class, auth_url = get_auth_provider_class(
+ credentials)
+ _auth_provider = auth_provider_class(credentials, auth_url,
+ scope=scope,
+ **default_params)
+ if pre_auth:
+ _auth_provider.set_auth()
+ return _auth_provider
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 80de6f5..af86fe3 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -106,7 +106,7 @@
self._load_json()
def _cleanup(self):
- print ("Begin cleanup")
+ print("Begin cleanup")
is_dry_run = self.options.dry_run
is_preserve = not self.options.delete_tempest_conf_objects
is_save_state = False
@@ -124,7 +124,7 @@
'is_save_state': is_save_state}
tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
tenants = tenant_service.list()
- print ("Process %s tenants" % len(tenants))
+ print("Process %s tenants" % len(tenants))
# Loop through list of tenants and clean them up.
for tenant in tenants:
@@ -155,7 +155,7 @@
self._remove_admin_role(tenant_id)
def _clean_tenant(self, tenant):
- print ("Cleaning tenant: %s " % tenant['name'])
+ print("Cleaning tenant: %s " % tenant['name'])
is_dry_run = self.options.dry_run
dry_run_data = self.dry_run_data
is_preserve = not self.options.delete_tempest_conf_objects
@@ -266,7 +266,7 @@
return False
def _init_state(self):
- print ("Initializing saved state.")
+ print("Initializing saved state.")
data = {}
admin_mgr = self.admin_mgr
kwargs = {'data': data,
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 8d2cfdc..426571f 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,
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 08ad94f..a9e5167 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -126,15 +126,15 @@
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.identity.v2.json import roles_client
-from tempest.services.identity.v2.json import tenants_client
-from tempest.services.identity.v2.json import users_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
from tempest.services.volume.v1.json import volumes_client
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
index 5f6b3e6..36e45a5 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -19,13 +19,10 @@
"""
from cliff import command
-from oslo_log import log as logging
import prettytable
from tempest.test_discover.plugins import TempestTestPluginManager
-LOG = logging.getLogger(__name__)
-
class TempestListPlugins(command.Command):
def take_action(self, parsed_args):
diff --git a/tempest/cmd/main.py b/tempest/cmd/main.py
index acd97a8..641d11c 100644
--- a/tempest/cmd/main.py
+++ b/tempest/cmd/main.py
@@ -26,7 +26,7 @@
def __init__(self):
super(Main, self).__init__(
description='Tempest cli application',
- version=version.VersionInfo('tempest').version_string(),
+ version=version.VersionInfo('tempest').version_string_with_vcs(),
command_manager=commandmanager.CommandManager('tempest.cm'),
deferred_help=True,
)
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 26bd418..1c0d9c4 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -23,6 +23,27 @@
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
+let you pass a filepath to tempest run with the file format being a line
+separated regex, with '#' used to signify the start of a comment on a line.
+For example::
+
+ # Regex file
+ ^regex1 # Match these tests
+ .*regex2 # Match those tests
+
+The blacklist file will be used to construct a negative lookahead regex and
+the whitelist file will simply OR all the regexes in the file. The whitelist
+and blacklist file options are mutually exclusive so you can't use them
+together. However, you can combine either with a normal regex or the *--smoke*
+flag. When used with a blacklist file the generated regex will be combined to
+something like::
+
+ ^((?!black_regex1|black_regex2).)*$cli_regex1
+
+When combined with a whitelist file all the regexes from the file and the CLI
+regexes will be ORed.
+
You can also use the **--list-tests** option in conjunction with selection
arguments to list which tests will be run.
@@ -33,6 +54,24 @@
If you want to adjust the number of workers use the **--concurrency** option
and if you want to run tests serially use **--serial**
+Running with Workspaces
+-----------------------
+Tempest run enables you to run your tempest tests from any setup tempest
+workspace it relies on you having setup a tempest workspace with either the
+``tempest init`` or ``tempest workspace`` commands. Then using the
+``--workspace`` CLI option you can specify which one of your workspaces you
+want to run tempest from. Using this option you don't have to run Tempest
+directly with you current working directory being the workspace, Tempest will
+take care of managing everything to be executed from there.
+
+Running from Anywhere
+---------------------
+Tempest run provides you with an option to execute tempest from anywhere on
+your system. You are required to provide a config file in this case with the
+``--config-file`` option. When run tempest will create a .testrepository
+directory and a .testr.conf file in your current working directory. This way
+you can use testr commands directly to inspect the state of the previous run.
+
Test Output
===========
By default tempest run's output to STDOUT will be generated using the
@@ -47,20 +86,23 @@
import threading
from cliff import command
+from os_testr import regex_builder
from os_testr import subunit_trace
-from oslo_log import log as logging
from testrepository.commands import run_argv
+from tempest.cmd import init
+from tempest.cmd import workspace
from tempest import config
-LOG = logging.getLogger(__name__)
CONF = config.CONF
class TempestRun(command.Command):
- def _set_env(self):
+ def _set_env(self, config_file=None):
+ if config_file:
+ CONF.set_config_path(os.path.abspath(config_file))
# NOTE(mtreinish): This is needed so that testr doesn't gobble up any
# stacktraces on failure.
if 'TESTR_PDB' in os.environ:
@@ -68,18 +110,45 @@
else:
os.environ["TESTR_PDB"] = ""
+ def _create_testrepository(self):
+ if not os.path.isdir('.testrepository'):
+ returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
+ sys.stderr)
+ if returncode:
+ sys.exit(returncode)
+
+ def _create_testr_conf(self):
+ top_level_path = os.path.dirname(os.path.dirname(__file__))
+ discover_path = os.path.join(top_level_path, 'test_discover')
+ file_contents = init.TESTR_CONF % (top_level_path, discover_path)
+ with open('.testr.conf', 'w+') as testr_conf_file:
+ testr_conf_file.write(file_contents)
+
def take_action(self, parsed_args):
- self._set_env()
returncode = 0
+ if parsed_args.config_file:
+ self._set_env(parsed_args.config_file)
+ else:
+ self._set_env()
+ # Workspace execution mode
+ if parsed_args.workspace:
+ workspace_mgr = workspace.WorkspaceManager(
+ parsed_args.workspace_path)
+ path = workspace_mgr.get_workspace(parsed_args.workspace)
+ os.chdir(path)
+ # NOTE(mtreinish): tempest init should create a .testrepository dir
+ # but since workspaces can be imported let's sanity check and
+ # ensure that one is created
+ self._create_testrepository()
# Local execution mode
- if os.path.isfile('.testr.conf'):
+ elif os.path.isfile('.testr.conf'):
# If you're running in local execution mode and there is not a
# testrepository dir create one
- if not os.path.isdir('.testrepository'):
- returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
- sys.stderr)
- if returncode:
- sys.exit(returncode)
+ self._create_testrepository()
+ # local execution with config file mode
+ elif parsed_args.config_file:
+ self._create_testr_conf()
+ self._create_testrepository()
else:
print("No .testr.conf file was found for local execution")
sys.exit(2)
@@ -102,6 +171,18 @@
return parser
def _add_args(self, parser):
+ # workspace args
+ parser.add_argument('--workspace', default=None,
+ help='Name of tempest workspace to use for running'
+ ' tests. You can see a list of workspaces '
+ 'with tempest workspace list')
+ parser.add_argument('--workspace-path', default=None,
+ dest='workspace_path',
+ help="The path to the workspace file, the default "
+ "is ~/.tempest/workspace.yaml")
+ # Configuration flags
+ parser.add_argument('--config-file', default=None, dest='config_file',
+ help='Configuration file to run tempest with')
# test selection args
regex = parser.add_mutually_exclusive_group()
regex.add_argument('--smoke', action='store_true',
@@ -109,11 +190,20 @@
regex.add_argument('--regex', '-r', default='',
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',
+ help="Path to a whitelist file, this file "
+ "contains a separate regex on each "
+ "newline.")
+ list_selector.add_argument('--blacklist_file',
+ help='Path to a blacklist file, this file '
+ 'contains a separate regex exclude on '
+ 'each newline')
# list only args
parser.add_argument('--list-tests', '-l', action='store_true',
help='List tests',
default=False)
- # exectution args
+ # execution args
parser.add_argument('--concurrency', '-w',
help="The number of workers to use, defaults to "
"the number of cpus")
@@ -138,6 +228,10 @@
regex = 'smoke'
elif parsed_args.regex:
regex = parsed_args.regex
+ if parsed_args.whitelist_file or parsed_args.blacklist_file:
+ regex = regex_builder.construct_regex(parsed_args.blacklist_file,
+ parsed_args.whitelist_file,
+ regex, False)
return regex
def _build_options(self, parsed_args):
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 535fe71..0f868a9 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -47,6 +47,7 @@
Ports file JSON structure
^^^^^^^^^^^^^^^^^^^^^^^^^
+::
{
"<port number>": "<name of service>",
@@ -56,6 +57,8 @@
Output file JSON structure
^^^^^^^^^^^^^^^^^^^^^^^^^^
+::
+
{
"full_test_name[with_id_and_tags]": [
{
@@ -241,7 +244,7 @@
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>",
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 4b12ecb..77b88f9 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -285,7 +285,6 @@
'data_processing': 'sahara',
'baremetal': 'ironic',
'identity': 'keystone',
- 'database': 'trove'
}
# Get catalog list for endpoints to use for validation
_token, auth_data = os.auth_provider.get_auth()
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index cc82284..b36cf4e 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -53,13 +53,11 @@
from cliff import command
from oslo_concurrency import lockutils
-from oslo_log import log as logging
import prettytable
import yaml
from tempest import config
-LOG = logging.getLogger(__name__)
CONF = config.CONF
@@ -185,25 +183,35 @@
subparsers = parser.add_subparsers()
- list_parser = subparsers.add_parser('list')
+ list_parser = subparsers.add_parser(
+ 'list', help='Outputs the name and path of all known tempest '
+ 'workspaces')
list_parser.set_defaults(list=True)
- register_parser = subparsers.add_parser('register')
+ register_parser = subparsers.add_parser(
+ 'register', help='Registers a new tempest workspace via a given '
+ '--name and --path')
register_parser.add_argument('--name', required=True)
register_parser.add_argument('--path', required=True)
register_parser.set_defaults(register=True)
- update_parser = subparsers.add_parser('rename')
+ update_parser = subparsers.add_parser(
+ 'rename', help='Renames a tempest workspace from --old-name to '
+ '--new-name')
update_parser.add_argument('--old-name', required=True)
update_parser.add_argument('--new-name', required=True)
update_parser.set_defaults(rename=True)
- move_parser = subparsers.add_parser('move')
+ move_parser = subparsers.add_parser(
+ 'move', help='Changes the path of a given tempest workspace '
+ '--name to --path')
move_parser.add_argument('--name', required=True)
move_parser.add_argument('--path', required=True)
move_parser.set_defaults(move=True)
- remove_parser = subparsers.add_parser('remove')
+ remove_parser = subparsers.add_parser(
+ 'remove', help='Deletes the entry for a given tempest workspace '
+ '--name')
remove_parser.add_argument('--name', required=True)
remove_parser.set_defaults(remove=True)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 7ebc283..a2edcdc 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -30,7 +30,8 @@
def create_test_server(clients, validatable=False, validation_resources=None,
tenant_network=None, wait_until=None,
volume_backed=False, name=None, flavor=None,
- image_id=None, **kwargs):
+ image_id=None, delete_vol_on_termination=True,
+ **kwargs):
"""Common wrapper utility returning a test server.
This method is a common wrapper returning a test server that can be
@@ -39,11 +40,20 @@
:param clients: Client manager which provides OpenStack Tempest clients.
:param validatable: Whether the server will be pingable or sshable.
:param validation_resources: Resources created for the connection to the
- server. Include a keypair, a security group and an IP.
+ server. Include a keypair, a security group and an IP.
:param tenant_network: Tenant network to be used for creating a server.
:param wait_until: Server status to wait for the server to reach after
- its creation.
+ its creation.
:param volume_backed: Whether the instance is volume backed or not.
+ :param name: Name of the server to be provisioned. If not defined a random
+ string ending with '-instance' will be generated.
+ :param flavor: Flavor of the server to be provisioned. If not defined,
+ CONF.compute.flavor_ref will be used instead.
+ :param image_id: ID of the image to be used to provision the server. If not
+ defined, CONF.compute.image_ref will be used instead.
+ :param delete_vol_on_termination: Controls whether the backing volume
+ should be deleted when the server is deleted. Only applies to volume
+ backed servers.
:returns: a tuple
"""
@@ -106,7 +116,7 @@
'source_type': 'volume',
'destination_type': 'volume',
'boot_index': 0,
- 'delete_on_termination': True}]
+ 'delete_on_termination': delete_vol_on_termination}]
kwargs['block_device_mapping_v2'] = bd_map_v2
# Since this is boot from volume an image does not need
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index bf6c537..1b450ab 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -94,7 +94,7 @@
self.router)
def set_resources(self, **kwargs):
- for key in kwargs.keys():
+ for key in kwargs:
if hasattr(self, key):
setattr(self, key, kwargs[key])
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index b0c01f5..c9b9db1 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -159,7 +159,10 @@
# it must beassigned a role on the project. So we need to ensure that
# our newly created user has a role on the newly created project.
if self.identity_version == 'v3' and not role_assigned:
- self.creds_client.create_user_role('Member')
+ try:
+ self.creds_client.create_user_role('Member')
+ except lib_exc.Conflict:
+ LOG.warning('Member role already exists, ignoring conflict.')
self.creds_client.assign_user_role(user, project, 'Member')
creds = self.creds_client.get_credentials(user, project, user_password)
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index 5f0685e..f57c18a 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -14,7 +14,7 @@
from oslo_log import log as logging
from tempest import exceptions
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
LOG = logging.getLogger(__name__)
@@ -31,7 +31,7 @@
list returns a 404, there are no found networks, or the found network
is invalid
"""
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if not name:
raise exceptions.InvalidTestResource(type='network', name=name)
@@ -84,7 +84,7 @@
tenant network is available in the creds provider
:returns: a dict with 'id' and 'name' of the network
"""
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
net_creds = creds_provider.get_primary_creds()
network = getattr(net_creds, 'network', None)
if not network or not network.get('name'):
diff --git a/tempest/common/image.py b/tempest/common/image.py
index 42ce5ac..95a7d1a 100644
--- a/tempest/common/image.py
+++ b/tempest/common/image.py
@@ -13,6 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
+import six
+
def get_image_meta_from_headers(resp):
meta = {'properties': {}}
@@ -36,3 +40,28 @@
except ValueError:
pass
return meta
+
+
+def image_meta_to_headers(**metadata):
+ headers = {}
+ fields_copy = copy.deepcopy(metadata)
+
+ copy_from = fields_copy.pop('copy_from', None)
+ purge = fields_copy.pop('purge_props', None)
+
+ if purge is not None:
+ headers['x-glance-registry-purge-props'] = purge
+
+ if copy_from is not None:
+ headers['x-glance-api-copy-from'] = copy_from
+
+ for key, value in six.iteritems(fields_copy.pop('properties', {})):
+ headers['x-image-meta-property-%s' % key] = str(value)
+
+ for key, value in six.iteritems(fields_copy.pop('api', {})):
+ headers['x-glance-api-property-%s' % key] = str(value)
+
+ for key, value in six.iteritems(fields_copy):
+ headers['x-image-meta-%s' % key] = str(value)
+
+ return headers
diff --git a/tempest/common/utils/net_utils.py b/tempest/common/utils/net_utils.py
index d98fb32..fd0391d 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
@@ -39,10 +38,11 @@
alloc_set.add(fixed_ip['ip_address'])
av_set = subnet_set - alloc_set
- ip_list = [str(ip) for ip in itertools.islice(av_set, count)]
-
- if len(ip_list) != count:
- msg = "Insufficient IP addresses available"
- raise lib_exc.BadRequest(message=msg)
-
- return ip_list
+ 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 d8dad69..df08e30 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -18,9 +18,9 @@
from tempest.common import image as common_image
from tempest import config
from tempest import exceptions
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
-from tempest.services.image.v1.json import images_client as images_v1_client
+from tempest.lib.services.image.v1 import images_client as images_v1_client
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -91,7 +91,7 @@
'timeout': timeout})
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
@@ -162,7 +162,7 @@
'status': status,
'current_status': current_status,
'timeout': client.build_timeout})
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
@@ -235,7 +235,7 @@
'status': status,
'timeout': client.build_timeout})
message += ' Current state of %s: %s.' % (attr, status_curr)
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index b3d409f..0c2b913 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -25,6 +25,7 @@
from oslo_log import log as logging
import testtools
+from tempest.lib import exceptions
from tempest.test_discover import plugins
@@ -191,7 +192,12 @@
help="A list of enabled identity extensions with a special "
"entry all which indicates every extension is enabled. "
"Empty list indicates all extensions are disabled. "
- "To get the list of extensions run: 'keystone discover'")
+ "To get the list of extensions run: 'keystone discover'"),
+ # TODO(rodrigods): Remove the reseller flag when Kilo and Liberty is end
+ # of life.
+ cfg.BoolOpt('reseller',
+ default=False,
+ help='Does the environment support reseller?')
]
compute_group = cfg.OptGroup(name='compute',
@@ -366,7 +372,7 @@
default=True,
help='Does the test environment have the nova cert running?'),
cfg.BoolOpt('personality',
- default=True,
+ default=False,
help='Does the test environment support server personality'),
cfg.BoolOpt('attach_encrypted_volume',
default=True,
@@ -811,21 +817,6 @@
help="Execute discoverability tests"),
]
-database_group = cfg.OptGroup(name='database',
- title='Database Service Options')
-
-DatabaseGroup = [
- cfg.StrOpt('catalog_type',
- default='database',
- help="Catalog type of the Database service."),
- cfg.StrOpt('db_flavor_ref',
- default="1",
- help="Valid primary flavor to use in database tests."),
- cfg.StrOpt('db_current_version',
- default="v1.0",
- help="Current database version to use in database tests."),
-]
-
orchestration_group = cfg.OptGroup(name='orchestration',
title='Orchestration Service Options')
@@ -1002,9 +993,6 @@
cfg.BoolOpt('ironic',
default=False,
help="Whether or not Ironic is expected to be available"),
- cfg.BoolOpt('trove',
- default=False,
- help="Whether or not Trove is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1141,7 +1129,6 @@
(volume_feature_group, VolumeFeaturesGroup),
(object_storage_group, ObjectStoreGroup),
(object_storage_feature_group, ObjectStoreFeaturesGroup),
- (database_group, DatabaseGroup),
(orchestration_group, OrchestrationGroup),
(data_processing_group, DataProcessingGroup),
(data_processing_feature_group, DataProcessingFeaturesGroup),
@@ -1208,7 +1195,6 @@
self.object_storage = _CONF['object-storage']
self.object_storage_feature_enabled = _CONF[
'object-storage-feature-enabled']
- self.database = _CONF.database
self.orchestration = _CONF.orchestration
self.data_processing = _CONF['data-processing']
self.data_processing_feature_enabled = _CONF[
@@ -1365,3 +1351,84 @@
return f(self, *func_args, **func_kwargs)
return wrapper
return decorator
+
+
+def service_client_config(service_client_name=None):
+ """Return a dict with the parameters to init service clients
+
+ Extracts from CONF the settings specific to the service_client_name and
+ api_version, and formats them as dict ready to be passed to the service
+ clients __init__:
+
+ * `region` (default to identity)
+ * `catalog_type`
+ * `endpoint_type`
+ * `build_timeout` (object-storage and identity default to compute)
+ * `build_interval` (object-storage and identity default to compute)
+
+ The following common settings are always returned, even if
+ `service_client_name` is None:
+
+ * `disable_ssl_certificate_validation`
+ * `ca_certs`
+ * `trace_requests`
+
+ The dict returned by this does not fit a few service clients:
+
+ * The endpoint type is not returned for identity client, since it takes
+ three different values for v2 admin, v2 public and v3
+ * The `ServersClient` from compute accepts an optional
+ `enable_instance_password` parameter, which is not returned.
+ * The `VolumesClient` for both v1 and v2 volume accept an optional
+ `default_volume_size` parameter, which is not returned.
+ * The `TokenClient` and `V3TokenClient` have a very different
+ interface, only auth_url is needed for them.
+
+ :param service_client_name: str Name of the service. Supported values are
+ 'compute', 'identity', 'image', 'network', 'object-storage', 'volume'
+ :return: dictionary of __init__ parameters for the service clients
+ :rtype: dict
+ """
+ _parameters = {
+ 'disable_ssl_certificate_validation':
+ CONF.identity.disable_ssl_certificate_validation,
+ 'ca_certs': CONF.identity.ca_certificates_file,
+ 'trace_requests': CONF.debug.trace_requests
+ }
+
+ if service_client_name is None:
+ return _parameters
+
+ # Get the group of options first, by normalising the service_group_name
+ # Services with a '-' in the name have an '_' in the option group name
+ config_group = service_client_name.replace('-', '_')
+ # NOTE(andreaf) Check if the config group exists. This allows for this
+ # helper to be used for settings from registered plugins as well
+ try:
+ options = getattr(CONF, config_group)
+ except cfg.NoSuchOptError:
+ # Option group not defined
+ raise exceptions.UnknownServiceClient(services=service_client_name)
+ # Set endpoint_type
+ # Identity uses different settings depending on API version, so do not
+ # return the endpoint at all.
+ if service_client_name != 'identity':
+ _parameters['endpoint_type'] = getattr(options, 'endpoint_type')
+ # Set build_*
+ # Object storage and identity groups do not have conf settings for
+ # build_* parameters, and we default to compute in any case
+ for setting in ['build_timeout', 'build_interval']:
+ if not hasattr(options, setting) or not getattr(options, setting):
+ _parameters[setting] = getattr(CONF.compute, setting)
+ else:
+ _parameters[setting] = getattr(options, setting)
+ # Set region
+ # If a service client does not define region or region is not set
+ # default to the identity region
+ if not hasattr(options, 'region') or not getattr(options, 'region'):
+ _parameters['region'] = CONF.identity.region
+ else:
+ _parameters['region'] = getattr(options, 'region')
+ # Set service
+ _parameters['service'] = getattr(options, 'catalog_type')
+ return _parameters
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index f534f30..67faad5 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -70,7 +70,7 @@
# 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/hacking/checks.py b/tempest/hacking/checks.py
index 09106d1..e2d6585 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -19,7 +19,7 @@
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
- 'trove', 'ironic', 'savanna', 'heat', 'sahara']
+ 'ironic', 'savanna', 'heat', 'sahara']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 3289f04..44497db 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -174,7 +174,16 @@
'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
- 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ },
+ },
'config_drive': {'type': 'string'}
})
server_detail['properties']['addresses']['patternProperties'][
diff --git a/tempest/api/database/__init__.py b/tempest/lib/api_schema/response/compute/v2_16/__init__.py
similarity index 100%
rename from tempest/api/database/__init__.py
rename to tempest/lib/api_schema/response/compute/v2_16/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
new file mode 100644
index 0000000..6868110
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -0,0 +1,160 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_9 import servers
+
+# Compute microversion 2.16:
+# 1. New attributes in 'server' dict.
+# 'host_status'
+
+server_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6,
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'config_drive': {'type': 'string'},
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'delete_on_termination': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ },
+ },
+ 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']},
+ 'locked': {'type': 'boolean'},
+ # NOTE(gmann): new attributes in version 2.16
+ 'host_status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index 883839e..05cc32c 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -15,15 +15,18 @@
import copy
from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21
-from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
+from tempest.lib.api_schema.response.compute.v2_16 import servers \
+ as serversv216
-get_server = copy.deepcopy(serversv29.get_server)
+list_servers = copy.deepcopy(serversv216.list_servers)
+
+get_server = copy.deepcopy(serversv216.get_server)
get_server['response_body']['properties']['server'][
'properties'].update({'description': {'type': ['string', 'null']}})
get_server['response_body']['properties']['server'][
'required'].append('description')
-list_servers_detail = copy.deepcopy(serversv29.list_servers_detail)
+list_servers_detail = copy.deepcopy(serversv216.list_servers_detail)
list_servers_detail['response_body']['properties']['servers']['items'][
'properties'].update({'description': {'type': ['string', 'null']}})
list_servers_detail['response_body']['properties']['servers']['items'][
diff --git a/tempest/api/database/__init__.py b/tempest/lib/api_schema/response/compute/v2_23/__init__.py
similarity index 100%
copy from tempest/api/database/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_23/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_23/migrations.py b/tempest/lib/api_schema/response/compute/v2_23/migrations.py
new file mode 100644
index 0000000..3cd0f6e
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_23/migrations.py
@@ -0,0 +1,62 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+# Compute microversion 2.23:
+# New attributes in 'migrations' list.
+# 'migration_type'
+# 'links'
+
+list_migrations = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'migrations': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'integer'},
+ 'status': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'source_node': {'type': ['string', 'null']},
+ 'source_compute': {'type': ['string', 'null']},
+ 'dest_node': {'type': ['string', 'null']},
+ 'dest_compute': {'type': ['string', 'null']},
+ 'dest_host': {'type': ['string', 'null']},
+ 'old_instance_type_id': {'type': ['integer', 'null']},
+ 'new_instance_type_id': {'type': ['integer', 'null']},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ # New attributes in version 2.23
+ 'migration_type': {'type': ['string', 'null']},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'id', 'status', 'instance_uuid', 'source_node',
+ 'source_compute', 'dest_node', 'dest_compute',
+ 'dest_host', 'old_instance_type_id',
+ 'new_instance_type_id', 'created_at', 'updated_at',
+ 'migration_type'
+ ]
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['migrations']
+ }
+}
diff --git a/tempest/api/database/__init__.py b/tempest/lib/api_schema/response/compute/v2_3/__init__.py
similarity index 100%
copy from tempest/api/database/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_3/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
new file mode 100644
index 0000000..ee16333
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -0,0 +1,166 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_1 import servers
+
+# Compute microversion 2.3:
+# 1. New attributes in 'os-extended-volumes:volumes_attached' dict.
+# 'delete_on_termination'
+# 2. New attributes in 'server' dict.
+# 'OS-EXT-SRV-ATTR:reservation_id'
+# 'OS-EXT-SRV-ATTR:launch_index'
+# 'OS-EXT-SRV-ATTR:kernel_id'
+# 'OS-EXT-SRV-ATTR:ramdisk_id'
+# 'OS-EXT-SRV-ATTR:hostname'
+# 'OS-EXT-SRV-ATTR:root_device_name'
+# 'OS-EXT-SRV-ATTR:user_data'
+
+server_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6,
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'config_drive': {'type': 'string'},
+ # NOTE(gmann): new attributes in version 2.3
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'delete_on_termination': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ },
+ },
+ 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index e9b7249..470190c 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,7 +14,9 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers
+from tempest.lib.api_schema.response.compute.v2_3 import servers
+
+list_servers = copy.deepcopy(servers.list_servers)
get_server = copy.deepcopy(servers.get_server)
get_server['response_body']['properties']['server'][
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 425758f..54a7002 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -665,7 +665,7 @@
msg = ('Cannot have conflicting values for %s and %s' %
(key1, key2))
raise exceptions.InvalidCredentials(msg)
- for key in attr.keys():
+ for key in attr:
if key in self.ATTRIBUTES:
setattr(self, key, attr[key])
else:
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index 227ac37..f687343 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -13,14 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import os
import fixtures
import testtools
-LOG = logging.getLogger(__name__)
-
class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase):
setUpClassCalled = False
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
index f35b14c..d95aa46 100755
--- a/tempest/lib/cmd/skip_tracker.py
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -103,7 +103,7 @@
def get_results(result_dict):
results = []
- for bug_no in result_dict.keys():
+ for bug_no in result_dict:
for method in result_dict[bug_no]:
results.append((method, bug_no))
return results
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 7fcec8c..93382c0 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))
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 2a6a788..5ca78f9 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -225,3 +225,7 @@
message = ("Command '%(command)s', exit status: %(exit_status)d, "
"stderr:\n%(stderr)s\n"
"stdout:\n%(stdout)s")
+
+
+class UnknownServiceClient(TempestException):
+ message = "Service clients named %(services)s are not known"
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
index ae747d8..7ad14bc 100644
--- a/tempest/lib/services/compute/aggregates_client.py
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -41,7 +41,7 @@
"""Create a new aggregate.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#createaggregate
+ api-ref-compute-v2.1.html#createAggregate
"""
post_body = json.dumps({'aggregate': kwargs})
resp, body = self.post('os-aggregates', post_body)
@@ -54,7 +54,7 @@
"""Update an aggregate.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#updateaggregate
+ api-ref-compute-v2.1.html#updateAggregate
"""
put_body = json.dumps({'aggregate': kwargs})
resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body)
@@ -85,7 +85,7 @@
"""Add a host to the given aggregate.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#addhost
+ api-ref-compute-v2.1.html#addHost
"""
post_body = json.dumps({'add_host': kwargs})
resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
@@ -98,7 +98,7 @@
"""Remove a host from the given aggregate.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#removehost
+ api-ref-compute-v2.1.html#removeAggregateHost
"""
post_body = json.dumps({'remove_host': kwargs})
resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 0d80a82..5be8272 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -91,7 +91,7 @@
"""Set extra Specs to the mentioned flavor.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#updateFlavorExtraSpec
+ api-ref-compute-v2.1.html#createFlavorExtraSpec
"""
post_body = json.dumps({'extra_specs': kwargs})
resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
@@ -123,7 +123,7 @@
"""Update specified extra Specs of the mentioned flavor and key.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#updateflavorspec
+ api-ref-compute-v2.1.html#updateFlavorExtraSpec
"""
resp, body = self.put('flavors/%s/os-extra_specs/%s' %
(flavor_id, key), json.dumps(kwargs))
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 5eae8aa..c3bdba7 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -16,17 +16,22 @@
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.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#returnmigrations
+ api-ref-compute-v2.1.html#listMigrations
"""
url = 'os-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/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index 184a3d7..6d41f4b 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -46,7 +46,7 @@
"""Updates the tenant's quota limits for one or more resources.
Available params: see http://developer.openstack.org/
- api-ref-compute-v2.1.html#updatesquotatenant
+ api-ref-compute-v2.1.html#updateQuota
"""
post_body = json.dumps({'quota_set': kwargs})
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 0d31ac7..9444e20 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -20,7 +20,9 @@
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_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,8 +30,10 @@
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': 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': None, 'schema': schemav219}]
def __init__(self, auth_provider, service, region,
diff --git a/tempest/services/identity/v2/json/roles_client.py b/tempest/lib/services/identity/v2/roles_client.py
similarity index 100%
rename from tempest/services/identity/v2/json/roles_client.py
rename to tempest/lib/services/identity/v2/roles_client.py
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/lib/services/identity/v2/services_client.py
old mode 100644
new mode 100755
similarity index 82%
rename from tempest/services/identity/v2/json/services_client.py
rename to tempest/lib/services/identity/v2/services_client.py
index 4a63d56..c26d419
--- a/tempest/services/identity/v2/json/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/services/identity/v2/json/tenants_client.py b/tempest/lib/services/identity/v2/tenants_client.py
similarity index 100%
rename from tempest/services/identity/v2/json/tenants_client.py
rename to tempest/lib/services/identity/v2/tenants_client.py
diff --git a/tempest/services/identity/v2/json/users_client.py b/tempest/lib/services/identity/v2/users_client.py
similarity index 100%
rename from tempest/services/identity/v2/json/users_client.py
rename to tempest/lib/services/identity/v2/users_client.py
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/lib/services/image/__init__.py b/tempest/lib/services/image/__init__.py
index e69de29..4b01663 100644
--- a/tempest/lib/services/image/__init__.py
+++ b/tempest/lib/services/image/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from tempest.lib.services.image import v1
+from tempest.lib.services.image import v2
+
+__all__ = ['v1', 'v2']
diff --git a/tempest/lib/services/image/v1/__init__.py b/tempest/lib/services/image/v1/__init__.py
index e69de29..9bd8262 100644
--- a/tempest/lib/services/image/v1/__init__.py
+++ b/tempest/lib/services/image/v1/__init__.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from tempest.lib.services.image.v1.image_members_client import \
+ ImageMembersClient
+from tempest.lib.services.image.v1.images_client import ImagesClient
+
+__all__ = ['ImageMembersClient', 'ImagesClient']
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/lib/services/image/v1/images_client.py
similarity index 84%
rename from tempest/services/image/v1/json/images_client.py
rename to tempest/lib/services/image/v1/images_client.py
index ed0a676..0db98f8 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -13,11 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import functools
from oslo_serialization import jsonutils as json
-import six
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -29,20 +27,6 @@
class ImagesClient(rest_client.RestClient):
api_version = "v1"
- def _image_meta_to_headers(self, fields):
- headers = {}
- fields_copy = copy.deepcopy(fields)
- copy_from = fields_copy.pop('copy_from', None)
- if copy_from is not None:
- headers['x-glance-api-copy-from'] = copy_from
- for key, value in six.iteritems(fields_copy.pop('properties', {})):
- headers['x-image-meta-property-%s' % key] = str(value)
- for key, value in six.iteritems(fields_copy.pop('api', {})):
- headers['x-glance-api-property-%s' % key] = str(value)
- for key, value in six.iteritems(fields_copy):
- headers['x-image-meta-%s' % key] = str(value)
- return headers
-
def _create_with_data(self, headers, data):
# We are going to do chunked transfert, so split the input data
# info fixed-sized chunks.
@@ -74,14 +58,14 @@
self._http = self._get_http()
return self._http
- def create_image(self, data=None, **kwargs):
+ def create_image(self, data=None, headers=None):
"""Create an image.
Available params: http://developer.openstack.org/
api-ref-image-v1.html#createImage-v1
"""
- headers = {}
- headers.update(self._image_meta_to_headers(kwargs))
+ if headers is None:
+ headers = {}
if data is not None:
return self._create_with_data(headers, data)
@@ -91,14 +75,14 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
- def update_image(self, image_id, data=None, **kwargs):
+ def update_image(self, image_id, data=None, headers=None):
"""Update an image.
Available params: http://developer.openstack.org/
api-ref-image-v1.html#updateImage-v1
"""
- headers = {}
- headers.update(self._image_meta_to_headers(kwargs))
+ if headers is None:
+ headers = {}
if data is not None:
return self._update_with_data(image_id, headers, data)
diff --git a/tempest/lib/services/image/v2/image_members_client.py b/tempest/lib/services/image/v2/image_members_client.py
index 2ae7516..d0ab165 100644
--- a/tempest/lib/services/image/v2/image_members_client.py
+++ b/tempest/lib/services/image/v2/image_members_client.py
@@ -19,6 +19,11 @@
api_version = "v2"
def list_image_members(self, image_id):
+ """List image members.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#listImageMembers-v2
+ """
url = 'images/%s/members' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
@@ -52,12 +57,22 @@
return rest_client.ResponseBody(resp, body)
def show_image_member(self, image_id, member_id):
+ """Show an image member.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#showImageMember-v2
+ """
url = 'images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, json.loads(body))
def delete_image_member(self, image_id, member_id):
+ """Delete an image member.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteImageMember-v2
+ """
url = 'images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 71e7c6b..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,18 +145,33 @@
return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
+ """Download binary image data.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#showImageFile-v2
+ """
url = 'images/%s/file' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
+ """Add an image tag.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#addImageTag-v2
+ """
url = 'images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_image_tag(self, image_id, tag):
+ """Delete an image tag.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteImageTag-v2
+ """
url = 'images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/image/v2/namespaces_client.py b/tempest/lib/services/image/v2/namespaces_client.py
index 97400e1..5bd096d 100644
--- a/tempest/lib/services/image/v2/namespaces_client.py
+++ b/tempest/lib/services/image/v2/namespaces_client.py
@@ -58,6 +58,11 @@
return rest_client.ResponseBody(resp, body)
def delete_namespace(self, namespace):
+ """Delete a namespace.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteNamespace-v2
+ """
url = 'metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
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/metering_labels_client.py b/tempest/lib/services/network/metering_labels_client.py
old mode 100644
new mode 100755
index 2350ecd..12a5834
--- a/tempest/lib/services/network/metering_labels_client.py
+++ b/tempest/lib/services/network/metering_labels_client.py
@@ -16,18 +16,41 @@
class MeteringLabelsClient(base.BaseNetworkClient):
def create_metering_label(self, **kwargs):
+ """Creates an L3 metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#
+ createMeteringLabel
+ """
uri = '/metering/metering-labels'
post_data = {'metering_label': kwargs}
return self.create_resource(uri, post_data)
def show_metering_label(self, metering_label_id, **fields):
+ """Shows details for a metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#showMeteringLabel
+ """
uri = '/metering/metering-labels/%s' % metering_label_id
return self.show_resource(uri, **fields)
def delete_metering_label(self, metering_label_id):
+ """Deletes an L3 metering label.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#
+ deleteMeteringLabel
+ """
uri = '/metering/metering-labels/%s' % metering_label_id
return self.delete_resource(uri)
def list_metering_labels(self, **filters):
+ """Lists all L3 metering labels that belong to the tenant.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#
+ listMeteringLabels
+ """
uri = '/metering/metering-labels'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
old mode 100644
new mode 100755
index 24c2ec5..7d75bf7
--- a/tempest/lib/services/network/networks_client.py
+++ b/tempest/lib/services/network/networks_client.py
@@ -16,16 +16,31 @@
class NetworksClient(base.BaseNetworkClient):
def create_network(self, **kwargs):
+ """Creates a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#createNetwork
+ """
uri = '/networks'
post_data = {'network': kwargs}
return self.create_resource(uri, post_data)
def update_network(self, network_id, **kwargs):
+ """Updates a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#updateNetwork
+ """
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.html#showNetwork
+ """
uri = '/networks/%s' % network_id
return self.show_resource(uri, **fields)
@@ -34,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.html#listNetworks
+ """
uri = '/networks'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/ports_client.py b/tempest/lib/services/network/ports_client.py
old mode 100644
new mode 100755
index eba11d3..71f1103
--- a/tempest/lib/services/network/ports_client.py
+++ b/tempest/lib/services/network/ports_client.py
@@ -17,24 +17,49 @@
class PortsClient(base.BaseNetworkClient):
def create_port(self, **kwargs):
+ """Creates a port on a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#createPort
+ """
uri = '/ports'
post_data = {'port': kwargs}
return self.create_resource(uri, post_data)
def update_port(self, port_id, **kwargs):
+ """Updates a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#updatePort
+ """
uri = '/ports/%s' % port_id
post_data = {'port': kwargs}
return self.update_resource(uri, post_data)
def show_port(self, port_id, **fields):
+ """Shows details for a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#showPort
+ """
uri = '/ports/%s' % port_id
return self.show_resource(uri, **fields)
def delete_port(self, port_id):
+ """Deletes a port.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#removePort
+ """
uri = '/ports/%s' % port_id
return self.delete_resource(uri)
def list_ports(self, **filters):
+ """Lists ports to which the tenant has access.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#listPorts
+ """
uri = '/ports'
return self.list_resources(uri, **filters)
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 78ffa77..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#removeRouterInterface
+ 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/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
old mode 100644
new mode 100755
index 0e25339..5c89a6f
--- a/tempest/lib/services/network/security_groups_client.py
+++ b/tempest/lib/services/network/security_groups_client.py
@@ -16,23 +16,48 @@
class SecurityGroupsClient(base.BaseNetworkClient):
def create_security_group(self, **kwargs):
+ """Creates an OpenStack Networking security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#createSecGroup
+ """
uri = '/security-groups'
post_data = {'security_group': kwargs}
return self.create_resource(uri, post_data)
def update_security_group(self, security_group_id, **kwargs):
+ """Updates a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#updateSecGroup
+ """
uri = '/security-groups/%s' % security_group_id
post_data = {'security_group': kwargs}
return self.update_resource(uri, post_data)
def show_security_group(self, security_group_id, **fields):
+ """Shows details for a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#showSecGroup
+ """
uri = '/security-groups/%s' % security_group_id
return self.show_resource(uri, **fields)
def delete_security_group(self, security_group_id):
+ """Deletes an OpenStack Networking security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#deleteSecGroup
+ """
uri = '/security-groups/%s' % security_group_id
return self.delete_resource(uri)
def list_security_groups(self, **filters):
+ """Lists OpenStack Networking security groups.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#listSecGroups
+ """
uri = '/security-groups'
return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
old mode 100644
new mode 100755
index 12349b1..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
old mode 100644
new mode 100755
index 63ed13e..0fde3ee
--- a/tempest/lib/services/network/subnets_client.py
+++ b/tempest/lib/services/network/subnets_client.py
@@ -16,16 +16,31 @@
class SubnetsClient(base.BaseNetworkClient):
def create_subnet(self, **kwargs):
+ """Creates a subnet on a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#createSubnet
+ """
uri = '/subnets'
post_data = {'subnet': kwargs}
return self.create_resource(uri, post_data)
def update_subnet(self, subnet_id, **kwargs):
+ """Updates a subnet.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#updateSubnet
+ """
uri = '/subnets/%s' % subnet_id
post_data = {'subnet': kwargs}
return self.update_resource(uri, post_data)
def show_subnet(self, subnet_id, **fields):
+ """Shows details for a subnet.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#showSubnet
+ """
uri = '/subnets/%s' % subnet_id
return self.show_resource(uri, **fields)
@@ -34,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/manager.py b/tempest/manager.py
index f2659a8..3d495b6 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -13,61 +13,50 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
+
+from tempest import clients
from tempest import config
-from tempest.lib import auth
-from tempest.lib import exceptions
+from tempest import service_clients
CONF = config.CONF
+LOG = logging.getLogger(__name__)
-class Manager(object):
- """Base manager class
+class Manager(service_clients.ServiceClients):
+ """Service client manager class for backward compatibility
- Manager objects are responsible for providing a configuration object
- and a client object for a test case to use in performing actions.
+ The former manager.Manager is not a stable interface in Tempest,
+ nonetheless it is consumed by a number of plugins already. This class
+ exists to provide some grace time for the move to tempest.lib.
"""
def __init__(self, credentials, scope='project'):
- """Initialization of base manager class
-
- Credentials to be used within the various client classes managed by the
- Manager object must be defined.
-
- :param credentials: An instance of `auth.Credentials`
- :param scope: default scope for tokens produced by the auth provider
- """
- self.credentials = credentials
- # Check if passed or default credentials are valid
- if not self.credentials.is_valid():
- raise exceptions.InvalidCredentials()
- self.auth_version = CONF.identity.auth_version
- # Creates an auth provider for the credentials
- self.auth_provider = get_auth_provider(
- self.credentials, pre_auth=True, scope=scope)
-
-
-def get_auth_provider_class(credentials):
- if isinstance(credentials, auth.KeystoneV3Credentials):
- return auth.KeystoneV3AuthProvider, CONF.identity.uri_v3
- else:
- return auth.KeystoneV2AuthProvider, CONF.identity.uri
+ msg = ("tempest.manager.Manager is not a stable interface and as such "
+ "it should not imported directly. It will be removed as "
+ "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)
+ super(Manager, self).__init__(
+ credentials=credentials, scope=scope,
+ identity_uri=uri,
+ disable_ssl_certificate_validation=dscv,
+ ca_certs=CONF.identity.ca_certificates_file,
+ trace_requests=CONF.debug.trace_requests)
def get_auth_provider(credentials, pre_auth=False, scope='project'):
- 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
- }
- if credentials is None:
- raise exceptions.InvalidCredentials(
- 'Credentials must be specified')
- auth_provider_class, auth_url = get_auth_provider_class(
- credentials)
- _auth_provider = auth_provider_class(credentials, auth_url,
- scope=scope,
- **default_params)
- if pre_auth:
- _auth_provider.set_auth()
- return _auth_provider
+ """Shim to get_auth_provider in clients.py
+
+ get_auth_provider used to be hosted in this module, but it has been
+ moved to clients.py now as a more permanent location.
+ This module will be removed eventually, and this shim is only
+ maintained for the benefit of plugins already consuming this interface.
+ """
+ msg = ("tempest.manager.get_auth_provider is not a stable interface and "
+ "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)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 69da1c9..f889c44 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -79,8 +79,6 @@
cls.security_groups_client = cls.manager.security_groups_client
cls.security_group_rules_client = (
cls.manager.security_group_rules_client)
- # Heat client
- cls.orchestration_client = cls.manager.orchestration_client
if CONF.volume_feature_enabled.api_v1:
cls.volumes_client = cls.manager.volumes_client
@@ -388,6 +386,7 @@
if CONF.image_feature_enabled.api_v1:
params['is_public'] = 'False'
params['properties'] = properties
+ params = {'headers': common_image.image_meta_to_headers(**params)}
else:
params['visibility'] = 'private'
# Additional properties are flattened out in the v2 API.
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index cace90b..086b82d 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -74,7 +74,7 @@
self.assertEqual(aggregate_name, aggregate['name'])
self.assertEqual(azone, aggregate['availability_zone'])
self.assertEqual(hosts, aggregate['hosts'])
- for meta_key in metadata.keys():
+ for meta_key in metadata:
self.assertIn(meta_key, aggregate['metadata'])
self.assertEqual(metadata[meta_key],
aggregate['metadata'][meta_key])
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 402a70c..9c48080 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -642,6 +642,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 +672,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_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 80728dc..60dca3d 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -14,8 +14,7 @@
# under the License.
import json
-
-from oslo_log import log as logging
+import re
from tempest import config
from tempest import exceptions
@@ -24,8 +23,6 @@
CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
class TestServerBasicOps(manager.ScenarioTest):
@@ -80,22 +77,42 @@
'verify metadata on server. '
'%s is empty.' % md_url)
+ def _mount_config_drive(self):
+ cmd_blkid = 'blkid | grep -i config-2'
+ result = self.ssh_client.exec_command(cmd_blkid)
+ dev_name = re.match('([^:]+)', result).group()
+ self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+
+ def _unmount_config_drive(self):
+ self.ssh_client.exec_command('sudo umount /mnt')
+
def verify_metadata_on_config_drive(self):
if self.run_ssh and CONF.compute_feature_enabled.config_drive:
# Verify metadata on config_drive
- cmd_blkid = 'blkid -t LABEL=config-2 -o device'
- dev_name = self.ssh_client.exec_command(cmd_blkid)
- dev_name = dev_name.rstrip()
- self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+ self._mount_config_drive()
cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
result = self.ssh_client.exec_command(cmd_md)
- self.ssh_client.exec_command('sudo umount /mnt')
+ self._unmount_config_drive()
result = json.loads(result)
self.assertIn('meta', result)
msg = ('Failed while verifying metadata on config_drive on server.'
' Result of command "%s" is NOT "%s".' % (cmd_md, self.md))
self.assertEqual(self.md, result['meta'], msg)
+ def verify_networkdata_on_config_drive(self):
+ if self.run_ssh and CONF.compute_feature_enabled.config_drive:
+ # Verify network data on config_drive
+ self._mount_config_drive()
+ cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json'
+ result = self.ssh_client.exec_command(cmd_md)
+ self._unmount_config_drive()
+ result = json.loads(result)
+ self.assertIn('services', result)
+ self.assertIn('links', result)
+ self.assertIn('networks', result)
+ # TODO(clarkb) construct network_data from known network
+ # instance info and do direct comparison.
+
@test.idempotent_id('7fff3fb3-91d8-4fd0-bd7d-0204f1f180ba')
@test.attr(type='smoke')
@test.services('compute', 'network')
@@ -115,4 +132,5 @@
self.verify_ssh(keypair)
self.verify_metadata()
self.verify_metadata_on_config_drive()
+ self.verify_networkdata_on_config_drive()
self.servers_client.delete_server(self.instance['id'])
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 6d3ecd4..4b9c61c 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common import compute
from tempest.common import waiters
from tempest import config
@@ -35,6 +33,12 @@
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestShelveInstance, cls).skip_checks()
+ if not CONF.compute_feature_enabled.shelve:
+ raise cls.skipException("Shelve is not available.")
+
def _shelve_then_unshelve_server(self, server):
compute.shelve_server(self.servers_client, server['id'],
force_shelve_offload=True)
@@ -83,15 +87,11 @@
self.assertEqual(timestamp, timestamp2)
@test.idempotent_id('1164e700-0af0-4a4c-8792-35909a88743c')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
@test.services('compute', 'network', 'image')
def test_shelve_instance(self):
self._create_server_then_shelve_and_unshelve()
@test.idempotent_id('c1b6318c-b9da-490b-9c67-9339b627271f')
- @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
- 'Shelve is not available.')
@test.services('compute', 'volume', 'network', 'image')
def test_shelve_volume_backed_instance(self):
self._create_server_then_shelve_and_unshelve(boot_from_volume=True)
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index d6528a3..47c6e8d 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest import config
from tempest.scenario import manager
from tempest import test
@@ -33,9 +31,13 @@
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestSnapshotPattern, cls).skip_checks()
+ if not CONF.compute_feature_enabled.snapshot:
+ raise cls.skipException("Snapshotting is not available.")
+
@test.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4')
- @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
- 'Snapshotting is not available.')
@test.services('compute', 'network', 'image')
def test_snapshot_pattern(self):
# prepare for booting an instance
diff --git a/tempest/service_clients.py b/tempest/service_clients.py
new file mode 100644
index 0000000..252ebf4
--- /dev/null
+++ b/tempest/service_clients.py
@@ -0,0 +1,264 @@
+# 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
+
+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 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.
+ """
+ # TODO(andreaf) This version includes ClientsFactory but it does not
+ # use it yet in ServiceClients
+
+ 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))
+
+ @classmethod
+ def _get_partial_class(cls, klass, auth_provider, kwargs):
+
+ # Define a function that returns a new class instance by
+ # combining default kwargs with extra ones
+ def partial_class(**later_kwargs):
+ kwargs.update(later_kwargs)
+ return klass(auth_provider=auth_provider, **kwargs)
+
+ 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 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/database/__init__.py b/tempest/services/database/__init__.py
deleted file mode 100644
index 9a742d8..0000000
--- a/tempest/services/database/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.services.database.json.flavors_client import \
- DatabaseFlavorsClient
-from tempest.services.database.json.limits_client import \
- DatabaseLimitsClient
-from tempest.services.database.json.versions_client import \
- DatabaseVersionsClient
-
-__all__ = ['DatabaseFlavorsClient', 'DatabaseLimitsClient',
- 'DatabaseVersionsClient']
diff --git a/tempest/services/database/json/__init__.py b/tempest/services/database/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/database/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/database/json/flavors_client.py b/tempest/services/database/json/flavors_client.py
deleted file mode 100644
index bd8ffb0..0000000
--- a/tempest/services/database/json/flavors_client.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves import urllib
-
-from tempest.lib.common import rest_client
-
-
-class DatabaseFlavorsClient(rest_client.RestClient):
-
- def list_db_flavors(self, params=None):
- url = 'flavors'
- if params:
- url += '?%s' % urllib.parse.urlencode(params)
-
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_db_flavor(self, db_flavor_id):
- resp, body = self.get("flavors/%s" % db_flavor_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/database/json/limits_client.py b/tempest/services/database/json/limits_client.py
deleted file mode 100644
index a1c58c2..0000000
--- a/tempest/services/database/json/limits_client.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class DatabaseLimitsClient(rest_client.RestClient):
-
- def list_db_limits(self, params=None):
- """List all limits."""
- url = 'limits'
- 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/database/json/versions_client.py b/tempest/services/database/json/versions_client.py
deleted file mode 100644
index 2f28203..0000000
--- a/tempest/services/database/json/versions_client.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class DatabaseVersionsClient(rest_client.RestClient):
-
- def __init__(self, auth_provider, service, region, **kwargs):
- super(DatabaseVersionsClient, self).__init__(
- auth_provider, service, region, **kwargs)
- self.skip_path()
-
- def list_db_versions(self, params=None):
- """List all versions."""
- url = ''
- 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/v2/__init__.py b/tempest/services/identity/v2/__init__.py
index 6f4ebcf..ac2a874 100644
--- a/tempest/services/identity/v2/__init__.py
+++ b/tempest/services/identity/v2/__init__.py
@@ -13,12 +13,12 @@
# the License.
from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
+from tempest.lib.services.identity.v2.roles_client import RolesClient
+from tempest.lib.services.identity.v2.services_client import ServicesClient
+from tempest.lib.services.identity.v2.tenants_client import TenantsClient
from tempest.lib.services.identity.v2.token_client import TokenClient
+from tempest.lib.services.identity.v2.users_client import UsersClient
from tempest.services.identity.v2.json.identity_client import IdentityClient
-from tempest.services.identity.v2.json.roles_client import RolesClient
-from tempest.services.identity.v2.json.services_client import ServicesClient
-from tempest.services.identity.v2.json.tenants_client import TenantsClient
-from tempest.services.identity.v2.json.users_client import UsersClient
__all__ = ['EndpointsClient', 'TokenClient', 'IdentityClient', 'RolesClient',
'ServicesClient', 'TenantsClient', 'UsersClient']
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
index 144c5a9..6ad8ef2 100644
--- a/tempest/services/identity/v3/__init__.py
+++ b/tempest/services/identity/v3/__init__.py
@@ -12,14 +12,14 @@
# 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.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
@@ -27,7 +27,7 @@
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',
+__all__ = ['EndPointsClient', 'PoliciesClient', 'V3TokenClient',
+ 'CredentialsClient', 'DomainsClient', 'GroupsClient',
+ 'IdentityClient', 'ProjectsClient', 'RegionsClient', 'RolesClient',
'ServicesClient', 'TrustsClient', 'UsersClient', ]
diff --git a/tempest/services/image/__init__.py b/tempest/services/image/__init__.py
deleted file mode 100644
index 7ff0886..0000000
--- a/tempest/services/image/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.lib.services.image import v2
-from tempest.services.image import v1
-
-__all__ = ['v1', 'v2']
diff --git a/tempest/services/image/v1/__init__.py b/tempest/services/image/v1/__init__.py
deleted file mode 100644
index 67dca39..0000000
--- a/tempest/services/image/v1/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.lib.services.image.v1.image_members_client import \
- ImageMembersClient
-from tempest.services.image.v1.json.images_client import ImagesClient
-
-__all__ = ['ImageMembersClient', 'ImagesClient']
diff --git a/tempest/services/image/v1/json/__init__.py b/tempest/services/image/v1/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/image/v1/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
old mode 100644
new mode 100755
index 95ddff6..afca752
--- 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)
@@ -59,7 +63,11 @@
return rest_client.ResponseBody(resp, body)
def show_volume_type(self, volume_id):
- """Returns the details of a single volume_type."""
+ """Returns the details of a single volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#showVolumeType
+ """
url = "types/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -79,7 +87,11 @@
return rest_client.ResponseBody(resp, body)
def delete_volume_type(self, volume_id):
- """Deletes the Specified Volume_type."""
+ """Deletes the Specified Volume_type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#deleteVolumeType
+ """
resp, body = self.delete("types/%s" % str(volume_id))
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -90,8 +102,6 @@
TODO: Current api-site doesn't contain this API description.
After fixing the api-site, we need to fix here also for putting
the link to api-site.
-
-
"""
url = 'types/%s/extra_specs' % str(vol_type_id)
if params:
@@ -139,6 +149,9 @@
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))
@@ -179,3 +192,42 @@
"/types/%s/encryption/provider" % str(vol_type_id))
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def add_type_access(self, volume_type_id, **kwargs):
+ """Adds volume type access for the given project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #createVolumeTypeAccessExt
+ """
+ post_body = json.dumps({'addProjectAccess': kwargs})
+ url = 'types/%s/action' % (volume_type_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_type_access(self, volume_type_id, **kwargs):
+ """Removes volume type access for the given project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #removeVolumeTypeAccessExt
+ """
+ post_body = json.dumps({'removeProjectAccess': kwargs})
+ url = 'types/%s/action' % (volume_type_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_type_access(self, volume_type_id):
+ """Print access information about the given volume type.
+
+ 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/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index 3842d66..63c5417 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -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:
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
old mode 100644
new mode 100755
index da7bb01..7a8e12b
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
@@ -18,16 +17,17 @@
from tempest.lib import exceptions as lib_exc
-LOG = logging.getLogger(__name__)
-
-
class BaseSnapshotsClient(rest_client.RestClient):
"""Base Client class to send CRUD Volume API requests."""
create_resp = 200
def list_snapshots(self, detail=False, **params):
- """List all the snapshot."""
+ """List all the snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#listSnapshots
+ """
url = 'snapshots'
if detail:
url += '/detail'
@@ -40,7 +40,11 @@
return rest_client.ResponseBody(resp, body)
def show_snapshot(self, snapshot_id):
- """Returns the details of a single snapshot."""
+ """Returns the details of a single snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#showSnapshot
+ """
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -72,7 +76,11 @@
return rest_client.ResponseBody(resp, body)
def delete_snapshot(self, snapshot_id):
- """Delete Snapshot."""
+ """Delete Snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#deleteSnapshot
+ """
resp, body = self.delete("snapshots/%s" % str(snapshot_id))
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -119,7 +127,12 @@
return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata(self, snapshot_id):
- """Get metadata of the snapshot."""
+ """Get metadata of the snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#
+ showSnapshotMetadata
+ """
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -130,7 +143,8 @@
"""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)
diff --git a/tempest/stress/actions/unit_test.py b/tempest/stress/actions/unit_test.py
index 3b27885..e016c61 100644
--- a/tempest/stress/actions/unit_test.py
+++ b/tempest/stress/actions/unit_test.py
@@ -80,8 +80,6 @@
try:
self.run_core()
- except Exception as e:
- raise e
finally:
if (CONF.stress.leave_dirty_stack is False
and self.class_setup_per == SetUpClassRunTime.action):
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 2beaaa9..925d765 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -249,13 +249,13 @@
had_errors = True
sum_runs += process['statistic']['runs']
sum_fails += process['statistic']['fails']
- print ("Process %d (%s): Run %d actions (%d failed)" % (
- process['p_number'],
- process['action'],
- process['statistic']['runs'],
- process['statistic']['fails']))
- print ("Summary:")
- print ("Run %d actions (%d failed)" % (sum_runs, sum_fails))
+ print("Process %d (%s): Run %d actions (%d failed)" % (
+ process['p_number'],
+ process['action'],
+ process['statistic']['runs'],
+ process['statistic']['fails']))
+ print("Summary:")
+ print("Run %d actions (%d failed)" % (sum_runs, sum_fails))
if not had_errors and CONF.stress.full_clean_stack:
LOG.info("cleaning up")
diff --git a/tempest/test.py b/tempest/test.py
index 4e06db8..97ab25c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -72,14 +72,9 @@
'image': CONF.service_available.glance,
'baremetal': CONF.service_available.ironic,
'volume': CONF.service_available.cinder,
- 'orchestration': CONF.service_available.heat,
- # NOTE(mtreinish) nova-network will provide networking functionality
- # if neutron isn't available, so always set to True.
'network': True,
'identity': True,
'object_storage': CONF.service_available.swift,
- 'data_processing': CONF.service_available.sahara,
- 'database': CONF.service_available.trove
}
return service_list
@@ -91,9 +86,8 @@
exercised by a test case.
"""
def decorator(f):
- services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
- 'network', 'identity', 'object_storage', 'data_processing',
- 'database']
+ services = ['compute', 'image', 'baremetal', 'volume',
+ 'network', 'identity', 'object_storage']
for service in args:
if service not in services:
raise exceptions.InvalidServiceTag('%s is not a valid '
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index dcffd21..772391f 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -46,18 +46,24 @@
args = mock.Mock(spec=argparse.Namespace)
setattr(args, 'smoke', False)
setattr(args, 'regex', '')
+ setattr(args, 'whitelist_file', None)
+ setattr(args, 'blacklist_file', None)
self.assertEqual('', self.run_cmd._build_regex(args))
def test__build_regex_smoke(self):
args = mock.Mock(spec=argparse.Namespace)
setattr(args, "smoke", True)
setattr(args, 'regex', '')
+ setattr(args, 'whitelist_file', None)
+ setattr(args, 'blacklist_file', None)
self.assertEqual('smoke', self.run_cmd._build_regex(args))
def test__build_regex_regex(self):
args = mock.Mock(spec=argparse.Namespace)
setattr(args, 'smoke', False)
setattr(args, "regex", 'i_am_a_fun_little_regex')
+ setattr(args, 'whitelist_file', None)
+ setattr(args, 'blacklist_file', None)
self.assertEqual('i_am_a_fun_little_regex',
self.run_cmd._build_regex(args))
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index e97f65f..b7cc05d 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -21,14 +21,15 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import roles_client as v2_roles_client
+from tempest.lib.services.identity.v2 import tenants_client as \
+ v2_tenants_client
from tempest.lib.services.identity.v2 import token_client as v2_token_client
+from tempest.lib.services.identity.v2 import users_client as v2_users_client
from tempest.lib.services.identity.v3 import token_client as v3_token_client
from tempest.lib.services.network import routers_client
from tempest.services.identity.v2.json import identity_client as v2_iden_client
-from tempest.services.identity.v2.json import roles_client as v2_roles_client
-from tempest.services.identity.v2.json import tenants_client as \
- v2_tenants_client
-from tempest.services.identity.v2.json import users_client as v2_users_client
from tempest.services.identity.v3.json import domains_client
from tempest.services.identity.v3.json import identity_client as v3_iden_client
from tempest.services.identity.v3.json import projects_client as \
@@ -635,3 +636,15 @@
return_value=(rest_client.ResponseBody
(200, {'project': {'id': id, 'name': name}}))))
return project_fix
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_member_role_creation_with_duplicate(self, rest_client_mock):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ creds.creds_client = mock.MagicMock()
+ creds.creds_client.create_user_role.side_effect = lib_exc.Conflict
+ with mock.patch('tempest.common.dynamic_creds.LOG') as log_mock:
+ creds._create_creds()
+ log_mock.warning.assert_called_once_with(
+ "Member role already exists, ignoring conflict.")
+ creds.creds_client.assign_user_role.assert_called_once_with(
+ mock.ANY, mock.ANY, 'Member')
diff --git a/tempest/tests/common/test_image.py b/tempest/tests/common/test_image.py
index fdd0ae8..240df4d 100644
--- a/tempest/tests/common/test_image.py
+++ b/tempest/tests/common/test_image.py
@@ -38,3 +38,24 @@
'name': 'New Http Image'
}
self.assertEqual(expected, observed)
+
+ def test_image_meta_to_headers(self):
+ observed = image.image_meta_to_headers(
+ name='test',
+ container_format='wrong',
+ disk_format='vhd',
+ copy_from='http://localhost/images/10',
+ properties={'foo': 'bar'},
+ api={'abc': 'def'},
+ purge_props=True)
+
+ expected = {
+ 'x-image-meta-name': 'test',
+ 'x-image-meta-container_format': 'wrong',
+ 'x-image-meta-disk_format': 'vhd',
+ 'x-glance-api-copy-from': 'http://localhost/images/10',
+ 'x-image-meta-property-foo': 'bar',
+ 'x-glance-api-property-abc': 'def',
+ 'x-glance-registry-purge-props': True
+ }
+ self.assertEqual(expected, observed)
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 65164a0..71a4c81 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -57,3 +57,57 @@
def __init__(self, parse_conf=True, config_path=None):
self._set_attrs()
self.lock_path = cfg.CONF.oslo_concurrency.lock_path
+
+fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1')
+
+FakeService1Group = [
+ cfg.StrOpt('catalog_type', default='fake-service1'),
+ cfg.StrOpt('endpoint_type', default='faketype'),
+ cfg.StrOpt('region', default='fake_region'),
+ cfg.IntOpt('build_timeout', default=99),
+ cfg.IntOpt('build_interval', default=9)]
+
+fake_service2_group = cfg.OptGroup(name='fake-service2', title='Fake service2')
+
+FakeService2Group = [
+ cfg.StrOpt('catalog_type', default='fake-service2'),
+ cfg.StrOpt('endpoint_type', default='faketype')]
+
+
+class ServiceClientsConfigFixture(conf_fixture.Config):
+
+ def __init__(self):
+ cfg.CONF([], default_config_files=[])
+ config._opts.append((fake_service1_group, FakeService1Group))
+ config._opts.append((fake_service2_group, FakeService2Group))
+ config.register_opts()
+ super(ServiceClientsConfigFixture, self).__init__()
+
+ def setUp(self):
+ super(ServiceClientsConfigFixture, self).setUp()
+ # Debug default values
+ self.conf.set_default('trace_requests', 'fake_module', 'debug')
+ # Identity default values
+ self.conf.set_default('disable_ssl_certificate_validation', True,
+ group='identity')
+ self.conf.set_default('ca_certificates_file', '/fake/certificates',
+ group='identity')
+ self.conf.set_default('region', 'fake_region', 'identity')
+ # Identity endpoints
+ self.conf.set_default('v3_endpoint_type', 'fake_v3_uri', 'identity')
+ self.conf.set_default('v2_public_endpoint_type', 'fake_v2_public_uri',
+ 'identity')
+ self.conf.set_default('v2_admin_endpoint_type', 'fake_v2_admin_uri',
+ 'identity')
+ # Compute default values
+ self.conf.set_default('build_interval', 88, group='compute')
+ self.conf.set_default('build_timeout', 8, group='compute')
+
+
+class ServiceClientsFakePrivate(config.TempestConfigPrivate):
+ def __init__(self, parse_conf=True, config_path=None):
+ self._set_attrs()
+ self.fake_service1 = cfg.CONF['fake-service1']
+ self.fake_service2 = cfg.CONF['fake-service2']
+ print('Services registered')
+ self.lock_path = cfg.CONF.oslo_concurrency.lock_path
diff --git a/tempest/tests/lib/fake_auth_provider.py b/tempest/tests/lib/fake_auth_provider.py
index 8095453..fa8ab47 100644
--- a/tempest/tests/lib/fake_auth_provider.py
+++ b/tempest/tests/lib/fake_auth_provider.py
@@ -31,5 +31,5 @@
class FakeCredentials(object):
def __init__(self, creds_dict):
- for key in creds_dict.keys():
+ for key in creds_dict:
setattr(self, key, creds_dict[key])
diff --git a/tempest/tests/services/identity/v2/test_roles_client.py b/tempest/tests/lib/services/identity/v2/test_roles_client.py
similarity index 98%
rename from tempest/tests/services/identity/v2/test_roles_client.py
rename to tempest/tests/lib/services/identity/v2/test_roles_client.py
index e36ec18..464a715 100644
--- a/tempest/tests/services/identity/v2/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_roles_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.identity.v2.json import roles_client
+from tempest.lib.services.identity.v2 import roles_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/identity/v2/test_services_client.py b/tempest/tests/lib/services/identity/v2/test_services_client.py
new file mode 100644
index 0000000..bafb6b1
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_services_client.py
@@ -0,0 +1,97 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.identity.v2 import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServicesClient(base.BaseServiceTest):
+ FAKE_SERVICE_INFO = {
+ "OS-KSADM:service": {
+ "id": "1",
+ "name": "test",
+ "type": "compute",
+ "description": "test_description"
+ }
+ }
+
+ FAKE_LIST_SERVICES = {
+ "OS-KSADM:services": [
+ {
+ "id": "1",
+ "name": "test",
+ "type": "compute",
+ "description": "test_description"
+ },
+ {
+ "id": "2",
+ "name": "test2",
+ "type": "compute",
+ "description": "test2_description"
+ }
+ ]
+ }
+
+ 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_SERVICE_INFO,
+ bytes_body,
+ id="1",
+ name="test",
+ type="compute",
+ description="test_description")
+
+ 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="1")
+
+ 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_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_delete_service(self):
+ self.check_service_client_function(
+ self.client.delete_service,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ service_id="1",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v2/test_tenants_client.py b/tempest/tests/lib/services/identity/v2/test_tenants_client.py
new file mode 100644
index 0000000..ae3d13a
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_tenants_client.py
@@ -0,0 +1,131 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.identity.v2 import tenants_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTenantsClient(base.BaseServiceTest):
+ FAKE_TENANT_INFO = {
+ "tenant": {
+ "id": "1",
+ "name": "test",
+ "description": "test_description",
+ "enabled": True
+ }
+ }
+
+ FAKE_LIST_TENANTS = {
+ "tenants": [
+ {
+ "id": "1",
+ "name": "test",
+ "description": "test_description",
+ "enabled": True
+ },
+ {
+ "id": "2",
+ "name": "test2",
+ "description": "test2_description",
+ "enabled": True
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestTenantsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tenants_client.TenantsClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_tenant(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_tenant,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_TENANT_INFO,
+ bytes_body,
+ name="test",
+ description="test_description")
+
+ def _test_show_tenant(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_tenant,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TENANT_INFO,
+ bytes_body,
+ tenant_id="1")
+
+ def _test_update_tenant(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_tenant,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_TENANT_INFO,
+ bytes_body,
+ tenant_id="1",
+ name="test",
+ description="test_description")
+
+ def _test_list_tenants(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenants,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_TENANTS,
+ bytes_body)
+
+ def _test_list_tenant_users(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenant_users,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_TENANTS,
+ bytes_body,
+ tenant_id="1")
+
+ def test_create_tenant_with_str_body(self):
+ self._test_create_tenant()
+
+ def test_create_tenant_with_bytes_body(self):
+ self._test_create_tenant(bytes_body=True)
+
+ def test_show_tenant_with_str_body(self):
+ self._test_show_tenant()
+
+ def test_show_tenant_with_bytes_body(self):
+ self._test_show_tenant(bytes_body=True)
+
+ def test_update_tenant_with_str_body(self):
+ self._test_update_tenant()
+
+ def test_update_tenant_with_bytes_body(self):
+ self._test_update_tenant(bytes_body=True)
+
+ def test_list_tenants_with_str_body(self):
+ self._test_list_tenants()
+
+ def test_list_tenants_with_bytes_body(self):
+ self._test_list_tenants(bytes_body=True)
+
+ def test_delete_tenant(self):
+ self.check_service_client_function(
+ self.client.delete_tenant,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ tenant_id="1",
+ status=204)
+
+ def test_list_tenant_users_with_str_body(self):
+ self._test_list_tenant_users()
+
+ def test_list_tenant_users_with_bytes_body(self):
+ self._test_list_tenant_users(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v2/test_users_client.py b/tempest/tests/lib/services/identity/v2/test_users_client.py
new file mode 100644
index 0000000..9534e44
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_users_client.py
@@ -0,0 +1,243 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.identity.v2 import users_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestUsersClient(base.BaseServiceTest):
+ FAKE_USER_INFO = {
+ "user": {
+ "id": "1",
+ "name": "test",
+ "email": "john.smith@example.org",
+ "enabled": True
+ }
+ }
+
+ FAKE_LIST_USERS = {
+ "users": [
+ {
+ "id": "1",
+ "name": "test",
+ "email": "john.smith@example.org",
+ "enabled": True
+ },
+ {
+ "id": "2",
+ "name": "test2",
+ "email": "john.smith@example.org",
+ "enabled": True
+ }
+ ]
+ }
+
+ FAKE_USER_EC2_CREDENTIAL_INFO = {
+ "credential": {
+ 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+ 'access': '79abf59acc77492a86170cbe2f1feafa',
+ 'secret': 'c4e7d3a691fd4563873d381a40320f46',
+ 'trust_id': None,
+ 'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+ }
+ }
+
+ FAKE_LIST_USER_EC2_CREDENTIALS = {
+ "credentials": [
+ {
+ 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+ 'access': '79abf59acc77492a86170cbe2f1feafa',
+ 'secret': 'c4e7d3a691fd4563873d381a40320f46',
+ 'trust_id': None,
+ 'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+ },
+ {
+ 'user_id': '3beb0e12f3e5416db8d7cccfc785de4r',
+ 'access': '45abf59acc77492a86170cbe2f1fesde',
+ 'secret': 'g4e7d3a691fd4563873d381a40320e45',
+ 'trust_id': None,
+ 'tenant_id': '123557269d7b4dd78631a602eb9f112f'
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestUsersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = users_client.UsersClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ name="test",
+ email="john.smith@example.org")
+
+ def _test_update_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_user,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id="1",
+ name="test")
+
+ def _test_show_user(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_user,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id="1")
+
+ def _test_list_users(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_users,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_USERS,
+ bytes_body)
+
+ def _test_update_user_enabled(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_user_enabled,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id="1",
+ enabled=True)
+
+ def _test_update_user_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_user_password,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id="1",
+ password="pass")
+
+ def _test_update_user_own_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_user_own_password,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_USER_INFO,
+ bytes_body,
+ user_id="1",
+ password="pass")
+
+ def _test_create_user_ec2_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_USER_EC2_CREDENTIAL_INFO,
+ bytes_body,
+ user_id="1",
+ tenant_id="123")
+
+ def _test_show_user_ec2_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_EC2_CREDENTIAL_INFO,
+ bytes_body,
+ user_id="1",
+ access="123")
+
+ def _test_list_user_ec2_credentials(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_ec2_credentials,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_USER_EC2_CREDENTIALS,
+ bytes_body,
+ user_id="1")
+
+ def test_create_user_with_str_body(self):
+ self._test_create_user()
+
+ def test_create_user_with_bytes_body(self):
+ self._test_create_user(bytes_body=True)
+
+ def test_update_user_with_str_body(self):
+ self._test_update_user()
+
+ def test_update_user_with_bytes_body(self):
+ self._test_update_user(bytes_body=True)
+
+ def test_show_user_with_str_body(self):
+ self._test_show_user()
+
+ def test_show_user_with_bytes_body(self):
+ self._test_show_user(bytes_body=True)
+
+ def test_list_users_with_str_body(self):
+ self._test_list_users()
+
+ def test_list_users_with_bytes_body(self):
+ self._test_list_users(bytes_body=True)
+
+ def test_delete_user(self):
+ self.check_service_client_function(
+ self.client.delete_user,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id="1",
+ status=204)
+
+ def test_update_user_enabled_with_str_body(self):
+ self._test_update_user_enabled()
+
+ def test_update_user_enabled_with_bytes_body(self):
+ self._test_update_user_enabled(bytes_body=True)
+
+ def test_update_user_password_with_str_body(self):
+ self._test_update_user_password()
+
+ def test_update_user_password_with_bytes_body(self):
+ self._test_update_user_password(bytes_body=True)
+
+ def test_update_user_own_password_with_str_body(self):
+ self._test_update_user_own_password()
+
+ def test_update_user_own_password_with_bytes_body(self):
+ self._test_update_user_own_password(bytes_body=True)
+
+ def test_create_user_ec2_credential_with_str_body(self):
+ self._test_create_user_ec2_credential()
+
+ def test_create_user_ec2_credential_with_bytes_body(self):
+ self._test_create_user_ec2_credential(bytes_body=True)
+
+ def test_show_user_ec2_credential_with_str_body(self):
+ self._test_show_user_ec2_credential()
+
+ def test_show_user_ec2_credential_with_bytes_body(self):
+ self._test_show_user_ec2_credential(bytes_body=True)
+
+ def test_list_user_ec2_credentials_with_str_body(self):
+ self._test_list_user_ec2_credentials()
+
+ def test_list_user_ec2_credentials_with_bytes_body(self):
+ self._test_list_user_ec2_credentials(bytes_body=True)
+
+ def test_delete_user_ec2_credential(self):
+ self.check_service_client_function(
+ self.client.delete_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id="123",
+ access="1234",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
new file mode 100644
index 0000000..f8c553f
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -0,0 +1,100 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import endpoints_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndpointsClient(base.BaseServiceTest):
+ FAKE_CREATE_ENDPOINT = {
+ "endpoint": {
+ "id": 1,
+ "tenantId": 1,
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ }
+
+ FAKE_LIST_ENDPOINTS = {
+ "endpoints": [
+ {
+ "id": 1,
+ "tenantId": "1",
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ },
+ {
+ "id": 2,
+ "tenantId": "1",
+ "region": "South",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestEndpointsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoints_client.EndPointsClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_ENDPOINT,
+ bytes_body,
+ status=201,
+ service_id="b344506af7644f6794d9cb316600b020",
+ region="region-demo",
+ publicurl="https://compute.north.public.com/v1",
+ adminurl="https://compute.north.internal.com/v1",
+ internalurl="https://compute.north.internal.com/v1")
+
+ def _test_list_endpoints(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS,
+ bytes_body)
+
+ def test_create_endpoint_with_str_body(self):
+ self._test_create_endpoint()
+
+ def test_create_endpoint_with_bytes_body(self):
+ self._test_create_endpoint(bytes_body=True)
+
+ def test_list_endpoints_with_str_body(self):
+ self._test_list_endpoints()
+
+ def test_list_endpoints_with_bytes_body(self):
+ self._test_list_endpoints(bytes_body=True)
+
+ def test_delete_endpoint(self):
+ self.check_service_client_function(
+ self.client.delete_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ endpoint_id="b344506af7644f6794d9cb316600b020",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_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/test_credentials.py b/tempest/tests/lib/test_credentials.py
index b6f2cf6..c910d6d 100644
--- a/tempest/tests/lib/test_credentials.py
+++ b/tempest/tests/lib/test_credentials.py
@@ -99,7 +99,7 @@
def _test_is_not_valid(self, ignore_key):
creds = self._get_credentials()
- for attr in self.attributes.keys():
+ for attr in self.attributes:
if attr == ignore_key:
continue
temp_attr = getattr(creds, attr)
diff --git a/tempest/tests/services/identity/__init__.py b/tempest/tests/services/identity/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/services/identity/__init__.py
+++ /dev/null
diff --git a/tempest/tests/services/identity/v2/__init__.py b/tempest/tests/services/identity/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/services/identity/v2/__init__.py
+++ /dev/null
diff --git a/tempest/tests/test_config.py b/tempest/tests/test_config.py
new file mode 100644
index 0000000..2808a9c
--- /dev/null
+++ b/tempest/tests/test_config.py
@@ -0,0 +1,89 @@
+# 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 testtools
+
+from tempest import config
+from tempest.lib import exceptions
+from tempest.tests import base
+from tempest.tests import fake_config
+
+
+class TestServiceClientConfig(base.TestCase):
+
+ expected_common_params = set(['disable_ssl_certificate_validation',
+ 'ca_certs', 'trace_requests'])
+ expected_extra_params = set(['service', 'endpoint_type', 'region',
+ 'build_timeout', 'build_interval'])
+
+ def setUp(self):
+ super(TestServiceClientConfig, self).setUp()
+ self.useFixture(fake_config.ServiceClientsConfigFixture())
+ self.patchobject(config, 'CONF',
+ fake_config.ServiceClientsFakePrivate())
+ self.CONF = config.CONF
+
+ def test_service_client_config_no_service(self):
+ params = config.service_client_config()
+ for param_name in self.expected_common_params:
+ self.assertIn(param_name, params)
+ for param_name in self.expected_extra_params:
+ self.assertNotIn(param_name, params)
+ self.assertEqual(
+ self.CONF.identity.disable_ssl_certificate_validation,
+ params['disable_ssl_certificate_validation'])
+ self.assertEqual(self.CONF.identity.ca_certificates_file,
+ params['ca_certs'])
+ self.assertEqual(self.CONF.debug.trace_requests,
+ params['trace_requests'])
+
+ def test_service_client_config_service_all(self):
+ params = config.service_client_config(
+ service_client_name='fake-service1')
+ for param_name in self.expected_common_params:
+ self.assertIn(param_name, params)
+ for param_name in self.expected_extra_params:
+ self.assertIn(param_name, params)
+ self.assertEqual(self.CONF.fake_service1.catalog_type,
+ params['service'])
+ self.assertEqual(self.CONF.fake_service1.endpoint_type,
+ params['endpoint_type'])
+ self.assertEqual(self.CONF.fake_service1.region, params['region'])
+ self.assertEqual(self.CONF.fake_service1.build_timeout,
+ params['build_timeout'])
+ self.assertEqual(self.CONF.fake_service1.build_interval,
+ params['build_interval'])
+
+ def test_service_client_config_service_minimal(self):
+ params = config.service_client_config(
+ service_client_name='fake-service2')
+ for param_name in self.expected_common_params:
+ self.assertIn(param_name, params)
+ for param_name in self.expected_extra_params:
+ self.assertIn(param_name, params)
+ self.assertEqual(self.CONF.fake_service2.catalog_type,
+ params['service'])
+ self.assertEqual(self.CONF.fake_service2.endpoint_type,
+ params['endpoint_type'])
+ self.assertEqual(self.CONF.identity.region, params['region'])
+ self.assertEqual(self.CONF.compute.build_timeout,
+ params['build_timeout'])
+ self.assertEqual(self.CONF.compute.build_interval,
+ params['build_interval'])
+
+ def test_service_client_config_service_unknown(self):
+ unknown_service = 'unknown_service'
+ with testtools.ExpectedException(exceptions.UnknownServiceClient,
+ '.*' + unknown_service + '.*'):
+ config.service_client_config(service_client_name=unknown_service)
diff --git a/tempest/tests/test_service_clients.py b/tempest/tests/test_service_clients.py
new file mode 100644
index 0000000..26cc93f
--- /dev/null
+++ b/tempest/tests/test_service_clients.py
@@ -0,0 +1,265 @@
+# 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 import service_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.service_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 = service_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.service_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 = service_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.*'):
+ service_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):
+ service_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):
+ service_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 = service_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 = service_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)
+
+
+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/test-requirements.txt b/test-requirements.txt
index 763f0ba..04c3d6d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,12 +1,12 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-hacking<0.11,>=0.10.0
+hacking<0.12,>=0.11.0 # Apache-2.0
# needed for doc build
-sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
+sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
-reno>=1.6.2 # Apache2
+reno>=1.8.0 # Apache2
mock>=2.0 # BSD
coverage>=3.6 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index b554514..55f41a6 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -95,7 +95,7 @@
def get_results(result_dict):
results = []
- for bug_no in result_dict.keys():
+ for bug_no in result_dict:
for method in result_dict[bug_no]:
results.append((method, bug_no))
return results